@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/auth/providers/evm-keypair-provider.d.ts +14 -2
- package/dist/auth/providers/solana-keypair-provider.d.ts +1 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +257 -470
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +253 -455
- package/dist/index.mjs.map +1 -1
- package/dist/wallet-client.d.ts +23 -5
- package/dist/webhooks.d.ts +7 -0
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { init as init$1,
|
|
2
|
-
export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl } from '@bounded-sh/core';
|
|
3
|
-
import { Connection,
|
|
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, reconcileSetResult, waitForSetResult, 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
|
+
export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl, reconcileSetResult, waitForSetResult } from '@bounded-sh/core';
|
|
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
|
-
import { http, createWalletClient as createWalletClient$1, createPublicClient, defineChain } from 'viem';
|
|
7
|
+
import { http, createWalletClient as createWalletClient$1, createPublicClient, keccak256, defineChain } from 'viem';
|
|
9
8
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
10
9
|
|
|
11
10
|
async function init(newConfig) {
|
|
@@ -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(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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;
|
|
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.');
|
|
518
200
|
}
|
|
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
|
-
};
|
|
610
|
-
}
|
|
611
|
-
/* ----------------------------------------------------------- */
|
|
612
201
|
async getNativeMethods() {
|
|
613
202
|
return { keypair: this.keypair };
|
|
614
203
|
}
|
|
@@ -652,6 +241,48 @@ class OffchainAuthProvider {
|
|
|
652
241
|
}
|
|
653
242
|
}
|
|
654
243
|
|
|
244
|
+
function evmErrorText(error) {
|
|
245
|
+
const message = error === null || error === void 0 ? void 0 : error.message;
|
|
246
|
+
return typeof message === 'string' && message.length > 0 ? message : String(error);
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Whether a broadcast failure PROVES the transaction never entered the network.
|
|
250
|
+
* A node-level validation rejection is definitive and keeps throwing; a
|
|
251
|
+
* transport failure (timeout, socket reset, aborted request) is ambiguous — the
|
|
252
|
+
* node may hold the bytes — and must resolve as `submitted`.
|
|
253
|
+
*
|
|
254
|
+
* "already known"/"already imported" is deliberately NOT definitive: it means
|
|
255
|
+
* the node already has these exact bytes, which is success, not failure.
|
|
256
|
+
*/
|
|
257
|
+
const DEFINITIVE_EVM_SEND_MARKERS = [
|
|
258
|
+
'nonce too low',
|
|
259
|
+
'insufficient funds',
|
|
260
|
+
'intrinsic gas too low',
|
|
261
|
+
'gas required exceeds',
|
|
262
|
+
'exceeds block gas limit',
|
|
263
|
+
'invalid sender',
|
|
264
|
+
'invalid signature',
|
|
265
|
+
'execution reverted',
|
|
266
|
+
'transaction underpriced',
|
|
267
|
+
'oversized data',
|
|
268
|
+
];
|
|
269
|
+
function isDefinitiveEvmSendFailure(error) {
|
|
270
|
+
var _a, _b;
|
|
271
|
+
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();
|
|
272
|
+
if (!message)
|
|
273
|
+
return false;
|
|
274
|
+
if (message.includes('already known') || message.includes('already imported'))
|
|
275
|
+
return false;
|
|
276
|
+
return DEFINITIVE_EVM_SEND_MARKERS.some(marker => message.includes(marker));
|
|
277
|
+
}
|
|
278
|
+
/** viem raises a dedicated not-found error when a receipt simply is not mined yet. */
|
|
279
|
+
function isReceiptNotFound(error) {
|
|
280
|
+
var _a, _b;
|
|
281
|
+
const name = String((_a = error === null || error === void 0 ? void 0 : error.name) !== null && _a !== void 0 ? _a : '');
|
|
282
|
+
if (name === 'TransactionReceiptNotFoundError')
|
|
283
|
+
return true;
|
|
284
|
+
return String((_b = error === null || error === void 0 ? void 0 : error.message) !== null && _b !== void 0 ? _b : '').toLowerCase().includes('could not be found');
|
|
285
|
+
}
|
|
655
286
|
const NO_SOLANA = 'An EVM keypair client cannot sign Solana transactions.';
|
|
656
287
|
function normalizeChainConfig(input) {
|
|
657
288
|
var _a;
|
|
@@ -703,18 +334,21 @@ class EvmKeypairProvider {
|
|
|
703
334
|
user() {
|
|
704
335
|
return { id: this.address, address: null, evmAddress: this.address, provider: this };
|
|
705
336
|
}
|
|
706
|
-
async createBoundedSession() {
|
|
707
|
-
var _a, _b, _c;
|
|
708
|
-
|
|
709
|
-
|
|
337
|
+
async createBoundedSession(sessionConfig) {
|
|
338
|
+
var _a, _b, _c, _d;
|
|
339
|
+
// Bind the SIWE nonce/message/session to the snapshotted app+issuer when a
|
|
340
|
+
// wallet client supplies one, so a later global init() cannot retarget this
|
|
341
|
+
// session mint (CTA-05); otherwise read the mutable global init() config.
|
|
342
|
+
const nonce = await genAuthNonce(sessionConfig);
|
|
343
|
+
const appId = (_a = sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId) !== null && _a !== void 0 ? _a : (await getConfig()).appId;
|
|
710
344
|
const message = genSiweMessage(this.address, nonce, appId, 'server', 'https://server/login', { chainId: this.defaultChainId });
|
|
711
345
|
const signature = await this.account.signMessage({ message });
|
|
712
|
-
const session = await createSessionWithSignature(this.address, message, signature, 'siwe');
|
|
346
|
+
const session = await createSessionWithSignature(this.address, message, signature, 'siwe', sessionConfig);
|
|
713
347
|
return {
|
|
714
348
|
address: this.address,
|
|
715
|
-
accessToken: String((
|
|
716
|
-
idToken: String((
|
|
717
|
-
refreshToken: String((
|
|
349
|
+
accessToken: String((_b = session.accessToken) !== null && _b !== void 0 ? _b : ''),
|
|
350
|
+
idToken: String((_c = session.idToken) !== null && _c !== void 0 ? _c : ''),
|
|
351
|
+
refreshToken: String((_d = session.refreshToken) !== null && _d !== void 0 ? _d : ''),
|
|
718
352
|
};
|
|
719
353
|
}
|
|
720
354
|
async login() { return this.user(); }
|
|
@@ -763,25 +397,145 @@ class EvmKeypairProvider {
|
|
|
763
397
|
const transport = http(config.rpcUrl);
|
|
764
398
|
const wallet = createWalletClient$1({ account: this.account, chain, transport });
|
|
765
399
|
const publicClient = createPublicClient({ chain, transport });
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
400
|
+
// Unlike a browser wallet this provider OWNS the private key, so it prepares
|
|
401
|
+
// and signs locally with an EXPLICIT nonce and derives the hash before the
|
|
402
|
+
// transaction is ever broadcast. Learning the hash only from
|
|
403
|
+
// `sendTransaction()` discards an identifier we could have known, so an
|
|
404
|
+
// accepted-but-lost response would strand a landed write.
|
|
405
|
+
let request;
|
|
406
|
+
let signedTransaction;
|
|
407
|
+
let hash;
|
|
408
|
+
let nonce;
|
|
409
|
+
try {
|
|
410
|
+
request = await wallet.prepareTransactionRequest({
|
|
411
|
+
account: this.account,
|
|
412
|
+
chain,
|
|
413
|
+
to: config.runtimeAddress,
|
|
414
|
+
data: evm.data,
|
|
415
|
+
value: BigInt(0),
|
|
416
|
+
});
|
|
417
|
+
nonce = Number(request.nonce);
|
|
418
|
+
if (!Number.isSafeInteger(nonce) || nonce < 0)
|
|
419
|
+
throw new Error('the prepared transaction carries no usable nonce');
|
|
420
|
+
signedTransaction = await wallet.signTransaction(request);
|
|
421
|
+
hash = keccak256(signedTransaction);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
// Preparation and local signing happen entirely before broadcast, so any
|
|
425
|
+
// failure here is definitive.
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
const fence = {
|
|
429
|
+
chainId: config.chainId,
|
|
430
|
+
sender: this.address,
|
|
431
|
+
nonce,
|
|
432
|
+
signedTransaction,
|
|
433
|
+
};
|
|
434
|
+
try {
|
|
435
|
+
await publicClient.sendRawTransaction({ serializedTransaction: signedTransaction });
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
if (isDefinitiveEvmSendFailure(error))
|
|
439
|
+
throw error;
|
|
440
|
+
// Ambiguous transport outcome: the node may already hold these exact
|
|
441
|
+
// bytes. Report the pre-derived hash and the fence so reconciliation can
|
|
442
|
+
// poll it, or rebroadcast the byte-identical bytes while the nonce is
|
|
443
|
+
// unused — which cannot produce a second execution.
|
|
444
|
+
return {
|
|
445
|
+
outcome: 'submitted',
|
|
446
|
+
lane: 'evm',
|
|
447
|
+
transactionSignature: hash,
|
|
448
|
+
fence,
|
|
449
|
+
reason: `Broadcast response was lost or inconclusive: ${evmErrorText(error)}`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
let receipt;
|
|
453
|
+
try {
|
|
454
|
+
receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
// The bytes were accepted; only the confirmation outcome is unknown.
|
|
458
|
+
return {
|
|
459
|
+
outcome: 'submitted',
|
|
460
|
+
lane: 'evm',
|
|
461
|
+
transactionSignature: hash,
|
|
462
|
+
fence,
|
|
463
|
+
reason: `Confirmation outcome unknown: ${evmErrorText(error)}`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
774
466
|
if (receipt.status !== 'success')
|
|
775
467
|
throw new Error(`EVM transaction ${hash} reverted on-chain`);
|
|
776
468
|
if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER))
|
|
777
469
|
throw new Error('EVM receipt block number exceeds the SDK safe-integer range');
|
|
778
470
|
return {
|
|
471
|
+
outcome: 'confirmed',
|
|
472
|
+
lane: 'evm',
|
|
779
473
|
transactionSignature: hash,
|
|
780
474
|
blockNumber: Number(receipt.blockNumber),
|
|
781
475
|
gasUsed: receipt.gasUsed.toString(),
|
|
782
476
|
data: receipt,
|
|
783
477
|
};
|
|
784
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Poll the SAME hash, and — only while the fenced nonce is still unused — put
|
|
481
|
+
* the byte-identical signed transaction back on the wire. Same nonce, same
|
|
482
|
+
* signature, so a rebroadcast can never produce a second execution.
|
|
483
|
+
*
|
|
484
|
+
* It NEVER reports a retry-safe terminal state. Nonce advancement is not proof
|
|
485
|
+
* the write did not execute: a replacement can occupy the same sender and
|
|
486
|
+
* nonce carrying identical Bounded calldata under a different hash, and plain
|
|
487
|
+
* JSON-RPC offers no transaction-by-sender-and-nonce lookup to identify it.
|
|
488
|
+
*/
|
|
489
|
+
async reconcileEvmTransaction(request) {
|
|
490
|
+
var _a, _b;
|
|
491
|
+
const chainId = (_b = (_a = request.fence) === null || _a === void 0 ? void 0 : _a.chainId) !== null && _b !== void 0 ? _b : this.defaultChainId;
|
|
492
|
+
const config = this.chains.get(chainId);
|
|
493
|
+
if (!config)
|
|
494
|
+
return { state: 'unknown', reason: `EVM chain ${chainId} is not configured for this wallet client` };
|
|
495
|
+
const chain = asViemChain(config);
|
|
496
|
+
const transport = http(config.rpcUrl);
|
|
497
|
+
const publicClient = createPublicClient({ chain, transport });
|
|
498
|
+
try {
|
|
499
|
+
const receipt = await publicClient.getTransactionReceipt({ hash: request.transactionHash });
|
|
500
|
+
if (receipt) {
|
|
501
|
+
if (receipt.status !== 'success') {
|
|
502
|
+
return { state: 'failed', reason: `EVM transaction ${request.transactionHash} reverted on-chain` };
|
|
503
|
+
}
|
|
504
|
+
if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
505
|
+
return { state: 'unknown', reason: 'EVM receipt block number exceeds the SDK safe-integer range' };
|
|
506
|
+
}
|
|
507
|
+
return { state: 'confirmed', blockNumber: Number(receipt.blockNumber) };
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
// viem throws `TransactionReceiptNotFoundError` when it is simply not
|
|
512
|
+
// mined yet; that is not an error condition, just "still unknown".
|
|
513
|
+
if (!isReceiptNotFound(error)) {
|
|
514
|
+
return { state: 'unknown', reason: `receipt lookup failed: ${evmErrorText(error)}` };
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const fence = request.fence;
|
|
518
|
+
if (!request.allowRebroadcast || !fence) {
|
|
519
|
+
return { state: 'unknown', reason: 'no receipt yet for this transaction hash' };
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const nonceNow = await publicClient.getTransactionCount({ address: fence.sender, blockTag: 'latest' });
|
|
523
|
+
if (nonceNow > fence.nonce) {
|
|
524
|
+
// The nonce is spent. It may have been spent by THIS transaction under a
|
|
525
|
+
// different hash (a replacement), so this is emphatically not retry-safe.
|
|
526
|
+
return {
|
|
527
|
+
state: 'unknown',
|
|
528
|
+
reason: `nonce ${fence.nonce} on ${fence.sender} has been consumed but this hash has no receipt; ` +
|
|
529
|
+
'the transaction may have been replaced, so the write is not proven absent',
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
await publicClient.sendRawTransaction({ serializedTransaction: fence.signedTransaction });
|
|
533
|
+
return { state: 'unknown', reason: 'rebroadcast the byte-identical fenced transaction; still awaiting a receipt' };
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
return { state: 'unknown', reason: `rebroadcast attempt was inconclusive: ${evmErrorText(error)}` };
|
|
537
|
+
}
|
|
538
|
+
}
|
|
785
539
|
async getNativeMethods() { return this.account; }
|
|
786
540
|
transactionPolicy() {
|
|
787
541
|
return {
|
|
@@ -847,10 +601,11 @@ function jwtExpiringSoon(token) {
|
|
|
847
601
|
/* WalletClient */
|
|
848
602
|
/* ------------------------------------------------------------------ */
|
|
849
603
|
class WalletClient {
|
|
850
|
-
constructor(provider, sessionManager, address, evmTransactionPolicy) {
|
|
604
|
+
constructor(provider, sessionManager, address, config, evmTransactionPolicy) {
|
|
851
605
|
this.provider = provider;
|
|
852
606
|
this.sessionManager = sessionManager;
|
|
853
607
|
this.address = address;
|
|
608
|
+
this.config = config;
|
|
854
609
|
this.evmTransactionPolicy = evmTransactionPolicy;
|
|
855
610
|
this.live = {
|
|
856
611
|
intent: (roomPath, intent, opts = {}) => live$1.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
|
|
@@ -876,6 +631,7 @@ class WalletClient {
|
|
|
876
631
|
_getAuthHeaders: ovr._getAuthHeaders,
|
|
877
632
|
_clearAuth: ovr._clearAuth,
|
|
878
633
|
_walletAddress: ovr._walletAddress,
|
|
634
|
+
_config: ovr._config,
|
|
879
635
|
},
|
|
880
636
|
});
|
|
881
637
|
},
|
|
@@ -883,7 +639,10 @@ class WalletClient {
|
|
|
883
639
|
}
|
|
884
640
|
/* ---- Auth override helpers ---- */
|
|
885
641
|
buildOverrides() {
|
|
886
|
-
return Object.assign({ authProvider: this.provider,
|
|
642
|
+
return Object.assign({ authProvider: this.provider,
|
|
643
|
+
// Pin every request to this wallet client's snapshotted app + endpoints so
|
|
644
|
+
// a later global init() can never retarget its reads/writes (CTA-05).
|
|
645
|
+
_config: this.config, _getAuthHeaders: async () => {
|
|
887
646
|
let s = await this.sessionManager.getSession();
|
|
888
647
|
// Keypair-backed session: if the cached token is expired/near-expiry,
|
|
889
648
|
// re-sign for a fresh one (we always hold the key). Keeps long-lived WS
|
|
@@ -905,22 +664,22 @@ class WalletClient {
|
|
|
905
664
|
mergeSetOverrides(opts) {
|
|
906
665
|
var _a;
|
|
907
666
|
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 }) });
|
|
667
|
+
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
668
|
}
|
|
910
669
|
mergeReadOverrides(existing) {
|
|
911
670
|
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 });
|
|
671
|
+
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
672
|
}
|
|
914
673
|
mergeLiveIntentOptions(opts) {
|
|
915
674
|
var _a;
|
|
916
675
|
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 }) });
|
|
676
|
+
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
677
|
return merged;
|
|
919
678
|
}
|
|
920
679
|
mergeLiveStatusOptions(opts) {
|
|
921
680
|
var _a;
|
|
922
681
|
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 }) });
|
|
682
|
+
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
683
|
return merged;
|
|
925
684
|
}
|
|
926
685
|
/* ---- Data operations ---- */
|
|
@@ -933,6 +692,24 @@ class WalletClient {
|
|
|
933
692
|
async setFile(path, file, options) {
|
|
934
693
|
return setFile$1(path, file, { _overrides: this.mergeReadOverrides(options === null || options === void 0 ? void 0 : options._overrides) });
|
|
935
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Reconcile a `submitted` write receipt through THIS client's own provider
|
|
697
|
+
* and app binding. A bare top-level `reconcileSetResult` cannot serve a wallet
|
|
698
|
+
* client: the provider and config are private to the instance, and a later
|
|
699
|
+
* global `init()` would otherwise reconcile against the wrong app.
|
|
700
|
+
*/
|
|
701
|
+
async reconcileSetResult(receipt, options) {
|
|
702
|
+
return reconcileSetResult(receipt, this.mergeReconcileOptions(options));
|
|
703
|
+
}
|
|
704
|
+
/** Bounded repeat of {@link reconcileSetResult}; see `waitForSetResult`. */
|
|
705
|
+
async waitForSetResult(receipt, options) {
|
|
706
|
+
return waitForSetResult(receipt, this.mergeReconcileOptions(options));
|
|
707
|
+
}
|
|
708
|
+
mergeReconcileOptions(opts) {
|
|
709
|
+
var _a;
|
|
710
|
+
const ovr = this.buildOverrides();
|
|
711
|
+
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 }) });
|
|
712
|
+
}
|
|
936
713
|
async get(path, opts) {
|
|
937
714
|
return get$1(path, Object.assign(Object.assign({}, opts), { _overrides: this.mergeReadOverrides(opts === null || opts === void 0 ? void 0 : opts._overrides) }));
|
|
938
715
|
}
|
|
@@ -981,6 +758,7 @@ class WalletClient {
|
|
|
981
758
|
_getAuthHeaders: ovr._getAuthHeaders,
|
|
982
759
|
_clearAuth: ovr._clearAuth,
|
|
983
760
|
_walletAddress: ovr._walletAddress,
|
|
761
|
+
_config: ovr._config,
|
|
984
762
|
} }));
|
|
985
763
|
}
|
|
986
764
|
/**
|
|
@@ -991,7 +769,7 @@ class WalletClient {
|
|
|
991
769
|
*/
|
|
992
770
|
async invoke(name, args = {}, opts = {}) {
|
|
993
771
|
const ovr = this.buildOverrides();
|
|
994
|
-
return functions$1.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders } }));
|
|
772
|
+
return functions$1.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders, _config: ovr._config } }));
|
|
995
773
|
}
|
|
996
774
|
/* ---- Signing operations (use provider directly) ---- */
|
|
997
775
|
async signMessage(message) {
|
|
@@ -1023,7 +801,17 @@ class WalletClient {
|
|
|
1023
801
|
*/
|
|
1024
802
|
async function createWalletClient(opts) {
|
|
1025
803
|
var _a;
|
|
1026
|
-
|
|
804
|
+
// Snapshot an IMMUTABLE copy of the resolved config now. Every request this
|
|
805
|
+
// client makes is threaded with this snapshot (as `_config`) and its session
|
|
806
|
+
// manager mints against this app+issuer, so a later global `init(...)` for a
|
|
807
|
+
// different app/environment can never retarget this client's reads, writes,
|
|
808
|
+
// function calls, live intents, or session mint (CTA-05).
|
|
809
|
+
const config = Object.freeze(Object.assign({}, (await getConfig())));
|
|
810
|
+
const sessionConfig = {
|
|
811
|
+
appId: config.appId,
|
|
812
|
+
authApiUrl: config.authApiUrl,
|
|
813
|
+
authMethod: config.authMethod,
|
|
814
|
+
};
|
|
1027
815
|
if ('evmPrivateKey' in opts && typeof opts.evmPrivateKey === 'string') {
|
|
1028
816
|
const chainConfigs = [];
|
|
1029
817
|
if (opts.chains) {
|
|
@@ -1044,9 +832,9 @@ async function createWalletClient(opts) {
|
|
|
1044
832
|
if (chainConfigs.length === 0)
|
|
1045
833
|
throw new Error('EVM wallet clients require an explicit RPC URL and BoundedRuntime address');
|
|
1046
834
|
const provider = new EvmKeypairProvider(opts.evmPrivateKey, opts.chainId, chainConfigs);
|
|
1047
|
-
const sessionManager = ServerSessionManager.forSessionFactory(() => provider.createBoundedSession());
|
|
835
|
+
const sessionManager = ServerSessionManager.forSessionFactory(() => provider.createBoundedSession(sessionConfig));
|
|
1048
836
|
await sessionManager.getSession();
|
|
1049
|
-
return new WalletClient(provider, sessionManager, provider.address, provider.transactionPolicy());
|
|
837
|
+
return new WalletClient(provider, sessionManager, provider.address, config, provider.transactionPolicy());
|
|
1050
838
|
}
|
|
1051
839
|
if (!('keypair' in opts) || typeof opts.keypair !== 'string') {
|
|
1052
840
|
throw new Error('createWalletClient requires exactly one of keypair or evmPrivateKey');
|
|
@@ -1056,10 +844,10 @@ async function createWalletClient(opts) {
|
|
|
1056
844
|
if (config.chain === 'offchain') {
|
|
1057
845
|
provider = new OffchainAuthProvider(provider);
|
|
1058
846
|
}
|
|
1059
|
-
const sessionManager = ServerSessionManager.forKeypair(kp);
|
|
847
|
+
const sessionManager = ServerSessionManager.forKeypair(kp, sessionConfig);
|
|
1060
848
|
// Eagerly authenticate so failures surface immediately
|
|
1061
849
|
await sessionManager.getSession();
|
|
1062
|
-
return new WalletClient(provider, sessionManager, kp.publicKey.toBase58());
|
|
850
|
+
return new WalletClient(provider, sessionManager, kp.publicKey.toBase58(), config);
|
|
1063
851
|
}
|
|
1064
852
|
|
|
1065
853
|
// ---------------------------------------------------------------------------
|
|
@@ -1236,7 +1024,7 @@ function clearWebhookReplayCache() {
|
|
|
1236
1024
|
* @throws {WebhookVerificationError} if anything fails verification.
|
|
1237
1025
|
*/
|
|
1238
1026
|
async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
1239
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1027
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1240
1028
|
// Resolution order: explicit opts.keysUrl > the configured Bounded network's
|
|
1241
1029
|
// hosted keys (so a staging receiver verifies staging-signed deliveries
|
|
1242
1030
|
// without passing keysUrl) > the production default (fail-closed when the
|
|
@@ -1311,7 +1099,7 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1311
1099
|
try {
|
|
1312
1100
|
payload = JSON.parse(rawBody);
|
|
1313
1101
|
}
|
|
1314
|
-
catch (
|
|
1102
|
+
catch (_j) {
|
|
1315
1103
|
throw new WebhookVerificationError("Webhook body is not valid JSON.");
|
|
1316
1104
|
}
|
|
1317
1105
|
if (!payload ||
|
|
@@ -1363,15 +1151,25 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1363
1151
|
}
|
|
1364
1152
|
// Replay cache: reject a previously-seen signed delivery within the skew
|
|
1365
1153
|
// 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.
|
|
1367
|
-
//
|
|
1154
|
+
// would reject legitimate rapid updates to the same document.
|
|
1155
|
+
//
|
|
1156
|
+
// SHW-05: delivery is durable + at-least-once, and a RETRY of one delivery
|
|
1157
|
+
// uses a fresh timestamp (to stay inside the skew window) and therefore a
|
|
1158
|
+
// different signature. Keying replay on the signed, per-delivery `deliveryId`
|
|
1159
|
+
// (stable across retries) is what actually dedupes those retries; the old
|
|
1160
|
+
// `${keyId}:${signature}` key only caught exact byte-identical resends and
|
|
1161
|
+
// would wave every retry of the same delivery straight through. Fall back to
|
|
1162
|
+
// the signature only for a legacy payload with no deliveryId. Pair a shared
|
|
1163
|
+
// store (retained for the retry window) with this for exactly-once handling.
|
|
1368
1164
|
const replayStore = opts.replayStore === undefined ? defaultReplayStore : opts.replayStore;
|
|
1369
1165
|
if (replayStore) {
|
|
1370
1166
|
const expiresAtMs = (payload.timestamp + maxSkew) * 1000;
|
|
1371
|
-
const replayKey =
|
|
1167
|
+
const replayKey = typeof payload.deliveryId === "string" && payload.deliveryId.length > 0
|
|
1168
|
+
? `${keyId}:did:${payload.deliveryId}`
|
|
1169
|
+
: `${keyId}:${signature}`;
|
|
1372
1170
|
const replayed = await replayStore.checkAndRecord(replayKey, expiresAtMs);
|
|
1373
1171
|
if (replayed) {
|
|
1374
|
-
throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}".`);
|
|
1172
|
+
throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}" (delivery "${(_h = payload.deliveryId) !== null && _h !== void 0 ? _h : "?"}").`);
|
|
1375
1173
|
}
|
|
1376
1174
|
}
|
|
1377
1175
|
return payload;
|