@bounded-sh/server 0.0.64 → 0.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,30 +4,10 @@ var core = require('@bounded-sh/core');
4
4
  var web3_js = require('@solana/web3.js');
5
5
  var bs58 = require('bs58');
6
6
  var buffer = require('buffer');
7
- var anchor = require('@coral-xyz/anchor');
8
7
  var nacl = require('tweetnacl');
9
8
  var viem = require('viem');
10
9
  var accounts = require('viem/accounts');
11
10
 
12
- function _interopNamespaceDefault(e) {
13
- var n = Object.create(null);
14
- if (e) {
15
- Object.keys(e).forEach(function (k) {
16
- if (k !== 'default') {
17
- var d = Object.getOwnPropertyDescriptor(e, k);
18
- Object.defineProperty(n, k, d.get ? d : {
19
- enumerable: true,
20
- get: function () { return e[k]; }
21
- });
22
- }
23
- });
24
- }
25
- n.default = e;
26
- return Object.freeze(n);
27
- }
28
-
29
- var anchor__namespace = /*#__PURE__*/_interopNamespaceDefault(anchor);
30
-
31
11
  async function init(newConfig) {
32
12
  await core.init(Object.assign(Object.assign({}, newConfig), { authProvider: null, isServer: true, skipBackendInit: true }));
33
13
  }
@@ -73,245 +53,6 @@ async function getIdToken() {
73
53
  throw new Error('getIdToken is not available as a tarobase-server top-level auth operation. Use createWalletClient({ keypair }) and call the returned client methods.');
74
54
  }
75
55
 
76
- const PRESIGNED_BLOCKHASH_ERROR = 'Server signedTransaction blockhash is stale or expired';
77
- const BOUNDED_PROGRAM_MAINNET = 'openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F';
78
- const BOUNDED_PROGRAM_DEVNET = 'openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F';
79
- const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
80
- const SYSTEM_PROGRAM_ID = '11111111111111111111111111111111';
81
- const SUPPORTED_SOLANA_NETWORKS = ['solana_devnet', 'solana_mainnet'];
82
- const SUPPORTED_SOLANA_NETWORK_SET = new Set(SUPPORTED_SOLANA_NETWORKS);
83
- const ALLOWED_SERVER_TX_PROGRAMS = new Set([
84
- BOUNDED_PROGRAM_MAINNET,
85
- BOUNDED_PROGRAM_DEVNET,
86
- COMPUTE_BUDGET_PROGRAM,
87
- SYSTEM_PROGRAM_ID,
88
- ]);
89
- // Canonical set_documents discriminator (single wire format: ra_indices Vec<u8>).
90
- const SET_DOCUMENTS_DISCRIMINATOR = '79,46,72,73,24,79,66,245';
91
- const ALLOWED_BOUNDED_SET_DISCRIMINATORS = new Set([
92
- SET_DOCUMENTS_DISCRIMINATOR,
93
- ]);
94
- /* ──────────────────────────────────────────────────────────
95
- * Helper – fetch getPriorityFeeEstimate
96
- * ──────────────────────────────────────────────────────── */
97
- async function fetchPriorityFee(rpcEndpoint, rawUnsignedTxBuffer) {
98
- var _a, _b;
99
- try {
100
- const res = await fetch(rpcEndpoint, {
101
- method: "POST",
102
- headers: { "Content-Type": "application/json" },
103
- body: JSON.stringify({
104
- jsonrpc: "2.0",
105
- id: "1",
106
- method: "getPriorityFeeEstimate",
107
- params: [{
108
- transaction: bs58.encode(rawUnsignedTxBuffer), options: {
109
- recommended: true
110
- }
111
- }]
112
- })
113
- });
114
- const data = await res.json();
115
- const fee = (_b = (_a = data === null || data === void 0 ? void 0 : data.result) === null || _a === void 0 ? void 0 : _a.priorityFeeEstimate) !== null && _b !== void 0 ? _b : null;
116
- if (fee != null && fee > 0) {
117
- return Math.ceil(Number(fee) * 1.2);
118
- }
119
- console.warn("Priority-fee estimate returned no positive fee; skipping priority fee instruction");
120
- return null;
121
- }
122
- catch (err) {
123
- console.warn("Priority-fee estimate failed; skipping priority fee instruction:", err);
124
- return null;
125
- }
126
- }
127
- function isPresignedBlockhashError(error) {
128
- return typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' && error.message.includes(PRESIGNED_BLOCKHASH_ERROR);
129
- }
130
- function isPermanentPresignedTransactionError(error) {
131
- return isPresignedBlockhashError(error) ||
132
- (typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' && (error.message.startsWith('Server signedTransaction') ||
133
- error.message.startsWith('Server preInstruction')));
134
- }
135
- class BorshCursor {
136
- constructor(data, offset) {
137
- this.data = data;
138
- this.offset = offset;
139
- }
140
- requireBytes(length, field) {
141
- if (this.offset + length > this.data.length) {
142
- throw new Error(`Server signedTransaction has malformed Bounded instruction data while reading ${field}`);
143
- }
144
- }
145
- readU8(field) {
146
- this.requireBytes(1, field);
147
- return this.data[this.offset++];
148
- }
149
- readU32(field) {
150
- this.requireBytes(4, field);
151
- const value = this.data[this.offset] |
152
- (this.data[this.offset + 1] << 8) |
153
- (this.data[this.offset + 2] << 16) |
154
- (this.data[this.offset + 3] << 24);
155
- this.offset += 4;
156
- return value >>> 0;
157
- }
158
- skip(length, field) {
159
- this.requireBytes(length, field);
160
- this.offset += length;
161
- }
162
- readString(field) {
163
- const length = this.readU32(`${field} length`);
164
- this.requireBytes(length, field);
165
- const raw = this.data.slice(this.offset, this.offset + length);
166
- this.offset += length;
167
- return buffer.Buffer.from(raw).toString('utf8');
168
- }
169
- skipBytes(field) {
170
- const length = this.readU32(`${field} length`);
171
- this.skip(length, field);
172
- }
173
- isAtEnd() {
174
- return this.offset === this.data.length;
175
- }
176
- }
177
- function normalizeOnchainPath(path) {
178
- let normalized = path.startsWith('/') ? path.slice(1) : path;
179
- if (normalized.endsWith('*') && normalized.length > 1) {
180
- normalized = normalized.slice(0, -1);
181
- }
182
- if (normalized.endsWith('/')) {
183
- normalized = normalized.slice(0, -1);
184
- }
185
- return normalized;
186
- }
187
- function skipBoundedFieldValue(cursor) {
188
- const option = cursor.readU8('operation value option');
189
- if (option === 0)
190
- return;
191
- if (option !== 1) {
192
- throw new Error('Server signedTransaction has malformed Bounded field value option');
193
- }
194
- const variant = cursor.readU8('operation value variant');
195
- switch (variant) {
196
- case 0:
197
- case 1:
198
- cursor.skip(8, 'operation numeric value');
199
- return;
200
- case 2:
201
- cursor.skip(1, 'operation bool value');
202
- return;
203
- case 3:
204
- cursor.readString('operation string value');
205
- return;
206
- case 4:
207
- cursor.skip(32, 'operation address value');
208
- return;
209
- default:
210
- throw new Error(`Server signedTransaction has unsupported Bounded field value variant: ${variant}`);
211
- }
212
- }
213
- function skipBoundedFieldOperation(cursor) {
214
- cursor.readString('operation key');
215
- skipBoundedFieldValue(cursor);
216
- cursor.skip(1, 'operation kind');
217
- }
218
- function skipBoundedTxData(cursor) {
219
- cursor.readString('txData plugin function key');
220
- cursor.skipBytes('txData bytes');
221
- cursor.skipBytes('txData raIndices');
222
- }
223
- function parseBoundedSetDocumentsInstruction(data) {
224
- if (data.length < 8) {
225
- throw new Error('Server signedTransaction has malformed Bounded instruction data');
226
- }
227
- const discriminator = Array.from(data.slice(0, 8)).join(',');
228
- if (!ALLOWED_BOUNDED_SET_DISCRIMINATORS.has(discriminator)) {
229
- throw new Error('Server signedTransaction contains unsupported Bounded instruction');
230
- }
231
- const cursor = new BorshCursor(data, 8);
232
- const appId = cursor.readString('appId');
233
- const documentPaths = [];
234
- const documentCount = cursor.readU32('documents length');
235
- for (let i = 0; i < documentCount; i++) {
236
- documentPaths.push(normalizeOnchainPath(cursor.readString('document path')));
237
- const operationCount = cursor.readU32('operations length');
238
- for (let j = 0; j < operationCount; j++) {
239
- skipBoundedFieldOperation(cursor);
240
- }
241
- }
242
- const deletePaths = [];
243
- const deleteCount = cursor.readU32('delete paths length');
244
- for (let i = 0; i < deleteCount; i++) {
245
- deletePaths.push(normalizeOnchainPath(cursor.readString('delete path')));
246
- }
247
- const txDataCount = cursor.readU32('txData length');
248
- for (let i = 0; i < txDataCount; i++) {
249
- skipBoundedTxData(cursor);
250
- }
251
- const simulate = cursor.readU8('simulate');
252
- if (simulate !== 0 && simulate !== 1) {
253
- throw new Error('Server signedTransaction has malformed Bounded simulate flag');
254
- }
255
- if (!cursor.isAtEnd()) {
256
- throw new Error('Server signedTransaction has trailing Bounded instruction data');
257
- }
258
- return { appId, documentPaths, deletePaths };
259
- }
260
- function assertSamePathSet(expectedPaths, actualPaths) {
261
- const expected = new Set(expectedPaths.map(normalizeOnchainPath).filter(Boolean));
262
- const actual = new Set(actualPaths.map(normalizeOnchainPath).filter(Boolean));
263
- const missing = [...expected].filter(path => !actual.has(path));
264
- const extra = [...actual].filter(path => !expected.has(path));
265
- if (missing.length > 0 || extra.length > 0) {
266
- const details = [
267
- missing.length ? `missing paths: ${missing.join(', ')}` : '',
268
- extra.length ? `unexpected paths: ${extra.join(', ')}` : '',
269
- ].filter(Boolean).join('; ');
270
- throw new Error(`Server signedTransaction Bounded instruction paths do not match requested write paths (${details})`);
271
- }
272
- }
273
- function validatePresignedSignedTransaction(transaction, sol) {
274
- var _a, _b, _c, _d, _e, _f, _g;
275
- const accountKeys = transaction.message.staticAccountKeys;
276
- let boundedInstructionCount = 0;
277
- const actualWritePaths = [];
278
- for (const ix of transaction.message.compiledInstructions) {
279
- if (ix.programIdIndex >= accountKeys.length) {
280
- throw new Error('Server signedTransaction has program ID in lookup table (not allowed)');
281
- }
282
- const programId = accountKeys[ix.programIdIndex].toBase58();
283
- if (!ALLOWED_SERVER_TX_PROGRAMS.has(programId)) {
284
- throw new Error(`Server signedTransaction contains unauthorized program: ${programId}`);
285
- }
286
- const data = ix.data instanceof Uint8Array ? ix.data : buffer.Buffer.from((_a = ix.data) !== null && _a !== void 0 ? _a : []);
287
- if (programId === SYSTEM_PROGRAM_ID) {
288
- throw new Error('Server signedTransaction contains unauthorized System Program instruction');
289
- }
290
- if (programId === BOUNDED_PROGRAM_MAINNET || programId === BOUNDED_PROGRAM_DEVNET) {
291
- boundedInstructionCount += 1;
292
- const parsed = parseBoundedSetDocumentsInstruction(data);
293
- if (parsed.appId !== sol.appId) {
294
- throw new Error('Server signedTransaction Bounded instruction appId does not match configured appId');
295
- }
296
- actualWritePaths.push(...parsed.documentPaths, ...parsed.deletePaths);
297
- }
298
- }
299
- if (boundedInstructionCount !== 1) {
300
- throw new Error('Server signedTransaction must contain exactly one Bounded set-documents instruction');
301
- }
302
- const expectedWritePaths = [
303
- ...(((_d = (_c = (_b = sol.txArgs) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.setDocumentData) !== null && _d !== void 0 ? _d : []).map((doc) => doc.path)),
304
- ...((_g = (_f = (_e = sol.txArgs) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.deletePaths) !== null && _g !== void 0 ? _g : []),
305
- ];
306
- assertSamePathSet(expectedWritePaths, actualWritePaths);
307
- }
308
- function assertAllowedServerPreInstructions(preInstructions) {
309
- for (const [index, ix] of (preInstructions !== null && preInstructions !== void 0 ? preInstructions : []).entries()) {
310
- if (ix.programId.equals(web3_js.SystemProgram.programId)) {
311
- throw new Error(`Server preInstruction[${index}] contains unauthorized System Program instruction`);
312
- }
313
- }
314
- }
315
56
  class SolanaKeypairProvider {
316
57
  constructor(rpcUrl, serverKeypair) {
317
58
  this.rpcUrl = rpcUrl;
@@ -440,16 +181,6 @@ class SolanaKeypairProvider {
440
181
  }
441
182
  return { signature: sig, txInfo };
442
183
  }
443
- requireSupportedNetwork(network) {
444
- const networkName = network === null || network === void 0 ? void 0 : network.trim();
445
- if (!networkName) {
446
- throw new Error(`Supported sol.network is required for Solana transaction submission; expected one of: ${SUPPORTED_SOLANA_NETWORKS.join(', ')}.`);
447
- }
448
- if (!SUPPORTED_SOLANA_NETWORK_SET.has(networkName)) {
449
- throw new Error(`Unsupported Solana network "${networkName}". Supported networks: ${SUPPORTED_SOLANA_NETWORKS.join(', ')}.`);
450
- }
451
- return networkName;
452
- }
453
184
  getRpcUrl() {
454
185
  var _a;
455
186
  const explicitRpcUrl = (_a = this.rpcUrl) === null || _a === void 0 ? void 0 : _a.trim();
@@ -461,174 +192,13 @@ class SolanaKeypairProvider {
461
192
  /* ----------------------------------------------------------- *
462
193
  * Transaction runner
463
194
  * ----------------------------------------------------------- */
464
- async runTransaction(_evm, sol, opts) {
465
- if (!sol)
466
- throw new Error('Solana transaction data required');
467
- const kp = this.keypair;
468
- this.requireSupportedNetwork(sol.network);
469
- const rpcUrl = this.getRpcUrl();
470
- // Helper for duck typing - checks if transaction is legacy Transaction vs VersionedTransaction
471
- const isLegacyTx = (tx) => {
472
- return 'recentBlockhash' in tx && !('message' in tx && 'staticAccountKeys' in tx.message);
473
- };
474
- const wallet = {
475
- publicKey: kp.publicKey,
476
- signTransaction: async (tx) => {
477
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
478
- if (isLegacyTx(tx)) {
479
- tx.partialSign(kp);
480
- }
481
- else {
482
- tx.sign([kp]);
483
- }
484
- return tx;
485
- },
486
- signAllTransactions: async (txs) => txs.map((t) => {
487
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
488
- if (isLegacyTx(t)) {
489
- t.partialSign(kp);
490
- }
491
- else {
492
- t.sign([kp]);
493
- }
494
- return t;
495
- }),
496
- };
497
- /* de-dupe remaining accounts */
498
- const deduped = (() => {
499
- const fin = [];
500
- for (const acc of core.convertRemainingAccounts(sol.txArgs[0].remainingAccounts)) {
501
- const ex = fin.find((x) => x.pubkey.equals(acc.pubkey));
502
- if (ex) {
503
- ex.isSigner || (ex.isSigner = acc.isSigner);
504
- ex.isWritable || (ex.isWritable = acc.isWritable);
505
- }
506
- else
507
- fin.push(acc);
508
- }
509
- return fin;
510
- })();
511
- let retries = 0;
512
- let didPass = false;
513
- let delay = 1000;
514
- let toReturn = null;
515
- let errorMessage = "";
516
- while (retries < 5) {
517
- try {
518
- toReturn = await this.runTransactionInner(sol, opts, wallet, kp, deduped, rpcUrl);
519
- didPass = true;
520
- break;
521
- }
522
- catch (error) {
523
- if (isPermanentPresignedTransactionError(error)) {
524
- throw error;
525
- }
526
- console.log("Error building and sending transaction on retry:", retries, error);
527
- await new Promise(resolve => setTimeout(resolve, delay));
528
- // Exponential backoff
529
- delay *= 1.5;
530
- retries++;
531
- errorMessage = error.message || JSON.stringify(error, null, 2);
532
- }
533
- }
534
- if (!didPass) {
535
- throw new Error(`Failed to send transaction after 5 retries: ${errorMessage}`);
536
- }
537
- return toReturn;
538
- }
539
- async runTransactionInner(sol, opts, wallet, kp, deduped, rpcUrl) {
540
- var _a, _b, _c;
541
- const connection = new web3_js.Connection(rpcUrl, 'confirmed');
542
- const anchorProvider = new anchor__namespace.AnchorProvider(connection, wallet, anchor__namespace.AnchorProvider.defaultOptions());
543
- const app_id = sol.appId;
544
- if (!app_id)
545
- throw new Error('app_id missing');
546
- // When the server has co-signed a transaction (CPI attestation),
547
- // deserialize it and just add the wallet signature instead of
548
- // building from components.
549
- let tx;
550
- let blockhash;
551
- let lastValidBlockHeight;
552
- if (sol.signedTransaction) {
553
- tx = web3_js.VersionedTransaction.deserialize(buffer.Buffer.from(sol.signedTransaction, 'base64'));
554
- validatePresignedSignedTransaction(tx, sol);
555
- blockhash = tx.message.recentBlockhash;
556
- const isValid = await connection.isBlockhashValid(blockhash, { commitment: 'confirmed' });
557
- if (!isValid.value) {
558
- throw new Error(`${PRESIGNED_BLOCKHASH_ERROR}; request a fresh server-built transaction.`);
559
- }
560
- lastValidBlockHeight = 0;
561
- }
562
- else {
563
- assertAllowedServerPreInstructions(sol.preInstructions);
564
- const result = await core.buildSetDocumentsTransaction(connection, sol.txArgs[0].idl, anchorProvider, kp.publicKey, {
565
- app_id,
566
- documents: sol.txArgs[0].setDocumentData,
567
- delete_paths: sol.txArgs[0].deletePaths,
568
- txData: sol.txArgs[0].txData
569
- }, deduped, sol.lutKey, sol.preInstructions, false, sol.additionalLutAddresses);
570
- tx = result.tx;
571
- blockhash = result.blockhash;
572
- lastValidBlockHeight = result.lastValidBlockHeight;
573
- }
574
- // Helper for duck typing - checks if transaction is legacy Transaction vs VersionedTransaction
575
- const isLegacyTx = (t) => {
576
- return 'recentBlockhash' in t && !('message' in t && 'staticAccountKeys' in t.message);
577
- };
578
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
579
- if (isLegacyTx(tx)) {
580
- if (!sol.signedTransaction) {
581
- tx.recentBlockhash = blockhash;
582
- tx.lastValidBlockHeight = lastValidBlockHeight;
583
- tx.feePayer = kp.publicKey;
584
- }
585
- tx.partialSign(kp);
586
- }
587
- else {
588
- if (!sol.signedTransaction) {
589
- tx.message.recentBlockhash = blockhash;
590
- }
591
- tx.sign([kp]);
592
- }
593
- // 3️⃣ Optional priority-fee instruction. Pre-signed transactions are
594
- // immutable at this point; preserve their exact message and skip estimation.
595
- if (!sol.signedTransaction) {
596
- const rawUnsigned = tx.serialize({ requireAllSignatures: false });
597
- const microLamports = await fetchPriorityFee(connection.rpcEndpoint, rawUnsigned);
598
- if (microLamports != null) {
599
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
600
- if (isLegacyTx(tx)) {
601
- tx.instructions.unshift(web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports }));
602
- tx.partialSign(kp);
603
- }
604
- else {
605
- const roundTwo = await core.buildSetDocumentsTransaction(connection, sol.txArgs[0].idl, anchorProvider, kp.publicKey, {
606
- app_id,
607
- documents: sol.txArgs[0].setDocumentData,
608
- delete_paths: sol.txArgs[0].deletePaths,
609
- txData: sol.txArgs[0].txData
610
- }, deduped, sol.lutKey, [web3_js.ComputeBudgetProgram.setComputeUnitPrice({ microLamports }), ...sol.preInstructions], false, sol.additionalLutAddresses);
611
- tx = roundTwo.tx;
612
- blockhash = roundTwo.blockhash;
613
- lastValidBlockHeight = roundTwo.lastValidBlockHeight;
614
- tx.message.recentBlockhash = blockhash;
615
- tx.sign([kp]);
616
- }
617
- }
618
- }
619
- if ((opts === null || opts === void 0 ? void 0 : opts.shouldSubmitTx) === false) {
620
- return { signedTransaction: tx, blockNumber: 0, gasUsed: '0', data: '' };
621
- }
622
- // Submit and confirm using shared helper
623
- const { signature, txInfo } = await this.submitAndConfirmTransaction(tx, connection, { fetchTxInfo: true });
624
- return {
625
- transactionSignature: signature,
626
- blockNumber: (_a = txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) !== null && _a !== void 0 ? _a : 0,
627
- gasUsed: (_c = (_b = txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta) === null || _b === void 0 ? void 0 : _b.fee.toString()) !== null && _c !== void 0 ? _c : '0',
628
- data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
629
- };
195
+ async runTransaction(_evmTransactionData, _solTransactionData, _options) {
196
+ // Client-side Solana transaction building is retired: the realtime worker
197
+ // returns a fully-built (and, when sponsored, partially-signed) transaction
198
+ // on its 202, which the SDK validates and signs via signTransaction /
199
+ // signAndSubmitTransaction. This provider serves no EVM payload either.
200
+ throw new Error('runTransaction is not supported by this provider; Bounded returns pre-built Solana transactions that are signed with signTransaction/signAndSubmitTransaction.');
630
201
  }
631
- /* ----------------------------------------------------------- */
632
202
  async getNativeMethods() {
633
203
  return { keypair: this.keypair };
634
204
  }
@@ -672,6 +242,48 @@ class OffchainAuthProvider {
672
242
  }
673
243
  }
674
244
 
245
+ function evmErrorText(error) {
246
+ const message = error === null || error === void 0 ? void 0 : error.message;
247
+ return typeof message === 'string' && message.length > 0 ? message : String(error);
248
+ }
249
+ /**
250
+ * Whether a broadcast failure PROVES the transaction never entered the network.
251
+ * A node-level validation rejection is definitive and keeps throwing; a
252
+ * transport failure (timeout, socket reset, aborted request) is ambiguous — the
253
+ * node may hold the bytes — and must resolve as `submitted`.
254
+ *
255
+ * "already known"/"already imported" is deliberately NOT definitive: it means
256
+ * the node already has these exact bytes, which is success, not failure.
257
+ */
258
+ const DEFINITIVE_EVM_SEND_MARKERS = [
259
+ 'nonce too low',
260
+ 'insufficient funds',
261
+ 'intrinsic gas too low',
262
+ 'gas required exceeds',
263
+ 'exceeds block gas limit',
264
+ 'invalid sender',
265
+ 'invalid signature',
266
+ 'execution reverted',
267
+ 'transaction underpriced',
268
+ 'oversized data',
269
+ ];
270
+ function isDefinitiveEvmSendFailure(error) {
271
+ var _a, _b;
272
+ const message = String((_b = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : error) !== null && _b !== void 0 ? _b : '').toLowerCase();
273
+ if (!message)
274
+ return false;
275
+ if (message.includes('already known') || message.includes('already imported'))
276
+ return false;
277
+ return DEFINITIVE_EVM_SEND_MARKERS.some(marker => message.includes(marker));
278
+ }
279
+ /** viem raises a dedicated not-found error when a receipt simply is not mined yet. */
280
+ function isReceiptNotFound(error) {
281
+ var _a, _b;
282
+ const name = String((_a = error === null || error === void 0 ? void 0 : error.name) !== null && _a !== void 0 ? _a : '');
283
+ if (name === 'TransactionReceiptNotFoundError')
284
+ return true;
285
+ return String((_b = error === null || error === void 0 ? void 0 : error.message) !== null && _b !== void 0 ? _b : '').toLowerCase().includes('could not be found');
286
+ }
675
287
  const NO_SOLANA = 'An EVM keypair client cannot sign Solana transactions.';
676
288
  function normalizeChainConfig(input) {
677
289
  var _a;
@@ -723,18 +335,21 @@ class EvmKeypairProvider {
723
335
  user() {
724
336
  return { id: this.address, address: null, evmAddress: this.address, provider: this };
725
337
  }
726
- async createBoundedSession() {
727
- var _a, _b, _c;
728
- const nonce = await core.genAuthNonce();
729
- const { appId } = await core.getConfig();
338
+ async createBoundedSession(sessionConfig) {
339
+ var _a, _b, _c, _d;
340
+ // Bind the SIWE nonce/message/session to the snapshotted app+issuer when a
341
+ // wallet client supplies one, so a later global init() cannot retarget this
342
+ // session mint (CTA-05); otherwise read the mutable global init() config.
343
+ const nonce = await core.genAuthNonce(sessionConfig);
344
+ const appId = (_a = sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId) !== null && _a !== void 0 ? _a : (await core.getConfig()).appId;
730
345
  const message = core.genSiweMessage(this.address, nonce, appId, 'server', 'https://server/login', { chainId: this.defaultChainId });
731
346
  const signature = await this.account.signMessage({ message });
732
- const session = await core.createSessionWithSignature(this.address, message, signature, 'siwe');
347
+ const session = await core.createSessionWithSignature(this.address, message, signature, 'siwe', sessionConfig);
733
348
  return {
734
349
  address: this.address,
735
- accessToken: String((_a = session.accessToken) !== null && _a !== void 0 ? _a : ''),
736
- idToken: String((_b = session.idToken) !== null && _b !== void 0 ? _b : ''),
737
- refreshToken: String((_c = session.refreshToken) !== null && _c !== void 0 ? _c : ''),
350
+ accessToken: String((_b = session.accessToken) !== null && _b !== void 0 ? _b : ''),
351
+ idToken: String((_c = session.idToken) !== null && _c !== void 0 ? _c : ''),
352
+ refreshToken: String((_d = session.refreshToken) !== null && _d !== void 0 ? _d : ''),
738
353
  };
739
354
  }
740
355
  async login() { return this.user(); }
@@ -783,25 +398,145 @@ class EvmKeypairProvider {
783
398
  const transport = viem.http(config.rpcUrl);
784
399
  const wallet = viem.createWalletClient({ account: this.account, chain, transport });
785
400
  const publicClient = viem.createPublicClient({ chain, transport });
786
- const hash = await wallet.sendTransaction({
787
- account: this.account,
788
- chain,
789
- to: config.runtimeAddress,
790
- data: evm.data,
791
- value: BigInt(0),
792
- });
793
- const receipt = await publicClient.waitForTransactionReceipt({ hash });
401
+ // Unlike a browser wallet this provider OWNS the private key, so it prepares
402
+ // and signs locally with an EXPLICIT nonce and derives the hash before the
403
+ // transaction is ever broadcast. Learning the hash only from
404
+ // `sendTransaction()` discards an identifier we could have known, so an
405
+ // accepted-but-lost response would strand a landed write.
406
+ let request;
407
+ let signedTransaction;
408
+ let hash;
409
+ let nonce;
410
+ try {
411
+ request = await wallet.prepareTransactionRequest({
412
+ account: this.account,
413
+ chain,
414
+ to: config.runtimeAddress,
415
+ data: evm.data,
416
+ value: BigInt(0),
417
+ });
418
+ nonce = Number(request.nonce);
419
+ if (!Number.isSafeInteger(nonce) || nonce < 0)
420
+ throw new Error('the prepared transaction carries no usable nonce');
421
+ signedTransaction = await wallet.signTransaction(request);
422
+ hash = viem.keccak256(signedTransaction);
423
+ }
424
+ catch (error) {
425
+ // Preparation and local signing happen entirely before broadcast, so any
426
+ // failure here is definitive.
427
+ throw error;
428
+ }
429
+ const fence = {
430
+ chainId: config.chainId,
431
+ sender: this.address,
432
+ nonce,
433
+ signedTransaction,
434
+ };
435
+ try {
436
+ await publicClient.sendRawTransaction({ serializedTransaction: signedTransaction });
437
+ }
438
+ catch (error) {
439
+ if (isDefinitiveEvmSendFailure(error))
440
+ throw error;
441
+ // Ambiguous transport outcome: the node may already hold these exact
442
+ // bytes. Report the pre-derived hash and the fence so reconciliation can
443
+ // poll it, or rebroadcast the byte-identical bytes while the nonce is
444
+ // unused — which cannot produce a second execution.
445
+ return {
446
+ outcome: 'submitted',
447
+ lane: 'evm',
448
+ transactionSignature: hash,
449
+ fence,
450
+ reason: `Broadcast response was lost or inconclusive: ${evmErrorText(error)}`,
451
+ };
452
+ }
453
+ let receipt;
454
+ try {
455
+ receipt = await publicClient.waitForTransactionReceipt({ hash });
456
+ }
457
+ catch (error) {
458
+ // The bytes were accepted; only the confirmation outcome is unknown.
459
+ return {
460
+ outcome: 'submitted',
461
+ lane: 'evm',
462
+ transactionSignature: hash,
463
+ fence,
464
+ reason: `Confirmation outcome unknown: ${evmErrorText(error)}`,
465
+ };
466
+ }
794
467
  if (receipt.status !== 'success')
795
468
  throw new Error(`EVM transaction ${hash} reverted on-chain`);
796
469
  if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER))
797
470
  throw new Error('EVM receipt block number exceeds the SDK safe-integer range');
798
471
  return {
472
+ outcome: 'confirmed',
473
+ lane: 'evm',
799
474
  transactionSignature: hash,
800
475
  blockNumber: Number(receipt.blockNumber),
801
476
  gasUsed: receipt.gasUsed.toString(),
802
477
  data: receipt,
803
478
  };
804
479
  }
480
+ /**
481
+ * Poll the SAME hash, and — only while the fenced nonce is still unused — put
482
+ * the byte-identical signed transaction back on the wire. Same nonce, same
483
+ * signature, so a rebroadcast can never produce a second execution.
484
+ *
485
+ * It NEVER reports a retry-safe terminal state. Nonce advancement is not proof
486
+ * the write did not execute: a replacement can occupy the same sender and
487
+ * nonce carrying identical Bounded calldata under a different hash, and plain
488
+ * JSON-RPC offers no transaction-by-sender-and-nonce lookup to identify it.
489
+ */
490
+ async reconcileEvmTransaction(request) {
491
+ var _a, _b;
492
+ const chainId = (_b = (_a = request.fence) === null || _a === void 0 ? void 0 : _a.chainId) !== null && _b !== void 0 ? _b : this.defaultChainId;
493
+ const config = this.chains.get(chainId);
494
+ if (!config)
495
+ return { state: 'unknown', reason: `EVM chain ${chainId} is not configured for this wallet client` };
496
+ const chain = asViemChain(config);
497
+ const transport = viem.http(config.rpcUrl);
498
+ const publicClient = viem.createPublicClient({ chain, transport });
499
+ try {
500
+ const receipt = await publicClient.getTransactionReceipt({ hash: request.transactionHash });
501
+ if (receipt) {
502
+ if (receipt.status !== 'success') {
503
+ return { state: 'failed', reason: `EVM transaction ${request.transactionHash} reverted on-chain` };
504
+ }
505
+ if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER)) {
506
+ return { state: 'unknown', reason: 'EVM receipt block number exceeds the SDK safe-integer range' };
507
+ }
508
+ return { state: 'confirmed', blockNumber: Number(receipt.blockNumber) };
509
+ }
510
+ }
511
+ catch (error) {
512
+ // viem throws `TransactionReceiptNotFoundError` when it is simply not
513
+ // mined yet; that is not an error condition, just "still unknown".
514
+ if (!isReceiptNotFound(error)) {
515
+ return { state: 'unknown', reason: `receipt lookup failed: ${evmErrorText(error)}` };
516
+ }
517
+ }
518
+ const fence = request.fence;
519
+ if (!request.allowRebroadcast || !fence) {
520
+ return { state: 'unknown', reason: 'no receipt yet for this transaction hash' };
521
+ }
522
+ try {
523
+ const nonceNow = await publicClient.getTransactionCount({ address: fence.sender, blockTag: 'latest' });
524
+ if (nonceNow > fence.nonce) {
525
+ // The nonce is spent. It may have been spent by THIS transaction under a
526
+ // different hash (a replacement), so this is emphatically not retry-safe.
527
+ return {
528
+ state: 'unknown',
529
+ reason: `nonce ${fence.nonce} on ${fence.sender} has been consumed but this hash has no receipt; ` +
530
+ 'the transaction may have been replaced, so the write is not proven absent',
531
+ };
532
+ }
533
+ await publicClient.sendRawTransaction({ serializedTransaction: fence.signedTransaction });
534
+ return { state: 'unknown', reason: 'rebroadcast the byte-identical fenced transaction; still awaiting a receipt' };
535
+ }
536
+ catch (error) {
537
+ return { state: 'unknown', reason: `rebroadcast attempt was inconclusive: ${evmErrorText(error)}` };
538
+ }
539
+ }
805
540
  async getNativeMethods() { return this.account; }
806
541
  transactionPolicy() {
807
542
  return {
@@ -867,10 +602,11 @@ function jwtExpiringSoon(token) {
867
602
  /* WalletClient */
868
603
  /* ------------------------------------------------------------------ */
869
604
  class WalletClient {
870
- constructor(provider, sessionManager, address, evmTransactionPolicy) {
605
+ constructor(provider, sessionManager, address, config, evmTransactionPolicy) {
871
606
  this.provider = provider;
872
607
  this.sessionManager = sessionManager;
873
608
  this.address = address;
609
+ this.config = config;
874
610
  this.evmTransactionPolicy = evmTransactionPolicy;
875
611
  this.live = {
876
612
  intent: (roomPath, intent, opts = {}) => core.live.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
@@ -896,6 +632,7 @@ class WalletClient {
896
632
  _getAuthHeaders: ovr._getAuthHeaders,
897
633
  _clearAuth: ovr._clearAuth,
898
634
  _walletAddress: ovr._walletAddress,
635
+ _config: ovr._config,
899
636
  },
900
637
  });
901
638
  },
@@ -903,7 +640,10 @@ class WalletClient {
903
640
  }
904
641
  /* ---- Auth override helpers ---- */
905
642
  buildOverrides() {
906
- return Object.assign({ authProvider: this.provider, _getAuthHeaders: async () => {
643
+ return Object.assign({ authProvider: this.provider,
644
+ // Pin every request to this wallet client's snapshotted app + endpoints so
645
+ // a later global init() can never retarget its reads/writes (CTA-05).
646
+ _config: this.config, _getAuthHeaders: async () => {
907
647
  let s = await this.sessionManager.getSession();
908
648
  // Keypair-backed session: if the cached token is expired/near-expiry,
909
649
  // re-sign for a fresh one (we always hold the key). Keeps long-lived WS
@@ -925,22 +665,22 @@ class WalletClient {
925
665
  mergeSetOverrides(opts) {
926
666
  var _a;
927
667
  const ovr = this.buildOverrides();
928
- return Object.assign(Object.assign({}, opts), { _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), authProvider: ovr.authProvider, _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _evmTransactionPolicy: ovr._evmTransactionPolicy }) });
668
+ return Object.assign(Object.assign({}, opts), { _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), authProvider: ovr.authProvider, _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _evmTransactionPolicy: ovr._evmTransactionPolicy, _config: ovr._config }) });
929
669
  }
930
670
  mergeReadOverrides(existing) {
931
671
  const ovr = this.buildOverrides();
932
- return Object.assign(Object.assign({}, existing), { headers: this.sanitizeHeaders(existing === null || existing === void 0 ? void 0 : existing.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress });
672
+ return Object.assign(Object.assign({}, existing), { headers: this.sanitizeHeaders(existing === null || existing === void 0 ? void 0 : existing.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress, _config: ovr._config });
933
673
  }
934
674
  mergeLiveIntentOptions(opts) {
935
675
  var _a;
936
676
  const ovr = this.buildOverrides();
937
- const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress }) });
677
+ const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress, _config: ovr._config }) });
938
678
  return merged;
939
679
  }
940
680
  mergeLiveStatusOptions(opts) {
941
681
  var _a;
942
682
  const ovr = this.buildOverrides();
943
- const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress }) });
683
+ const merged = Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts === null || opts === void 0 ? void 0 : opts.headers), _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress, _config: ovr._config }) });
944
684
  return merged;
945
685
  }
946
686
  /* ---- Data operations ---- */
@@ -953,6 +693,24 @@ class WalletClient {
953
693
  async setFile(path, file, options) {
954
694
  return core.setFile(path, file, { _overrides: this.mergeReadOverrides(options === null || options === void 0 ? void 0 : options._overrides) });
955
695
  }
696
+ /**
697
+ * Reconcile a `submitted` write receipt through THIS client's own provider
698
+ * and app binding. A bare top-level `reconcileSetResult` cannot serve a wallet
699
+ * client: the provider and config are private to the instance, and a later
700
+ * global `init()` would otherwise reconcile against the wrong app.
701
+ */
702
+ async reconcileSetResult(receipt, options) {
703
+ return core.reconcileSetResult(receipt, this.mergeReconcileOptions(options));
704
+ }
705
+ /** Bounded repeat of {@link reconcileSetResult}; see `waitForSetResult`. */
706
+ async waitForSetResult(receipt, options) {
707
+ return core.waitForSetResult(receipt, this.mergeReconcileOptions(options));
708
+ }
709
+ mergeReconcileOptions(opts) {
710
+ var _a;
711
+ const ovr = this.buildOverrides();
712
+ return Object.assign(Object.assign({}, (opts !== null && opts !== void 0 ? opts : {})), { _overrides: Object.assign(Object.assign({}, opts === null || opts === void 0 ? void 0 : opts._overrides), { headers: this.sanitizeHeaders((_a = opts === null || opts === void 0 ? void 0 : opts._overrides) === null || _a === void 0 ? void 0 : _a.headers), authProvider: ovr.authProvider, _getAuthHeaders: ovr._getAuthHeaders, _clearAuth: ovr._clearAuth, _walletAddress: ovr._walletAddress, _config: ovr._config }) });
713
+ }
956
714
  async get(path, opts) {
957
715
  return core.get(path, Object.assign(Object.assign({}, opts), { _overrides: this.mergeReadOverrides(opts === null || opts === void 0 ? void 0 : opts._overrides) }));
958
716
  }
@@ -1001,6 +759,7 @@ class WalletClient {
1001
759
  _getAuthHeaders: ovr._getAuthHeaders,
1002
760
  _clearAuth: ovr._clearAuth,
1003
761
  _walletAddress: ovr._walletAddress,
762
+ _config: ovr._config,
1004
763
  } }));
1005
764
  }
1006
765
  /**
@@ -1011,7 +770,7 @@ class WalletClient {
1011
770
  */
1012
771
  async invoke(name, args = {}, opts = {}) {
1013
772
  const ovr = this.buildOverrides();
1014
- return core.functions.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders } }));
773
+ return core.functions.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders, _config: ovr._config } }));
1015
774
  }
1016
775
  /* ---- Signing operations (use provider directly) ---- */
1017
776
  async signMessage(message) {
@@ -1043,7 +802,17 @@ class WalletClient {
1043
802
  */
1044
803
  async function createWalletClient(opts) {
1045
804
  var _a;
1046
- const config = await core.getConfig();
805
+ // Snapshot an IMMUTABLE copy of the resolved config now. Every request this
806
+ // client makes is threaded with this snapshot (as `_config`) and its session
807
+ // manager mints against this app+issuer, so a later global `init(...)` for a
808
+ // different app/environment can never retarget this client's reads, writes,
809
+ // function calls, live intents, or session mint (CTA-05).
810
+ const config = Object.freeze(Object.assign({}, (await core.getConfig())));
811
+ const sessionConfig = {
812
+ appId: config.appId,
813
+ authApiUrl: config.authApiUrl,
814
+ authMethod: config.authMethod,
815
+ };
1047
816
  if ('evmPrivateKey' in opts && typeof opts.evmPrivateKey === 'string') {
1048
817
  const chainConfigs = [];
1049
818
  if (opts.chains) {
@@ -1064,9 +833,9 @@ async function createWalletClient(opts) {
1064
833
  if (chainConfigs.length === 0)
1065
834
  throw new Error('EVM wallet clients require an explicit RPC URL and BoundedRuntime address');
1066
835
  const provider = new EvmKeypairProvider(opts.evmPrivateKey, opts.chainId, chainConfigs);
1067
- const sessionManager = core.ServerSessionManager.forSessionFactory(() => provider.createBoundedSession());
836
+ const sessionManager = core.ServerSessionManager.forSessionFactory(() => provider.createBoundedSession(sessionConfig));
1068
837
  await sessionManager.getSession();
1069
- return new WalletClient(provider, sessionManager, provider.address, provider.transactionPolicy());
838
+ return new WalletClient(provider, sessionManager, provider.address, config, provider.transactionPolicy());
1070
839
  }
1071
840
  if (!('keypair' in opts) || typeof opts.keypair !== 'string') {
1072
841
  throw new Error('createWalletClient requires exactly one of keypair or evmPrivateKey');
@@ -1076,10 +845,10 @@ async function createWalletClient(opts) {
1076
845
  if (config.chain === 'offchain') {
1077
846
  provider = new OffchainAuthProvider(provider);
1078
847
  }
1079
- const sessionManager = core.ServerSessionManager.forKeypair(kp);
848
+ const sessionManager = core.ServerSessionManager.forKeypair(kp, sessionConfig);
1080
849
  // Eagerly authenticate so failures surface immediately
1081
850
  await sessionManager.getSession();
1082
- return new WalletClient(provider, sessionManager, kp.publicKey.toBase58());
851
+ return new WalletClient(provider, sessionManager, kp.publicKey.toBase58(), config);
1083
852
  }
1084
853
 
1085
854
  // ---------------------------------------------------------------------------
@@ -1256,7 +1025,7 @@ function clearWebhookReplayCache() {
1256
1025
  * @throws {WebhookVerificationError} if anything fails verification.
1257
1026
  */
1258
1027
  async function verifyWebhook(rawBody, headers, opts = {}) {
1259
- var _a, _b, _c, _d, _e, _f, _g;
1028
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1260
1029
  // Resolution order: explicit opts.keysUrl > the configured Bounded network's
1261
1030
  // hosted keys (so a staging receiver verifies staging-signed deliveries
1262
1031
  // without passing keysUrl) > the production default (fail-closed when the
@@ -1331,7 +1100,7 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
1331
1100
  try {
1332
1101
  payload = JSON.parse(rawBody);
1333
1102
  }
1334
- catch (_h) {
1103
+ catch (_j) {
1335
1104
  throw new WebhookVerificationError("Webhook body is not valid JSON.");
1336
1105
  }
1337
1106
  if (!payload ||
@@ -1383,15 +1152,25 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
1383
1152
  }
1384
1153
  // Replay cache: reject a previously-seen signed delivery within the skew
1385
1154
  // window. The worker's payload.id is the document id/path, so using it alone
1386
- // would reject legitimate rapid updates to the same document. The signature
1387
- // is over the exact raw body and changes whenever the signed delivery changes.
1155
+ // would reject legitimate rapid updates to the same document.
1156
+ //
1157
+ // SHW-05: delivery is durable + at-least-once, and a RETRY of one delivery
1158
+ // uses a fresh timestamp (to stay inside the skew window) and therefore a
1159
+ // different signature. Keying replay on the signed, per-delivery `deliveryId`
1160
+ // (stable across retries) is what actually dedupes those retries; the old
1161
+ // `${keyId}:${signature}` key only caught exact byte-identical resends and
1162
+ // would wave every retry of the same delivery straight through. Fall back to
1163
+ // the signature only for a legacy payload with no deliveryId. Pair a shared
1164
+ // store (retained for the retry window) with this for exactly-once handling.
1388
1165
  const replayStore = opts.replayStore === undefined ? defaultReplayStore : opts.replayStore;
1389
1166
  if (replayStore) {
1390
1167
  const expiresAtMs = (payload.timestamp + maxSkew) * 1000;
1391
- const replayKey = `${keyId}:${signature}`;
1168
+ const replayKey = typeof payload.deliveryId === "string" && payload.deliveryId.length > 0
1169
+ ? `${keyId}:did:${payload.deliveryId}`
1170
+ : `${keyId}:${signature}`;
1392
1171
  const replayed = await replayStore.checkAndRecord(replayKey, expiresAtMs);
1393
1172
  if (replayed) {
1394
- throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}".`);
1173
+ throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}" (delivery "${(_h = payload.deliveryId) !== null && _h !== void 0 ? _h : "?"}").`);
1395
1174
  }
1396
1175
  }
1397
1176
  return payload;
@@ -1429,6 +1208,14 @@ Object.defineProperty(exports, "getWebhookKeysUrl", {
1429
1208
  enumerable: true,
1430
1209
  get: function () { return core.getWebhookKeysUrl; }
1431
1210
  });
1211
+ Object.defineProperty(exports, "reconcileSetResult", {
1212
+ enumerable: true,
1213
+ get: function () { return core.reconcileSetResult; }
1214
+ });
1215
+ Object.defineProperty(exports, "waitForSetResult", {
1216
+ enumerable: true,
1217
+ get: function () { return core.waitForSetResult; }
1218
+ });
1432
1219
  exports.DEFAULT_WEBHOOK_KEYS_URL = DEFAULT_WEBHOOK_KEYS_URL;
1433
1220
  exports.EvmKeypairProvider = EvmKeypairProvider;
1434
1221
  exports.InMemoryReplayStore = InMemoryReplayStore;