@orbinum/sdk 1.4.0 → 2.1.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 +445 -116
- package/dist/index.d.ts +445 -116
- package/dist/index.js +573 -325
- package/dist/index.mjs +465 -244
- 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
|
|
@@ -3376,7 +3460,11 @@ var ShieldedPoolModule = class {
|
|
|
3376
3460
|
* Withdraws tokens from the shielded pool to a public address.
|
|
3377
3461
|
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
3378
3462
|
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
3379
|
-
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee, changeCommitment, changeEncryptedMemo,
|
|
3463
|
+
* Extrinsic: shieldedPool.unshield(proof, merkleRoot, nullifier, assetId, amount, recipient, fee, changeCommitment, changeEncryptedMemo, circuitVersion)
|
|
3464
|
+
*
|
|
3465
|
+
* The relay fee recipient is NOT a parameter: the chain takes it from the
|
|
3466
|
+
* dispatch origin. Submitting unsigned credits the block author; submitting
|
|
3467
|
+
* through the EVM precompile credits whoever signed that transaction.
|
|
3380
3468
|
*/
|
|
3381
3469
|
async unshield(params, signer, options) {
|
|
3382
3470
|
const entry = resolveTx(this.substrate.unsafe, "ShieldedPool", "unshield");
|
|
@@ -3400,8 +3488,6 @@ var ShieldedPoolModule = class {
|
|
|
3400
3488
|
fee: params.fee ?? 0n,
|
|
3401
3489
|
change_commitment: changeCommitment,
|
|
3402
3490
|
change_encrypted_memo: changeEncryptedMemo,
|
|
3403
|
-
relayer: void 0,
|
|
3404
|
-
// Option<H160> — None for direct Substrate submissions
|
|
3405
3491
|
circuit_version: params.circuitVersion
|
|
3406
3492
|
});
|
|
3407
3493
|
if (signer) {
|
|
@@ -3413,7 +3499,11 @@ var ShieldedPoolModule = class {
|
|
|
3413
3499
|
* Performs a private (shielded) transfer between two notes.
|
|
3414
3500
|
* Submits as an UNSIGNED (gasless) transaction — fee is embedded in the ZK proof.
|
|
3415
3501
|
* Pass a `signer` to fall back to signed submission (e.g. for testing).
|
|
3416
|
-
* Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee,
|
|
3502
|
+
* Extrinsic: shieldedPool.privateTransfer(proof, merkleRoot, nullifiers, commitments, memos, assetId, fee, circuitVersion)
|
|
3503
|
+
*
|
|
3504
|
+
* The relay fee recipient is NOT a parameter: the chain takes it from the
|
|
3505
|
+
* dispatch origin. Submitting unsigned credits the block author; submitting
|
|
3506
|
+
* through the EVM precompile credits whoever signed that transaction.
|
|
3417
3507
|
*/
|
|
3418
3508
|
async privateTransfer(params, signer, options) {
|
|
3419
3509
|
const nullifiers = params.inputs.map((inp) => inp.nullifier);
|
|
@@ -3434,8 +3524,6 @@ var ShieldedPoolModule = class {
|
|
|
3434
3524
|
encrypted_memos: memos,
|
|
3435
3525
|
asset_id: params.assetId,
|
|
3436
3526
|
fee: params.fee ?? 0n,
|
|
3437
|
-
relayer: void 0,
|
|
3438
|
-
// Option<H160> — None for direct Substrate submissions
|
|
3439
3527
|
circuit_version: params.circuitVersion
|
|
3440
3528
|
});
|
|
3441
3529
|
if (signer) {
|
|
@@ -3674,6 +3762,23 @@ function padTo32Multiple(data) {
|
|
|
3674
3762
|
padded.set(data);
|
|
3675
3763
|
return padded;
|
|
3676
3764
|
}
|
|
3765
|
+
function bytes32Slot(value) {
|
|
3766
|
+
if (value.length !== 32) {
|
|
3767
|
+
throw new Error(`encodeAbi: bytes32 needs 32 bytes, got ${value.length}`);
|
|
3768
|
+
}
|
|
3769
|
+
const slot = new Uint8Array(32);
|
|
3770
|
+
slot.set(value);
|
|
3771
|
+
return slot;
|
|
3772
|
+
}
|
|
3773
|
+
function addressSlot(address) {
|
|
3774
|
+
const clean = address.startsWith("0x") ? address.slice(2) : address;
|
|
3775
|
+
if (clean.length > 40) {
|
|
3776
|
+
throw new Error(`encodeAbi: address needs at most 20 bytes, got ${clean.length / 2}`);
|
|
3777
|
+
}
|
|
3778
|
+
const slot = new Uint8Array(32);
|
|
3779
|
+
slot.set(fromHex("0x" + clean.padStart(40, "0")), 12);
|
|
3780
|
+
return slot;
|
|
3781
|
+
}
|
|
3677
3782
|
function encodeStaticParam(param) {
|
|
3678
3783
|
const buf = new Uint8Array(32);
|
|
3679
3784
|
switch (param.type) {
|
|
@@ -3681,14 +3786,10 @@ function encodeStaticParam(param) {
|
|
|
3681
3786
|
return bigintTo32Be(param.value);
|
|
3682
3787
|
}
|
|
3683
3788
|
case "bytes32": {
|
|
3684
|
-
|
|
3685
|
-
return buf;
|
|
3789
|
+
return bytes32Slot(param.value);
|
|
3686
3790
|
}
|
|
3687
3791
|
case "address": {
|
|
3688
|
-
|
|
3689
|
-
const bytes = fromHex("0x" + clean.padStart(40, "0"));
|
|
3690
|
-
buf.set(bytes, 12);
|
|
3691
|
-
return buf;
|
|
3792
|
+
return addressSlot(param.value);
|
|
3692
3793
|
}
|
|
3693
3794
|
case "bool": {
|
|
3694
3795
|
buf[31] = param.value ? 1 : 0;
|
|
@@ -3711,25 +3812,13 @@ function encodeDynamicParam(param) {
|
|
|
3711
3812
|
return concat([bigintTo32Be(BigInt(data.length)), padTo32Multiple(data)]);
|
|
3712
3813
|
}
|
|
3713
3814
|
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
|
-
}
|
|
3815
|
+
const parts = [bigintTo32Be(BigInt(param.value.length))];
|
|
3816
|
+
for (const b32 of param.value) parts.push(bytes32Slot(b32));
|
|
3721
3817
|
return concat(parts);
|
|
3722
3818
|
}
|
|
3723
3819
|
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
|
-
}
|
|
3820
|
+
const parts = [bigintTo32Be(BigInt(param.value.length))];
|
|
3821
|
+
for (const addr of param.value) parts.push(addressSlot(addr));
|
|
3733
3822
|
return concat(parts);
|
|
3734
3823
|
}
|
|
3735
3824
|
case "bytes[]": {
|
|
@@ -3785,6 +3874,122 @@ function decodeUint(data, offset = 0) {
|
|
|
3785
3874
|
return result;
|
|
3786
3875
|
}
|
|
3787
3876
|
|
|
3877
|
+
// src/chain/evm/precompiles/shieldedPoolCalldata.ts
|
|
3878
|
+
var CLAIM_PUBLIC_SIGNALS_SIZE = 76;
|
|
3879
|
+
function bytes32(hex, field) {
|
|
3880
|
+
if (!isHexOfLength(hex, 32)) {
|
|
3881
|
+
throw new Error(`${field}: expected a 0x-prefixed 32-byte hex string, got ${hex}`);
|
|
3882
|
+
}
|
|
3883
|
+
return fromHex(hex);
|
|
3884
|
+
}
|
|
3885
|
+
function uint32(value, field) {
|
|
3886
|
+
if (!Number.isInteger(value) || value < 0 || value > 4294967295) {
|
|
3887
|
+
throw new Error(`${field}: expected a uint32 (0..4294967295), got ${value}`);
|
|
3888
|
+
}
|
|
3889
|
+
return BigInt(value);
|
|
3890
|
+
}
|
|
3891
|
+
function accountId32(address, field) {
|
|
3892
|
+
const raw = address.startsWith("0x") ? address.slice(2) : address;
|
|
3893
|
+
if (raw.length > 64) {
|
|
3894
|
+
throw new Error(`${field}: expected at most 32 bytes, got ${raw.length / 2}`);
|
|
3895
|
+
}
|
|
3896
|
+
return bytes32("0x" + raw.padEnd(64, "0"), field);
|
|
3897
|
+
}
|
|
3898
|
+
function buildShieldCalldata(params) {
|
|
3899
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildShieldCalldata.encryptedMemo");
|
|
3900
|
+
const commitment = bytes32(params.commitment, "buildShieldCalldata.commitment");
|
|
3901
|
+
return encodeHex(
|
|
3902
|
+
SP_SEL.SHIELD,
|
|
3903
|
+
{ type: "uint", value: uint32(params.assetId, "buildShieldCalldata.assetId") },
|
|
3904
|
+
{ type: "bytes32", value: commitment },
|
|
3905
|
+
{ type: "bytes", value: params.encryptedMemo }
|
|
3906
|
+
);
|
|
3907
|
+
}
|
|
3908
|
+
function buildPrivateTransferCalldata(params) {
|
|
3909
|
+
const nullifiers = params.inputs.map(
|
|
3910
|
+
(input, i) => bytes32(input.nullifier, `buildPrivateTransferCalldata.inputs[${i}].nullifier`)
|
|
3911
|
+
);
|
|
3912
|
+
const commitments = params.outputs.map(
|
|
3913
|
+
(output, i) => bytes32(output.commitment, `buildPrivateTransferCalldata.outputs[${i}].commitment`)
|
|
3914
|
+
);
|
|
3915
|
+
const memos = params.outputs.map((output, i) => {
|
|
3916
|
+
EncryptedMemo.validate(
|
|
3917
|
+
output.encryptedMemo,
|
|
3918
|
+
`buildPrivateTransferCalldata.outputs[${i}].encryptedMemo`
|
|
3919
|
+
);
|
|
3920
|
+
return output.encryptedMemo;
|
|
3921
|
+
});
|
|
3922
|
+
const root = bytes32(params.merkleRoot, "buildPrivateTransferCalldata.merkleRoot");
|
|
3923
|
+
return encodeHex(
|
|
3924
|
+
SP_SEL.PRIVATE_TRANSFER,
|
|
3925
|
+
{ type: "bytes", value: params.proof },
|
|
3926
|
+
{ type: "bytes32", value: root },
|
|
3927
|
+
{ type: "bytes32[]", value: nullifiers },
|
|
3928
|
+
{ type: "bytes32[]", value: commitments },
|
|
3929
|
+
{ type: "bytes[]", value: memos },
|
|
3930
|
+
{ type: "uint", value: uint32(params.assetId, "buildPrivateTransferCalldata.assetId") },
|
|
3931
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
3932
|
+
{
|
|
3933
|
+
type: "uint",
|
|
3934
|
+
value: uint32(params.circuitVersion, "buildPrivateTransferCalldata.circuitVersion")
|
|
3935
|
+
}
|
|
3936
|
+
);
|
|
3937
|
+
}
|
|
3938
|
+
function buildUnshieldCalldata(params) {
|
|
3939
|
+
const root = bytes32(params.merkleRoot, "buildUnshieldCalldata.merkleRoot");
|
|
3940
|
+
const nullifier = bytes32(params.nullifier, "buildUnshieldCalldata.nullifier");
|
|
3941
|
+
const recipient = accountId32(
|
|
3942
|
+
params.recipientAddress,
|
|
3943
|
+
"buildUnshieldCalldata.recipientAddress"
|
|
3944
|
+
);
|
|
3945
|
+
const changeCommitment = bytes32(
|
|
3946
|
+
params.changeCommitment ?? "0x" + "00".repeat(32),
|
|
3947
|
+
"buildUnshieldCalldata.changeCommitment"
|
|
3948
|
+
);
|
|
3949
|
+
const changeEncryptedMemo = params.changeEncryptedMemo ?? new Uint8Array();
|
|
3950
|
+
return encodeHex(
|
|
3951
|
+
SP_SEL.UNSHIELD,
|
|
3952
|
+
{ type: "bytes", value: params.proof },
|
|
3953
|
+
{ type: "bytes32", value: root },
|
|
3954
|
+
{ type: "bytes32", value: nullifier },
|
|
3955
|
+
{ type: "uint", value: uint32(params.assetId, "buildUnshieldCalldata.assetId") },
|
|
3956
|
+
{ type: "uint", value: params.amount },
|
|
3957
|
+
{ type: "bytes32", value: recipient },
|
|
3958
|
+
{ type: "uint", value: params.fee ?? 0n },
|
|
3959
|
+
{ type: "bytes32", value: changeCommitment },
|
|
3960
|
+
{ type: "bytes", value: changeEncryptedMemo },
|
|
3961
|
+
{
|
|
3962
|
+
type: "uint",
|
|
3963
|
+
value: uint32(params.circuitVersion, "buildUnshieldCalldata.circuitVersion")
|
|
3964
|
+
}
|
|
3965
|
+
);
|
|
3966
|
+
}
|
|
3967
|
+
function buildClaimShieldedFeesCalldata(params) {
|
|
3968
|
+
EncryptedMemo.validate(params.encryptedMemo, "buildClaimShieldedFeesCalldata.encryptedMemo");
|
|
3969
|
+
if (params.proof.length === 0) {
|
|
3970
|
+
throw new Error("claimShieldedFees: proof must not be empty");
|
|
3971
|
+
}
|
|
3972
|
+
if (params.publicSignals.length !== CLAIM_PUBLIC_SIGNALS_SIZE) {
|
|
3973
|
+
throw new Error(
|
|
3974
|
+
`claimShieldedFees: publicSignals must be ${CLAIM_PUBLIC_SIGNALS_SIZE} bytes, got ${params.publicSignals.length}`
|
|
3975
|
+
);
|
|
3976
|
+
}
|
|
3977
|
+
const commitment = bytes32(params.commitment, "buildClaimShieldedFeesCalldata.commitment");
|
|
3978
|
+
return encodeHex(
|
|
3979
|
+
SP_SEL.CLAIM_SHIELDED_FEES,
|
|
3980
|
+
{ type: "bytes32", value: commitment },
|
|
3981
|
+
{ type: "uint", value: params.amount },
|
|
3982
|
+
{ type: "uint", value: uint32(params.assetId, "buildClaimShieldedFeesCalldata.assetId") },
|
|
3983
|
+
{ type: "bytes", value: params.encryptedMemo },
|
|
3984
|
+
{ type: "bytes", value: params.proof },
|
|
3985
|
+
{ type: "bytes", value: params.publicSignals },
|
|
3986
|
+
{
|
|
3987
|
+
type: "uint",
|
|
3988
|
+
value: uint32(params.circuitVersion, "buildClaimShieldedFeesCalldata.circuitVersion")
|
|
3989
|
+
}
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3788
3993
|
// src/chain/evm/precompiles/ShieldedPoolPrecompile.ts
|
|
3789
3994
|
var ShieldedPoolPrecompile = class {
|
|
3790
3995
|
constructor(evm) {
|
|
@@ -3792,228 +3997,127 @@ var ShieldedPoolPrecompile = class {
|
|
|
3792
3997
|
}
|
|
3793
3998
|
evm;
|
|
3794
3999
|
addr = PRECOMPILE_ADDR.SHIELDED_POOL;
|
|
3795
|
-
// ───
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
*/
|
|
4000
|
+
// ─── Calldata ────────────────────────────────────────────────────────────
|
|
4001
|
+
//
|
|
4002
|
+
// Thin delegates to `shieldedPoolCalldata`, kept because they are public
|
|
4003
|
+
// API. New code should import those functions directly: they are pure, so
|
|
4004
|
+
// using them needs no `EvmClient` to construct.
|
|
3801
4005
|
buildShieldCalldata(params) {
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
4006
|
+
return buildShieldCalldata(params);
|
|
4007
|
+
}
|
|
4008
|
+
buildPrivateTransferCalldata(params) {
|
|
4009
|
+
return buildPrivateTransferCalldata(params);
|
|
4010
|
+
}
|
|
4011
|
+
buildUnshieldCalldata(params) {
|
|
4012
|
+
return buildUnshieldCalldata(params);
|
|
4013
|
+
}
|
|
4014
|
+
buildClaimShieldedFeesCalldata(params) {
|
|
4015
|
+
return buildClaimShieldedFeesCalldata(params);
|
|
3810
4016
|
}
|
|
4017
|
+
// ─── shield ──────────────────────────────────────────────────────────────
|
|
3811
4018
|
/**
|
|
3812
4019
|
* Deposits tokens into the shielded pool from a payable EVM transaction.
|
|
3813
4020
|
*
|
|
3814
|
-
* The
|
|
3815
|
-
* the
|
|
3816
|
-
*
|
|
3817
|
-
*
|
|
3818
|
-
* precompile → pool (via pallet transfer)
|
|
3819
|
-
* This avoids double-deduction while keeping the displayed amount accurate.
|
|
4021
|
+
* The amount rides as `msg.value` so EVM wallets show the correct figure on
|
|
4022
|
+
* the confirmation screen. The precompile then dispatches with its OWN
|
|
4023
|
+
* address as origin, so funds flow caller → precompile → pool. That avoids
|
|
4024
|
+
* a double deduction while keeping the displayed amount accurate.
|
|
3820
4025
|
*
|
|
3821
4026
|
* Extrinsic: `shieldedPool.shield(assetId, amount, commitment, encryptedMemo)`
|
|
3822
4027
|
*/
|
|
3823
4028
|
async shield(params, signer) {
|
|
3824
4029
|
return signer({
|
|
3825
4030
|
to: this.addr,
|
|
3826
|
-
data:
|
|
4031
|
+
data: buildShieldCalldata(params),
|
|
3827
4032
|
value: params.amount
|
|
3828
4033
|
});
|
|
3829
4034
|
}
|
|
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
|
-
}
|
|
4035
|
+
// ─── privateTransfer ─────────────────────────────────────────────────────
|
|
3859
4036
|
/**
|
|
3860
|
-
* Submits a private transfer within the shielded pool
|
|
4037
|
+
* Submits a private transfer within the shielded pool.
|
|
3861
4038
|
*
|
|
3862
|
-
* The EVM caller identity is
|
|
3863
|
-
* hidden by design
|
|
4039
|
+
* The EVM caller identity is IRRELEVANT to the ZK proof — the sender is
|
|
4040
|
+
* hidden by design, so any address (a relayer included) can submit a valid
|
|
4041
|
+
* proof.
|
|
3864
4042
|
*
|
|
3865
|
-
* Extrinsic: `shieldedPool.privateTransfer(proof, merkleRoot, nullifiers,
|
|
4043
|
+
* Extrinsic: `shieldedPool.privateTransfer(proof, merkleRoot, nullifiers,
|
|
4044
|
+
* commitments, memos, assetId, fee, circuitVersion)` — eight arguments; see
|
|
4045
|
+
* `buildPrivateTransferCalldata` for the encoding order.
|
|
3866
4046
|
*/
|
|
3867
4047
|
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
|
-
);
|
|
4048
|
+
return signer({ to: this.addr, data: buildPrivateTransferCalldata(params) });
|
|
3900
4049
|
}
|
|
4050
|
+
// ─── unshield ────────────────────────────────────────────────────────────
|
|
3901
4051
|
/**
|
|
3902
4052
|
* Withdraws tokens from the shielded pool to a recipient account.
|
|
3903
4053
|
*
|
|
3904
|
-
* `params.recipientAddress` must be a 0x-prefixed
|
|
3905
|
-
*
|
|
3906
|
-
*
|
|
4054
|
+
* `params.recipientAddress` must be a 0x-prefixed AccountId32. To send to an
|
|
4055
|
+
* EVM address, derive it first with `evmToImplicitSubstrate(evmAddr)`.
|
|
4056
|
+
*
|
|
4057
|
+
* Extrinsic: `shieldedPool.unshield(proof, merkleRoot, nullifier, assetId,
|
|
4058
|
+
* amount, recipient, fee, changeCommitment, changeEncryptedMemo,
|
|
4059
|
+
* circuitVersion)` — ten arguments; see `buildUnshieldCalldata`.
|
|
3907
4060
|
*
|
|
3908
|
-
*
|
|
4061
|
+
* **The relay fee goes to whoever `signer` is.** The chain takes the recipient
|
|
4062
|
+
* from `msg.sender`, not from calldata, so the account behind this signer is
|
|
4063
|
+
* the one credited — and it is also the one paying gas. Relaying on someone
|
|
4064
|
+
* else's behalf and being paid for it is the same act here.
|
|
3909
4065
|
*/
|
|
3910
4066
|
async unshield(params, signer) {
|
|
3911
|
-
return signer({ to: this.addr, data:
|
|
4067
|
+
return signer({ to: this.addr, data: buildUnshieldCalldata(params) });
|
|
3912
4068
|
}
|
|
3913
|
-
// ───
|
|
4069
|
+
// ─── claimShieldedFees ───────────────────────────────────────────────────
|
|
3914
4070
|
/**
|
|
3915
|
-
*
|
|
3916
|
-
*
|
|
4071
|
+
* Claims accrued relay fees as a private shielded note.
|
|
4072
|
+
*
|
|
4073
|
+
* For validators/relayers holding fees in `pallet-relayer` who want them
|
|
4074
|
+
* paid privately into the shielded pool rather than as a public balance
|
|
4075
|
+
* credit. The ZK `value_proof` binds `commitment` to
|
|
4076
|
+
* `(amount, assetId, ownerPk, blinding)`, so the runtime can verify the note
|
|
4077
|
+
* encodes exactly the claimed amount and a malicious relayer cannot inflate
|
|
4078
|
+
* the withdrawal.
|
|
4079
|
+
*
|
|
4080
|
+
* The `msg.sender` address is the validator identity, and must match the
|
|
4081
|
+
* one with pending fees.
|
|
4082
|
+
*
|
|
4083
|
+
* Extrinsic: `shieldedPool.claim_shielded_fees(commitment, amount, assetId,
|
|
4084
|
+
* memo, proof, publicSignals, circuitVersion)` — seven arguments; see
|
|
4085
|
+
* `buildClaimShieldedFeesCalldata`.
|
|
3917
4086
|
*/
|
|
3918
|
-
async
|
|
3919
|
-
return
|
|
3920
|
-
from,
|
|
4087
|
+
async claimShieldedFees(params, signer) {
|
|
4088
|
+
return signer({
|
|
3921
4089
|
to: this.addr,
|
|
3922
|
-
data:
|
|
4090
|
+
data: buildClaimShieldedFeesCalldata(params)
|
|
3923
4091
|
});
|
|
3924
4092
|
}
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
4093
|
+
// ─── Gas estimation ──────────────────────────────────────────────────────
|
|
4094
|
+
//
|
|
4095
|
+
// `from` must be the real sender: the precompile resolves it to an
|
|
4096
|
+
// AccountId32, so estimating from a different address measures a different
|
|
4097
|
+
// call.
|
|
4098
|
+
async estimateShieldGas(params, from) {
|
|
3929
4099
|
return this.evm.estimateGas({
|
|
3930
4100
|
from,
|
|
3931
4101
|
to: this.addr,
|
|
3932
|
-
data:
|
|
4102
|
+
data: buildShieldCalldata(params),
|
|
4103
|
+
value: `0x${params.amount.toString(16)}`
|
|
3933
4104
|
});
|
|
3934
4105
|
}
|
|
3935
|
-
|
|
3936
|
-
* Estimates the EVM gas for an `unshield` call.
|
|
3937
|
-
*/
|
|
3938
|
-
async estimateUnshieldGas(params, from) {
|
|
4106
|
+
async estimatePrivateTransferGas(params, from) {
|
|
3939
4107
|
return this.evm.estimateGas({
|
|
3940
4108
|
from,
|
|
3941
4109
|
to: this.addr,
|
|
3942
|
-
data:
|
|
4110
|
+
data: buildPrivateTransferCalldata(params)
|
|
3943
4111
|
});
|
|
3944
4112
|
}
|
|
3945
|
-
|
|
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
|
-
});
|
|
4113
|
+
async estimateUnshieldGas(params, from) {
|
|
4114
|
+
return this.evm.estimateGas({ from, to: this.addr, data: buildUnshieldCalldata(params) });
|
|
4008
4115
|
}
|
|
4009
|
-
/**
|
|
4010
|
-
* Estimates the EVM gas for a `claimShieldedFees` call.
|
|
4011
|
-
*/
|
|
4012
4116
|
async estimateClaimShieldedFeesGas(params, from) {
|
|
4013
4117
|
return this.evm.estimateGas({
|
|
4014
4118
|
from,
|
|
4015
4119
|
to: this.addr,
|
|
4016
|
-
data:
|
|
4120
|
+
data: buildClaimShieldedFeesCalldata(params)
|
|
4017
4121
|
});
|
|
4018
4122
|
}
|
|
4019
4123
|
};
|
|
@@ -4185,7 +4289,7 @@ var OrbinumClient = class _OrbinumClient {
|
|
|
4185
4289
|
*/
|
|
4186
4290
|
static async connect(config) {
|
|
4187
4291
|
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;
|
|
4292
|
+
const evm = config.evmRpc ? new EvmClient(config.evmRpc, config.evmRpcPeer) : null;
|
|
4189
4293
|
return new _OrbinumClient(substrate, evm, config.circuitsBaseUrl);
|
|
4190
4294
|
}
|
|
4191
4295
|
/**
|
|
@@ -4325,6 +4429,7 @@ var OrbinumClientProvider = class _OrbinumClientProvider {
|
|
|
4325
4429
|
connectTimeoutMs: this.connectTimeoutMs
|
|
4326
4430
|
};
|
|
4327
4431
|
if (this.config.evmRpc) connectConfig.evmRpc = this.config.evmRpc;
|
|
4432
|
+
if (this.config.evmRpcPeer) connectConfig.evmRpcPeer = this.config.evmRpcPeer;
|
|
4328
4433
|
if (this.config.circuitsBaseUrl)
|
|
4329
4434
|
connectConfig.circuitsBaseUrl = this.config.circuitsBaseUrl;
|
|
4330
4435
|
clientPromise = OrbinumClient.connect(connectConfig);
|
|
@@ -5103,9 +5208,13 @@ function methodOf(fnSig) {
|
|
|
5103
5208
|
if (fnSig.startsWith("unshield(")) return "unshield";
|
|
5104
5209
|
if (fnSig.startsWith("privateTransfer(")) return "privateTransfer";
|
|
5105
5210
|
if (fnSig.startsWith("shieldBatch(")) return "shieldBatch";
|
|
5211
|
+
if (fnSig.startsWith("claimShieldedFees(")) return "claimShieldedFees";
|
|
5106
5212
|
if (fnSig.startsWith("shield(")) return "shield";
|
|
5107
5213
|
return null;
|
|
5108
5214
|
}
|
|
5215
|
+
function hasFullHead(data, slots) {
|
|
5216
|
+
return data.length >= slots * 32;
|
|
5217
|
+
}
|
|
5109
5218
|
function decodePrecompileCalldata(address, input) {
|
|
5110
5219
|
const info = KNOWN_PRECOMPILES[address.toLowerCase()];
|
|
5111
5220
|
if (!info || !input || input.length < 10) return null;
|
|
@@ -5115,6 +5224,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5115
5224
|
if (fnSig.startsWith("shield(")) {
|
|
5116
5225
|
try {
|
|
5117
5226
|
const data = fromHex(input.slice(10));
|
|
5227
|
+
if (!hasFullHead(data, 3)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5118
5228
|
const assetId = decodeUint(data, 0);
|
|
5119
5229
|
const commitment = toHex(data.slice(32, 64));
|
|
5120
5230
|
return { fnSig, method: methodOf(fnSig), args: { assetId, commitment } };
|
|
@@ -5125,6 +5235,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5125
5235
|
if (fnSig.startsWith("unshield(")) {
|
|
5126
5236
|
try {
|
|
5127
5237
|
const data = fromHex(input.slice(10));
|
|
5238
|
+
if (!hasFullHead(data, 10)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5128
5239
|
const root = toHex(data.slice(32, 64));
|
|
5129
5240
|
const nullifier = toHex(data.slice(64, 96));
|
|
5130
5241
|
const assetId = decodeUint(data, 96);
|
|
@@ -5154,18 +5265,25 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5154
5265
|
if (fnSig.startsWith("privateTransfer(")) {
|
|
5155
5266
|
try {
|
|
5156
5267
|
const data = fromHex(input.slice(10));
|
|
5268
|
+
if (!hasFullHead(data, 8)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5157
5269
|
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
5270
|
const assetId = decodeUint(data, 160);
|
|
5163
5271
|
const fee = decodeUint(data, 192);
|
|
5164
5272
|
const circuitVersion = decodeUint(data, 224);
|
|
5273
|
+
const counts = {};
|
|
5274
|
+
for (const [name, slot] of [
|
|
5275
|
+
["nullifiers", 64],
|
|
5276
|
+
["commitments", 96]
|
|
5277
|
+
]) {
|
|
5278
|
+
const offset = decodeUint(data, slot);
|
|
5279
|
+
if (offset <= BigInt(data.length - 32)) {
|
|
5280
|
+
counts[name] = Number(decodeUint(data, Number(offset)));
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5165
5283
|
return {
|
|
5166
5284
|
fnSig,
|
|
5167
5285
|
method: methodOf(fnSig),
|
|
5168
|
-
args: { root,
|
|
5286
|
+
args: { root, ...counts, assetId, fee, circuitVersion }
|
|
5169
5287
|
};
|
|
5170
5288
|
} catch {
|
|
5171
5289
|
return { fnSig, method: methodOf(fnSig), args: {} };
|
|
@@ -5174,6 +5292,7 @@ function decodePrecompileCalldata(address, input) {
|
|
|
5174
5292
|
if (fnSig.startsWith("claimShieldedFees(")) {
|
|
5175
5293
|
try {
|
|
5176
5294
|
const data = fromHex(input.slice(10));
|
|
5295
|
+
if (!hasFullHead(data, 7)) return { fnSig, method: methodOf(fnSig), args: {} };
|
|
5177
5296
|
const commitment = toHex(data.slice(0, 32));
|
|
5178
5297
|
const amount = decodeUint(data, 32);
|
|
5179
5298
|
const assetId = decodeUint(data, 64);
|
|
@@ -5380,7 +5499,7 @@ var MemoryVaultStorage = class {
|
|
|
5380
5499
|
};
|
|
5381
5500
|
|
|
5382
5501
|
// src/wallet/vault/storage/config.ts
|
|
5383
|
-
var VAULT_SCHEMA_VERSION =
|
|
5502
|
+
var VAULT_SCHEMA_VERSION = 5;
|
|
5384
5503
|
function normalizeChainFingerprint(chainFingerprint) {
|
|
5385
5504
|
return chainFingerprint ? chainFingerprint.toLowerCase() : void 0;
|
|
5386
5505
|
}
|
|
@@ -5529,7 +5648,7 @@ async function noteBlindTag(blindKey, hex) {
|
|
|
5529
5648
|
|
|
5530
5649
|
// src/wallet/vault/notes/meta.ts
|
|
5531
5650
|
function noteOrigin(note) {
|
|
5532
|
-
return note.
|
|
5651
|
+
return note.sourcePk === 0n ? "shield" : "private-transfer";
|
|
5533
5652
|
}
|
|
5534
5653
|
function noteCreatedAt(note) {
|
|
5535
5654
|
return note.createdAt ?? null;
|
|
@@ -5598,9 +5717,9 @@ var NOTE_BIGINT_FIELDS = [
|
|
|
5598
5717
|
"spendingKey",
|
|
5599
5718
|
"commitment",
|
|
5600
5719
|
"nullifier",
|
|
5601
|
-
"
|
|
5720
|
+
"sourcePk"
|
|
5602
5721
|
];
|
|
5603
|
-
var ABSENT_MEANS_ZERO = /* @__PURE__ */ new Set(["
|
|
5722
|
+
var ABSENT_MEANS_ZERO = /* @__PURE__ */ new Set(["sourcePk"]);
|
|
5604
5723
|
function normalizeNote(note) {
|
|
5605
5724
|
let patch = null;
|
|
5606
5725
|
for (const field of NOTE_BIGINT_FIELDS) {
|
|
@@ -5853,12 +5972,12 @@ var VaultStore = class {
|
|
|
5853
5972
|
const { blindKey } = this.keys();
|
|
5854
5973
|
if (commitmentHexes.length === 0) return 0;
|
|
5855
5974
|
const toRemove = new Set(commitmentHexes);
|
|
5856
|
-
const
|
|
5857
|
-
if (
|
|
5858
|
-
const tags = await Promise.all(
|
|
5975
|
+
const present2 = this.deps.notes.get().filter((n) => toRemove.has(n.commitmentHex));
|
|
5976
|
+
if (present2.length === 0) return 0;
|
|
5977
|
+
const tags = await Promise.all(present2.map((n) => noteBlindTag(blindKey, n.commitmentHex)));
|
|
5859
5978
|
await this.deps.storage.deleteNotes(tags);
|
|
5860
5979
|
this.deps.notes.set(removeByCommitment(this.deps.notes.get(), toRemove));
|
|
5861
|
-
return
|
|
5980
|
+
return present2.length;
|
|
5862
5981
|
}
|
|
5863
5982
|
/**
|
|
5864
5983
|
* Stores an outgoing transaction, encrypted.
|
|
@@ -6460,26 +6579,147 @@ function parseFeeArg(argsJson) {
|
|
|
6460
6579
|
}
|
|
6461
6580
|
}
|
|
6462
6581
|
|
|
6582
|
+
// src/wallet/provenance/selectDescribingNote.ts
|
|
6583
|
+
function hasSourcePk(note) {
|
|
6584
|
+
return typeof note.sourcePk === "bigint" && note.sourcePk !== 0n;
|
|
6585
|
+
}
|
|
6586
|
+
function selectDescribingNote(candidates) {
|
|
6587
|
+
return candidates.find(hasSourcePk) ?? candidates[0];
|
|
6588
|
+
}
|
|
6589
|
+
function selectDescribingNoteByCommitment(commitments, noteByCommitment) {
|
|
6590
|
+
const owned = commitments.map((hex) => noteByCommitment.get(hex)).filter((note) => note !== void 0);
|
|
6591
|
+
return selectDescribingNote(owned);
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
// src/wallet/provenance/merge.ts
|
|
6595
|
+
var SOURCE_RANK = {
|
|
6596
|
+
witnessed: 3,
|
|
6597
|
+
memo: 2,
|
|
6598
|
+
chain: 1,
|
|
6599
|
+
inferred: 0
|
|
6600
|
+
};
|
|
6601
|
+
function rankOf(source) {
|
|
6602
|
+
return SOURCE_RANK[source] ?? -1;
|
|
6603
|
+
}
|
|
6604
|
+
function outranks(a, b) {
|
|
6605
|
+
return rankOf(a) > rankOf(b);
|
|
6606
|
+
}
|
|
6607
|
+
function mergeProvenance(existing, incoming) {
|
|
6608
|
+
if (existing.id !== incoming.id) {
|
|
6609
|
+
throw new Error(
|
|
6610
|
+
`mergeProvenance: id mismatch \u2014 refusing to merge ${existing.id} with ${incoming.id}`
|
|
6611
|
+
);
|
|
6612
|
+
}
|
|
6613
|
+
const incomingWins = outranks(incoming.source, existing.source);
|
|
6614
|
+
const base = incomingWins ? incoming : existing;
|
|
6615
|
+
const other = incomingWins ? existing : incoming;
|
|
6616
|
+
const fee = base.feePlanck ?? other.feePlanck;
|
|
6617
|
+
const publicRecipient = firstPresent(base.publicRecipient, other.publicRecipient);
|
|
6618
|
+
const slip = present(base.slip?.encoded) ? base.slip : other.slip ?? base.slip;
|
|
6619
|
+
const note = base.note ?? other.note;
|
|
6620
|
+
return {
|
|
6621
|
+
// The loser is spread FIRST so a field only it carries survives. A host
|
|
6622
|
+
// stores its own record type through this — `ReconstructedTxRecord` has
|
|
6623
|
+
// `amountApproximate`, which marks an amount derived without subtracting
|
|
6624
|
+
// the fee — and dropping such a field turns an approximate figure into
|
|
6625
|
+
// one that merely looks exact. Every key the winner knows about is
|
|
6626
|
+
// overwritten below, so rank still decides every shared fact.
|
|
6627
|
+
...other,
|
|
6628
|
+
...base,
|
|
6629
|
+
// A known peer beats an unknown one even when the winner is silent:
|
|
6630
|
+
// backfilling the recipient is the whole point of re-running this.
|
|
6631
|
+
// `scope: 'none'` is "this operation has no counterparty", so it is a
|
|
6632
|
+
// gap too — treating it as known would block the backfill it exists for.
|
|
6633
|
+
peer: copyPeer(knownPeer(base.peer) ?? knownPeer(other.peer) ?? base.peer ?? other.peer),
|
|
6634
|
+
// A number that stands for "not known yet" must not win by rank. Zero is
|
|
6635
|
+
// exactly that here: `RECOVERED_TX_RESULT` and a failed submission both
|
|
6636
|
+
// report block 0, and their own comment says a caller who needs the
|
|
6637
|
+
// value must look it up rather than trust the field.
|
|
6638
|
+
blockNumber: firstPositive(base.blockNumber, other.blockNumber),
|
|
6639
|
+
timestampMs: firstPositive(base.timestampMs, other.timestampMs),
|
|
6640
|
+
// Reconstruction writes an empty hash when the extrinsic was not decoded.
|
|
6641
|
+
hash: firstPresent(base.hash, other.hash) ?? base.hash,
|
|
6642
|
+
// `exact` is a property of the FIGURE, not of the source. A `witnessed`
|
|
6643
|
+
// row whose amount was marked approximate is not better data than an
|
|
6644
|
+
// exact one from a weaker source, so rank only breaks a tie between two
|
|
6645
|
+
// figures of equal standing.
|
|
6646
|
+
amount: { ...betterAmount(base.amount, other.amount) },
|
|
6647
|
+
// The chain's outcome is not something one source knows better than
|
|
6648
|
+
// another — anyone who looks sees the same thing. Letting rank decide
|
|
6649
|
+
// would let a row written at submit time mark a transaction failed that
|
|
6650
|
+
// the chain went on to accept.
|
|
6651
|
+
status: base.status === "success" || other.status === "success" ? "success" : "failed",
|
|
6652
|
+
...fee !== void 0 && { feePlanck: fee },
|
|
6653
|
+
...publicRecipient !== void 0 && { publicRecipient },
|
|
6654
|
+
...slip !== void 0 && { slip: { ...slip } },
|
|
6655
|
+
...note !== void 0 && { note: { ...note } }
|
|
6656
|
+
};
|
|
6657
|
+
}
|
|
6658
|
+
function betterAmount(base, other) {
|
|
6659
|
+
if (base.exact === other.exact) return base;
|
|
6660
|
+
return base.exact ? base : other;
|
|
6661
|
+
}
|
|
6662
|
+
function present(value) {
|
|
6663
|
+
return value !== void 0 && value.length > 0;
|
|
6664
|
+
}
|
|
6665
|
+
function firstPresent(base, other) {
|
|
6666
|
+
return present(base) ? base : present(other) ? other : void 0;
|
|
6667
|
+
}
|
|
6668
|
+
function firstPositive(base, other) {
|
|
6669
|
+
return base > 0 ? base : other > 0 ? other : base;
|
|
6670
|
+
}
|
|
6671
|
+
function copyPeer(peer) {
|
|
6672
|
+
return peer ? { ...peer } : null;
|
|
6673
|
+
}
|
|
6674
|
+
function knownPeer(peer) {
|
|
6675
|
+
return peer && peer.scope !== "none" ? peer : null;
|
|
6676
|
+
}
|
|
6677
|
+
|
|
6678
|
+
// src/wallet/provenance/regenerateSlip.ts
|
|
6679
|
+
function regeneratePaymentSlip(facts, recipientIvkPacked, txHash) {
|
|
6680
|
+
if (!isHexOfLength(facts.commitmentHex, 32)) {
|
|
6681
|
+
throw new Error("regeneratePaymentSlip: commitmentHex must be 32 bytes of hex");
|
|
6682
|
+
}
|
|
6683
|
+
if (!isHexOfLength(facts.encryptedMemo, ENCRYPTED_MEMO_SIZE)) {
|
|
6684
|
+
throw new Error(
|
|
6685
|
+
`regeneratePaymentSlip: encryptedMemo must be ${ENCRYPTED_MEMO_SIZE} bytes of hex`
|
|
6686
|
+
);
|
|
6687
|
+
}
|
|
6688
|
+
if (facts.leafIndex !== void 0 && !isValidLeafIndex(facts.leafIndex)) {
|
|
6689
|
+
throw new Error("regeneratePaymentSlip: leafIndex must be a real tree position");
|
|
6690
|
+
}
|
|
6691
|
+
const envelope = sealPaymentSlip(recipientIvkPacked, {
|
|
6692
|
+
commitmentHex: facts.commitmentHex,
|
|
6693
|
+
encryptedMemo: facts.encryptedMemo,
|
|
6694
|
+
...facts.leafIndex !== void 0 ? { leafIndex: facts.leafIndex } : {},
|
|
6695
|
+
// Rendered as an explorer link by the recipient, where an unconstrained
|
|
6696
|
+
// string is a URL injection wearing the authority of a decrypted slip.
|
|
6697
|
+
// Dropped rather than fatal: the slip still rebuilds the note, which is
|
|
6698
|
+
// the part that matters.
|
|
6699
|
+
...isHexOfLength(txHash, 32) ? { txHash } : {}
|
|
6700
|
+
});
|
|
6701
|
+
return encodePaymentSlip(envelope);
|
|
6702
|
+
}
|
|
6703
|
+
|
|
6463
6704
|
// src/wallet/scanner/history/reconstruct.ts
|
|
6464
6705
|
var ZERO_PK = "0x" + "00".repeat(32);
|
|
6465
6706
|
function extrinsicKey(row) {
|
|
6466
6707
|
return `${row.blockNumber}:${row.extrinsicIndex ?? "null"}`;
|
|
6467
6708
|
}
|
|
6468
|
-
function toPkHex(
|
|
6469
|
-
return
|
|
6470
|
-
}
|
|
6471
|
-
function findChangeNote(commitments, noteByCommitment) {
|
|
6472
|
-
const candidates = commitments.map((h) => noteByCommitment.get(h)).filter((n) => n !== void 0);
|
|
6473
|
-
return candidates.find((n) => n.counterpartyPk != null && n.counterpartyPk !== 0n) ?? candidates[0];
|
|
6709
|
+
function toPkHex(sourcePk) {
|
|
6710
|
+
return typeof sourcePk === "bigint" && hasSourcePk({ sourcePk }) ? scalarToHex(sourcePk) : ZERO_PK;
|
|
6474
6711
|
}
|
|
6475
6712
|
async function loadExistingRecords(vault) {
|
|
6476
6713
|
try {
|
|
6477
6714
|
const records = await vault.getTxRecords();
|
|
6478
|
-
return new Map(records.map((r) => [r.
|
|
6715
|
+
return new Map(records.map((r) => [r.id, r]));
|
|
6479
6716
|
} catch {
|
|
6480
6717
|
return /* @__PURE__ */ new Map();
|
|
6481
6718
|
}
|
|
6482
6719
|
}
|
|
6720
|
+
function recordKey(transfer) {
|
|
6721
|
+
return transfer.hash ?? `${transfer.blockNumber}-${transfer.extrinsicIndex ?? 0}`;
|
|
6722
|
+
}
|
|
6483
6723
|
async function reconstructOutgoingTxRecords(deps) {
|
|
6484
6724
|
const { vault, transfers } = deps;
|
|
6485
6725
|
const now = deps.now ?? Date.now;
|
|
@@ -6496,17 +6736,17 @@ async function reconstructOutgoingTxRecords(deps) {
|
|
|
6496
6736
|
const commitmentsByExtrinsic = new Map(
|
|
6497
6737
|
commitmentTransfers.map((ct) => [extrinsicKey(ct), ct.matchedCommitments ?? []])
|
|
6498
6738
|
);
|
|
6499
|
-
const
|
|
6739
|
+
const existingByKey = await loadExistingRecords(vault);
|
|
6500
6740
|
for (const transfer of outgoingTransfers) {
|
|
6501
|
-
const existing =
|
|
6741
|
+
const existing = existingByKey.get(recordKey(transfer));
|
|
6502
6742
|
if (existing?.recipientPkHex && existing.recipientPkHex !== ZERO_PK) continue;
|
|
6503
6743
|
const inputNotes = (transfer.matchedNullifiers ?? []).map((h) => noteByNullifier.get(h)).filter((n) => n !== void 0);
|
|
6504
6744
|
if (inputNotes.length === 0) continue;
|
|
6505
|
-
const changeNote =
|
|
6745
|
+
const changeNote = selectDescribingNoteByCommitment(
|
|
6506
6746
|
commitmentsByExtrinsic.get(extrinsicKey(transfer)) ?? [],
|
|
6507
6747
|
noteByCommitment
|
|
6508
6748
|
);
|
|
6509
|
-
const recipientPkHex = toPkHex(changeNote?.
|
|
6749
|
+
const recipientPkHex = toPkHex(changeNote?.sourcePk);
|
|
6510
6750
|
if (existing) {
|
|
6511
6751
|
if (recipientPkHex === ZERO_PK) continue;
|
|
6512
6752
|
await vault.saveTxRecord({ ...existing, recipientPkHex });
|
|
@@ -6517,7 +6757,7 @@ async function reconstructOutgoingTxRecords(deps) {
|
|
|
6517
6757
|
const transferAmount = totalInputValue - (changeNote?.value ?? 0n) - (fee ?? 0n);
|
|
6518
6758
|
if (transferAmount <= 0n) continue;
|
|
6519
6759
|
const record = {
|
|
6520
|
-
id: transfer
|
|
6760
|
+
id: recordKey(transfer),
|
|
6521
6761
|
type: "private_transfer",
|
|
6522
6762
|
blockNumber: transfer.blockNumber,
|
|
6523
6763
|
hash: transfer.hash ?? "",
|
|
@@ -6568,7 +6808,7 @@ async function buildZkNote(params, deps) {
|
|
|
6568
6808
|
circuitVersion: params.circuitVersion,
|
|
6569
6809
|
// Omitted rather than passed as undefined: the builder distinguishes an
|
|
6570
6810
|
// absent recipient (a self note) from one explicitly set.
|
|
6571
|
-
...params.
|
|
6811
|
+
...params.sourcePk !== void 0 && { sourcePk: params.sourcePk },
|
|
6572
6812
|
...params.recipientOwnerPk !== void 0 && {
|
|
6573
6813
|
recipientOwnerPk: params.recipientOwnerPk
|
|
6574
6814
|
},
|
|
@@ -6887,7 +7127,7 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
6887
7127
|
value: transferAmount,
|
|
6888
7128
|
assetId: noteA.assetId,
|
|
6889
7129
|
ownerPk: recipientPk,
|
|
6890
|
-
|
|
7130
|
+
sourcePk: effectiveSenderPk,
|
|
6891
7131
|
// Undefined → dummy memo; the recipient finds the note by scanning.
|
|
6892
7132
|
...recipientViewingPublicKey !== void 0 ? { viewingPublicKey: recipientViewingPublicKey } : {},
|
|
6893
7133
|
// With a viewing key present this activates stealth derivation.
|
|
@@ -6898,7 +7138,7 @@ async function transferNotes(deps, params, onProgress) {
|
|
|
6898
7138
|
assetId: noteA.assetId,
|
|
6899
7139
|
ownerPk: effectiveSenderPk,
|
|
6900
7140
|
spendingKey: noteA.spendingKey,
|
|
6901
|
-
|
|
7141
|
+
sourcePk: recipientNote.ownerPk,
|
|
6902
7142
|
viewingPublicKey: senderViewingPublicKey
|
|
6903
7143
|
});
|
|
6904
7144
|
onProgress?.("generating-zk");
|
|
@@ -7083,7 +7323,7 @@ async function unshieldNote(deps, params, onProgress) {
|
|
|
7083
7323
|
var import_proof_generator8 = require("@orbinum/proof-generator");
|
|
7084
7324
|
async function claimFees(deps, { assetId, amount, signer }, onStep) {
|
|
7085
7325
|
onStep?.("building-note");
|
|
7086
|
-
const note = await deps.buildNote({ value: amount, assetId: BigInt(assetId) });
|
|
7326
|
+
const { note } = await deps.buildNote({ value: amount, assetId: BigInt(assetId) });
|
|
7087
7327
|
const { provider } = await deps.resolver.resolve(import_proof_generator8.CircuitType.ValueProof, note.circuitVersion);
|
|
7088
7328
|
onStep?.("generating-proof");
|
|
7089
7329
|
const proofOutput = await generateFeeClaimProof(
|
|
@@ -7930,6 +8170,7 @@ var OrbinumWallet = class {
|
|
|
7930
8170
|
clearKnownEphWindow,
|
|
7931
8171
|
clearSession,
|
|
7932
8172
|
collectNullifiersToQuery,
|
|
8173
|
+
collectOutgoingFacts,
|
|
7933
8174
|
collectScanEntries,
|
|
7934
8175
|
commitmentHexOf,
|
|
7935
8176
|
computeNoteCommitment,
|
|
@@ -8005,6 +8246,7 @@ var OrbinumWallet = class {
|
|
|
8005
8246
|
getSubstrateSignerFromExtension,
|
|
8006
8247
|
hasCachedSession,
|
|
8007
8248
|
hasInjectedExtensions,
|
|
8249
|
+
hasSourcePk,
|
|
8008
8250
|
hexToBigint,
|
|
8009
8251
|
hexToNumber,
|
|
8010
8252
|
implicitSubstrateToEvm,
|
|
@@ -8016,6 +8258,7 @@ var OrbinumWallet = class {
|
|
|
8016
8258
|
isConnectionLossError,
|
|
8017
8259
|
isEvmAddress,
|
|
8018
8260
|
isGhostNoteError,
|
|
8261
|
+
isHexOfLength,
|
|
8019
8262
|
isImplicitEvmAccount,
|
|
8020
8263
|
isNativeAsset,
|
|
8021
8264
|
isNoteSelfConsistent,
|
|
@@ -8028,6 +8271,7 @@ var OrbinumWallet = class {
|
|
|
8028
8271
|
mapExtrinsicArgs,
|
|
8029
8272
|
mapZkEventData,
|
|
8030
8273
|
markInputsSpent,
|
|
8274
|
+
mergeProvenance,
|
|
8031
8275
|
normalizeChainFingerprint,
|
|
8032
8276
|
normalizeEvmAddress,
|
|
8033
8277
|
normalizeNote,
|
|
@@ -8042,6 +8286,7 @@ var OrbinumWallet = class {
|
|
|
8042
8286
|
noteTxKind,
|
|
8043
8287
|
openOutgoingBlob,
|
|
8044
8288
|
openPaymentSlip,
|
|
8289
|
+
outranks,
|
|
8045
8290
|
pairwiseEphWindow,
|
|
8046
8291
|
palletErrorKind,
|
|
8047
8292
|
parseAmount,
|
|
@@ -8056,6 +8301,7 @@ var OrbinumWallet = class {
|
|
|
8056
8301
|
recoverOwnerPkPoint,
|
|
8057
8302
|
recoverSelfStealthNote,
|
|
8058
8303
|
refuseIfAlreadySpent,
|
|
8304
|
+
regeneratePaymentSlip,
|
|
8059
8305
|
removeByCommitment,
|
|
8060
8306
|
requireSessionKeys,
|
|
8061
8307
|
reservePairwiseIndex,
|
|
@@ -8069,6 +8315,8 @@ var OrbinumWallet = class {
|
|
|
8069
8315
|
scanAbortError,
|
|
8070
8316
|
sealOutgoingBlob,
|
|
8071
8317
|
sealPaymentSlip,
|
|
8318
|
+
selectDescribingNote,
|
|
8319
|
+
selectDescribingNoteByCommitment,
|
|
8072
8320
|
selectGhosts,
|
|
8073
8321
|
selectNotes,
|
|
8074
8322
|
selfEphWindow,
|