@orbinum/sdk 1.3.1 → 2.0.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/{chunk-A2ZRMEYW.mjs → chunk-VYKKBXOE.mjs} +51 -28
- package/dist/{index-V5Z9igEN.d.mts → index-CLpM1984.d.mts} +42 -9
- package/dist/{index-V5Z9igEN.d.ts → index-CLpM1984.d.ts} +42 -9
- package/dist/index.d.mts +399 -105
- package/dist/index.d.ts +399 -105
- package/dist/index.js +595 -338
- package/dist/index.mjs +487 -257
- package/dist/wallet/worker/index.d.mts +1 -1
- package/dist/wallet/worker/index.d.ts +1 -1
- package/dist/wallet/worker/index.js +18 -11
- package/dist/wallet/worker/index.mjs +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -109,6 +109,7 @@ __export(index_exports, {
|
|
|
109
109
|
clearKnownEphWindow: () => clearKnownEphWindow,
|
|
110
110
|
clearSession: () => clearSession,
|
|
111
111
|
collectNullifiersToQuery: () => collectNullifiersToQuery,
|
|
112
|
+
collectOutgoingFacts: () => collectOutgoingFacts,
|
|
112
113
|
collectScanEntries: () => collectScanEntries,
|
|
113
114
|
commitmentHexOf: () => commitmentHexOf,
|
|
114
115
|
computeNoteCommitment: () => computeNoteCommitment,
|
|
@@ -184,6 +185,7 @@ __export(index_exports, {
|
|
|
184
185
|
getSubstrateSignerFromExtension: () => import_pjs_signer.getPolkadotSignerFromPjs,
|
|
185
186
|
hasCachedSession: () => hasCachedSession,
|
|
186
187
|
hasInjectedExtensions: () => hasInjectedExtensions,
|
|
188
|
+
hasSourcePk: () => hasSourcePk,
|
|
187
189
|
hexToBigint: () => hexToBigint,
|
|
188
190
|
hexToNumber: () => hexToNumber,
|
|
189
191
|
implicitSubstrateToEvm: () => implicitSubstrateToEvm,
|
|
@@ -195,6 +197,7 @@ __export(index_exports, {
|
|
|
195
197
|
isConnectionLossError: () => isConnectionLossError,
|
|
196
198
|
isEvmAddress: () => isEvmAddress,
|
|
197
199
|
isGhostNoteError: () => isGhostNoteError,
|
|
200
|
+
isHexOfLength: () => isHexOfLength,
|
|
198
201
|
isImplicitEvmAccount: () => isImplicitEvmAccount,
|
|
199
202
|
isNativeAsset: () => isNativeAsset,
|
|
200
203
|
isNoteSelfConsistent: () => isNoteSelfConsistent,
|
|
@@ -207,6 +210,7 @@ __export(index_exports, {
|
|
|
207
210
|
mapExtrinsicArgs: () => mapExtrinsicArgs,
|
|
208
211
|
mapZkEventData: () => mapZkEventData,
|
|
209
212
|
markInputsSpent: () => markInputsSpent,
|
|
213
|
+
mergeProvenance: () => mergeProvenance,
|
|
210
214
|
normalizeChainFingerprint: () => normalizeChainFingerprint,
|
|
211
215
|
normalizeEvmAddress: () => normalizeEvmAddress,
|
|
212
216
|
normalizeNote: () => normalizeNote,
|
|
@@ -221,6 +225,7 @@ __export(index_exports, {
|
|
|
221
225
|
noteTxKind: () => noteTxKind,
|
|
222
226
|
openOutgoingBlob: () => openOutgoingBlob,
|
|
223
227
|
openPaymentSlip: () => openPaymentSlip,
|
|
228
|
+
outranks: () => outranks,
|
|
224
229
|
pairwiseEphWindow: () => pairwiseEphWindow,
|
|
225
230
|
palletErrorKind: () => palletErrorKind,
|
|
226
231
|
parseAmount: () => parseAmount,
|
|
@@ -235,6 +240,7 @@ __export(index_exports, {
|
|
|
235
240
|
recoverOwnerPkPoint: () => recoverOwnerPkPoint,
|
|
236
241
|
recoverSelfStealthNote: () => recoverSelfStealthNote,
|
|
237
242
|
refuseIfAlreadySpent: () => refuseIfAlreadySpent,
|
|
243
|
+
regeneratePaymentSlip: () => regeneratePaymentSlip,
|
|
238
244
|
removeByCommitment: () => removeByCommitment,
|
|
239
245
|
requireSessionKeys: () => requireSessionKeys,
|
|
240
246
|
reservePairwiseIndex: () => reservePairwiseIndex,
|
|
@@ -248,6 +254,8 @@ __export(index_exports, {
|
|
|
248
254
|
scanAbortError: () => scanAbortError,
|
|
249
255
|
sealOutgoingBlob: () => sealOutgoingBlob,
|
|
250
256
|
sealPaymentSlip: () => sealPaymentSlip,
|
|
257
|
+
selectDescribingNote: () => selectDescribingNote,
|
|
258
|
+
selectDescribingNoteByCommitment: () => selectDescribingNoteByCommitment,
|
|
251
259
|
selectGhosts: () => selectGhosts,
|
|
252
260
|
selectNotes: () => selectNotes,
|
|
253
261
|
selfEphWindow: () => selfEphWindow,
|
|
@@ -285,6 +293,9 @@ __export(index_exports, {
|
|
|
285
293
|
module.exports = __toCommonJS(index_exports);
|
|
286
294
|
|
|
287
295
|
// src/foundation/encoding/hex.ts
|
|
296
|
+
function isHexOfLength(value, byteLen) {
|
|
297
|
+
return typeof value === "string" && new RegExp(`^0x[0-9a-fA-F]{${byteLen * 2}}$`).test(value);
|
|
298
|
+
}
|
|
288
299
|
function toHex(bytes) {
|
|
289
300
|
return "0x" + Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
290
301
|
}
|
|
@@ -293,11 +304,12 @@ function fromHex(hex) {
|
|
|
293
304
|
if (clean.length % 2 !== 0) {
|
|
294
305
|
throw new Error(`Invalid hex string \u2014 odd length: "${hex}"`);
|
|
295
306
|
}
|
|
307
|
+
if (!/^[0-9a-fA-F]*$/.test(clean)) {
|
|
308
|
+
throw new Error("Invalid hex string \u2014 non-hex characters");
|
|
309
|
+
}
|
|
296
310
|
const bytes = new Uint8Array(clean.length / 2);
|
|
297
311
|
for (let i = 0; i < bytes.length; i++) {
|
|
298
|
-
|
|
299
|
-
if (isNaN(byte)) throw new Error(`Invalid hex character at position ${i * 2}`);
|
|
300
|
-
bytes[i] = byte;
|
|
312
|
+
bytes[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
|
|
301
313
|
}
|
|
302
314
|
return bytes;
|
|
303
315
|
}
|
|
@@ -315,7 +327,13 @@ function scalarToHex(value) {
|
|
|
315
327
|
}
|
|
316
328
|
|
|
317
329
|
// src/foundation/encoding/bytes.ts
|
|
330
|
+
function assert32ByteRange(n, fn) {
|
|
331
|
+
if (n < 0n || n >= 1n << 256n) {
|
|
332
|
+
throw new Error(`${fn}: value does not fit in 32 bytes: ${n}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
318
335
|
function bigintTo32Le(n) {
|
|
336
|
+
assert32ByteRange(n, "bigintTo32Le");
|
|
319
337
|
const buf = new Uint8Array(32);
|
|
320
338
|
let v = n;
|
|
321
339
|
for (let i = 0; i < 32; i++) {
|
|
@@ -332,6 +350,7 @@ function bytesToBigintLE(bytes) {
|
|
|
332
350
|
return result;
|
|
333
351
|
}
|
|
334
352
|
function bigintTo32Be(n) {
|
|
353
|
+
assert32ByteRange(n, "bigintTo32Be");
|
|
335
354
|
const buf = new Uint8Array(32);
|
|
336
355
|
let v = n;
|
|
337
356
|
for (let i = 31; i >= 0 && v > 0n; i--) {
|
|
@@ -341,6 +360,7 @@ function bigintTo32Be(n) {
|
|
|
341
360
|
return buf;
|
|
342
361
|
}
|
|
343
362
|
function bigintTo32LeArr(n) {
|
|
363
|
+
assert32ByteRange(n, "bigintTo32LeArr");
|
|
344
364
|
const out = new Array(32).fill(0);
|
|
345
365
|
let v = n;
|
|
346
366
|
for (let i = 0; i < 32; i++) {
|
|
@@ -827,7 +847,7 @@ var import_sha22 = require("@noble/hashes/sha2.js");
|
|
|
827
847
|
var KEY_DOMAIN = new TextEncoder().encode("orbinum-note-encryption-v1");
|
|
828
848
|
var VIEW_TAG_DOMAIN = new TextEncoder().encode("orbinum-view-tag-v1");
|
|
829
849
|
var MEMO_PLAINTEXT_SIZE = 120;
|
|
830
|
-
function serializeMemo(value, ownerPk, blinding, assetId,
|
|
850
|
+
function serializeMemo(value, ownerPk, blinding, assetId, sourcePk, circuitVersion) {
|
|
831
851
|
const buf = new Uint8Array(MEMO_PLAINTEXT_SIZE);
|
|
832
852
|
const view = new DataView(buf.buffer);
|
|
833
853
|
view.setBigUint64(0, value & 0xffffffffffffffffn, true);
|
|
@@ -835,7 +855,7 @@ function serializeMemo(value, ownerPk, blinding, assetId, counterpartyPk, circui
|
|
|
835
855
|
buf.set(ownerPk.slice(0, 32), 16);
|
|
836
856
|
buf.set(blinding.slice(0, 32), 48);
|
|
837
857
|
view.setUint32(80, assetId >>> 0, true);
|
|
838
|
-
buf.set(
|
|
858
|
+
buf.set(sourcePk.slice(0, 32), 84);
|
|
839
859
|
view.setUint32(116, circuitVersion >>> 0, true);
|
|
840
860
|
return buf;
|
|
841
861
|
}
|
|
@@ -873,9 +893,9 @@ function parsePlaintext(nonce, ciphertextWithMac, encKey) {
|
|
|
873
893
|
const ownerPk = bytesToBigintLE(plaintext.slice(16, 48));
|
|
874
894
|
const blinding = bytesToBigintLE(plaintext.slice(48, 80));
|
|
875
895
|
const assetId = BigInt(view.getUint32(80, true));
|
|
876
|
-
const
|
|
896
|
+
const sourcePk = bytesToBigintLE(plaintext.slice(84, 116));
|
|
877
897
|
const circuitVersion = view.getUint32(116, true);
|
|
878
|
-
return { value, ownerPk, blinding, assetId,
|
|
898
|
+
return { value, ownerPk, blinding, assetId, sourcePk, circuitVersion };
|
|
879
899
|
} catch {
|
|
880
900
|
return null;
|
|
881
901
|
}
|
|
@@ -893,19 +913,19 @@ var EncryptedMemo = {
|
|
|
893
913
|
* (from PrivacyKeyManager.getViewingPublicKeyPacked() or
|
|
894
914
|
* decoded from a privacy address).
|
|
895
915
|
* Pass `new Uint8Array(32)` (all zeros) for a publicly-readable memo.
|
|
896
|
-
* @param
|
|
916
|
+
* @param sourcePk 32-byte counterparty BJJ Ax. Default: all zeros.
|
|
897
917
|
* @param circuitVersion ZK circuit version the note is spent under. Default: 0.
|
|
898
918
|
* @param ephSkOverride 32-byte ephemeral secret key (stealth coordination). Optional.
|
|
899
919
|
* @returns 180-byte encrypted memo: nonce(12) || ciphertext+MAC(136) || ephPk(32).
|
|
900
920
|
*/
|
|
901
|
-
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked,
|
|
921
|
+
encrypt(value, ownerPk, blinding, assetId, commitment, recipientIvkPacked, sourcePk = new Uint8Array(32), circuitVersion = 0, ephSkOverride) {
|
|
902
922
|
const nonce = (0, import_utils.randomBytes)(NONCE_SIZE);
|
|
903
923
|
const plaintext = serializeMemo(
|
|
904
924
|
value,
|
|
905
925
|
ownerPk,
|
|
906
926
|
blinding,
|
|
907
927
|
assetId,
|
|
908
|
-
|
|
928
|
+
sourcePk,
|
|
909
929
|
circuitVersion
|
|
910
930
|
);
|
|
911
931
|
const isZeroKey = recipientIvkPacked.every((b) => b === 0);
|
|
@@ -1117,10 +1137,72 @@ var import_utils3 = require("@noble/ciphers/utils.js");
|
|
|
1117
1137
|
var import_hkdf3 = require("@noble/hashes/hkdf.js");
|
|
1118
1138
|
var import_sha24 = require("@noble/hashes/sha2.js");
|
|
1119
1139
|
var import_baby_jubjub4 = require("@zk-kit/baby-jubjub");
|
|
1140
|
+
|
|
1141
|
+
// src/protocol/spend/coinSelection.ts
|
|
1142
|
+
var TRANSFER_TREE_DEPTH = 20;
|
|
1143
|
+
var LEAVES_PER_TREE = 1 << TRANSFER_TREE_DEPTH;
|
|
1144
|
+
function isValidLeafIndex(leafIndex) {
|
|
1145
|
+
return leafIndex !== null && leafIndex !== void 0 && Number.isSafeInteger(leafIndex) && leafIndex >= 0 && leafIndex < 2 ** 32;
|
|
1146
|
+
}
|
|
1147
|
+
function treeIdOf(note) {
|
|
1148
|
+
const idx = note.leafIndex;
|
|
1149
|
+
return isValidLeafIndex(idx) ? Math.floor(idx / LEAVES_PER_TREE) : 0;
|
|
1150
|
+
}
|
|
1151
|
+
function isSpendable(note) {
|
|
1152
|
+
return !note.spent && note.value > 0n;
|
|
1153
|
+
}
|
|
1154
|
+
function canPairWith(a, b) {
|
|
1155
|
+
return a.circuitVersion === b.circuitVersion && treeIdOf(a) === treeIdOf(b);
|
|
1156
|
+
}
|
|
1157
|
+
function selectNotes(notes, needed) {
|
|
1158
|
+
const unspent = notes.filter(isSpendable);
|
|
1159
|
+
const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
|
|
1160
|
+
const single = sorted.find((n) => n.value >= needed);
|
|
1161
|
+
if (single) return [single, null];
|
|
1162
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
1163
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
1164
|
+
const a = sorted[i];
|
|
1165
|
+
const b = sorted[j];
|
|
1166
|
+
if (a !== void 0 && b !== void 0 && canPairWith(a, b) && a.value + b.value >= needed) {
|
|
1167
|
+
return [a, b];
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
1172
|
+
for (let j = i + 1; j < sorted.length; j++) {
|
|
1173
|
+
const a = sorted[i];
|
|
1174
|
+
const b = sorted[j];
|
|
1175
|
+
if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
|
|
1176
|
+
return { needsConsolidation: true };
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
function buildDummyTransferInput(assetId) {
|
|
1183
|
+
const zeroSibling = "0x" + "00".repeat(32);
|
|
1184
|
+
return {
|
|
1185
|
+
nullifier: 0n,
|
|
1186
|
+
// Constraint 9: nullifier * is_dummy.out === 0 → must be 0
|
|
1187
|
+
value: 0n,
|
|
1188
|
+
// triggers is_dummy[i].out = 1 in the circuit
|
|
1189
|
+
assetId,
|
|
1190
|
+
// must match real note (Constraint 7)
|
|
1191
|
+
ownerPk: 0n,
|
|
1192
|
+
blinding: 0n,
|
|
1193
|
+
spendingKey: 1n,
|
|
1194
|
+
// arbitrary; EdDSA is disabled (enabled = 0) for dummy inputs
|
|
1195
|
+
pathSiblings: Array(TRANSFER_TREE_DEPTH).fill(zeroSibling),
|
|
1196
|
+
leafIndex: 0
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/protocol/memo/PaymentSlip.ts
|
|
1120
1201
|
var SLIP_DOMAIN = new TextEncoder().encode("orbinum-payment-slip-v1");
|
|
1121
1202
|
var NONCE_PREFIX2 = new TextEncoder().encode("SLP1");
|
|
1122
1203
|
var EPH_PK_SIZE2 = 32;
|
|
1123
1204
|
var NONCE_SUFFIX_SIZE2 = 8;
|
|
1205
|
+
var MAX_SLIP_ENVELOPE_SIZE = 4096;
|
|
1124
1206
|
function deriveSlipKey(sharedSecret) {
|
|
1125
1207
|
return (0, import_hkdf3.hkdf)(import_sha24.sha256, sharedSecret, void 0, SLIP_DOMAIN, 32);
|
|
1126
1208
|
}
|
|
@@ -1155,6 +1237,7 @@ function sealPaymentSlip(recipientIvkPacked, fields) {
|
|
|
1155
1237
|
}
|
|
1156
1238
|
function openPaymentSlip(recipientIvsk, envelope) {
|
|
1157
1239
|
if (envelope.length < EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2 + 16) return null;
|
|
1240
|
+
if (envelope.length > MAX_SLIP_ENVELOPE_SIZE) return null;
|
|
1158
1241
|
try {
|
|
1159
1242
|
const ephPkPacked = envelope.subarray(0, EPH_PK_SIZE2);
|
|
1160
1243
|
const ephPkPoint = (0, import_baby_jubjub4.unpackPoint)(bytesToBigintLE(ephPkPacked));
|
|
@@ -1166,11 +1249,19 @@ function openPaymentSlip(recipientIvsk, envelope) {
|
|
|
1166
1249
|
const sealed = envelope.subarray(EPH_PK_SIZE2 + NONCE_SUFFIX_SIZE2);
|
|
1167
1250
|
const cipher = (0, import_chacha3.chacha20poly1305)(slipKey, buildNonce2(suffix));
|
|
1168
1251
|
const plaintext = cipher.decrypt(sealed);
|
|
1169
|
-
const
|
|
1170
|
-
if (
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
return
|
|
1252
|
+
const parsed = JSON.parse(new TextDecoder().decode(plaintext));
|
|
1253
|
+
if (!isHexOfLength(parsed["commitmentHex"], 32)) return null;
|
|
1254
|
+
if (!isHexOfLength(parsed["encryptedMemo"], ENCRYPTED_MEMO_SIZE)) return null;
|
|
1255
|
+
const leafIndex = parsed["leafIndex"];
|
|
1256
|
+
if (leafIndex !== void 0 && !isValidLeafIndex(leafIndex)) return null;
|
|
1257
|
+
const rawTxHash = parsed["txHash"];
|
|
1258
|
+
const txHash = isHexOfLength(rawTxHash, 32) ? rawTxHash : void 0;
|
|
1259
|
+
return {
|
|
1260
|
+
commitmentHex: parsed["commitmentHex"],
|
|
1261
|
+
encryptedMemo: parsed["encryptedMemo"],
|
|
1262
|
+
...leafIndex !== void 0 ? { leafIndex } : {},
|
|
1263
|
+
...txHash !== void 0 ? { txHash } : {}
|
|
1264
|
+
};
|
|
1174
1265
|
} catch {
|
|
1175
1266
|
return null;
|
|
1176
1267
|
}
|
|
@@ -1299,14 +1390,14 @@ var NoteBuilder = class {
|
|
|
1299
1390
|
const ownerPk = input.ownerPk ?? 0n;
|
|
1300
1391
|
const blinding = input.blinding ?? BigInt(Date.now());
|
|
1301
1392
|
const spendingKey = input.spendingKey ?? 0n;
|
|
1302
|
-
const
|
|
1393
|
+
const sourcePk = input.sourcePk ?? 0n;
|
|
1303
1394
|
const circuitVersion = input.circuitVersion ?? CURRENT_CIRCUIT_VERSION;
|
|
1304
1395
|
const useStealth = input.viewingPublicKey !== void 0 && input.recipientOwnerPk !== void 0;
|
|
1305
1396
|
let memo;
|
|
1306
1397
|
if (useStealth) {
|
|
1307
1398
|
const recipientOwnerPk = input.recipientOwnerPk;
|
|
1308
1399
|
const recipientIvkPacked = input.viewingPublicKey;
|
|
1309
|
-
const ephSk = (0, import_utils4.randomBytes)(32);
|
|
1400
|
+
const ephSk = input.ephSkOverride ?? (0, import_utils4.randomBytes)(32);
|
|
1310
1401
|
const ivkPackedBigint = bytesToBigintLE(recipientIvkPacked);
|
|
1311
1402
|
const ivkPoint = (0, import_baby_jubjub7.unpackPoint)(ivkPackedBigint);
|
|
1312
1403
|
if (!ivkPoint)
|
|
@@ -1334,7 +1425,7 @@ var NoteBuilder = class {
|
|
|
1334
1425
|
Number(assetId),
|
|
1335
1426
|
stealthCommitmentBytes,
|
|
1336
1427
|
recipientIvkPacked,
|
|
1337
|
-
bigintTo32Le(
|
|
1428
|
+
bigintTo32Le(sourcePk),
|
|
1338
1429
|
circuitVersion,
|
|
1339
1430
|
ephSk
|
|
1340
1431
|
)
|
|
@@ -1376,7 +1467,7 @@ var NoteBuilder = class {
|
|
|
1376
1467
|
commitmentHex: toHex(commitmentBytes2),
|
|
1377
1468
|
nullifierHex: toHex(nullifierBytes2),
|
|
1378
1469
|
memo,
|
|
1379
|
-
|
|
1470
|
+
sourcePk,
|
|
1380
1471
|
...ovkBlob ? { ovkBlob } : {}
|
|
1381
1472
|
};
|
|
1382
1473
|
}
|
|
@@ -1392,7 +1483,7 @@ var NoteBuilder = class {
|
|
|
1392
1483
|
Number(assetId),
|
|
1393
1484
|
commitmentBytes,
|
|
1394
1485
|
input.viewingPublicKey,
|
|
1395
|
-
bigintTo32Le(
|
|
1486
|
+
bigintTo32Le(sourcePk),
|
|
1396
1487
|
circuitVersion,
|
|
1397
1488
|
input.ephSkOverride
|
|
1398
1489
|
)
|
|
@@ -1415,7 +1506,7 @@ var NoteBuilder = class {
|
|
|
1415
1506
|
commitmentHex: toHex(commitmentBytes),
|
|
1416
1507
|
nullifierHex: toHex(nullifierBytes),
|
|
1417
1508
|
memo,
|
|
1418
|
-
|
|
1509
|
+
sourcePk
|
|
1419
1510
|
};
|
|
1420
1511
|
}
|
|
1421
1512
|
/**
|
|
@@ -1427,10 +1518,10 @@ var NoteBuilder = class {
|
|
|
1427
1518
|
* @param note The ZkNote whose fields populate the plaintext.
|
|
1428
1519
|
* @param recipientIvkPacked 32-byte LE packed BJJ viewing public key of the recipient.
|
|
1429
1520
|
* Pass `new Uint8Array(32)` (default) for a public/dummy memo.
|
|
1430
|
-
* @param
|
|
1521
|
+
* @param sourcePk 32-byte counterparty BabyJubJub Ax.
|
|
1431
1522
|
* Pass `new Uint8Array(32)` (default) for no counterparty.
|
|
1432
1523
|
*/
|
|
1433
|
-
static buildMemo(note, recipientIvkPacked,
|
|
1524
|
+
static buildMemo(note, recipientIvkPacked, sourcePk) {
|
|
1434
1525
|
return EncryptedMemo.encrypt(
|
|
1435
1526
|
note.value,
|
|
1436
1527
|
bigintTo32Le(note.ownerPk),
|
|
@@ -1438,7 +1529,7 @@ var NoteBuilder = class {
|
|
|
1438
1529
|
Number(note.assetId),
|
|
1439
1530
|
bigintTo32Le(note.commitment),
|
|
1440
1531
|
recipientIvkPacked ?? new Uint8Array(32),
|
|
1441
|
-
|
|
1532
|
+
sourcePk ?? bigintTo32Le(note.sourcePk ?? 0n),
|
|
1442
1533
|
note.circuitVersion
|
|
1443
1534
|
);
|
|
1444
1535
|
}
|
|
@@ -1446,67 +1537,6 @@ var NoteBuilder = class {
|
|
|
1446
1537
|
|
|
1447
1538
|
// src/protocol/note/NoteDecryptor.ts
|
|
1448
1539
|
var import_poseidon_lite2 = require("poseidon-lite");
|
|
1449
|
-
|
|
1450
|
-
// src/protocol/spend/coinSelection.ts
|
|
1451
|
-
var TRANSFER_TREE_DEPTH = 20;
|
|
1452
|
-
var LEAVES_PER_TREE = 1 << TRANSFER_TREE_DEPTH;
|
|
1453
|
-
function isValidLeafIndex(leafIndex) {
|
|
1454
|
-
return leafIndex !== null && leafIndex !== void 0 && Number.isSafeInteger(leafIndex) && leafIndex >= 0 && leafIndex < 2 ** 32;
|
|
1455
|
-
}
|
|
1456
|
-
function treeIdOf(note) {
|
|
1457
|
-
const idx = note.leafIndex;
|
|
1458
|
-
return isValidLeafIndex(idx) ? Math.floor(idx / LEAVES_PER_TREE) : 0;
|
|
1459
|
-
}
|
|
1460
|
-
function isSpendable(note) {
|
|
1461
|
-
return !note.spent && note.value > 0n;
|
|
1462
|
-
}
|
|
1463
|
-
function canPairWith(a, b) {
|
|
1464
|
-
return a.circuitVersion === b.circuitVersion && treeIdOf(a) === treeIdOf(b);
|
|
1465
|
-
}
|
|
1466
|
-
function selectNotes(notes, needed) {
|
|
1467
|
-
const unspent = notes.filter(isSpendable);
|
|
1468
|
-
const sorted = [...unspent].sort((a, b) => a.value < b.value ? -1 : 1);
|
|
1469
|
-
const single = sorted.find((n) => n.value >= needed);
|
|
1470
|
-
if (single) return [single, null];
|
|
1471
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
1472
|
-
for (let j = i + 1; j < sorted.length; j++) {
|
|
1473
|
-
const a = sorted[i];
|
|
1474
|
-
const b = sorted[j];
|
|
1475
|
-
if (a !== void 0 && b !== void 0 && canPairWith(a, b) && a.value + b.value >= needed) {
|
|
1476
|
-
return [a, b];
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
}
|
|
1480
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
1481
|
-
for (let j = i + 1; j < sorted.length; j++) {
|
|
1482
|
-
const a = sorted[i];
|
|
1483
|
-
const b = sorted[j];
|
|
1484
|
-
if (a !== void 0 && b !== void 0 && a.circuitVersion === b.circuitVersion && a.value + b.value >= needed) {
|
|
1485
|
-
return { needsConsolidation: true };
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
}
|
|
1489
|
-
return null;
|
|
1490
|
-
}
|
|
1491
|
-
function buildDummyTransferInput(assetId) {
|
|
1492
|
-
const zeroSibling = "0x" + "00".repeat(32);
|
|
1493
|
-
return {
|
|
1494
|
-
nullifier: 0n,
|
|
1495
|
-
// Constraint 9: nullifier * is_dummy.out === 0 → must be 0
|
|
1496
|
-
value: 0n,
|
|
1497
|
-
// triggers is_dummy[i].out = 1 in the circuit
|
|
1498
|
-
assetId,
|
|
1499
|
-
// must match real note (Constraint 7)
|
|
1500
|
-
ownerPk: 0n,
|
|
1501
|
-
blinding: 0n,
|
|
1502
|
-
spendingKey: 1n,
|
|
1503
|
-
// arbitrary; EdDSA is disabled (enabled = 0) for dummy inputs
|
|
1504
|
-
pathSiblings: Array(TRANSFER_TREE_DEPTH).fill(zeroSibling),
|
|
1505
|
-
leafIndex: 0
|
|
1506
|
-
};
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
|
-
// src/protocol/note/NoteDecryptor.ts
|
|
1510
1540
|
function decryptAndVerifyPlaintext(memoBytes, commitmentBytes, sharedSecret, effectiveOwnerPk) {
|
|
1511
1541
|
const plaintext = EncryptedMemo.decryptWithSharedSecret(
|
|
1512
1542
|
memoBytes,
|
|
@@ -1597,7 +1627,7 @@ function tryDecryptNoteVerbose(commitment, viewingSecretKey, spendingKey, ownOwn
|
|
|
1597
1627
|
commitmentHex: toHex(bigintTo32Le(recomputed)),
|
|
1598
1628
|
nullifierHex: toHex(bigintTo32Le(nullifier)),
|
|
1599
1629
|
memo: Array.from(memoBytes),
|
|
1600
|
-
|
|
1630
|
+
sourcePk: plaintext.sourcePk
|
|
1601
1631
|
}
|
|
1602
1632
|
};
|
|
1603
1633
|
}
|
|
@@ -1631,10 +1661,19 @@ function tryRecoverOutgoing(hint, ovk, opts) {
|
|
|
1631
1661
|
assetId: plaintext.assetId,
|
|
1632
1662
|
recipientStealthPk: plaintext.ownerPk,
|
|
1633
1663
|
blinding: plaintext.blinding,
|
|
1634
|
-
|
|
1664
|
+
sourcePk: plaintext.sourcePk,
|
|
1635
1665
|
circuitVersion: plaintext.circuitVersion
|
|
1636
1666
|
};
|
|
1637
1667
|
}
|
|
1668
|
+
function collectOutgoingFacts(hint) {
|
|
1669
|
+
if (!isHexOfLength(hint.commitmentHex, 32)) return null;
|
|
1670
|
+
if (!isHexOfLength(hint.encryptedMemo, ENCRYPTED_MEMO_SIZE)) return null;
|
|
1671
|
+
return {
|
|
1672
|
+
commitmentHex: hint.commitmentHex,
|
|
1673
|
+
...isValidLeafIndex(hint.leafIndex) ? { leafIndex: hint.leafIndex } : {},
|
|
1674
|
+
encryptedMemo: hint.encryptedMemo
|
|
1675
|
+
};
|
|
1676
|
+
}
|
|
1638
1677
|
|
|
1639
1678
|
// src/protocol/note/NoteDisclosure.ts
|
|
1640
1679
|
var import_poseidon_lite3 = require("poseidon-lite");
|
|
@@ -2769,11 +2808,17 @@ var SubstrateClient = class _SubstrateClient {
|
|
|
2769
2808
|
|
|
2770
2809
|
// src/chain/evm/EvmClient.ts
|
|
2771
2810
|
var EvmClient = class {
|
|
2772
|
-
/**
|
|
2773
|
-
|
|
2811
|
+
/**
|
|
2812
|
+
* @param rpcUrl - HTTP URL of the EVM JSON-RPC endpoint (e.g. `"http://localhost:9933"`).
|
|
2813
|
+
* @param peerRpcUrl - Optional second endpoint, used only to tell a genuinely
|
|
2814
|
+
* pending transaction from one stranded on `rpcUrl` alone. See `waitForReceipt`.
|
|
2815
|
+
*/
|
|
2816
|
+
constructor(rpcUrl, peerRpcUrl) {
|
|
2774
2817
|
this.rpcUrl = rpcUrl;
|
|
2818
|
+
this.peerRpcUrl = peerRpcUrl;
|
|
2775
2819
|
}
|
|
2776
2820
|
rpcUrl;
|
|
2821
|
+
peerRpcUrl;
|
|
2777
2822
|
/**
|
|
2778
2823
|
* Performs a single JSON-RPC call and returns the typed result.
|
|
2779
2824
|
* Throws on HTTP errors, RPC-level errors, or a `null` result.
|
|
@@ -2831,10 +2876,18 @@ var EvmClient = class {
|
|
|
2831
2876
|
const hex = await this.request("eth_getTransactionCount", [address, "latest"]);
|
|
2832
2877
|
return hexToNumber(hex);
|
|
2833
2878
|
}
|
|
2834
|
-
/**
|
|
2835
|
-
|
|
2879
|
+
/**
|
|
2880
|
+
* Returns the current gas price in wei, padded by `bumpPercent`.
|
|
2881
|
+
*
|
|
2882
|
+
* `eth_gasPrice` reports the base fee exactly, and the base fee moves
|
|
2883
|
+
* between signing and the pool's next revalidation. A transaction priced at
|
|
2884
|
+
* the bare minimum is evicted as `GasPriceTooLow` the moment it rises, which
|
|
2885
|
+
* leaves every later nonce from that account stranded in the future queue.
|
|
2886
|
+
* The default 25% pad absorbs the usual movement.
|
|
2887
|
+
*/
|
|
2888
|
+
async getGasPrice(bumpPercent = 25) {
|
|
2836
2889
|
const hex = await this.request("eth_gasPrice", []);
|
|
2837
|
-
return hexToBigint(hex);
|
|
2890
|
+
return hexToBigint(hex) * BigInt(100 + bumpPercent) / 100n;
|
|
2838
2891
|
}
|
|
2839
2892
|
/** Submits a signed raw transaction. Returns the transaction hash. */
|
|
2840
2893
|
async sendRawTransaction(signedHex) {
|
|
@@ -2952,10 +3005,41 @@ var EvmClient = class {
|
|
|
2952
3005
|
deadline = Math.min(deadline + timeoutMs, hardDeadline);
|
|
2953
3006
|
}
|
|
2954
3007
|
}
|
|
3008
|
+
if (await this.isStrandedOnThisNode(txHash)) {
|
|
3009
|
+
throw new Error(
|
|
3010
|
+
`Transaction dropped from the tx pool (never propagated beyond the submitting node, ${Date.now() - start}ms): ${txHash}`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
2955
3013
|
throw new Error(
|
|
2956
3014
|
`Transaction still pending after ${Date.now() - start}ms: ${txHash} \u2014 it may still confirm; check the hash on the explorer before retrying`
|
|
2957
3015
|
);
|
|
2958
3016
|
}
|
|
3017
|
+
/**
|
|
3018
|
+
* True when `rpcUrl` knows the transaction but the configured peer does not.
|
|
3019
|
+
*
|
|
3020
|
+
* Returns false without a peer configured, and on any peer error — an
|
|
3021
|
+
* unreachable peer is not evidence that a live transaction is stranded.
|
|
3022
|
+
*/
|
|
3023
|
+
async isStrandedOnThisNode(txHash) {
|
|
3024
|
+
if (!this.peerRpcUrl) return false;
|
|
3025
|
+
try {
|
|
3026
|
+
const res = await postJsonWithRetry(
|
|
3027
|
+
this.peerRpcUrl,
|
|
3028
|
+
JSON.stringify({
|
|
3029
|
+
id: 1,
|
|
3030
|
+
jsonrpc: "2.0",
|
|
3031
|
+
method: "eth_getTransactionByHash",
|
|
3032
|
+
params: [txHash]
|
|
3033
|
+
})
|
|
3034
|
+
);
|
|
3035
|
+
if (!res.ok) return false;
|
|
3036
|
+
const json = await res.json();
|
|
3037
|
+
if (json.error) return false;
|
|
3038
|
+
return json.result === null;
|
|
3039
|
+
} catch {
|
|
3040
|
+
return false;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
2959
3043
|
};
|
|
2960
3044
|
|
|
2961
3045
|
// src/chain/evm/explorer/EvmExplorer.ts
|
|
@@ -3674,6 +3758,23 @@ function padTo32Multiple(data) {
|
|
|
3674
3758
|
padded.set(data);
|
|
3675
3759
|
return padded;
|
|
3676
3760
|
}
|
|
3761
|
+
function bytes32Slot(value) {
|
|
3762
|
+
if (value.length !== 32) {
|
|
3763
|
+
throw new Error(`encodeAbi: bytes32 needs 32 bytes, got ${value.length}`);
|
|
3764
|
+
}
|
|
3765
|
+
const slot = new Uint8Array(32);
|
|
3766
|
+
slot.set(value);
|
|
3767
|
+
return slot;
|
|
3768
|
+
}
|
|
3769
|
+
function addressSlot(address) {
|
|
3770
|
+
const clean = address.startsWith("0x") ? address.slice(2) : address;
|
|
3771
|
+
if (clean.length > 40) {
|
|
3772
|
+
throw new Error(`encodeAbi: address needs at most 20 bytes, got ${clean.length / 2}`);
|
|
3773
|
+
}
|
|
3774
|
+
const slot = new Uint8Array(32);
|
|
3775
|
+
slot.set(fromHex("0x" + clean.padStart(40, "0")), 12);
|
|
3776
|
+
return slot;
|
|
3777
|
+
}
|
|
3677
3778
|
function encodeStaticParam(param) {
|
|
3678
3779
|
const buf = new Uint8Array(32);
|
|
3679
3780
|
switch (param.type) {
|
|
@@ -3681,14 +3782,10 @@ function encodeStaticParam(param) {
|
|
|
3681
3782
|
return bigintTo32Be(param.value);
|
|
3682
3783
|
}
|
|
3683
3784
|
case "bytes32": {
|
|
3684
|
-
|
|
3685
|
-
return buf;
|
|
3785
|
+
return bytes32Slot(param.value);
|
|
3686
3786
|
}
|
|
3687
3787
|
case "address": {
|
|
3688
|
-
|
|
3689
|
-
const bytes = fromHex("0x" + clean.padStart(40, "0"));
|
|
3690
|
-
buf.set(bytes, 12);
|
|
3691
|
-
return buf;
|
|
3788
|
+
return addressSlot(param.value);
|
|
3692
3789
|
}
|
|
3693
3790
|
case "bool": {
|
|
3694
3791
|
buf[31] = param.value ? 1 : 0;
|
|
@@ -3711,25 +3808,13 @@ function encodeDynamicParam(param) {
|
|
|
3711
3808
|
return concat([bigintTo32Be(BigInt(data.length)), padTo32Multiple(data)]);
|
|
3712
3809
|
}
|
|
3713
3810
|
case "bytes32[]": {
|
|
3714
|
-
const
|
|
3715
|
-
const
|
|
3716
|
-
for (const b32 of param.value) {
|
|
3717
|
-
const slot = new Uint8Array(32);
|
|
3718
|
-
slot.set(b32.slice(0, 32));
|
|
3719
|
-
parts.push(slot);
|
|
3720
|
-
}
|
|
3811
|
+
const parts = [bigintTo32Be(BigInt(param.value.length))];
|
|
3812
|
+
for (const b32 of param.value) parts.push(bytes32Slot(b32));
|
|
3721
3813
|
return concat(parts);
|
|
3722
3814
|
}
|
|
3723
3815
|
case "address[]": {
|
|
3724
|
-
const
|
|
3725
|
-
const
|
|
3726
|
-
for (const addr of param.value) {
|
|
3727
|
-
const slot = new Uint8Array(32);
|
|
3728
|
-
const clean = addr.startsWith("0x") ? addr.slice(2) : addr;
|
|
3729
|
-
const bytes = fromHex("0x" + clean.padStart(40, "0"));
|
|
3730
|
-
slot.set(bytes, 12);
|
|
3731
|
-
parts.push(slot);
|
|
3732
|
-
}
|
|
3816
|
+
const parts = [bigintTo32Be(BigInt(param.value.length))];
|
|
3817
|
+
for (const addr of param.value) parts.push(addressSlot(addr));
|
|
3733
3818
|
return concat(parts);
|
|
3734
3819
|
}
|
|
3735
3820
|
case "bytes[]": {
|
|
@@ -3785,6 +3870,122 @@ function decodeUint(data, offset = 0) {
|
|
|
3785
3870
|
return result;
|
|
3786
3871
|
}
|
|
3787
3872
|
|
|
3873
|
+
// src/chain/evm/precompiles/shieldedPoolCalldata.ts
|
|
3874
|
+
var CLAIM_PUBLIC_SIGNALS_SIZE = 76;
|
|
3875
|
+
function bytes32(hex, field) {
|
|
3876
|
+
if (!isHexOfLength(hex, 32)) {
|
|
3877
|
+
throw new Error(`${field}: expected a 0x-prefixed 32-byte hex string, got ${hex}`);
|
|
3878
|
+
}
|
|
3879
|
+
return fromHex(hex);
|
|
3880
|
+
}
|
|
3881
|
+
function uint32(value, field) {
|
|
3882
|
+
if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
|
|
3883
|
+
throw new Error(`${field}: expected a uint32 (0..4294967295), got ${value}`);
|
|
3884
|
+
}
|
|
3885
|
+
return BigInt(value);
|
|
3886
|
+
}
|
|
3887
|
+
function accountId32(address, field) {
|
|
3888
|
+
const raw = address.startsWith("0x") ? address.slice(2) : address;
|
|
3889
|
+
if (raw.length > 64) {
|
|
3890
|
+
throw new Error(`${field}: expected at most 32 bytes, got ${raw.length / 2}`);
|
|
3891
|
+
}
|
|
3892
|
+
return bytes32("0x" + raw.padEnd(64, "0"), field);
|
|
3893
|
+
}
|
|
3894
|
+
function buildShieldCalldata(params) {
|
|
3895
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildShieldCalldata.encryptedMemo");
|
|
3896
|
+
const commitment = bytes32(params.commitment, "buildShieldCalldata.commitment");
|
|
3897
|
+
return encodeHex(
|
|
3898
|
+
SP_SEL.SHIELD,
|
|
3899
|
+
{ type: "uint", value: uint32(params.assetId, "buildShieldCalldata.assetId") },
|
|
3900
|
+
{ type: "bytes32", value: commitment },
|
|
3901
|
+
{ type: "bytes", value: params.encryptedMemo }
|
|
3902
|
+
);
|
|
3903
|
+
}
|
|
3904
|
+
function buildPrivateTransferCalldata(params) {
|
|
3905
|
+
const nullifiers = params.inputs.map(
|
|
3906
|
+
(input, i) => bytes32(input.nullifier, `buildPrivateTransferCalldata.inputs[${i}].nullifier`)
|
|
3907
|
+
);
|
|
3908
|
+
const commitments = params.outputs.map(
|
|
3909
|
+
(output, i) => bytes32(output.commitment, `buildPrivateTransferCalldata.outputs[${i}].commitment`)
|
|
3910
|
+
);
|
|
3911
|
+
const memos = params.outputs.map((output, i) => {
|
|
3912
|
+
EncryptedMemo.validate(
|
|
3913
|
+
output.encryptedMemo,
|
|
3914
|
+
`buildPrivateTransferCalldata.outputs[${i}].encryptedMemo`
|
|
3915
|
+
);
|
|
3916
|
+
return output.encryptedMemo;
|
|
3917
|
+
});
|
|
3918
|
+
const root = bytes32(params.merkleRoot, "buildPrivateTransferCalldata.merkleRoot");
|
|
3919
|
+
return encodeHex(
|
|
3920
|
+
SP_SEL.PRIVATE_TRANSFER,
|
|
3921
|
+
{ type: "bytes", value: params.proof },
|
|
3922
|
+
{ type: "bytes32", value: root },
|
|
3923
|
+
{ type: "bytes32[]", value: nullifiers },
|
|
3924
|
+
{ type: "bytes32[]", value: commitments },
|
|
3925
|
+
{ type: "bytes[]", value: memos },
|
|
3926
|
+
{ type: "uint", value: uint32(params.assetId, "buildPrivateTransferCalldata.assetId") },
|
|
3927
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
3928
|
+
{
|
|
3929
|
+
type: "uint",
|
|
3930
|
+
value: uint32(params.circuitVersion, "buildPrivateTransferCalldata.circuitVersion")
|
|
3931
|
+
}
|
|
3932
|
+
);
|
|
3933
|
+
}
|
|
3934
|
+
function buildUnshieldCalldata(params) {
|
|
3935
|
+
const root = bytes32(params.merkleRoot, "buildUnshieldCalldata.merkleRoot");
|
|
3936
|
+
const nullifier = bytes32(params.nullifier, "buildUnshieldCalldata.nullifier");
|
|
3937
|
+
const recipient = accountId32(
|
|
3938
|
+
params.recipientAddress,
|
|
3939
|
+
"buildUnshieldCalldata.recipientAddress"
|
|
3940
|
+
);
|
|
3941
|
+
const changeCommitment = bytes32(
|
|
3942
|
+
params.changeCommitment ?? "0x" + "00".repeat(32),
|
|
3943
|
+
"buildUnshieldCalldata.changeCommitment"
|
|
3944
|
+
);
|
|
3945
|
+
const changeEncryptedMemo = params.changeEncryptedMemo ?? new Uint8Array();
|
|
3946
|
+
return encodeHex(
|
|
3947
|
+
SP_SEL.UNSHIELD,
|
|
3948
|
+
{ type: "bytes", value: params.proof },
|
|
3949
|
+
{ type: "bytes32", value: root },
|
|
3950
|
+
{ type: "bytes32", value: nullifier },
|
|
3951
|
+
{ type: "uint", value: uint32(params.assetId, "buildUnshieldCalldata.assetId") },
|
|
3952
|
+
{ type: "uint", value: params.amount },
|
|
3953
|
+
{ type: "bytes32", value: recipient },
|
|
3954
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
3955
|
+
{ type: "bytes32", value: changeCommitment },
|
|
3956
|
+
{ type: "bytes", value: changeEncryptedMemo },
|
|
3957
|
+
{
|
|
3958
|
+
type: "uint",
|
|
3959
|
+
value: uint32(params.circuitVersion, "buildUnshieldCalldata.circuitVersion")
|
|
3960
|
+
}
|
|
3961
|
+
);
|
|
3962
|
+
}
|
|
3963
|
+
function buildClaimShieldedFeesCalldata(params) {
|
|
3964
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildClaimShieldedFeesCalldata.encryptedMemo");
|
|
3965
|
+
if (params.proof.length === 0) {
|
|
3966
|
+
throw new Error("claimShieldedFees: proof must not be empty");
|
|
3967
|
+
}
|
|
3968
|
+
if (params.publicSignals.length !== CLAIM_PUBLIC_SIGNALS_SIZE) {
|
|
3969
|
+
throw new Error(
|
|
3970
|
+
`claimShieldedFees: publicSignals must be ${CLAIM_PUBLIC_SIGNALS_SIZE} bytes, got ${params.publicSignals.length}`
|
|
3971
|
+
);
|
|
3972
|
+
}
|
|
3973
|
+
const commitment = bytes32(params.commitment, "buildClaimShieldedFeesCalldata.commitment");
|
|
3974
|
+
return encodeHex(
|
|
3975
|
+
SP_SEL.CLAIM_SHIELDED_FEES,
|
|
3976
|
+
{ type: "bytes32", value: commitment },
|
|
3977
|
+
{ type: "uint", value: params.amount },
|
|
3978
|
+
{ type: "uint", value: uint32(params.assetId, "buildClaimShieldedFeesCalldata.assetId") },
|
|
3979
|
+
{ type: "bytes", value: params.encryptedMemo },
|
|
3980
|
+
{ type: "bytes", value: params.proof },
|
|
3981
|
+
{ type: "bytes", value: params.publicSignals },
|
|
3982
|
+
{
|
|
3983
|
+
type: "uint",
|
|
3984
|
+
value: uint32(params.circuitVersion, "buildClaimShieldedFeesCalldata.circuitVersion")
|
|
3985
|
+
}
|
|
3986
|
+
);
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3788
3989
|
// src/chain/evm/precompiles/ShieldedPoolPrecompile.ts
|
|
3789
3990
|
var ShieldedPoolPrecompile = class {
|
|
3790
3991
|
constructor(evm) {
|
|
@@ -3792,228 +3993,111 @@ var ShieldedPoolPrecompile = class {
|
|
|
3792
3993
|
}
|
|
3793
3994
|
evm;
|
|
3794
3995
|
addr = PRECOMPILE_ADDR.SHIELDED_POOL;
|
|
3795
|
-
// ───
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
*/
|
|
3996
|
+
// ─── Calldata ────────────────────────────────────────────────────────────
|
|
3997
|
+
//
|
|
3998
|
+
// Thin delegates to `shieldedPoolCalldata`, kept because they are public
|
|
3999
|
+
// API. New code should import those functions directly: they are pure, so
|
|
4000
|
+
// using them needs no `EvmClient` to construct.
|
|
3801
4001
|
buildShieldCalldata(params) {
|
|
3802
|
-
|
|
3803
|
-
const commitment = fromHex(params.commitment);
|
|
3804
|
-
return encodeHex(
|
|
3805
|
-
SP_SEL.SHIELD,
|
|
3806
|
-
{ type: "uint", value: BigInt(params.assetId) },
|
|
3807
|
-
{ type: "bytes32", value: commitment },
|
|
3808
|
-
{ type: "bytes", value: params.encryptedMemo }
|
|
3809
|
-
);
|
|
4002
|
+
return buildShieldCalldata(params);
|
|
3810
4003
|
}
|
|
4004
|
+
buildPrivateTransferCalldata(params) {
|
|
4005
|
+
return buildPrivateTransferCalldata(params);
|
|
4006
|
+
}
|
|
4007
|
+
buildUnshieldCalldata(params) {
|
|
4008
|
+
return buildUnshieldCalldata(params);
|
|
4009
|
+
}
|
|
4010
|
+
buildClaimShieldedFeesCalldata(params) {
|
|
4011
|
+
return buildClaimShieldedFeesCalldata(params);
|
|
4012
|
+
}
|
|
4013
|
+
// ─── shield ──────────────────────────────────────────────────────────────
|
|
3811
4014
|
/**
|
|
3812
4015
|
* Deposits tokens into the shielded pool from a payable EVM transaction.
|
|
3813
4016
|
*
|
|
3814
|
-
* The
|
|
3815
|
-
* the
|
|
3816
|
-
*
|
|
3817
|
-
*
|
|
3818
|
-
* precompile → pool (via pallet transfer)
|
|
3819
|
-
* This avoids double-deduction while keeping the displayed amount accurate.
|
|
4017
|
+
* The amount rides as `msg.value` so EVM wallets show the correct figure on
|
|
4018
|
+
* the confirmation screen. The precompile then dispatches with its OWN
|
|
4019
|
+
* address as origin, so funds flow caller → precompile → pool. That avoids
|
|
4020
|
+
* a double deduction while keeping the displayed amount accurate.
|
|
3820
4021
|
*
|
|
3821
4022
|
* Extrinsic: `shieldedPool.shield(assetId, amount, commitment, encryptedMemo)`
|
|
3822
4023
|
*/
|
|
3823
4024
|
async shield(params, signer) {
|
|
3824
4025
|
return signer({
|
|
3825
4026
|
to: this.addr,
|
|
3826
|
-
data:
|
|
4027
|
+
data: buildShieldCalldata(params),
|
|
3827
4028
|
value: params.amount
|
|
3828
4029
|
});
|
|
3829
4030
|
}
|
|
3830
|
-
// ─── privateTransfer
|
|
3831
|
-
/**
|
|
3832
|
-
* Returns the ABI-encoded calldata for
|
|
3833
|
-
* `privateTransfer(bytes, bytes32, bytes32[], bytes32[], bytes[], uint32, uint256, uint32)`.
|
|
3834
|
-
* The trailing `uint32` is the circuit version the input notes were created under.
|
|
3835
|
-
*/
|
|
3836
|
-
buildPrivateTransferCalldata(params) {
|
|
3837
|
-
const nullifiers = params.inputs.map((i) => fromHex(i.nullifier));
|
|
3838
|
-
const commitments = params.outputs.map((o) => fromHex(o.commitment));
|
|
3839
|
-
const memos = params.outputs.map((o, i) => {
|
|
3840
|
-
EncryptedMemo.validate(
|
|
3841
|
-
o.encryptedMemo,
|
|
3842
|
-
`buildPrivateTransferCalldata.outputs[${i}].encryptedMemo`
|
|
3843
|
-
);
|
|
3844
|
-
return o.encryptedMemo;
|
|
3845
|
-
});
|
|
3846
|
-
const root = fromHex(params.merkleRoot);
|
|
3847
|
-
return encodeHex(
|
|
3848
|
-
SP_SEL.PRIVATE_TRANSFER,
|
|
3849
|
-
{ type: "bytes", value: params.proof },
|
|
3850
|
-
{ type: "bytes32", value: root },
|
|
3851
|
-
{ type: "bytes32[]", value: nullifiers },
|
|
3852
|
-
{ type: "bytes32[]", value: commitments },
|
|
3853
|
-
{ type: "bytes[]", value: memos },
|
|
3854
|
-
{ type: "uint", value: BigInt(params.assetId) },
|
|
3855
|
-
{ type: "uint", value: params.fee ?? 0n },
|
|
3856
|
-
{ type: "uint", value: BigInt(params.circuitVersion) }
|
|
3857
|
-
);
|
|
3858
|
-
}
|
|
4031
|
+
// ─── privateTransfer ─────────────────────────────────────────────────────
|
|
3859
4032
|
/**
|
|
3860
|
-
* Submits a private transfer within the shielded pool
|
|
4033
|
+
* Submits a private transfer within the shielded pool.
|
|
3861
4034
|
*
|
|
3862
|
-
* The EVM caller identity is
|
|
3863
|
-
* hidden by design
|
|
4035
|
+
* The EVM caller identity is IRRELEVANT to the ZK proof — the sender is
|
|
4036
|
+
* hidden by design, so any address (a relayer included) can submit a valid
|
|
4037
|
+
* proof.
|
|
3864
4038
|
*
|
|
3865
4039
|
* Extrinsic: `shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos)`
|
|
3866
4040
|
*/
|
|
3867
4041
|
async privateTransfer(params, signer) {
|
|
3868
|
-
return signer({ to: this.addr, data:
|
|
3869
|
-
}
|
|
3870
|
-
// ─── unshield ──────────────────────────────────────────────────────────────
|
|
3871
|
-
/**
|
|
3872
|
-
* Params for an `unshield` call via the EVM precompile.
|
|
3873
|
-
* The `recipient` is a full 32-byte AccountId32 (Substrate account or
|
|
3874
|
-
* EeSuffix-derived: `H160 ++ [0x00; 12]`).
|
|
3875
|
-
*/
|
|
3876
|
-
buildUnshieldCalldata(params) {
|
|
3877
|
-
const proof = params.proof;
|
|
3878
|
-
const root = fromHex(params.merkleRoot);
|
|
3879
|
-
const nullifier = fromHex(params.nullifier);
|
|
3880
|
-
const recipientRaw = params.recipientAddress.startsWith("0x") ? params.recipientAddress.slice(2) : params.recipientAddress;
|
|
3881
|
-
const recipientBytes = fromHex(
|
|
3882
|
-
"0x" + (recipientRaw.length === 64 ? recipientRaw : recipientRaw.padEnd(64, "0"))
|
|
3883
|
-
);
|
|
3884
|
-
const changeCommitmentHex = params.changeCommitment ?? "0x" + "00".repeat(32);
|
|
3885
|
-
const changeCommitment = fromHex(changeCommitmentHex);
|
|
3886
|
-
const changeEncryptedMemo = params.changeEncryptedMemo ?? new Uint8Array();
|
|
3887
|
-
return encodeHex(
|
|
3888
|
-
SP_SEL.UNSHIELD,
|
|
3889
|
-
{ type: "bytes", value: proof },
|
|
3890
|
-
{ type: "bytes32", value: root },
|
|
3891
|
-
{ type: "bytes32", value: nullifier },
|
|
3892
|
-
{ type: "uint", value: BigInt(params.assetId) },
|
|
3893
|
-
{ type: "uint", value: params.amount },
|
|
3894
|
-
{ type: "bytes32", value: recipientBytes },
|
|
3895
|
-
{ type: "uint", value: params.fee ?? 0n },
|
|
3896
|
-
{ type: "bytes32", value: changeCommitment },
|
|
3897
|
-
{ type: "bytes", value: changeEncryptedMemo },
|
|
3898
|
-
{ type: "uint", value: BigInt(params.circuitVersion) }
|
|
3899
|
-
);
|
|
4042
|
+
return signer({ to: this.addr, data: buildPrivateTransferCalldata(params) });
|
|
3900
4043
|
}
|
|
4044
|
+
// ─── unshield ────────────────────────────────────────────────────────────
|
|
3901
4045
|
/**
|
|
3902
4046
|
* Withdraws tokens from the shielded pool to a recipient account.
|
|
3903
4047
|
*
|
|
3904
|
-
* `params.recipientAddress` must be a 0x-prefixed
|
|
3905
|
-
*
|
|
3906
|
-
* `@orbinum/sdk` to derive the AccountId32 first.
|
|
4048
|
+
* `params.recipientAddress` must be a 0x-prefixed AccountId32. To send to an
|
|
4049
|
+
* EVM address, derive it first with `evmToImplicitSubstrate(evmAddr)`.
|
|
3907
4050
|
*
|
|
3908
4051
|
* Extrinsic: `shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient)`
|
|
3909
4052
|
*/
|
|
3910
4053
|
async unshield(params, signer) {
|
|
3911
|
-
return signer({ to: this.addr, data:
|
|
4054
|
+
return signer({ to: this.addr, data: buildUnshieldCalldata(params) });
|
|
3912
4055
|
}
|
|
3913
|
-
// ───
|
|
4056
|
+
// ─── claimShieldedFees ───────────────────────────────────────────────────
|
|
3914
4057
|
/**
|
|
3915
|
-
*
|
|
3916
|
-
*
|
|
4058
|
+
* Claims accrued relay fees as a private shielded note.
|
|
4059
|
+
*
|
|
4060
|
+
* For validators/relayers holding fees in `pallet-relayer` who want them
|
|
4061
|
+
* paid privately into the shielded pool rather than as a public balance
|
|
4062
|
+
* credit. The ZK `value_proof` binds `commitment` to
|
|
4063
|
+
* `(amount, assetId, ownerPk, blinding)`, so the runtime can verify the note
|
|
4064
|
+
* encodes exactly the claimed amount and a malicious relayer cannot inflate
|
|
4065
|
+
* the withdrawal.
|
|
4066
|
+
*
|
|
4067
|
+
* The `msg.sender` address is the validator identity, and must match the
|
|
4068
|
+
* one with pending fees.
|
|
4069
|
+
*
|
|
4070
|
+
* Extrinsic: `shieldedPool.claim_shielded_fees(commitment, amount, assetId, memo, proof, publicSignals)`
|
|
3917
4071
|
*/
|
|
3918
|
-
async
|
|
3919
|
-
return
|
|
3920
|
-
from,
|
|
4072
|
+
async claimShieldedFees(params, signer) {
|
|
4073
|
+
return signer({
|
|
3921
4074
|
to: this.addr,
|
|
3922
|
-
data:
|
|
4075
|
+
data: buildClaimShieldedFeesCalldata(params)
|
|
3923
4076
|
});
|
|
3924
4077
|
}
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
4078
|
+
// ─── Gas estimation ──────────────────────────────────────────────────────
|
|
4079
|
+
//
|
|
4080
|
+
// `from` must be the real sender: the precompile resolves it to an
|
|
4081
|
+
// AccountId32, so estimating from a different address measures a different
|
|
4082
|
+
// call.
|
|
4083
|
+
async estimateShieldGas(params, from) {
|
|
4084
|
+
return this.evm.estimateGas({ from, to: this.addr, data: buildShieldCalldata(params) });
|
|
4085
|
+
}
|
|
3928
4086
|
async estimatePrivateTransferGas(params, from) {
|
|
3929
4087
|
return this.evm.estimateGas({
|
|
3930
4088
|
from,
|
|
3931
4089
|
to: this.addr,
|
|
3932
|
-
data:
|
|
4090
|
+
data: buildPrivateTransferCalldata(params)
|
|
3933
4091
|
});
|
|
3934
4092
|
}
|
|
3935
|
-
/**
|
|
3936
|
-
* Estimates the EVM gas for an `unshield` call.
|
|
3937
|
-
*/
|
|
3938
4093
|
async estimateUnshieldGas(params, from) {
|
|
3939
|
-
return this.evm.estimateGas({
|
|
3940
|
-
from,
|
|
3941
|
-
to: this.addr,
|
|
3942
|
-
data: this.buildUnshieldCalldata(params)
|
|
3943
|
-
});
|
|
4094
|
+
return this.evm.estimateGas({ from, to: this.addr, data: buildUnshieldCalldata(params) });
|
|
3944
4095
|
}
|
|
3945
|
-
// ─── claimShieldedFees ───────────────────────────────────────────────────────────────────
|
|
3946
|
-
/**
|
|
3947
|
-
* Returns the ABI-encoded calldata for
|
|
3948
|
-
* `claimShieldedFees(bytes32,uint256,uint32,bytes,bytes,bytes,uint32)`.
|
|
3949
|
-
*
|
|
3950
|
-
* ABI layout (params after selector):
|
|
3951
|
-
* - `commitment` — bytes32 (fixed)
|
|
3952
|
-
* - `amount` — uint256 (fixed)
|
|
3953
|
-
* - `asset_id` — uint32 (fixed, right-aligned)
|
|
3954
|
-
* - `memo` — bytes (dynamic)
|
|
3955
|
-
* - `proof` — bytes (dynamic, 128 bytes Groth16)
|
|
3956
|
-
* - `publicSignals` — bytes (dynamic, 76 bytes)
|
|
3957
|
-
* - `circuitVersion` — uint32 (fixed, right-aligned)
|
|
3958
|
-
*
|
|
3959
|
-
* The validator identity is derived from `msg.sender` in the precompile —
|
|
3960
|
-
* do NOT include it in the calldata.
|
|
3961
|
-
*/
|
|
3962
|
-
buildClaimShieldedFeesCalldata(params) {
|
|
3963
|
-
EncryptedMemo.validate(
|
|
3964
|
-
params.encryptedMemo,
|
|
3965
|
-
"buildClaimShieldedFeesCalldata.encryptedMemo"
|
|
3966
|
-
);
|
|
3967
|
-
if (params.proof.length === 0) {
|
|
3968
|
-
throw new Error("claimShieldedFees: proof must not be empty");
|
|
3969
|
-
}
|
|
3970
|
-
if (params.publicSignals.length !== 76) {
|
|
3971
|
-
throw new Error(
|
|
3972
|
-
`claimShieldedFees: publicSignals must be 76 bytes, got ${params.publicSignals.length}`
|
|
3973
|
-
);
|
|
3974
|
-
}
|
|
3975
|
-
const commitment = fromHex(params.commitment);
|
|
3976
|
-
return encodeHex(
|
|
3977
|
-
SP_SEL.CLAIM_SHIELDED_FEES,
|
|
3978
|
-
{ type: "bytes32", value: commitment },
|
|
3979
|
-
{ type: "uint", value: params.amount },
|
|
3980
|
-
{ type: "uint", value: BigInt(params.assetId) },
|
|
3981
|
-
{ type: "bytes", value: params.encryptedMemo },
|
|
3982
|
-
{ type: "bytes", value: params.proof },
|
|
3983
|
-
{ type: "bytes", value: params.publicSignals },
|
|
3984
|
-
{ type: "uint", value: BigInt(params.circuitVersion) }
|
|
3985
|
-
);
|
|
3986
|
-
}
|
|
3987
|
-
/**
|
|
3988
|
-
* Claims accumulated relay fees as a private shielded note.
|
|
3989
|
-
*
|
|
3990
|
-
* This extrinsic is for **validators/relayers** who have accrued fees in
|
|
3991
|
-
* `pallet-relayer` and want to receive them privately inside the shielded pool
|
|
3992
|
-
* instead of as a public balance credit.
|
|
3993
|
-
*
|
|
3994
|
-
* The ZK `value_proof` binds `commitment` to `(amount, assetId, ownerPk, blinding)`
|
|
3995
|
-
* so the runtime can verify the note encodes exactly the claimed fee amount,
|
|
3996
|
-
* preventing a malicious relayer from inflating the withdrawal.
|
|
3997
|
-
*
|
|
3998
|
-
* The `msg.sender` EVM address is used as the validator identity; it must match
|
|
3999
|
-
* the address that has pending relay fees in `pallet-relayer`.
|
|
4000
|
-
*
|
|
4001
|
-
* Extrinsic: `shieldedPool.claim_shielded_fees(commitment, amount, assetId, memo, proof, publicSignals)`
|
|
4002
|
-
*/
|
|
4003
|
-
async claimShieldedFees(params, signer) {
|
|
4004
|
-
return signer({
|
|
4005
|
-
to: this.addr,
|
|
4006
|
-
data: this.buildClaimShieldedFeesCalldata(params)
|
|
4007
|
-
});
|
|
4008
|
-
}
|
|
4009
|
-
/**
|
|
4010
|
-
* Estimates the EVM gas for a `claimShieldedFees` call.
|
|
4011
|
-
*/
|
|
4012
4096
|
async estimateClaimShieldedFeesGas(params, from) {
|
|
4013
4097
|
return this.evm.estimateGas({
|
|
4014
4098
|
from,
|
|
4015
4099
|
to: this.addr,
|
|
4016
|
-
data:
|
|
4100
|
+
data: buildClaimShieldedFeesCalldata(params)
|
|
4017
4101
|
});
|
|
4018
4102
|
}
|
|
4019
4103
|
};
|
|
@@ -4185,7 +4269,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
4185
4269
|
*/
|
|
4186
4270
|
static async connect(config) {
|
|
4187
4271
|
const substrate = config.papi ? SubstrateClient.adopt(config.papi, config.substrateHttp) : await SubstrateClient.connect(config.substrateWs, config.connectTimeoutMs ?? 15e3);
|
|
4188
|
-
const evm = config.evmRpc ? new EvmClient(config.evmRpc) : null;
|
|
4272
|
+
const evm = config.evmRpc ? new EvmClient(config.evmRpc, config.evmRpcPeer) : null;
|
|
4189
4273
|
return new _OrbinumClient(substrate, evm, config.circuitsBaseUrl);
|
|
4190
4274
|
}
|
|
4191
4275
|
/**
|
|
@@ -4325,6 +4409,7 @@ var OrbinumClientProvider = class _OrbinumClientProvider {
|
|
|
4325
4409
|
connectTimeoutMs: this.connectTimeoutMs
|
|
4326
4410
|
};
|
|
4327
4411
|
if (this.config.evmRpc) connectConfig.evmRpc = this.config.evmRpc;
|
|
4412
|
+
if (this.config.evmRpcPeer) connectConfig.evmRpcPeer = this.config.evmRpcPeer;
|
|
4328
4413
|
if (this.config.circuitsBaseUrl)
|
|
4329
4414
|
connectConfig.circuitsBaseUrl = this.config.circuitsBaseUrl;
|
|
4330
4415
|
clientPromise = OrbinumClient.connect(connectConfig);
|
|
@@ -5106,6 +5191,9 @@ function methodOf(fnSig) {
|
|
|
5106
5191
|
if (fnSig.startsWith("shield(")) return "shield";
|
|
5107
5192
|
return null;
|
|
5108
5193
|
}
|
|
5194
|
+
function hasFullHead(data, slots) {
|
|
5195
|
+
return data.length >= slots * 32;
|
|
5196
|
+
}
|
|
5109
5197
|
function decodePrecompileCalldata(address, input) {
|
|
5110
5198
|
const info = KNOWN_PRECOMPILES[address.toLowerCase()];
|
|
5111
5199
|
if (!info || !input || input.length < 10) return null;
|
|
@@ -5115,6 +5203,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5115
5203
|
if (fnSig.startsWith("shield(")) {
|
|
5116
5204
|
try {
|
|
5117
5205
|
const data = fromHex(input.slice(10));
|
|
5206
|
+
if (!hasFullHead(data, 3)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5118
5207
|
const assetId = decodeUint(data, 0);
|
|
5119
5208
|
const commitment = toHex(data.slice(32, 64));
|
|
5120
5209
|
return { fnSig, method: methodOf(fnSig), args: { assetId, commitment } };
|
|
@@ -5125,6 +5214,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5125
5214
|
if (fnSig.startsWith("unshield(")) {
|
|
5126
5215
|
try {
|
|
5127
5216
|
const data = fromHex(input.slice(10));
|
|
5217
|
+
if (!hasFullHead(data, 10)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5128
5218
|
const root = toHex(data.slice(32, 64));
|
|
5129
5219
|
const nullifier = toHex(data.slice(64, 96));
|
|
5130
5220
|
const assetId = decodeUint(data, 96);
|
|
@@ -5154,18 +5244,25 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5154
5244
|
if (fnSig.startsWith("privateTransfer(")) {
|
|
5155
5245
|
try {
|
|
5156
5246
|
const data = fromHex(input.slice(10));
|
|
5247
|
+
if (!hasFullHead(data, 8)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5157
5248
|
const root = toHex(data.slice(32, 64));
|
|
5158
|
-
const nullOffset = Number(decodeUint(data, 64));
|
|
5159
|
-
const commOffset = Number(decodeUint(data, 96));
|
|
5160
|
-
const nullifiers = Number(decodeUint(data, nullOffset));
|
|
5161
|
-
const commitments = Number(decodeUint(data, commOffset));
|
|
5162
5249
|
const assetId = decodeUint(data, 160);
|
|
5163
5250
|
const fee = decodeUint(data, 192);
|
|
5164
5251
|
const circuitVersion = decodeUint(data, 224);
|
|
5252
|
+
const counts = {};
|
|
5253
|
+
for (const [name, slot] of [
|
|
5254
|
+
["nullifiers", 64],
|
|
5255
|
+
["commitments", 96]
|
|
5256
|
+
]) {
|
|
5257
|
+
const offset = decodeUint(data, slot);
|
|
5258
|
+
if (offset <= BigInt(data.length - 32)) {
|
|
5259
|
+
counts[name] = Number(decodeUint(data, Number(offset)));
|
|
5260
|
+
}
|
|
5261
|
+
}
|
|
5165
5262
|
return {
|
|
5166
5263
|
fnSig,
|
|
5167
5264
|
method: methodOf(fnSig),
|
|
5168
|
-
args: { root,
|
|
5265
|
+
args: { root, ...counts, assetId, fee, circuitVersion }
|
|
5169
5266
|
};
|
|
5170
5267
|
} catch {
|
|
5171
5268
|
return { fnSig, method: methodOf(fnSig), args: {} };
|
|
@@ -5174,6 +5271,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5174
5271
|
if (fnSig.startsWith("claimShieldedFees(")) {
|
|
5175
5272
|
try {
|
|
5176
5273
|
const data = fromHex(input.slice(10));
|
|
5274
|
+
if (!hasFullHead(data, 7)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5177
5275
|
const commitment = toHex(data.slice(0, 32));
|
|
5178
5276
|
const amount = decodeUint(data, 32);
|
|
5179
5277
|
const assetId = decodeUint(data, 64);
|
|
@@ -5380,7 +5478,7 @@ var MemoryVaultStorage = class {
|
|
|
5380
5478
|
};
|
|
5381
5479
|
|
|
5382
5480
|
// src/wallet/vault/storage/config.ts
|
|
5383
|
-
var VAULT_SCHEMA_VERSION =
|
|
5481
|
+
var VAULT_SCHEMA_VERSION = 5;
|
|
5384
5482
|
function normalizeChainFingerprint(chainFingerprint) {
|
|
5385
5483
|
return chainFingerprint ? chainFingerprint.toLowerCase() : void 0;
|
|
5386
5484
|
}
|
|
@@ -5414,13 +5512,29 @@ function mergeCounters(current, fallback) {
|
|
|
5414
5512
|
...parties !== void 0 ? { pairwiseCounterparties: parties } : {}
|
|
5415
5513
|
};
|
|
5416
5514
|
}
|
|
5417
|
-
var
|
|
5515
|
+
var counterOrNone = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
5516
|
+
var higher = (a, b) => {
|
|
5517
|
+
const x = counterOrNone(a);
|
|
5518
|
+
const y = counterOrNone(b);
|
|
5519
|
+
return x === void 0 ? y : y === void 0 ? x : Math.max(x, y);
|
|
5520
|
+
};
|
|
5521
|
+
function usableEntry(entry) {
|
|
5522
|
+
if (typeof entry !== "object" || entry === null) return void 0;
|
|
5523
|
+
const { nextIndex, addedAt } = entry;
|
|
5524
|
+
const index = counterOrNone(nextIndex);
|
|
5525
|
+
if (index === void 0) return void 0;
|
|
5526
|
+
return { nextIndex: index, addedAt: counterOrNone(addedAt) ?? Date.now() };
|
|
5527
|
+
}
|
|
5418
5528
|
function mergePairwise(current, fallback) {
|
|
5419
5529
|
if (!current && !fallback) return void 0;
|
|
5420
|
-
const merged = {
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5530
|
+
const merged = {};
|
|
5531
|
+
for (const [key, entry] of Object.entries(fallback ?? {})) {
|
|
5532
|
+
const usable = usableEntry(entry);
|
|
5533
|
+
if (usable) merged[key] = usable;
|
|
5534
|
+
}
|
|
5535
|
+
for (const [key, raw] of Object.entries(current ?? {})) {
|
|
5536
|
+
const entry = usableEntry(raw);
|
|
5537
|
+
if (!entry) continue;
|
|
5424
5538
|
const prev = merged[key];
|
|
5425
5539
|
merged[key] = prev ? {
|
|
5426
5540
|
nextIndex: Math.max(prev.nextIndex, entry.nextIndex),
|
|
@@ -5431,33 +5545,42 @@ function mergePairwise(current, fallback) {
|
|
|
5431
5545
|
}
|
|
5432
5546
|
|
|
5433
5547
|
// src/wallet/vault/storage/ephemeralIndex.ts
|
|
5548
|
+
var MAX_EPH_INDEX = 4294967294;
|
|
5549
|
+
function usableCounter(value) {
|
|
5550
|
+
if (typeof value !== "number" || !Number.isInteger(value)) return null;
|
|
5551
|
+
if (value < 0 || value > MAX_EPH_INDEX) return null;
|
|
5552
|
+
return value;
|
|
5553
|
+
}
|
|
5434
5554
|
async function reserveSelfEphIndex(storage) {
|
|
5435
5555
|
const updated = await storage.updateConfig((config) => ({
|
|
5436
5556
|
...config,
|
|
5437
|
-
selfEphCounter: (config.selfEphCounter ?? 0) + 1,
|
|
5557
|
+
selfEphCounter: (usableCounter(config.selfEphCounter) ?? 0) + 1,
|
|
5438
5558
|
updatedAt: Date.now()
|
|
5439
5559
|
}));
|
|
5440
5560
|
if (!updated) throw new Error("vault not initialized");
|
|
5441
|
-
return (updated.selfEphCounter ?? 1) - 1;
|
|
5561
|
+
return (usableCounter(updated.selfEphCounter) ?? 1) - 1;
|
|
5442
5562
|
}
|
|
5443
5563
|
async function reservePairwiseIndex(storage, ivkHex) {
|
|
5444
5564
|
const key = ivkHex.toLowerCase();
|
|
5445
5565
|
const updated = await storage.updateConfig((config) => {
|
|
5446
|
-
const
|
|
5566
|
+
const stored = usableCounter(config.pairwiseCounterparties?.[key]?.nextIndex);
|
|
5567
|
+
const addedAt = config.pairwiseCounterparties?.[key]?.addedAt;
|
|
5447
5568
|
return {
|
|
5448
5569
|
...config,
|
|
5449
5570
|
pairwiseCounterparties: {
|
|
5450
5571
|
...config.pairwiseCounterparties,
|
|
5451
5572
|
[key]: {
|
|
5452
|
-
nextIndex: (
|
|
5453
|
-
addedAt:
|
|
5573
|
+
nextIndex: (stored ?? 0) + 1,
|
|
5574
|
+
addedAt: typeof addedAt === "number" ? addedAt : Date.now()
|
|
5454
5575
|
}
|
|
5455
5576
|
},
|
|
5456
5577
|
updatedAt: Date.now()
|
|
5457
5578
|
};
|
|
5458
5579
|
});
|
|
5459
5580
|
if (!updated) throw new Error("vault not initialized");
|
|
5460
|
-
|
|
5581
|
+
const nextIndex = usableCounter(updated.pairwiseCounterparties?.[key]?.nextIndex);
|
|
5582
|
+
if (nextIndex === null || nextIndex <= 1) return null;
|
|
5583
|
+
return nextIndex - 1;
|
|
5461
5584
|
}
|
|
5462
5585
|
|
|
5463
5586
|
// src/wallet/vault/notes/record.ts
|
|
@@ -5504,7 +5627,7 @@ async function noteBlindTag(blindKey, hex) {
|
|
|
5504
5627
|
|
|
5505
5628
|
// src/wallet/vault/notes/meta.ts
|
|
5506
5629
|
function noteOrigin(note) {
|
|
5507
|
-
return note.
|
|
5630
|
+
return note.sourcePk === 0n ? "shield" : "private-transfer";
|
|
5508
5631
|
}
|
|
5509
5632
|
function noteCreatedAt(note) {
|
|
5510
5633
|
return note.createdAt ?? null;
|
|
@@ -5573,9 +5696,9 @@ var NOTE_BIGINT_FIELDS = [
|
|
|
5573
5696
|
"spendingKey",
|
|
5574
5697
|
"commitment",
|
|
5575
5698
|
"nullifier",
|
|
5576
|
-
"
|
|
5699
|
+
"sourcePk"
|
|
5577
5700
|
];
|
|
5578
|
-
var ABSENT_MEANS_ZERO = /* @__PURE__ */ new Set(["
|
|
5701
|
+
var ABSENT_MEANS_ZERO = /* @__PURE__ */ new Set(["sourcePk"]);
|
|
5579
5702
|
function normalizeNote(note) {
|
|
5580
5703
|
let patch = null;
|
|
5581
5704
|
for (const field of NOTE_BIGINT_FIELDS) {
|
|
@@ -5828,12 +5951,12 @@ var VaultStore = class {
|
|
|
5828
5951
|
const { blindKey } = this.keys();
|
|
5829
5952
|
if (commitmentHexes.length === 0) return 0;
|
|
5830
5953
|
const toRemove = new Set(commitmentHexes);
|
|
5831
|
-
const
|
|
5832
|
-
if (
|
|
5833
|
-
const tags = await Promise.all(
|
|
5954
|
+
const present2 = this.deps.notes.get().filter((n) => toRemove.has(n.commitmentHex));
|
|
5955
|
+
if (present2.length === 0) return 0;
|
|
5956
|
+
const tags = await Promise.all(present2.map((n) => noteBlindTag(blindKey, n.commitmentHex)));
|
|
5834
5957
|
await this.deps.storage.deleteNotes(tags);
|
|
5835
5958
|
this.deps.notes.set(removeByCommitment(this.deps.notes.get(), toRemove));
|
|
5836
|
-
return
|
|
5959
|
+
return present2.length;
|
|
5837
5960
|
}
|
|
5838
5961
|
/**
|
|
5839
5962
|
* Stores an outgoing transaction, encrypted.
|
|
@@ -6235,7 +6358,7 @@ function gapMargin(windowSize) {
|
|
|
6235
6358
|
}
|
|
6236
6359
|
function resolveSelfEphCeiling(params) {
|
|
6237
6360
|
const { notes, spendingKey, viewingKey, scanMaxIndex } = params;
|
|
6238
|
-
const windowSize = params.windowSize
|
|
6361
|
+
const windowSize = params.windowSize !== void 0 && params.windowSize > 0 ? params.windowSize : SELF_EPH_WINDOW;
|
|
6239
6362
|
if (scanMaxIndex === null) return null;
|
|
6240
6363
|
if (scanMaxIndex < windowSize - gapMargin(windowSize)) return scanMaxIndex;
|
|
6241
6364
|
const ownEphPks = /* @__PURE__ */ new Set();
|
|
@@ -6260,9 +6383,12 @@ function resolveSelfEphCeiling(params) {
|
|
|
6260
6383
|
}
|
|
6261
6384
|
return max;
|
|
6262
6385
|
}
|
|
6386
|
+
var MAX_EPH_WINDOW = SELF_EPH_WINDOW * 64;
|
|
6263
6387
|
function windowSizeForCounter(counter) {
|
|
6264
|
-
const
|
|
6265
|
-
|
|
6388
|
+
const used = Number.isFinite(counter) ? counter : 0;
|
|
6389
|
+
const needed = used + gapMargin(SELF_EPH_WINDOW);
|
|
6390
|
+
const rounded = Math.ceil(needed / SELF_EPH_WINDOW) * SELF_EPH_WINDOW;
|
|
6391
|
+
return Math.min(MAX_EPH_WINDOW, Math.max(SELF_EPH_WINDOW, rounded));
|
|
6266
6392
|
}
|
|
6267
6393
|
|
|
6268
6394
|
// src/wallet/scanner/phases/spentStatus.ts
|
|
@@ -6432,26 +6558,147 @@ function parseFeeArg(argsJson) {
|
|
|
6432
6558
|
}
|
|
6433
6559
|
}
|
|
6434
6560
|
|
|
6561
|
+
// src/wallet/provenance/selectDescribingNote.ts
|
|
6562
|
+
function hasSourcePk(note) {
|
|
6563
|
+
return typeof note.sourcePk === "bigint" && note.sourcePk !== 0n;
|
|
6564
|
+
}
|
|
6565
|
+
function selectDescribingNote(candidates) {
|
|
6566
|
+
return candidates.find(hasSourcePk) ?? candidates[0];
|
|
6567
|
+
}
|
|
6568
|
+
function selectDescribingNoteByCommitment(commitments, noteByCommitment) {
|
|
6569
|
+
const owned = commitments.map((hex) => noteByCommitment.get(hex)).filter((note) => note !== void 0);
|
|
6570
|
+
return selectDescribingNote(owned);
|
|
6571
|
+
}
|
|
6572
|
+
|
|
6573
|
+
// src/wallet/provenance/merge.ts
|
|
6574
|
+
var SOURCE_RANK = {
|
|
6575
|
+
witnessed: 3,
|
|
6576
|
+
memo: 2,
|
|
6577
|
+
chain: 1,
|
|
6578
|
+
inferred: 0
|
|
6579
|
+
};
|
|
6580
|
+
function rankOf(source) {
|
|
6581
|
+
return SOURCE_RANK[source] ?? -1;
|
|
6582
|
+
}
|
|
6583
|
+
function outranks(a, b) {
|
|
6584
|
+
return rankOf(a) > rankOf(b);
|
|
6585
|
+
}
|
|
6586
|
+
function mergeProvenance(existing, incoming) {
|
|
6587
|
+
if (existing.id !== incoming.id) {
|
|
6588
|
+
throw new Error(
|
|
6589
|
+
`mergeProvenance: id mismatch \u2014 refusing to merge ${existing.id} with ${incoming.id}`
|
|
6590
|
+
);
|
|
6591
|
+
}
|
|
6592
|
+
const incomingWins = outranks(incoming.source, existing.source);
|
|
6593
|
+
const base = incomingWins ? incoming : existing;
|
|
6594
|
+
const other = incomingWins ? existing : incoming;
|
|
6595
|
+
const fee = base.feePlanck ?? other.feePlanck;
|
|
6596
|
+
const publicRecipient = firstPresent(base.publicRecipient, other.publicRecipient);
|
|
6597
|
+
const slip = present(base.slip?.encoded) ? base.slip : other.slip ?? base.slip;
|
|
6598
|
+
const note = base.note ?? other.note;
|
|
6599
|
+
return {
|
|
6600
|
+
// The loser is spread FIRST so a field only it carries survives. A host
|
|
6601
|
+
// stores its own record type through this — `ReconstructedTxRecord` has
|
|
6602
|
+
// `amountApproximate`, which marks an amount derived without subtracting
|
|
6603
|
+
// the fee — and dropping such a field turns an approximate figure into
|
|
6604
|
+
// one that merely looks exact. Every key the winner knows about is
|
|
6605
|
+
// overwritten below, so rank still decides every shared fact.
|
|
6606
|
+
...other,
|
|
6607
|
+
...base,
|
|
6608
|
+
// A known peer beats an unknown one even when the winner is silent:
|
|
6609
|
+
// backfilling the recipient is the whole point of re-running this.
|
|
6610
|
+
// `scope: 'none'` is "this operation has no counterparty", so it is a
|
|
6611
|
+
// gap too — treating it as known would block the backfill it exists for.
|
|
6612
|
+
peer: copyPeer(knownPeer(base.peer) ?? knownPeer(other.peer) ?? base.peer ?? other.peer),
|
|
6613
|
+
// A number that stands for "not known yet" must not win by rank. Zero is
|
|
6614
|
+
// exactly that here: `RECOVERED_TX_RESULT` and a failed submission both
|
|
6615
|
+
// report block 0, and their own comment says a caller who needs the
|
|
6616
|
+
// value must look it up rather than trust the field.
|
|
6617
|
+
blockNumber: firstPositive(base.blockNumber, other.blockNumber),
|
|
6618
|
+
timestampMs: firstPositive(base.timestampMs, other.timestampMs),
|
|
6619
|
+
// Reconstruction writes an empty hash when the extrinsic was not decoded.
|
|
6620
|
+
hash: firstPresent(base.hash, other.hash) ?? base.hash,
|
|
6621
|
+
// `exact` is a property of the FIGURE, not of the source. A `witnessed`
|
|
6622
|
+
// row whose amount was marked approximate is not better data than an
|
|
6623
|
+
// exact one from a weaker source, so rank only breaks a tie between two
|
|
6624
|
+
// figures of equal standing.
|
|
6625
|
+
amount: { ...betterAmount(base.amount, other.amount) },
|
|
6626
|
+
// The chain's outcome is not something one source knows better than
|
|
6627
|
+
// another — anyone who looks sees the same thing. Letting rank decide
|
|
6628
|
+
// would let a row written at submit time mark a transaction failed that
|
|
6629
|
+
// the chain went on to accept.
|
|
6630
|
+
status: base.status === "success" || other.status === "success" ? "success" : "failed",
|
|
6631
|
+
...fee !== void 0 && { feePlanck: fee },
|
|
6632
|
+
...publicRecipient !== void 0 && { publicRecipient },
|
|
6633
|
+
...slip !== void 0 && { slip: { ...slip } },
|
|
6634
|
+
...note !== void 0 && { note: { ...note } }
|
|
6635
|
+
};
|
|
6636
|
+
}
|
|
6637
|
+
function betterAmount(base, other) {
|
|
6638
|
+
if (base.exact === other.exact) return base;
|
|
6639
|
+
return base.exact ? base : other;
|
|
6640
|
+
}
|
|
6641
|
+
function present(value) {
|
|
6642
|
+
return value !== void 0 && value.length > 0;
|
|
6643
|
+
}
|
|
6644
|
+
function firstPresent(base, other) {
|
|
6645
|
+
return present(base) ? base : present(other) ? other : void 0;
|
|
6646
|
+
}
|
|
6647
|
+
function firstPositive(base, other) {
|
|
6648
|
+
return base > 0 ? base : other > 0 ? other : base;
|
|
6649
|
+
}
|
|
6650
|
+
function copyPeer(peer) {
|
|
6651
|
+
return peer ? { ...peer } : null;
|
|
6652
|
+
}
|
|
6653
|
+
function knownPeer(peer) {
|
|
6654
|
+
return peer && peer.scope !== "none" ? peer : null;
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6657
|
+
// src/wallet/provenance/regenerateSlip.ts
|
|
6658
|
+
function regeneratePaymentSlip(facts, recipientIvkPacked, txHash) {
|
|
6659
|
+
if (!isHexOfLength(facts.commitmentHex, 32)) {
|
|
6660
|
+
throw new Error("regeneratePaymentSlip: commitmentHex must be 32 bytes of hex");
|
|
6661
|
+
}
|
|
6662
|
+
if (!isHexOfLength(facts.encryptedMemo, ENCRYPTED_MEMO_SIZE)) {
|
|
6663
|
+
throw new Error(
|
|
6664
|
+
`regeneratePaymentSlip: encryptedMemo must be ${ENCRYPTED_MEMO_SIZE} bytes of hex`
|
|
6665
|
+
);
|
|
6666
|
+
}
|
|
6667
|
+
if (facts.leafIndex !== void 0 && !isValidLeafIndex(facts.leafIndex)) {
|
|
6668
|
+
throw new Error("regeneratePaymentSlip: leafIndex must be a real tree position");
|
|
6669
|
+
}
|
|
6670
|
+
const envelope = sealPaymentSlip(recipientIvkPacked, {
|
|
6671
|
+
commitmentHex: facts.commitmentHex,
|
|
6672
|
+
encryptedMemo: facts.encryptedMemo,
|
|
6673
|
+
...facts.leafIndex !== void 0 ? { leafIndex: facts.leafIndex } : {},
|
|
6674
|
+
// Rendered as an explorer link by the recipient, where an unconstrained
|
|
6675
|
+
// string is a URL injection wearing the authority of a decrypted slip.
|
|
6676
|
+
// Dropped rather than fatal: the slip still rebuilds the note, which is
|
|
6677
|
+
// the part that matters.
|
|
6678
|
+
...isHexOfLength(txHash, 32) ? { txHash } : {}
|
|
6679
|
+
});
|
|
6680
|
+
return encodePaymentSlip(envelope);
|
|
6681
|
+
}
|
|
6682
|
+
|
|
6435
6683
|
// src/wallet/scanner/history/reconstruct.ts
|
|
6436
6684
|
var ZERO_PK = "0x" + "00".repeat(32);
|
|
6437
6685
|
function extrinsicKey(row) {
|
|
6438
6686
|
return `${row.blockNumber}:${row.extrinsicIndex ?? "null"}`;
|
|
6439
6687
|
}
|
|
6440
|
-
function toPkHex(
|
|
6441
|
-
return
|
|
6442
|
-
}
|
|
6443
|
-
function findChangeNote(commitments, noteByCommitment) {
|
|
6444
|
-
const candidates = commitments.map((h) => noteByCommitment.get(h)).filter((n) => n !== void 0);
|
|
6445
|
-
return candidates.find((n) => n.counterpartyPk != null && n.counterpartyPk !== 0n) ?? candidates[0];
|
|
6688
|
+
function toPkHex(sourcePk) {
|
|
6689
|
+
return typeof sourcePk === "bigint" && hasSourcePk({ sourcePk }) ? scalarToHex(sourcePk) : ZERO_PK;
|
|
6446
6690
|
}
|
|
6447
6691
|
async function loadExistingRecords(vault) {
|
|
6448
6692
|
try {
|
|
6449
6693
|
const records = await vault.getTxRecords();
|
|
6450
|
-
return new Map(records.map((r) => [r.
|
|
6694
|
+
return new Map(records.map((r) => [r.id, r]));
|
|
6451
6695
|
} catch {
|
|
6452
6696
|
return /* @__PURE__ */ new Map();
|
|
6453
6697
|
}
|
|
6454
6698
|
}
|
|
6699
|
+
function recordKey(transfer) {
|
|
6700
|
+
return transfer.hash ?? `${transfer.blockNumber}-${transfer.extrinsicIndex ?? 0}`;
|
|
6701
|
+
}
|
|
6455
6702
|
async function reconstructOutgoingTxRecords(deps) {
|
|
6456
6703
|
const { vault, transfers } = deps;
|
|
6457
6704
|
const now = deps.now ?? Date.now;
|
|
@@ -6468,17 +6715,17 @@ async function reconstructOutgoingTxRecords(deps) {
|
|
|
6468
6715
|
const commitmentsByExtrinsic = new Map(
|
|
6469
6716
|
commitmentTransfers.map((ct) => [extrinsicKey(ct), ct.matchedCommitments ?? []])
|
|
6470
6717
|
);
|
|
6471
|
-
const
|
|
6718
|
+
const existingByKey = await loadExistingRecords(vault);
|
|
6472
6719
|
for (const transfer of outgoingTransfers) {
|
|
6473
|
-
const existing =
|
|
6720
|
+
const existing = existingByKey.get(recordKey(transfer));
|
|
6474
6721
|
if (existing?.recipientPkHex && existing.recipientPkHex !== ZERO_PK) continue;
|
|
6475
6722
|
const inputNotes = (transfer.matchedNullifiers ?? []).map((h) => noteByNullifier.get(h)).filter((n) => n !== void 0);
|
|
6476
6723
|
if (inputNotes.length === 0) continue;
|
|
6477
|
-
const changeNote =
|
|
6724
|
+
const changeNote = selectDescribingNoteByCommitment(
|
|
6478
6725
|
commitmentsByExtrinsic.get(extrinsicKey(transfer)) ?? [],
|
|
6479
6726
|
noteByCommitment
|
|
6480
6727
|
);
|
|
6481
|
-
const recipientPkHex = toPkHex(changeNote?.
|
|
6728
|
+
const recipientPkHex = toPkHex(changeNote?.sourcePk);
|
|
6482
6729
|
if (existing) {
|
|
6483
6730
|
if (recipientPkHex === ZERO_PK) continue;
|
|
6484
6731
|
await vault.saveTxRecord({ ...existing, recipientPkHex });
|
|
@@ -6489,7 +6736,7 @@ async function reconstructOutgoingTxRecords(deps) {
|
|
|
6489
6736
|
const transferAmount = totalInputValue - (changeNote?.value ?? 0n) - (fee ?? 0n);
|
|
6490
6737
|
if (transferAmount <= 0n) continue;
|
|
6491
6738
|
const record = {
|
|
6492
|
-
id: transfer
|
|
6739
|
+
id: recordKey(transfer),
|
|
6493
6740
|
type: "private_transfer",
|
|
6494
6741
|
blockNumber: transfer.blockNumber,
|
|
6495
6742
|
hash: transfer.hash ?? "",
|
|
@@ -6520,11 +6767,13 @@ async function buildZkNote(params, deps) {
|
|
|
6520
6767
|
} else if (params.viewingPublicKey && storage && keys.viewingSecretKey) {
|
|
6521
6768
|
try {
|
|
6522
6769
|
const index = await reservePairwiseIndex(storage, toHex(params.viewingPublicKey));
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6770
|
+
if (index !== null) {
|
|
6771
|
+
const pairSecret = derivePairwiseSharedSecret(
|
|
6772
|
+
keys.viewingSecretKey,
|
|
6773
|
+
params.viewingPublicKey
|
|
6774
|
+
);
|
|
6775
|
+
ephSkOverride = derivePairwiseEphSk(pairSecret, index);
|
|
6776
|
+
}
|
|
6528
6777
|
} catch {
|
|
6529
6778
|
}
|
|
6530
6779
|
}
|
|
@@ -6538,7 +6787,7 @@ async function buildZkNote(params, deps) {
|
|
|
6538
6787
|
circuitVersion: params.circuitVersion,
|
|
6539
6788
|
// Omitted rather than passed as undefined: the builder distinguishes an
|
|
6540
6789
|
// absent recipient (a self note) from one explicitly set.
|
|
6541
|
-
...params.
|
|
6790
|
+
...params.sourcePk !== void 0 && { sourcePk: params.sourcePk },
|
|
6542
6791
|
...params.recipientOwnerPk !== void 0 && {
|
|
6543
6792
|
recipientOwnerPk: params.recipientOwnerPk
|
|
6544
6793
|
},
|
|
@@ -6857,7 +7106,7 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
6857
7106
|
value: transferAmount,
|
|
6858
7107
|
assetId: noteA.assetId,
|
|
6859
7108
|
ownerPk: recipientPk,
|
|
6860
|
-
|
|
7109
|
+
sourcePk: effectiveSenderPk,
|
|
6861
7110
|
// Undefined → dummy memo; the recipient finds the note by scanning.
|
|
6862
7111
|
...recipientViewingPublicKey !== void 0 ? { viewingPublicKey: recipientViewingPublicKey } : {},
|
|
6863
7112
|
// With a viewing key present this activates stealth derivation.
|
|
@@ -6868,7 +7117,7 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
6868
7117
|
assetId: noteA.assetId,
|
|
6869
7118
|
ownerPk: effectiveSenderPk,
|
|
6870
7119
|
spendingKey: noteA.spendingKey,
|
|
6871
|
-
|
|
7120
|
+
sourcePk: recipientNote.ownerPk,
|
|
6872
7121
|
viewingPublicKey: senderViewingPublicKey
|
|
6873
7122
|
});
|
|
6874
7123
|
onProgress?.("generating-zk");
|
|
@@ -7900,6 +8149,7 @@ var OrbinumWallet = class {
|
|
|
7900
8149
|
clearKnownEphWindow,
|
|
7901
8150
|
clearSession,
|
|
7902
8151
|
collectNullifiersToQuery,
|
|
8152
|
+
collectOutgoingFacts,
|
|
7903
8153
|
collectScanEntries,
|
|
7904
8154
|
commitmentHexOf,
|
|
7905
8155
|
computeNoteCommitment,
|
|
@@ -7975,6 +8225,7 @@ var OrbinumWallet = class {
|
|
|
7975
8225
|
getSubstrateSignerFromExtension,
|
|
7976
8226
|
hasCachedSession,
|
|
7977
8227
|
hasInjectedExtensions,
|
|
8228
|
+
hasSourcePk,
|
|
7978
8229
|
hexToBigint,
|
|
7979
8230
|
hexToNumber,
|
|
7980
8231
|
implicitSubstrateToEvm,
|
|
@@ -7986,6 +8237,7 @@ var OrbinumWallet = class {
|
|
|
7986
8237
|
isConnectionLossError,
|
|
7987
8238
|
isEvmAddress,
|
|
7988
8239
|
isGhostNoteError,
|
|
8240
|
+
isHexOfLength,
|
|
7989
8241
|
isImplicitEvmAccount,
|
|
7990
8242
|
isNativeAsset,
|
|
7991
8243
|
isNoteSelfConsistent,
|
|
@@ -7998,6 +8250,7 @@ var OrbinumWallet = class {
|
|
|
7998
8250
|
mapExtrinsicArgs,
|
|
7999
8251
|
mapZkEventData,
|
|
8000
8252
|
markInputsSpent,
|
|
8253
|
+
mergeProvenance,
|
|
8001
8254
|
normalizeChainFingerprint,
|
|
8002
8255
|
normalizeEvmAddress,
|
|
8003
8256
|
normalizeNote,
|
|
@@ -8012,6 +8265,7 @@ var OrbinumWallet = class {
|
|
|
8012
8265
|
noteTxKind,
|
|
8013
8266
|
openOutgoingBlob,
|
|
8014
8267
|
openPaymentSlip,
|
|
8268
|
+
outranks,
|
|
8015
8269
|
pairwiseEphWindow,
|
|
8016
8270
|
palletErrorKind,
|
|
8017
8271
|
parseAmount,
|
|
@@ -8026,6 +8280,7 @@ var OrbinumWallet = class {
|
|
|
8026
8280
|
recoverOwnerPkPoint,
|
|
8027
8281
|
recoverSelfStealthNote,
|
|
8028
8282
|
refuseIfAlreadySpent,
|
|
8283
|
+
regeneratePaymentSlip,
|
|
8029
8284
|
removeByCommitment,
|
|
8030
8285
|
requireSessionKeys,
|
|
8031
8286
|
reservePairwiseIndex,
|
|
@@ -8039,6 +8294,8 @@ var OrbinumWallet = class {
|
|
|
8039
8294
|
scanAbortError,
|
|
8040
8295
|
sealOutgoingBlob,
|
|
8041
8296
|
sealPaymentSlip,
|
|
8297
|
+
selectDescribingNote,
|
|
8298
|
+
selectDescribingNoteByCommitment,
|
|
8042
8299
|
selectGhosts,
|
|
8043
8300
|
selectNotes,
|
|
8044
8301
|
selfEphWindow,
|