@bounded-sh/server 0.0.64 → 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/auth/providers/evm-keypair-provider.d.ts +2 -1
- package/dist/auth/providers/solana-keypair-provider.d.ts +1 -3
- package/dist/index.js +61 -462
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +63 -445
- package/dist/index.mjs.map +1 -1
- package/dist/wallet-client.d.ts +10 -2
- package/dist/webhooks.d.ts +7 -0
- package/package.json +3 -3
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(
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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;
|
|
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.');
|
|
538
201
|
}
|
|
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
|
-
};
|
|
630
|
-
}
|
|
631
|
-
/* ----------------------------------------------------------- */
|
|
632
202
|
async getNativeMethods() {
|
|
633
203
|
return { keypair: this.keypair };
|
|
634
204
|
}
|
|
@@ -723,18 +293,21 @@ class EvmKeypairProvider {
|
|
|
723
293
|
user() {
|
|
724
294
|
return { id: this.address, address: null, evmAddress: this.address, provider: this };
|
|
725
295
|
}
|
|
726
|
-
async createBoundedSession() {
|
|
727
|
-
var _a, _b, _c;
|
|
728
|
-
|
|
729
|
-
|
|
296
|
+
async createBoundedSession(sessionConfig) {
|
|
297
|
+
var _a, _b, _c, _d;
|
|
298
|
+
// Bind the SIWE nonce/message/session to the snapshotted app+issuer when a
|
|
299
|
+
// wallet client supplies one, so a later global init() cannot retarget this
|
|
300
|
+
// session mint (CTA-05); otherwise read the mutable global init() config.
|
|
301
|
+
const nonce = await core.genAuthNonce(sessionConfig);
|
|
302
|
+
const appId = (_a = sessionConfig === null || sessionConfig === void 0 ? void 0 : sessionConfig.appId) !== null && _a !== void 0 ? _a : (await core.getConfig()).appId;
|
|
730
303
|
const message = core.genSiweMessage(this.address, nonce, appId, 'server', 'https://server/login', { chainId: this.defaultChainId });
|
|
731
304
|
const signature = await this.account.signMessage({ message });
|
|
732
|
-
const session = await core.createSessionWithSignature(this.address, message, signature, 'siwe');
|
|
305
|
+
const session = await core.createSessionWithSignature(this.address, message, signature, 'siwe', sessionConfig);
|
|
733
306
|
return {
|
|
734
307
|
address: this.address,
|
|
735
|
-
accessToken: String((
|
|
736
|
-
idToken: String((
|
|
737
|
-
refreshToken: String((
|
|
308
|
+
accessToken: String((_b = session.accessToken) !== null && _b !== void 0 ? _b : ''),
|
|
309
|
+
idToken: String((_c = session.idToken) !== null && _c !== void 0 ? _c : ''),
|
|
310
|
+
refreshToken: String((_d = session.refreshToken) !== null && _d !== void 0 ? _d : ''),
|
|
738
311
|
};
|
|
739
312
|
}
|
|
740
313
|
async login() { return this.user(); }
|
|
@@ -867,10 +440,11 @@ function jwtExpiringSoon(token) {
|
|
|
867
440
|
/* WalletClient */
|
|
868
441
|
/* ------------------------------------------------------------------ */
|
|
869
442
|
class WalletClient {
|
|
870
|
-
constructor(provider, sessionManager, address, evmTransactionPolicy) {
|
|
443
|
+
constructor(provider, sessionManager, address, config, evmTransactionPolicy) {
|
|
871
444
|
this.provider = provider;
|
|
872
445
|
this.sessionManager = sessionManager;
|
|
873
446
|
this.address = address;
|
|
447
|
+
this.config = config;
|
|
874
448
|
this.evmTransactionPolicy = evmTransactionPolicy;
|
|
875
449
|
this.live = {
|
|
876
450
|
intent: (roomPath, intent, opts = {}) => core.live.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
|
|
@@ -896,6 +470,7 @@ class WalletClient {
|
|
|
896
470
|
_getAuthHeaders: ovr._getAuthHeaders,
|
|
897
471
|
_clearAuth: ovr._clearAuth,
|
|
898
472
|
_walletAddress: ovr._walletAddress,
|
|
473
|
+
_config: ovr._config,
|
|
899
474
|
},
|
|
900
475
|
});
|
|
901
476
|
},
|
|
@@ -903,7 +478,10 @@ class WalletClient {
|
|
|
903
478
|
}
|
|
904
479
|
/* ---- Auth override helpers ---- */
|
|
905
480
|
buildOverrides() {
|
|
906
|
-
return Object.assign({ authProvider: this.provider,
|
|
481
|
+
return Object.assign({ authProvider: this.provider,
|
|
482
|
+
// Pin every request to this wallet client's snapshotted app + endpoints so
|
|
483
|
+
// a later global init() can never retarget its reads/writes (CTA-05).
|
|
484
|
+
_config: this.config, _getAuthHeaders: async () => {
|
|
907
485
|
let s = await this.sessionManager.getSession();
|
|
908
486
|
// Keypair-backed session: if the cached token is expired/near-expiry,
|
|
909
487
|
// re-sign for a fresh one (we always hold the key). Keeps long-lived WS
|
|
@@ -925,22 +503,22 @@ class WalletClient {
|
|
|
925
503
|
mergeSetOverrides(opts) {
|
|
926
504
|
var _a;
|
|
927
505
|
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 }) });
|
|
506
|
+
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
507
|
}
|
|
930
508
|
mergeReadOverrides(existing) {
|
|
931
509
|
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 });
|
|
510
|
+
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
511
|
}
|
|
934
512
|
mergeLiveIntentOptions(opts) {
|
|
935
513
|
var _a;
|
|
936
514
|
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 }) });
|
|
515
|
+
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
516
|
return merged;
|
|
939
517
|
}
|
|
940
518
|
mergeLiveStatusOptions(opts) {
|
|
941
519
|
var _a;
|
|
942
520
|
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 }) });
|
|
521
|
+
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
522
|
return merged;
|
|
945
523
|
}
|
|
946
524
|
/* ---- Data operations ---- */
|
|
@@ -1001,6 +579,7 @@ class WalletClient {
|
|
|
1001
579
|
_getAuthHeaders: ovr._getAuthHeaders,
|
|
1002
580
|
_clearAuth: ovr._clearAuth,
|
|
1003
581
|
_walletAddress: ovr._walletAddress,
|
|
582
|
+
_config: ovr._config,
|
|
1004
583
|
} }));
|
|
1005
584
|
}
|
|
1006
585
|
/**
|
|
@@ -1011,7 +590,7 @@ class WalletClient {
|
|
|
1011
590
|
*/
|
|
1012
591
|
async invoke(name, args = {}, opts = {}) {
|
|
1013
592
|
const ovr = this.buildOverrides();
|
|
1014
|
-
return core.functions.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders } }));
|
|
593
|
+
return core.functions.invoke(name, args, Object.assign(Object.assign({}, opts), { _overrides: { _getAuthHeaders: ovr._getAuthHeaders, _config: ovr._config } }));
|
|
1015
594
|
}
|
|
1016
595
|
/* ---- Signing operations (use provider directly) ---- */
|
|
1017
596
|
async signMessage(message) {
|
|
@@ -1043,7 +622,17 @@ class WalletClient {
|
|
|
1043
622
|
*/
|
|
1044
623
|
async function createWalletClient(opts) {
|
|
1045
624
|
var _a;
|
|
1046
|
-
|
|
625
|
+
// Snapshot an IMMUTABLE copy of the resolved config now. Every request this
|
|
626
|
+
// client makes is threaded with this snapshot (as `_config`) and its session
|
|
627
|
+
// manager mints against this app+issuer, so a later global `init(...)` for a
|
|
628
|
+
// different app/environment can never retarget this client's reads, writes,
|
|
629
|
+
// function calls, live intents, or session mint (CTA-05).
|
|
630
|
+
const config = Object.freeze(Object.assign({}, (await core.getConfig())));
|
|
631
|
+
const sessionConfig = {
|
|
632
|
+
appId: config.appId,
|
|
633
|
+
authApiUrl: config.authApiUrl,
|
|
634
|
+
authMethod: config.authMethod,
|
|
635
|
+
};
|
|
1047
636
|
if ('evmPrivateKey' in opts && typeof opts.evmPrivateKey === 'string') {
|
|
1048
637
|
const chainConfigs = [];
|
|
1049
638
|
if (opts.chains) {
|
|
@@ -1064,9 +653,9 @@ async function createWalletClient(opts) {
|
|
|
1064
653
|
if (chainConfigs.length === 0)
|
|
1065
654
|
throw new Error('EVM wallet clients require an explicit RPC URL and BoundedRuntime address');
|
|
1066
655
|
const provider = new EvmKeypairProvider(opts.evmPrivateKey, opts.chainId, chainConfigs);
|
|
1067
|
-
const sessionManager = core.ServerSessionManager.forSessionFactory(() => provider.createBoundedSession());
|
|
656
|
+
const sessionManager = core.ServerSessionManager.forSessionFactory(() => provider.createBoundedSession(sessionConfig));
|
|
1068
657
|
await sessionManager.getSession();
|
|
1069
|
-
return new WalletClient(provider, sessionManager, provider.address, provider.transactionPolicy());
|
|
658
|
+
return new WalletClient(provider, sessionManager, provider.address, config, provider.transactionPolicy());
|
|
1070
659
|
}
|
|
1071
660
|
if (!('keypair' in opts) || typeof opts.keypair !== 'string') {
|
|
1072
661
|
throw new Error('createWalletClient requires exactly one of keypair or evmPrivateKey');
|
|
@@ -1076,10 +665,10 @@ async function createWalletClient(opts) {
|
|
|
1076
665
|
if (config.chain === 'offchain') {
|
|
1077
666
|
provider = new OffchainAuthProvider(provider);
|
|
1078
667
|
}
|
|
1079
|
-
const sessionManager = core.ServerSessionManager.forKeypair(kp);
|
|
668
|
+
const sessionManager = core.ServerSessionManager.forKeypair(kp, sessionConfig);
|
|
1080
669
|
// Eagerly authenticate so failures surface immediately
|
|
1081
670
|
await sessionManager.getSession();
|
|
1082
|
-
return new WalletClient(provider, sessionManager, kp.publicKey.toBase58());
|
|
671
|
+
return new WalletClient(provider, sessionManager, kp.publicKey.toBase58(), config);
|
|
1083
672
|
}
|
|
1084
673
|
|
|
1085
674
|
// ---------------------------------------------------------------------------
|
|
@@ -1256,7 +845,7 @@ function clearWebhookReplayCache() {
|
|
|
1256
845
|
* @throws {WebhookVerificationError} if anything fails verification.
|
|
1257
846
|
*/
|
|
1258
847
|
async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
1259
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
848
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1260
849
|
// Resolution order: explicit opts.keysUrl > the configured Bounded network's
|
|
1261
850
|
// hosted keys (so a staging receiver verifies staging-signed deliveries
|
|
1262
851
|
// without passing keysUrl) > the production default (fail-closed when the
|
|
@@ -1331,7 +920,7 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1331
920
|
try {
|
|
1332
921
|
payload = JSON.parse(rawBody);
|
|
1333
922
|
}
|
|
1334
|
-
catch (
|
|
923
|
+
catch (_j) {
|
|
1335
924
|
throw new WebhookVerificationError("Webhook body is not valid JSON.");
|
|
1336
925
|
}
|
|
1337
926
|
if (!payload ||
|
|
@@ -1383,15 +972,25 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1383
972
|
}
|
|
1384
973
|
// Replay cache: reject a previously-seen signed delivery within the skew
|
|
1385
974
|
// 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.
|
|
1387
|
-
//
|
|
975
|
+
// would reject legitimate rapid updates to the same document.
|
|
976
|
+
//
|
|
977
|
+
// SHW-05: delivery is durable + at-least-once, and a RETRY of one delivery
|
|
978
|
+
// uses a fresh timestamp (to stay inside the skew window) and therefore a
|
|
979
|
+
// different signature. Keying replay on the signed, per-delivery `deliveryId`
|
|
980
|
+
// (stable across retries) is what actually dedupes those retries; the old
|
|
981
|
+
// `${keyId}:${signature}` key only caught exact byte-identical resends and
|
|
982
|
+
// would wave every retry of the same delivery straight through. Fall back to
|
|
983
|
+
// the signature only for a legacy payload with no deliveryId. Pair a shared
|
|
984
|
+
// store (retained for the retry window) with this for exactly-once handling.
|
|
1388
985
|
const replayStore = opts.replayStore === undefined ? defaultReplayStore : opts.replayStore;
|
|
1389
986
|
if (replayStore) {
|
|
1390
987
|
const expiresAtMs = (payload.timestamp + maxSkew) * 1000;
|
|
1391
|
-
const replayKey =
|
|
988
|
+
const replayKey = typeof payload.deliveryId === "string" && payload.deliveryId.length > 0
|
|
989
|
+
? `${keyId}:did:${payload.deliveryId}`
|
|
990
|
+
: `${keyId}:${signature}`;
|
|
1392
991
|
const replayed = await replayStore.checkAndRecord(replayKey, expiresAtMs);
|
|
1393
992
|
if (replayed) {
|
|
1394
|
-
throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}".`);
|
|
993
|
+
throw new WebhookVerificationError(`Webhook replay detected for event id "${payload.id}" (delivery "${(_h = payload.deliveryId) !== null && _h !== void 0 ? _h : "?"}").`);
|
|
1395
994
|
}
|
|
1396
995
|
}
|
|
1397
996
|
return payload;
|