@entros/pulse-sdk 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +68 -6
- package/dist/index.d.ts +68 -6
- package/dist/index.js +177 -99
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +177 -99
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -64,14 +64,25 @@ async function captureAudio(options = {}) {
|
|
|
64
64
|
autoGainControl: false
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
let ctx;
|
|
68
|
+
let source;
|
|
69
|
+
let capturedSampleRate;
|
|
70
|
+
try {
|
|
71
|
+
ctx = new AudioContext({ sampleRate: TARGET_SAMPLE_RATE });
|
|
72
|
+
await ctx.resume();
|
|
73
|
+
capturedSampleRate = ctx.sampleRate;
|
|
74
|
+
source = ctx.createMediaStreamSource(stream);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (!preAcquiredStream) {
|
|
77
|
+
stream.getTracks().forEach((t) => t.stop());
|
|
78
|
+
}
|
|
79
|
+
throw err;
|
|
80
|
+
}
|
|
71
81
|
const chunks = [];
|
|
72
82
|
const startTime = performance.now();
|
|
73
83
|
return new Promise((resolve) => {
|
|
74
84
|
let stopped = false;
|
|
85
|
+
let abortTimer = null;
|
|
75
86
|
const bufferSize = 4096;
|
|
76
87
|
const processor = ctx.createScriptProcessor(bufferSize, 1, 1);
|
|
77
88
|
processor.onaudioprocess = (e) => {
|
|
@@ -89,6 +100,7 @@ async function captureAudio(options = {}) {
|
|
|
89
100
|
if (stopped) return;
|
|
90
101
|
stopped = true;
|
|
91
102
|
clearTimeout(maxTimer);
|
|
103
|
+
if (abortTimer !== null) clearTimeout(abortTimer);
|
|
92
104
|
processor.disconnect();
|
|
93
105
|
source.disconnect();
|
|
94
106
|
stream.getTracks().forEach((t) => t.stop());
|
|
@@ -110,14 +122,14 @@ async function captureAudio(options = {}) {
|
|
|
110
122
|
const maxTimer = setTimeout(stopCapture, maxDurationMs);
|
|
111
123
|
if (signal) {
|
|
112
124
|
if (signal.aborted) {
|
|
113
|
-
setTimeout(stopCapture, minDurationMs);
|
|
125
|
+
abortTimer = setTimeout(stopCapture, minDurationMs);
|
|
114
126
|
} else {
|
|
115
127
|
signal.addEventListener(
|
|
116
128
|
"abort",
|
|
117
129
|
() => {
|
|
118
130
|
const elapsed = performance.now() - startTime;
|
|
119
131
|
const remaining = Math.max(0, minDurationMs - elapsed);
|
|
120
|
-
setTimeout(stopCapture, remaining);
|
|
132
|
+
abortTimer = setTimeout(stopCapture, remaining);
|
|
121
133
|
},
|
|
122
134
|
{ once: true }
|
|
123
135
|
);
|
|
@@ -169,6 +181,7 @@ async function captureMotion(options = {}) {
|
|
|
169
181
|
const startTime = performance.now();
|
|
170
182
|
return new Promise((resolve) => {
|
|
171
183
|
let stopped = false;
|
|
184
|
+
let abortTimer = null;
|
|
172
185
|
const handler = (e) => {
|
|
173
186
|
samples.push({
|
|
174
187
|
timestamp: performance.now(),
|
|
@@ -184,6 +197,7 @@ async function captureMotion(options = {}) {
|
|
|
184
197
|
if (stopped) return;
|
|
185
198
|
stopped = true;
|
|
186
199
|
clearTimeout(maxTimer);
|
|
200
|
+
if (abortTimer !== null) clearTimeout(abortTimer);
|
|
187
201
|
window.removeEventListener("devicemotion", handler);
|
|
188
202
|
resolve(samples);
|
|
189
203
|
}
|
|
@@ -191,14 +205,14 @@ async function captureMotion(options = {}) {
|
|
|
191
205
|
const maxTimer = setTimeout(stopCapture, maxDurationMs);
|
|
192
206
|
if (signal) {
|
|
193
207
|
if (signal.aborted) {
|
|
194
|
-
setTimeout(stopCapture, minDurationMs);
|
|
208
|
+
abortTimer = setTimeout(stopCapture, minDurationMs);
|
|
195
209
|
} else {
|
|
196
210
|
signal.addEventListener(
|
|
197
211
|
"abort",
|
|
198
212
|
() => {
|
|
199
213
|
const elapsed = performance.now() - startTime;
|
|
200
214
|
const remaining = Math.max(0, minDurationMs - elapsed);
|
|
201
|
-
setTimeout(stopCapture, remaining);
|
|
215
|
+
abortTimer = setTimeout(stopCapture, remaining);
|
|
202
216
|
},
|
|
203
217
|
{ once: true }
|
|
204
218
|
);
|
|
@@ -218,6 +232,7 @@ function captureTouch(element, options = {}) {
|
|
|
218
232
|
const startTime = performance.now();
|
|
219
233
|
return new Promise((resolve) => {
|
|
220
234
|
let stopped = false;
|
|
235
|
+
let abortTimer = null;
|
|
221
236
|
const handler = (e) => {
|
|
222
237
|
samples.push({
|
|
223
238
|
timestamp: performance.now(),
|
|
@@ -232,6 +247,7 @@ function captureTouch(element, options = {}) {
|
|
|
232
247
|
if (stopped) return;
|
|
233
248
|
stopped = true;
|
|
234
249
|
clearTimeout(maxTimer);
|
|
250
|
+
if (abortTimer !== null) clearTimeout(abortTimer);
|
|
235
251
|
element.removeEventListener("pointermove", handler);
|
|
236
252
|
element.removeEventListener("pointerdown", handler);
|
|
237
253
|
sdkLog(`[Entros SDK] Touch capture stopped: ${samples.length} samples collected`);
|
|
@@ -243,14 +259,14 @@ function captureTouch(element, options = {}) {
|
|
|
243
259
|
const maxTimer = setTimeout(stopCapture, maxDurationMs);
|
|
244
260
|
if (signal) {
|
|
245
261
|
if (signal.aborted) {
|
|
246
|
-
setTimeout(stopCapture, minDurationMs);
|
|
262
|
+
abortTimer = setTimeout(stopCapture, minDurationMs);
|
|
247
263
|
} else {
|
|
248
264
|
signal.addEventListener(
|
|
249
265
|
"abort",
|
|
250
266
|
() => {
|
|
251
267
|
const elapsed = performance.now() - startTime;
|
|
252
268
|
const remaining = Math.max(0, minDurationMs - elapsed);
|
|
253
|
-
setTimeout(stopCapture, remaining);
|
|
269
|
+
abortTimer = setTimeout(stopCapture, remaining);
|
|
254
270
|
},
|
|
255
271
|
{ once: true }
|
|
256
272
|
);
|
|
@@ -1257,6 +1273,45 @@ async function generateSolanaProof(current, previous, wasmPath, zkeyPath, thresh
|
|
|
1257
1273
|
return serializeProof(proof, publicSignals);
|
|
1258
1274
|
}
|
|
1259
1275
|
|
|
1276
|
+
// src/submit/receipt.ts
|
|
1277
|
+
var PUBKEY_BYTES = 32;
|
|
1278
|
+
var SIGNATURE_BYTES = 64;
|
|
1279
|
+
var MESSAGE_BYTES = 72;
|
|
1280
|
+
function bytesToHex(bytes) {
|
|
1281
|
+
let out = "";
|
|
1282
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
1283
|
+
out += (bytes[i] ?? 0).toString(16).padStart(2, "0");
|
|
1284
|
+
}
|
|
1285
|
+
return out;
|
|
1286
|
+
}
|
|
1287
|
+
function hexToBytes(hex, expectedLen) {
|
|
1288
|
+
const trimmed = hex.startsWith("0x") || hex.startsWith("0X") ? hex.slice(2) : hex;
|
|
1289
|
+
if (trimmed.length !== expectedLen * 2) return null;
|
|
1290
|
+
if (!/^[0-9a-fA-F]+$/.test(trimmed)) return null;
|
|
1291
|
+
const out = new Uint8Array(expectedLen);
|
|
1292
|
+
for (let i = 0; i < expectedLen; i += 1) {
|
|
1293
|
+
out[i] = parseInt(trimmed.substr(i * 2, 2), 16);
|
|
1294
|
+
}
|
|
1295
|
+
return out;
|
|
1296
|
+
}
|
|
1297
|
+
function decodeSignedReceipt(receipt) {
|
|
1298
|
+
const publicKey = hexToBytes(receipt.validator_pubkey_hex, PUBKEY_BYTES);
|
|
1299
|
+
const signature = hexToBytes(receipt.signature_hex, SIGNATURE_BYTES);
|
|
1300
|
+
const message = hexToBytes(receipt.message_hex, MESSAGE_BYTES);
|
|
1301
|
+
if (!publicKey || !signature || !message) return null;
|
|
1302
|
+
return { publicKey, signature, message };
|
|
1303
|
+
}
|
|
1304
|
+
async function buildEd25519ReceiptIx(receipt) {
|
|
1305
|
+
const decoded = decodeSignedReceipt(receipt);
|
|
1306
|
+
if (!decoded) return null;
|
|
1307
|
+
const { Ed25519Program } = await import("@solana/web3.js");
|
|
1308
|
+
return Ed25519Program.createInstructionWithPublicKey({
|
|
1309
|
+
publicKey: decoded.publicKey,
|
|
1310
|
+
message: decoded.message,
|
|
1311
|
+
signature: decoded.signature
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1260
1315
|
// src/submit/wallet.ts
|
|
1261
1316
|
async function requestSasAttestation(wallet, walletAddress, relayerUrl, relayerApiKey, serverNonce) {
|
|
1262
1317
|
try {
|
|
@@ -1298,14 +1353,22 @@ async function requestSasAttestation(wallet, walletAddress, relayerUrl, relayerA
|
|
|
1298
1353
|
return attestData.attestation_tx;
|
|
1299
1354
|
}
|
|
1300
1355
|
}
|
|
1301
|
-
} catch {
|
|
1356
|
+
} catch (err) {
|
|
1357
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1358
|
+
sdkWarn(`[Entros SDK] SAS attestation request failed: ${msg}`);
|
|
1302
1359
|
}
|
|
1303
1360
|
return void 0;
|
|
1304
1361
|
}
|
|
1305
1362
|
async function submitViaWallet(proof, commitment, options) {
|
|
1306
1363
|
try {
|
|
1307
1364
|
const anchor = await import("@coral-xyz/anchor");
|
|
1308
|
-
const {
|
|
1365
|
+
const {
|
|
1366
|
+
PublicKey,
|
|
1367
|
+
SystemProgram,
|
|
1368
|
+
Transaction,
|
|
1369
|
+
ComputeBudgetProgram,
|
|
1370
|
+
SYSVAR_INSTRUCTIONS_PUBKEY
|
|
1371
|
+
} = await import("@solana/web3.js");
|
|
1309
1372
|
const provider = new anchor.AnchorProvider(
|
|
1310
1373
|
options.connection,
|
|
1311
1374
|
options.wallet,
|
|
@@ -1474,7 +1537,7 @@ async function submitViaWallet(proof, commitment, options) {
|
|
|
1474
1537
|
false,
|
|
1475
1538
|
TOKEN_2022_PROGRAM_ID
|
|
1476
1539
|
);
|
|
1477
|
-
await anchorProgram.methods.mintAnchor(Array.from(commitment)).accounts({
|
|
1540
|
+
const mintAnchorIx = await anchorProgram.methods.mintAnchor(Array.from(commitment)).accounts({
|
|
1478
1541
|
user: provider.wallet.publicKey,
|
|
1479
1542
|
identityState: identityPda,
|
|
1480
1543
|
mint: mintPda,
|
|
@@ -1486,8 +1549,35 @@ async function submitViaWallet(proof, commitment, options) {
|
|
|
1486
1549
|
tokenProgram: TOKEN_2022_PROGRAM_ID,
|
|
1487
1550
|
systemProgram: SystemProgram.programId,
|
|
1488
1551
|
protocolConfig: protocolConfigPda,
|
|
1489
|
-
treasury: treasuryPda
|
|
1490
|
-
|
|
1552
|
+
treasury: treasuryPda,
|
|
1553
|
+
instructionsSysvar: SYSVAR_INSTRUCTIONS_PUBKEY
|
|
1554
|
+
}).instruction();
|
|
1555
|
+
let ed25519Ix = null;
|
|
1556
|
+
if (options.signedReceipt) {
|
|
1557
|
+
ed25519Ix = await buildEd25519ReceiptIx(options.signedReceipt);
|
|
1558
|
+
if (ed25519Ix) {
|
|
1559
|
+
sdkLog(
|
|
1560
|
+
"[Entros SDK] Bundling validator-signed mint receipt before mint_anchor"
|
|
1561
|
+
);
|
|
1562
|
+
} else {
|
|
1563
|
+
sdkWarn(
|
|
1564
|
+
"[Entros SDK] signedReceipt provided but failed to decode; minting without binding"
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
} else {
|
|
1568
|
+
sdkLog(
|
|
1569
|
+
"[Entros SDK] No validator receipt available; minting without binding (Phase 3 log-only)"
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const tx = new Transaction();
|
|
1573
|
+
if (ed25519Ix) tx.add(ed25519Ix);
|
|
1574
|
+
tx.add(mintAnchorIx);
|
|
1575
|
+
tx.feePayer = provider.wallet.publicKey;
|
|
1576
|
+
tx.recentBlockhash = (await options.connection.getLatestBlockhash("confirmed")).blockhash;
|
|
1577
|
+
txSig = await options.wallet.sendTransaction(tx, options.connection, {
|
|
1578
|
+
skipPreflight: true
|
|
1579
|
+
});
|
|
1580
|
+
await options.connection.confirmTransaction(txSig, "confirmed");
|
|
1491
1581
|
}
|
|
1492
1582
|
const attestationTx = options.relayerUrl ? await requestSasAttestation(
|
|
1493
1583
|
options.wallet,
|
|
@@ -1729,11 +1819,24 @@ function fromBase64(b64) {
|
|
|
1729
1819
|
var STORAGE_KEY = "entros-protocol-verification-data";
|
|
1730
1820
|
var ENCRYPTED_VERSION = 2;
|
|
1731
1821
|
var inMemoryStore = null;
|
|
1822
|
+
var privacyFallbackCallback = null;
|
|
1823
|
+
function setPrivacyFallback(cb) {
|
|
1824
|
+
privacyFallbackCallback = cb ?? null;
|
|
1825
|
+
}
|
|
1732
1826
|
function isEncryptedEnvelope(obj) {
|
|
1733
|
-
|
|
1827
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
1828
|
+
const o = obj;
|
|
1829
|
+
return o.v === ENCRYPTED_VERSION && typeof o.iv === "string" && o.iv.length > 0 && typeof o.ct === "string" && o.ct.length > 0;
|
|
1734
1830
|
}
|
|
1735
1831
|
function isPlaintextData(obj) {
|
|
1736
|
-
|
|
1832
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
1833
|
+
const o = obj;
|
|
1834
|
+
if (!Array.isArray(o.fingerprint)) return false;
|
|
1835
|
+
if (!o.fingerprint.every((bit) => typeof bit === "number")) return false;
|
|
1836
|
+
if (typeof o.salt !== "string" || o.salt.length === 0) return false;
|
|
1837
|
+
if (typeof o.commitment !== "string" || o.commitment.length === 0) return false;
|
|
1838
|
+
if (typeof o.timestamp !== "number" || !Number.isFinite(o.timestamp)) return false;
|
|
1839
|
+
return true;
|
|
1737
1840
|
}
|
|
1738
1841
|
async function fetchIdentityState(walletPubkey, connection) {
|
|
1739
1842
|
try {
|
|
@@ -1772,14 +1875,34 @@ async function fetchIdentityState(walletPubkey, connection) {
|
|
|
1772
1875
|
async function storeVerificationData(data) {
|
|
1773
1876
|
try {
|
|
1774
1877
|
if (!hasCryptoSupport()) {
|
|
1775
|
-
|
|
1776
|
-
|
|
1878
|
+
const allowPlaintext = privacyFallbackCallback ? await privacyFallbackCallback().catch(() => false) : false;
|
|
1879
|
+
if (allowPlaintext) {
|
|
1880
|
+
sdkWarn(
|
|
1881
|
+
"[Entros SDK] Crypto unavailable; user-approved plaintext storage"
|
|
1882
|
+
);
|
|
1883
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
|
1884
|
+
} else {
|
|
1885
|
+
sdkWarn(
|
|
1886
|
+
"[Entros SDK] Crypto unavailable and no privacy-fallback approval \u2014 using in-memory storage (data lost on reload)"
|
|
1887
|
+
);
|
|
1888
|
+
inMemoryStore = data;
|
|
1889
|
+
}
|
|
1777
1890
|
return;
|
|
1778
1891
|
}
|
|
1779
1892
|
const key = await getOrCreateEncryptionKey();
|
|
1780
1893
|
if (!key) {
|
|
1781
|
-
|
|
1782
|
-
|
|
1894
|
+
const allowPlaintext = privacyFallbackCallback ? await privacyFallbackCallback().catch(() => false) : false;
|
|
1895
|
+
if (allowPlaintext) {
|
|
1896
|
+
sdkWarn(
|
|
1897
|
+
"[Entros SDK] Encryption key unavailable; user-approved plaintext storage"
|
|
1898
|
+
);
|
|
1899
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
|
|
1900
|
+
} else {
|
|
1901
|
+
sdkWarn(
|
|
1902
|
+
"[Entros SDK] Encryption key unavailable and no privacy-fallback approval \u2014 using in-memory storage"
|
|
1903
|
+
);
|
|
1904
|
+
inMemoryStore = data;
|
|
1905
|
+
}
|
|
1783
1906
|
return;
|
|
1784
1907
|
}
|
|
1785
1908
|
const { iv, ct } = await encrypt(JSON.stringify(data), key);
|
|
@@ -1865,6 +1988,9 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
|
|
|
1865
1988
|
sdkLog(
|
|
1866
1989
|
`[Entros SDK] Feature vector: ${features.length} dimensions, ${nonZero} non-zero. Audio[0..43]: ${features.slice(0, 44).filter((v) => v !== 0).length} non-zero. Motion/Mouse[44..97]: ${features.slice(44, 98).filter((v) => v !== 0).length} non-zero. Touch[98..133]: ${features.slice(98, 134).filter((v) => v !== 0).length} non-zero.`
|
|
1867
1990
|
);
|
|
1991
|
+
const fingerprint = simhash(normalizedFeatures);
|
|
1992
|
+
const tbh = await generateTBH(fingerprint);
|
|
1993
|
+
let signedReceipt;
|
|
1868
1994
|
onProgress?.("Validating...");
|
|
1869
1995
|
if (config.relayerUrl && walletAddress) {
|
|
1870
1996
|
try {
|
|
@@ -1876,6 +2002,7 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
|
|
|
1876
2002
|
}
|
|
1877
2003
|
const audioSamplesB64 = sensorData.audio?.samples ? encodeAudioAsBase64(sensorData.audio.samples) : void 0;
|
|
1878
2004
|
const audioSampleRateHz = sensorData.audio?.sampleRate;
|
|
2005
|
+
const commitmentNewHex = bytesToHex(tbh.commitmentBytes);
|
|
1879
2006
|
const validateController = new AbortController();
|
|
1880
2007
|
const validateTimer = setTimeout(() => validateController.abort(), 15e3);
|
|
1881
2008
|
const validateResponse = await fetch(validateUrl, {
|
|
@@ -1887,32 +2014,39 @@ async function extractFingerprintAndValidate(sensorData, config, walletAddress,
|
|
|
1887
2014
|
accel_magnitude: accelMagnitude,
|
|
1888
2015
|
wallet_id: walletAddress,
|
|
1889
2016
|
audio_samples_b64: audioSamplesB64,
|
|
1890
|
-
audio_sample_rate_hz: audioSampleRateHz
|
|
2017
|
+
audio_sample_rate_hz: audioSampleRateHz,
|
|
2018
|
+
commitment_new_hex: commitmentNewHex
|
|
1891
2019
|
}),
|
|
1892
2020
|
signal: validateController.signal
|
|
1893
2021
|
});
|
|
1894
2022
|
clearTimeout(validateTimer);
|
|
1895
2023
|
if (!validateResponse.ok) {
|
|
1896
2024
|
const errorBody = await validateResponse.json().catch(() => ({}));
|
|
1897
|
-
|
|
1898
|
-
const reason = typeof body.reason === "string" ? body.reason : void 0;
|
|
1899
|
-
sdkWarn(
|
|
1900
|
-
`[Entros SDK] Feature validation rejected by server${reason ? ` (reason: ${reason})` : ""}`
|
|
1901
|
-
);
|
|
2025
|
+
sdkWarn("[Entros SDK] Feature validation rejected by server");
|
|
1902
2026
|
return {
|
|
1903
2027
|
ok: false,
|
|
1904
|
-
error:
|
|
1905
|
-
reason
|
|
2028
|
+
error: errorBody.error || "Feature validation failed",
|
|
2029
|
+
reason: errorBody.reason
|
|
1906
2030
|
};
|
|
1907
2031
|
}
|
|
2032
|
+
try {
|
|
2033
|
+
const successBody = await validateResponse.json();
|
|
2034
|
+
if (successBody.signed_receipt) {
|
|
2035
|
+
signedReceipt = successBody.signed_receipt;
|
|
2036
|
+
}
|
|
2037
|
+
} catch {
|
|
2038
|
+
}
|
|
1908
2039
|
} catch (err) {
|
|
1909
2040
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1910
|
-
sdkWarn(`[Entros SDK] Feature validation unavailable: ${msg}
|
|
2041
|
+
sdkWarn(`[Entros SDK] Feature validation unavailable: ${msg}`);
|
|
2042
|
+
return {
|
|
2043
|
+
ok: false,
|
|
2044
|
+
error: "Validation service unreachable. Please check your connection and try again.",
|
|
2045
|
+
reason: "validation_unavailable"
|
|
2046
|
+
};
|
|
1911
2047
|
}
|
|
1912
2048
|
}
|
|
1913
|
-
|
|
1914
|
-
const tbh = await generateTBH(fingerprint);
|
|
1915
|
-
return { ok: true, features, f0Contour, accelMagnitude, fingerprint, tbh };
|
|
2049
|
+
return { ok: true, features, f0Contour, accelMagnitude, fingerprint, tbh, signedReceipt };
|
|
1916
2050
|
}
|
|
1917
2051
|
async function processSensorData(sensorData, config, wallet, connection, onProgress) {
|
|
1918
2052
|
const audioSamples = sensorData.audio?.samples.length ?? 0;
|
|
@@ -1983,7 +2117,7 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
1983
2117
|
reason: extraction.reason
|
|
1984
2118
|
};
|
|
1985
2119
|
}
|
|
1986
|
-
const { fingerprint, tbh, features } = extraction;
|
|
2120
|
+
const { fingerprint, tbh, features, signedReceipt } = extraction;
|
|
1987
2121
|
let isFirstVerification;
|
|
1988
2122
|
const previousData = await loadVerificationData();
|
|
1989
2123
|
if (wallet && connection) {
|
|
@@ -2073,7 +2207,14 @@ async function processSensorData(sensorData, config, wallet, connection, onProgr
|
|
|
2073
2207
|
submission = await submitViaWallet(
|
|
2074
2208
|
solanaProof ?? { proofBytes: new Uint8Array(0), publicInputs: [] },
|
|
2075
2209
|
tbh.commitmentBytes,
|
|
2076
|
-
{
|
|
2210
|
+
{
|
|
2211
|
+
wallet,
|
|
2212
|
+
connection,
|
|
2213
|
+
isFirstVerification: true,
|
|
2214
|
+
relayerUrl: config.relayerUrl,
|
|
2215
|
+
relayerApiKey: config.relayerApiKey,
|
|
2216
|
+
signedReceipt
|
|
2217
|
+
}
|
|
2077
2218
|
);
|
|
2078
2219
|
} else {
|
|
2079
2220
|
submission = await submitViaWallet(solanaProof, tbh.commitmentBytes, {
|
|
@@ -2375,70 +2516,6 @@ var PulseSession = class {
|
|
|
2375
2516
|
this.motionStageState = "captured";
|
|
2376
2517
|
this.touchStageState = "captured";
|
|
2377
2518
|
}
|
|
2378
|
-
/**
|
|
2379
|
-
* @internal
|
|
2380
|
-
*
|
|
2381
|
-
* Run the validation step of the verify pipeline only: feature extraction
|
|
2382
|
-
* + `/validate-features` POST. Returns the validation outcome without ever
|
|
2383
|
-
* touching the on-chain submission path. Mirrors the production user
|
|
2384
|
-
* flow's pre-payment gate — the validation server runs without requiring
|
|
2385
|
-
* the wallet to have SOL, just like a real user gets a validation result
|
|
2386
|
-
* before being prompted to sign the on-chain mint.
|
|
2387
|
-
*
|
|
2388
|
-
* Note: this is a strict subset of `complete()`. It skips the data-quality
|
|
2389
|
-
* gates and re-verification check that `processSensorData` performs. The
|
|
2390
|
-
* validation server still runs its full pipeline (Tier 1 + Tier 2 +
|
|
2391
|
-
* phrase binding); only the client-side pre-flight checks differ.
|
|
2392
|
-
*
|
|
2393
|
-
* Use case: red team campaigns measuring server-side validation at scale
|
|
2394
|
-
* without per-attempt SOL funding. Build-time gated identically to
|
|
2395
|
-
* `__injectSensorData`; throws in production builds.
|
|
2396
|
-
*/
|
|
2397
|
-
async __validateOnly(walletAddress) {
|
|
2398
|
-
if (true) {
|
|
2399
|
-
throw new Error(
|
|
2400
|
-
"PulseSession.__validateOnly is only available in internal test builds. Set IAM_INTERNAL_TEST=1 when building pulse-sdk from source."
|
|
2401
|
-
);
|
|
2402
|
-
}
|
|
2403
|
-
if (typeof walletAddress !== "string" || walletAddress.length === 0) {
|
|
2404
|
-
throw new Error(
|
|
2405
|
-
"__validateOnly requires a non-empty walletAddress string (used as wallet_id in the /validate-features payload)."
|
|
2406
|
-
);
|
|
2407
|
-
}
|
|
2408
|
-
const active = [];
|
|
2409
|
-
if (this.audioStageState === "capturing") active.push("audio");
|
|
2410
|
-
if (this.motionStageState === "capturing") active.push("motion");
|
|
2411
|
-
if (this.touchStageState === "capturing") active.push("touch");
|
|
2412
|
-
if (active.length > 0) {
|
|
2413
|
-
throw new Error(
|
|
2414
|
-
`Cannot validate: stages still capturing: ${active.join(", ")}`
|
|
2415
|
-
);
|
|
2416
|
-
}
|
|
2417
|
-
if (!this.audioData || this.motionData.length === 0 || this.touchData.length === 0) {
|
|
2418
|
-
throw new Error(
|
|
2419
|
-
"__validateOnly requires sensor data first \u2014 call __injectSensorData() before this."
|
|
2420
|
-
);
|
|
2421
|
-
}
|
|
2422
|
-
const sensorData = {
|
|
2423
|
-
audio: this.audioData,
|
|
2424
|
-
motion: this.motionData,
|
|
2425
|
-
touch: this.touchData,
|
|
2426
|
-
modalities: {
|
|
2427
|
-
audio: true,
|
|
2428
|
-
motion: true,
|
|
2429
|
-
touch: true
|
|
2430
|
-
}
|
|
2431
|
-
};
|
|
2432
|
-
const extraction = await extractFingerprintAndValidate(
|
|
2433
|
-
sensorData,
|
|
2434
|
-
this.config,
|
|
2435
|
-
walletAddress
|
|
2436
|
-
);
|
|
2437
|
-
if (!extraction.ok) {
|
|
2438
|
-
return { validated: false, error: extraction.error, reason: extraction.reason };
|
|
2439
|
-
}
|
|
2440
|
-
return { validated: true };
|
|
2441
|
-
}
|
|
2442
2519
|
// --- Complete ---
|
|
2443
2520
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Solana types are optional peer deps
|
|
2444
2521
|
async complete(wallet, connection, onProgress) {
|
|
@@ -2513,6 +2590,7 @@ var PulseSDK = class {
|
|
|
2513
2590
|
...config
|
|
2514
2591
|
};
|
|
2515
2592
|
setDebug(config.debug ?? false);
|
|
2593
|
+
setPrivacyFallback(config.onPrivacyFallback);
|
|
2516
2594
|
}
|
|
2517
2595
|
/**
|
|
2518
2596
|
* Create a staged capture session for event-driven control.
|