@bounded-sh/server 0.0.62 → 0.0.65

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.mjs CHANGED
@@ -1,9 +1,8 @@
1
- import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl, getConfiguredAppId } from '@bounded-sh/core';
1
+ import { init as init$1, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl, getConfiguredAppId } from '@bounded-sh/core';
2
2
  export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl } from '@bounded-sh/core';
3
- import { Connection, VersionedTransaction, ComputeBudgetProgram, SystemProgram, Keypair } from '@solana/web3.js';
3
+ import { Connection, Keypair } from '@solana/web3.js';
4
4
  import bs58 from 'bs58';
5
5
  import { Buffer as Buffer$1 } from 'buffer';
6
- import * as anchor from '@coral-xyz/anchor';
7
6
  import nacl from 'tweetnacl';
8
7
  import { http, createWalletClient as createWalletClient$1, createPublicClient, defineChain } from 'viem';
9
8
  import { privateKeyToAccount } from 'viem/accounts';
@@ -53,245 +52,6 @@ async function getIdToken() {
53
52
  throw new Error('getIdToken is not available as a tarobase-server top-level auth operation. Use createWalletClient({ keypair }) and call the returned client methods.');
54
53
  }
55
54
 
56
- const PRESIGNED_BLOCKHASH_ERROR = 'Server signedTransaction blockhash is stale or expired';
57
- const BOUNDED_PROGRAM_MAINNET = 'openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F';
58
- const BOUNDED_PROGRAM_DEVNET = 'openTv7fbpYSseNHYmCZFZ1CZgj4r8D9fKNgEz1qo6F';
59
- const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
60
- const SYSTEM_PROGRAM_ID = '11111111111111111111111111111111';
61
- const SUPPORTED_SOLANA_NETWORKS = ['solana_devnet', 'solana_mainnet'];
62
- const SUPPORTED_SOLANA_NETWORK_SET = new Set(SUPPORTED_SOLANA_NETWORKS);
63
- const ALLOWED_SERVER_TX_PROGRAMS = new Set([
64
- BOUNDED_PROGRAM_MAINNET,
65
- BOUNDED_PROGRAM_DEVNET,
66
- COMPUTE_BUDGET_PROGRAM,
67
- SYSTEM_PROGRAM_ID,
68
- ]);
69
- // Canonical set_documents discriminator (single wire format: ra_indices Vec<u8>).
70
- const SET_DOCUMENTS_DISCRIMINATOR = '79,46,72,73,24,79,66,245';
71
- const ALLOWED_BOUNDED_SET_DISCRIMINATORS = new Set([
72
- SET_DOCUMENTS_DISCRIMINATOR,
73
- ]);
74
- /* ──────────────────────────────────────────────────────────
75
- * Helper – fetch getPriorityFeeEstimate
76
- * ──────────────────────────────────────────────────────── */
77
- async function fetchPriorityFee(rpcEndpoint, rawUnsignedTxBuffer) {
78
- var _a, _b;
79
- try {
80
- const res = await fetch(rpcEndpoint, {
81
- method: "POST",
82
- headers: { "Content-Type": "application/json" },
83
- body: JSON.stringify({
84
- jsonrpc: "2.0",
85
- id: "1",
86
- method: "getPriorityFeeEstimate",
87
- params: [{
88
- transaction: bs58.encode(rawUnsignedTxBuffer), options: {
89
- recommended: true
90
- }
91
- }]
92
- })
93
- });
94
- const data = await res.json();
95
- 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;
96
- if (fee != null && fee > 0) {
97
- return Math.ceil(Number(fee) * 1.2);
98
- }
99
- console.warn("Priority-fee estimate returned no positive fee; skipping priority fee instruction");
100
- return null;
101
- }
102
- catch (err) {
103
- console.warn("Priority-fee estimate failed; skipping priority fee instruction:", err);
104
- return null;
105
- }
106
- }
107
- function isPresignedBlockhashError(error) {
108
- return typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' && error.message.includes(PRESIGNED_BLOCKHASH_ERROR);
109
- }
110
- function isPermanentPresignedTransactionError(error) {
111
- return isPresignedBlockhashError(error) ||
112
- (typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' && (error.message.startsWith('Server signedTransaction') ||
113
- error.message.startsWith('Server preInstruction')));
114
- }
115
- class BorshCursor {
116
- constructor(data, offset) {
117
- this.data = data;
118
- this.offset = offset;
119
- }
120
- requireBytes(length, field) {
121
- if (this.offset + length > this.data.length) {
122
- throw new Error(`Server signedTransaction has malformed Bounded instruction data while reading ${field}`);
123
- }
124
- }
125
- readU8(field) {
126
- this.requireBytes(1, field);
127
- return this.data[this.offset++];
128
- }
129
- readU32(field) {
130
- this.requireBytes(4, field);
131
- const value = this.data[this.offset] |
132
- (this.data[this.offset + 1] << 8) |
133
- (this.data[this.offset + 2] << 16) |
134
- (this.data[this.offset + 3] << 24);
135
- this.offset += 4;
136
- return value >>> 0;
137
- }
138
- skip(length, field) {
139
- this.requireBytes(length, field);
140
- this.offset += length;
141
- }
142
- readString(field) {
143
- const length = this.readU32(`${field} length`);
144
- this.requireBytes(length, field);
145
- const raw = this.data.slice(this.offset, this.offset + length);
146
- this.offset += length;
147
- return Buffer$1.from(raw).toString('utf8');
148
- }
149
- skipBytes(field) {
150
- const length = this.readU32(`${field} length`);
151
- this.skip(length, field);
152
- }
153
- isAtEnd() {
154
- return this.offset === this.data.length;
155
- }
156
- }
157
- function normalizeOnchainPath(path) {
158
- let normalized = path.startsWith('/') ? path.slice(1) : path;
159
- if (normalized.endsWith('*') && normalized.length > 1) {
160
- normalized = normalized.slice(0, -1);
161
- }
162
- if (normalized.endsWith('/')) {
163
- normalized = normalized.slice(0, -1);
164
- }
165
- return normalized;
166
- }
167
- function skipBoundedFieldValue(cursor) {
168
- const option = cursor.readU8('operation value option');
169
- if (option === 0)
170
- return;
171
- if (option !== 1) {
172
- throw new Error('Server signedTransaction has malformed Bounded field value option');
173
- }
174
- const variant = cursor.readU8('operation value variant');
175
- switch (variant) {
176
- case 0:
177
- case 1:
178
- cursor.skip(8, 'operation numeric value');
179
- return;
180
- case 2:
181
- cursor.skip(1, 'operation bool value');
182
- return;
183
- case 3:
184
- cursor.readString('operation string value');
185
- return;
186
- case 4:
187
- cursor.skip(32, 'operation address value');
188
- return;
189
- default:
190
- throw new Error(`Server signedTransaction has unsupported Bounded field value variant: ${variant}`);
191
- }
192
- }
193
- function skipBoundedFieldOperation(cursor) {
194
- cursor.readString('operation key');
195
- skipBoundedFieldValue(cursor);
196
- cursor.skip(1, 'operation kind');
197
- }
198
- function skipBoundedTxData(cursor) {
199
- cursor.readString('txData plugin function key');
200
- cursor.skipBytes('txData bytes');
201
- cursor.skipBytes('txData raIndices');
202
- }
203
- function parseBoundedSetDocumentsInstruction(data) {
204
- if (data.length < 8) {
205
- throw new Error('Server signedTransaction has malformed Bounded instruction data');
206
- }
207
- const discriminator = Array.from(data.slice(0, 8)).join(',');
208
- if (!ALLOWED_BOUNDED_SET_DISCRIMINATORS.has(discriminator)) {
209
- throw new Error('Server signedTransaction contains unsupported Bounded instruction');
210
- }
211
- const cursor = new BorshCursor(data, 8);
212
- const appId = cursor.readString('appId');
213
- const documentPaths = [];
214
- const documentCount = cursor.readU32('documents length');
215
- for (let i = 0; i < documentCount; i++) {
216
- documentPaths.push(normalizeOnchainPath(cursor.readString('document path')));
217
- const operationCount = cursor.readU32('operations length');
218
- for (let j = 0; j < operationCount; j++) {
219
- skipBoundedFieldOperation(cursor);
220
- }
221
- }
222
- const deletePaths = [];
223
- const deleteCount = cursor.readU32('delete paths length');
224
- for (let i = 0; i < deleteCount; i++) {
225
- deletePaths.push(normalizeOnchainPath(cursor.readString('delete path')));
226
- }
227
- const txDataCount = cursor.readU32('txData length');
228
- for (let i = 0; i < txDataCount; i++) {
229
- skipBoundedTxData(cursor);
230
- }
231
- const simulate = cursor.readU8('simulate');
232
- if (simulate !== 0 && simulate !== 1) {
233
- throw new Error('Server signedTransaction has malformed Bounded simulate flag');
234
- }
235
- if (!cursor.isAtEnd()) {
236
- throw new Error('Server signedTransaction has trailing Bounded instruction data');
237
- }
238
- return { appId, documentPaths, deletePaths };
239
- }
240
- function assertSamePathSet(expectedPaths, actualPaths) {
241
- const expected = new Set(expectedPaths.map(normalizeOnchainPath).filter(Boolean));
242
- const actual = new Set(actualPaths.map(normalizeOnchainPath).filter(Boolean));
243
- const missing = [...expected].filter(path => !actual.has(path));
244
- const extra = [...actual].filter(path => !expected.has(path));
245
- if (missing.length > 0 || extra.length > 0) {
246
- const details = [
247
- missing.length ? `missing paths: ${missing.join(', ')}` : '',
248
- extra.length ? `unexpected paths: ${extra.join(', ')}` : '',
249
- ].filter(Boolean).join('; ');
250
- throw new Error(`Server signedTransaction Bounded instruction paths do not match requested write paths (${details})`);
251
- }
252
- }
253
- function validatePresignedSignedTransaction(transaction, sol) {
254
- var _a, _b, _c, _d, _e, _f, _g;
255
- const accountKeys = transaction.message.staticAccountKeys;
256
- let boundedInstructionCount = 0;
257
- const actualWritePaths = [];
258
- for (const ix of transaction.message.compiledInstructions) {
259
- if (ix.programIdIndex >= accountKeys.length) {
260
- throw new Error('Server signedTransaction has program ID in lookup table (not allowed)');
261
- }
262
- const programId = accountKeys[ix.programIdIndex].toBase58();
263
- if (!ALLOWED_SERVER_TX_PROGRAMS.has(programId)) {
264
- throw new Error(`Server signedTransaction contains unauthorized program: ${programId}`);
265
- }
266
- const data = ix.data instanceof Uint8Array ? ix.data : Buffer$1.from((_a = ix.data) !== null && _a !== void 0 ? _a : []);
267
- if (programId === SYSTEM_PROGRAM_ID) {
268
- throw new Error('Server signedTransaction contains unauthorized System Program instruction');
269
- }
270
- if (programId === BOUNDED_PROGRAM_MAINNET || programId === BOUNDED_PROGRAM_DEVNET) {
271
- boundedInstructionCount += 1;
272
- const parsed = parseBoundedSetDocumentsInstruction(data);
273
- if (parsed.appId !== sol.appId) {
274
- throw new Error('Server signedTransaction Bounded instruction appId does not match configured appId');
275
- }
276
- actualWritePaths.push(...parsed.documentPaths, ...parsed.deletePaths);
277
- }
278
- }
279
- if (boundedInstructionCount !== 1) {
280
- throw new Error('Server signedTransaction must contain exactly one Bounded set-documents instruction');
281
- }
282
- const expectedWritePaths = [
283
- ...(((_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)),
284
- ...((_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 : []),
285
- ];
286
- assertSamePathSet(expectedWritePaths, actualWritePaths);
287
- }
288
- function assertAllowedServerPreInstructions(preInstructions) {
289
- for (const [index, ix] of (preInstructions !== null && preInstructions !== void 0 ? preInstructions : []).entries()) {
290
- if (ix.programId.equals(SystemProgram.programId)) {
291
- throw new Error(`Server preInstruction[${index}] contains unauthorized System Program instruction`);
292
- }
293
- }
294
- }
295
55
  class SolanaKeypairProvider {
296
56
  constructor(rpcUrl, serverKeypair) {
297
57
  this.rpcUrl = rpcUrl;
@@ -420,16 +180,6 @@ class SolanaKeypairProvider {
420
180
  }
421
181
  return { signature: sig, txInfo };
422
182
  }
423
- requireSupportedNetwork(network) {
424
- const networkName = network === null || network === void 0 ? void 0 : network.trim();
425
- if (!networkName) {
426
- throw new Error(`Supported sol.network is required for Solana transaction submission; expected one of: ${SUPPORTED_SOLANA_NETWORKS.join(', ')}.`);
427
- }
428
- if (!SUPPORTED_SOLANA_NETWORK_SET.has(networkName)) {
429
- throw new Error(`Unsupported Solana network "${networkName}". Supported networks: ${SUPPORTED_SOLANA_NETWORKS.join(', ')}.`);
430
- }
431
- return networkName;
432
- }
433
183
  getRpcUrl() {
434
184
  var _a;
435
185
  const explicitRpcUrl = (_a = this.rpcUrl) === null || _a === void 0 ? void 0 : _a.trim();
@@ -441,174 +191,13 @@ class SolanaKeypairProvider {
441
191
  /* ----------------------------------------------------------- *
442
192
  * Transaction runner
443
193
  * ----------------------------------------------------------- */
444
- async runTransaction(_evm, sol, opts) {
445
- if (!sol)
446
- throw new Error('Solana transaction data required');
447
- const kp = this.keypair;
448
- this.requireSupportedNetwork(sol.network);
449
- const rpcUrl = this.getRpcUrl();
450
- // Helper for duck typing - checks if transaction is legacy Transaction vs VersionedTransaction
451
- const isLegacyTx = (tx) => {
452
- return 'recentBlockhash' in tx && !('message' in tx && 'staticAccountKeys' in tx.message);
453
- };
454
- const wallet = {
455
- publicKey: kp.publicKey,
456
- signTransaction: async (tx) => {
457
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
458
- if (isLegacyTx(tx)) {
459
- tx.partialSign(kp);
460
- }
461
- else {
462
- tx.sign([kp]);
463
- }
464
- return tx;
465
- },
466
- signAllTransactions: async (txs) => txs.map((t) => {
467
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
468
- if (isLegacyTx(t)) {
469
- t.partialSign(kp);
470
- }
471
- else {
472
- t.sign([kp]);
473
- }
474
- return t;
475
- }),
476
- };
477
- /* de-dupe remaining accounts */
478
- const deduped = (() => {
479
- const fin = [];
480
- for (const acc of convertRemainingAccounts(sol.txArgs[0].remainingAccounts)) {
481
- const ex = fin.find((x) => x.pubkey.equals(acc.pubkey));
482
- if (ex) {
483
- ex.isSigner || (ex.isSigner = acc.isSigner);
484
- ex.isWritable || (ex.isWritable = acc.isWritable);
485
- }
486
- else
487
- fin.push(acc);
488
- }
489
- return fin;
490
- })();
491
- let retries = 0;
492
- let didPass = false;
493
- let delay = 1000;
494
- let toReturn = null;
495
- let errorMessage = "";
496
- while (retries < 5) {
497
- try {
498
- toReturn = await this.runTransactionInner(sol, opts, wallet, kp, deduped, rpcUrl);
499
- didPass = true;
500
- break;
501
- }
502
- catch (error) {
503
- if (isPermanentPresignedTransactionError(error)) {
504
- throw error;
505
- }
506
- console.log("Error building and sending transaction on retry:", retries, error);
507
- await new Promise(resolve => setTimeout(resolve, delay));
508
- // Exponential backoff
509
- delay *= 1.5;
510
- retries++;
511
- errorMessage = error.message || JSON.stringify(error, null, 2);
512
- }
513
- }
514
- if (!didPass) {
515
- throw new Error(`Failed to send transaction after 5 retries: ${errorMessage}`);
516
- }
517
- return toReturn;
518
- }
519
- async runTransactionInner(sol, opts, wallet, kp, deduped, rpcUrl) {
520
- var _a, _b, _c;
521
- const connection = new Connection(rpcUrl, 'confirmed');
522
- const anchorProvider = new anchor.AnchorProvider(connection, wallet, anchor.AnchorProvider.defaultOptions());
523
- const app_id = sol.appId;
524
- if (!app_id)
525
- throw new Error('app_id missing');
526
- // When the server has co-signed a transaction (CPI attestation),
527
- // deserialize it and just add the wallet signature instead of
528
- // building from components.
529
- let tx;
530
- let blockhash;
531
- let lastValidBlockHeight;
532
- if (sol.signedTransaction) {
533
- tx = VersionedTransaction.deserialize(Buffer$1.from(sol.signedTransaction, 'base64'));
534
- validatePresignedSignedTransaction(tx, sol);
535
- blockhash = tx.message.recentBlockhash;
536
- const isValid = await connection.isBlockhashValid(blockhash, { commitment: 'confirmed' });
537
- if (!isValid.value) {
538
- throw new Error(`${PRESIGNED_BLOCKHASH_ERROR}; request a fresh server-built transaction.`);
539
- }
540
- lastValidBlockHeight = 0;
541
- }
542
- else {
543
- assertAllowedServerPreInstructions(sol.preInstructions);
544
- const result = await buildSetDocumentsTransaction(connection, sol.txArgs[0].idl, anchorProvider, kp.publicKey, {
545
- app_id,
546
- documents: sol.txArgs[0].setDocumentData,
547
- delete_paths: sol.txArgs[0].deletePaths,
548
- txData: sol.txArgs[0].txData
549
- }, deduped, sol.lutKey, sol.preInstructions, false, sol.additionalLutAddresses);
550
- tx = result.tx;
551
- blockhash = result.blockhash;
552
- lastValidBlockHeight = result.lastValidBlockHeight;
553
- }
554
- // Helper for duck typing - checks if transaction is legacy Transaction vs VersionedTransaction
555
- const isLegacyTx = (t) => {
556
- return 'recentBlockhash' in t && !('message' in t && 'staticAccountKeys' in t.message);
557
- };
558
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
559
- if (isLegacyTx(tx)) {
560
- if (!sol.signedTransaction) {
561
- tx.recentBlockhash = blockhash;
562
- tx.lastValidBlockHeight = lastValidBlockHeight;
563
- tx.feePayer = kp.publicKey;
564
- }
565
- tx.partialSign(kp);
566
- }
567
- else {
568
- if (!sol.signedTransaction) {
569
- tx.message.recentBlockhash = blockhash;
570
- }
571
- tx.sign([kp]);
572
- }
573
- // 3️⃣ Optional priority-fee instruction. Pre-signed transactions are
574
- // immutable at this point; preserve their exact message and skip estimation.
575
- if (!sol.signedTransaction) {
576
- const rawUnsigned = tx.serialize({ requireAllSignatures: false });
577
- const microLamports = await fetchPriorityFee(connection.rpcEndpoint, rawUnsigned);
578
- if (microLamports != null) {
579
- // Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
580
- if (isLegacyTx(tx)) {
581
- tx.instructions.unshift(ComputeBudgetProgram.setComputeUnitPrice({ microLamports }));
582
- tx.partialSign(kp);
583
- }
584
- else {
585
- const roundTwo = await buildSetDocumentsTransaction(connection, sol.txArgs[0].idl, anchorProvider, kp.publicKey, {
586
- app_id,
587
- documents: sol.txArgs[0].setDocumentData,
588
- delete_paths: sol.txArgs[0].deletePaths,
589
- txData: sol.txArgs[0].txData
590
- }, deduped, sol.lutKey, [ComputeBudgetProgram.setComputeUnitPrice({ microLamports }), ...sol.preInstructions], false, sol.additionalLutAddresses);
591
- tx = roundTwo.tx;
592
- blockhash = roundTwo.blockhash;
593
- lastValidBlockHeight = roundTwo.lastValidBlockHeight;
594
- tx.message.recentBlockhash = blockhash;
595
- tx.sign([kp]);
596
- }
597
- }
598
- }
599
- if ((opts === null || opts === void 0 ? void 0 : opts.shouldSubmitTx) === false) {
600
- return { signedTransaction: tx, blockNumber: 0, gasUsed: '0', data: '' };
601
- }
602
- // Submit and confirm using shared helper
603
- const { signature, txInfo } = await this.submitAndConfirmTransaction(tx, connection, { fetchTxInfo: true });
604
- return {
605
- transactionSignature: signature,
606
- blockNumber: (_a = txInfo === null || txInfo === void 0 ? void 0 : txInfo.slot) !== null && _a !== void 0 ? _a : 0,
607
- 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',
608
- data: txInfo === null || txInfo === void 0 ? void 0 : txInfo.meta,
609
- };
194
+ async runTransaction(_evmTransactionData, _solTransactionData, _options) {
195
+ // Client-side Solana transaction building is retired: the realtime worker
196
+ // returns a fully-built (and, when sponsored, partially-signed) transaction
197
+ // on its 202, which the SDK validates and signs via signTransaction /
198
+ // signAndSubmitTransaction. This provider serves no EVM payload either.
199
+ throw new Error('runTransaction is not supported by this provider; Bounded returns pre-built Solana transactions that are signed with signTransaction/signAndSubmitTransaction.');
610
200
  }
611
- /* ----------------------------------------------------------- */
612
201
  async getNativeMethods() {
613
202
  return { keypair: this.keypair };
614
203
  }
@@ -703,18 +292,21 @@ class EvmKeypairProvider {
703
292
  user() {
704
293
  return { id: this.address, address: null, evmAddress: this.address, provider: this };
705
294
  }
706
- async createBoundedSession() {
707
- var _a, _b, _c;
708
- const nonce = await genAuthNonce();
709
- const { appId } = await getConfig();
295
+ async createBoundedSession(sessionConfig) {
296
+ var _a, _b, _c, _d;
297
+ // Bind the SIWE nonce/message/session to the snapshotted app+issuer when a
298
+ // wallet client supplies one, so a later global init() cannot retarget this
299
+ // session mint (CTA-05); otherwise read the mutable global init() config.
300
+ const nonce = await genAuthNonce(sessionConfig);
301
+ const appId = (_a = sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId) !== null && _a !== void 0 ? _a : (await getConfig()).appId;
710
302
  const message = genSiweMessage(this.address, nonce, appId, 'server', 'https://server/login', { chainId: this.defaultChainId });
711
303
  const signature = await this.account.signMessage({ message });
712
- const session = await createSessionWithSignature(this.address, message, signature, 'siwe');
304
+ const session = await createSessionWithSignature(this.address, message, signature, 'siwe', sessionConfig);
713
305
  return {
714
306
  address: this.address,
715
- accessToken: String((_a = session.accessToken) !== null && _a !== void 0 ? _a : ''),
716
- idToken: String((_b = session.idToken) !== null && _b !== void 0 ? _b : ''),
717
- refreshToken: String((_c = session.refreshToken) !== null && _c !== void 0 ? _c : ''),
307
+ accessToken: String((_b = session.accessToken) !== null && _b !== void 0 ? _b : ''),
308
+ idToken: String((_c = session.idToken) !== null && _c !== void 0 ? _c : ''),
309
+ refreshToken: String((_d = session.refreshToken) !== null && _d !== void 0 ? _d : ''),
718
310
  };
719
311
  }
720
312
  async login() { return this.user(); }
@@ -847,10 +439,11 @@ function jwtExpiringSoon(token) {
847
439
  /* WalletClient */
848
440
  /* ------------------------------------------------------------------ */
849
441
  class WalletClient {
850
- constructor(provider, sessionManager, address, evmTransactionPolicy) {
442
+ constructor(provider, sessionManager, address, config, evmTransactionPolicy) {
851
443
  this.provider = provider;
852
444
  this.sessionManager = sessionManager;
853
445
  this.address = address;
446
+ this.config = config;
854
447
  this.evmTransactionPolicy = evmTransactionPolicy;
855
448
  this.live = {
856
449
  intent: (roomPath, intent, opts = {}) => live$1.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
@@ -876,6 +469,7 @@ class WalletClient {
876
469
  _getAuthHeaders: ovr._getAuthHeaders,
877
470
  _clearAuth: ovr._clearAuth,
878
471
  _walletAddress: ovr._walletAddress,
472
+ _config: ovr._config,
879
473
  },
880
474
  });
881
475
  },
@@ -883,7 +477,10 @@ class WalletClient {
883
477
  }
884
478
  /* ---- Auth override helpers ---- */
885
479
  buildOverrides() {
886
- return Object.assign({ authProvider: this.provider, _getAuthHeaders: async () => {
480
+ return Object.assign({ authProvider: this.provider,
481
+ // Pin every request to this wallet client's snapshotted app + endpoints so
482
+ // a later global init() can never retarget its reads/writes (CTA-05).
483
+ _config: this.config, _getAuthHeaders: async () => {
887
484
  let s = await this.sessionManager.getSession();
888
485
  // Keypair-backed session: if the cached token is expired/near-expiry,
889
486
  // re-sign for a fresh one (we always hold the key). Keeps long-lived WS
@@ -905,22 +502,22 @@ class WalletClient {
905
502
  mergeSetOverrides(opts) {
906
503
  var _a;
907
504
  const ovr = this.buildOverrides();
908
- 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 }) });
505
+ 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 }) });
909
506
  }
910
507
  mergeReadOverrides(existing) {
911
508
  const ovr = this.buildOverrides();
912
- 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 });
509
+ 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 });
913
510
  }
914
511
  mergeLiveIntentOptions(opts) {
915
512
  var _a;
916
513
  const ovr = this.buildOverrides();
917
- 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 }) });
514
+ 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 }) });
918
515
  return merged;
919
516
  }
920
517
  mergeLiveStatusOptions(opts) {
921
518
  var _a;
922
519
  const ovr = this.buildOverrides();
923
- 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 }) });
520
+ 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 }) });
924
521
  return merged;
925
522
  }
926
523
  /* ---- Data operations ---- */
@@ -981,6 +578,7 @@ class WalletClient {
981
578
  _getAuthHeaders: ovr._getAuthHeaders,
982
579
  _clearAuth: ovr._clearAuth,
983
580
  _walletAddress: ovr._walletAddress,
581
+ _config: ovr._config,
984
582
  } }));
985
583
  }
986
584
  /**
@@ -991,7 +589,7 @@ class WalletClient {
991
589
  */
992
590
  async invoke(name, args = {}, opts = {}) {
993
591
  const ovr = this.buildOverrides();
994
- return functions$1.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders } }));
592
+ return functions$1.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders, _config: ovr._config } }));
995
593
  }
996
594
  /* ---- Signing operations (use provider directly) ---- */
997
595
  async signMessage(message) {
@@ -1023,7 +621,17 @@ class WalletClient {
1023
621
  */
1024
622
  async function createWalletClient(opts) {
1025
623
  var _a;
1026
- const config = await getConfig();
624
+ // Snapshot an IMMUTABLE copy of the resolved config now. Every request this
625
+ // client makes is threaded with this snapshot (as `_config`) and its session
626
+ // manager mints against this app+issuer, so a later global `init(...)` for a
627
+ // different app/environment can never retarget this client's reads, writes,
628
+ // function calls, live intents, or session mint (CTA-05).
629
+ const config = Object.freeze(Object.assign({}, (await getConfig())));
630
+ const sessionConfig = {
631
+ appId: config.appId,
632
+ authApiUrl: config.authApiUrl,
633
+ authMethod: config.authMethod,
634
+ };
1027
635
  if ('evmPrivateKey' in opts && typeof opts.evmPrivateKey === 'string') {
1028
636
  const chainConfigs = [];
1029
637
  if (opts.chains) {
@@ -1044,9 +652,9 @@ async function createWalletClient(opts) {
1044
652
  if (chainConfigs.length === 0)
1045
653
  throw new Error('EVM wallet clients require an explicit RPC URL and BoundedRuntime address');
1046
654
  const provider = new EvmKeypairProvider(opts.evmPrivateKey, opts.chainId, chainConfigs);
1047
- const sessionManager = ServerSessionManager.forSessionFactory(() => provider.createBoundedSession());
655
+ const sessionManager = ServerSessionManager.forSessionFactory(() => provider.createBoundedSession(sessionConfig));
1048
656
  await sessionManager.getSession();
1049
- return new WalletClient(provider, sessionManager, provider.address, provider.transactionPolicy());
657
+ return new WalletClient(provider, sessionManager, provider.address, config, provider.transactionPolicy());
1050
658
  }
1051
659
  if (!('keypair' in opts) || typeof opts.keypair !== 'string') {
1052
660
  throw new Error('createWalletClient requires exactly one of keypair or evmPrivateKey');
@@ -1056,10 +664,10 @@ async function createWalletClient(opts) {
1056
664
  if (config.chain === 'offchain') {
1057
665
  provider = new OffchainAuthProvider(provider);
1058
666
  }
1059
- const sessionManager = ServerSessionManager.forKeypair(kp);
667
+ const sessionManager = ServerSessionManager.forKeypair(kp, sessionConfig);
1060
668
  // Eagerly authenticate so failures surface immediately
1061
669
  await sessionManager.getSession();
1062
- return new WalletClient(provider, sessionManager, kp.publicKey.toBase58());
670
+ return new WalletClient(provider, sessionManager, kp.publicKey.toBase58(), config);
1063
671
  }
1064
672
 
1065
673
  // ---------------------------------------------------------------------------
@@ -1236,7 +844,7 @@ function clearWebhookReplayCache() {
1236
844
  * @throws {WebhookVerificationError} if anything fails verification.
1237
845
  */
1238
846
  async function verifyWebhook(rawBody, headers, opts = {}) {
1239
- var _a, _b, _c, _d, _e, _f, _g;
847
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1240
848
  // Resolution order: explicit opts.keysUrl > the configured Bounded network's
1241
849
  // hosted keys (so a staging receiver verifies staging-signed deliveries
1242
850
  // without passing keysUrl) > the production default (fail-closed when the
@@ -1311,7 +919,7 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
1311
919
  try {
1312
920
  payload = JSON.parse(rawBody);
1313
921
  }
1314
- catch (_h) {
922
+ catch (_j) {
1315
923
  throw new WebhookVerificationError("Webhook body is not valid JSON.");
1316
924
  }
1317
925
  if (!payload ||
@@ -1363,15 +971,25 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
1363
971
  }
1364
972
  // Replay cache: reject a previously-seen signed delivery within the skew
1365
973
  // window. The worker's payload.id is the document id/path, so using it alone
1366
- // would reject legitimate rapid updates to the same document. The signature
1367
- // is over the exact raw body and changes whenever the signed delivery changes.
974
+ // would reject legitimate rapid updates to the same document.
975
+ //
976
+ // SHW-05: delivery is durable + at-least-once, and a RETRY of one delivery
977
+ // uses a fresh timestamp (to stay inside the skew window) and therefore a
978
+ // different signature. Keying replay on the signed, per-delivery `deliveryId`
979
+ // (stable across retries) is what actually dedupes those retries; the old
980
+ // `${keyId}:${signature}` key only caught exact byte-identical resends and
981
+ // would wave every retry of the same delivery straight through. Fall back to
982
+ // the signature only for a legacy payload with no deliveryId. Pair a shared
983
+ // store (retained for the retry window) with this for exactly-once handling.
1368
984
  const replayStore = opts.replayStore === undefined ? defaultReplayStore : opts.replayStore;
1369
985
  if (replayStore) {
1370
986
  const expiresAtMs = (payload.timestamp + maxSkew) * 1000;
1371
- const replayKey = `${keyId}:${signature}`;
987
+ const replayKey = typeof payload.deliveryId === "string" && payload.deliveryId.length > 0
988
+ ? `${keyId}:did:${payload.deliveryId}`
989
+ : `${keyId}:${signature}`;
1372
990
  const replayed = await replayStore.checkAndRecord(replayKey, expiresAtMs);
1373
991
  if (replayed) {
1374
- throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}".`);
992
+ throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}" (delivery "${(_h = payload.deliveryId) !== null && _h !== void 0 ? _h : "?"}").`);
1375
993
  }
1376
994
  }
1377
995
  return payload;