@bounded-sh/server 0.0.67 → 0.0.69
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/README.md +6 -38
- package/dist/auth/providers/offchain-auth-provider.d.ts +0 -1
- package/dist/auth/providers/solana-keypair-provider.d.ts +1 -2
- package/dist/index.d.ts +1 -3
- package/dist/index.js +54 -377
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +56 -378
- package/dist/index.mjs.map +1 -1
- package/dist/wallet-client.d.ts +3 -27
- package/package.json +4 -5
- package/dist/auth/providers/evm-keypair-provider.d.ts +0 -45
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { init as init$1,
|
|
1
|
+
import { init as init$1, 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, getConfig, ServerSessionManager, getWebhookKeysUrl, getConfiguredAppId } from '@bounded-sh/core';
|
|
2
2
|
export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl, reconcileSetResult, waitForSetResult } from '@bounded-sh/core';
|
|
3
3
|
import { Connection, Keypair } from '@solana/web3.js';
|
|
4
4
|
import bs58 from 'bs58';
|
|
5
5
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
6
6
|
import nacl from 'tweetnacl';
|
|
7
|
-
import { http, createWalletClient as createWalletClient$1, createPublicClient, keccak256, defineChain } from 'viem';
|
|
8
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
9
7
|
|
|
10
8
|
async function init(newConfig) {
|
|
11
9
|
await init$1(Object.assign(Object.assign({}, newConfig), { authProvider: null, isServer: true, skipBackendInit: true }));
|
|
@@ -52,6 +50,20 @@ async function getIdToken() {
|
|
|
52
50
|
throw new Error('getIdToken is not available as a tarobase-server top-level auth operation. Use createWalletClient({ keypair }) and call the returned client methods.');
|
|
53
51
|
}
|
|
54
52
|
|
|
53
|
+
/**
|
|
54
|
+
* DS3-0414: does the transaction already carry a REAL signer signature? A Solana
|
|
55
|
+
* signature is computed over the compiled message, which INCLUDES recentBlockhash, so
|
|
56
|
+
* the blockhash must not be rewritten once any signer (sponsor / multisig / co-signer)
|
|
57
|
+
* has signed - a rewrite silently invalidates their signature. Legacy signatures are
|
|
58
|
+
* `{ signature: Buffer | null }` entries; versioned signatures are 64-byte arrays that
|
|
59
|
+
* stay all-zero placeholders until a signer fills them. Mirrors turnkey-signer-bridge.ts.
|
|
60
|
+
*/
|
|
61
|
+
function transactionHasSignature(transaction) {
|
|
62
|
+
if ('serializeMessage' in transaction) {
|
|
63
|
+
return transaction.signatures.some((s) => s.signature != null);
|
|
64
|
+
}
|
|
65
|
+
return transaction.signatures.some((sig) => sig.some((b) => b !== 0));
|
|
66
|
+
}
|
|
55
67
|
class SolanaKeypairProvider {
|
|
56
68
|
constructor(rpcUrl, serverKeypair) {
|
|
57
69
|
this.rpcUrl = rpcUrl;
|
|
@@ -108,25 +120,34 @@ class SolanaKeypairProvider {
|
|
|
108
120
|
// 1. Get RPC URL and create connection
|
|
109
121
|
const rpcUrl = this.getRpcUrl();
|
|
110
122
|
const connection = new Connection(rpcUrl, 'confirmed');
|
|
111
|
-
// 2. Get fresh blockhash and set it on the transaction before signing
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
// 2. Get fresh blockhash and set it on the transaction before signing.
|
|
124
|
+
// DS3-0414: a Solana signature covers the compiled message (recentBlockhash included),
|
|
125
|
+
// so refreshing the blockhash after a sponsor / co-signer has already signed silently
|
|
126
|
+
// invalidates their signature - and this is the sponsored/fee-payer submit path, where a
|
|
127
|
+
// co-signed transaction is exactly what arrives. signTransaction only ADDS this keypair's
|
|
128
|
+
// signature (partialSign), it never re-signs the others. So only refresh the blockhash
|
|
129
|
+
// while the transaction is still unsigned; once a real signature is present, sign and
|
|
130
|
+
// submit the caller's message verbatim.
|
|
131
|
+
if (!transactionHasSignature(transaction)) {
|
|
132
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
|
133
|
+
// Use duck typing instead of instanceof to handle multiple @solana/web3.js versions
|
|
134
|
+
const isLegacyTransaction = 'recentBlockhash' in transaction && !('message' in transaction && 'staticAccountKeys' in transaction.message);
|
|
135
|
+
if (isLegacyTransaction) {
|
|
136
|
+
const legacyTx = transaction;
|
|
137
|
+
legacyTx.recentBlockhash = blockhash;
|
|
138
|
+
legacyTx.lastValidBlockHeight = lastValidBlockHeight;
|
|
139
|
+
// Set feePayer if not already set
|
|
140
|
+
if (!legacyTx.feePayer) {
|
|
141
|
+
legacyTx.feePayer = feePayer !== null && feePayer !== void 0 ? feePayer : this.keypair.publicKey;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
// VersionedTransaction
|
|
146
|
+
const versionedTx = transaction;
|
|
147
|
+
versionedTx.message.recentBlockhash = blockhash;
|
|
148
|
+
// Note: VersionedTransaction feePayer is set in the message at creation time
|
|
149
|
+
// and cannot be modified after creation
|
|
122
150
|
}
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
// VersionedTransaction
|
|
126
|
-
const versionedTx = transaction;
|
|
127
|
-
versionedTx.message.recentBlockhash = blockhash;
|
|
128
|
-
// Note: VersionedTransaction feePayer is set in the message at creation time
|
|
129
|
-
// and cannot be modified after creation
|
|
130
151
|
}
|
|
131
152
|
// 3. Sign the transaction
|
|
132
153
|
const signedTx = await this.signTransaction(transaction);
|
|
@@ -188,16 +209,6 @@ class SolanaKeypairProvider {
|
|
|
188
209
|
}
|
|
189
210
|
throw new Error('Solana RPC URL is required for server keypair transaction submission; pass an explicit rpcUrl when creating SolanaKeypairProvider.');
|
|
190
211
|
}
|
|
191
|
-
/* ----------------------------------------------------------- *
|
|
192
|
-
* Transaction runner
|
|
193
|
-
* ----------------------------------------------------------- */
|
|
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.');
|
|
200
|
-
}
|
|
201
212
|
async getNativeMethods() {
|
|
202
213
|
return { keypair: this.keypair };
|
|
203
214
|
}
|
|
@@ -236,312 +247,6 @@ class OffchainAuthProvider {
|
|
|
236
247
|
async signAndSubmitTransaction(_transaction, _feePayer) {
|
|
237
248
|
throw new Error('Poofnet does not support real Solana transactions. Deploy your project to mainnet to use this feature.');
|
|
238
249
|
}
|
|
239
|
-
async runTransaction(evmTransactionData, solTransactionData, options) {
|
|
240
|
-
return this.wrappedProvider.runTransaction(evmTransactionData, solTransactionData, options);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
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
|
-
}
|
|
286
|
-
const NO_SOLANA = 'An EVM keypair client cannot sign Solana transactions.';
|
|
287
|
-
function normalizeChainConfig(input) {
|
|
288
|
-
var _a;
|
|
289
|
-
if (!Number.isSafeInteger(input.chainId) || input.chainId <= 0)
|
|
290
|
-
throw new Error('EVM chainId must be a positive safe integer');
|
|
291
|
-
let rpc;
|
|
292
|
-
try {
|
|
293
|
-
rpc = new URL(input.rpcUrl);
|
|
294
|
-
}
|
|
295
|
-
catch (_b) {
|
|
296
|
-
throw new Error(`EVM RPC URL is invalid for chain ${input.chainId}`);
|
|
297
|
-
}
|
|
298
|
-
if (rpc.protocol !== 'https:' && !(rpc.protocol === 'http:' && (rpc.hostname === 'localhost' || rpc.hostname === '127.0.0.1' || rpc.hostname === '[::1]'))) {
|
|
299
|
-
throw new Error(`EVM RPC URL must use HTTPS for chain ${input.chainId}`);
|
|
300
|
-
}
|
|
301
|
-
const runtimeAddress = normalizeEvmAddress(input.runtimeAddress);
|
|
302
|
-
if (!runtimeAddress)
|
|
303
|
-
throw new Error(`BoundedRuntime address is invalid for chain ${input.chainId}`);
|
|
304
|
-
return { chainId: input.chainId, rpcUrl: rpc.toString(), runtimeAddress, name: ((_a = input.name) === null || _a === void 0 ? void 0 : _a.trim()) || `EVM ${input.chainId}` };
|
|
305
|
-
}
|
|
306
|
-
function asViemChain(config) {
|
|
307
|
-
return defineChain({
|
|
308
|
-
id: config.chainId,
|
|
309
|
-
name: config.name,
|
|
310
|
-
nativeCurrency: { name: 'Native token', symbol: 'NATIVE', decimals: 18 },
|
|
311
|
-
rpcUrls: { default: { http: [config.rpcUrl] } },
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
class EvmKeypairProvider {
|
|
315
|
-
constructor(privateKey, defaultChainId, chainConfigs) {
|
|
316
|
-
this.chains = new Map();
|
|
317
|
-
if (!/^0x[0-9a-fA-F]{64}$/.test(privateKey))
|
|
318
|
-
throw new Error('Invalid EVM private key: expected 0x followed by 64 hex characters.');
|
|
319
|
-
this.account = privateKeyToAccount(privateKey);
|
|
320
|
-
const address = normalizeEvmAddress(this.account.address);
|
|
321
|
-
if (!address)
|
|
322
|
-
throw new Error('Could not derive a valid EVM address from the private key');
|
|
323
|
-
this.address = address;
|
|
324
|
-
for (const raw of chainConfigs) {
|
|
325
|
-
const chain = normalizeChainConfig(raw);
|
|
326
|
-
if (this.chains.has(chain.chainId))
|
|
327
|
-
throw new Error(`Duplicate EVM chain configuration: ${chain.chainId}`);
|
|
328
|
-
this.chains.set(chain.chainId, chain);
|
|
329
|
-
}
|
|
330
|
-
if (!this.chains.has(defaultChainId))
|
|
331
|
-
throw new Error(`Default EVM chain ${defaultChainId} is not configured`);
|
|
332
|
-
this.defaultChainId = defaultChainId;
|
|
333
|
-
}
|
|
334
|
-
user() {
|
|
335
|
-
return { id: this.address, address: null, evmAddress: this.address, provider: this };
|
|
336
|
-
}
|
|
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;
|
|
344
|
-
const message = genSiweMessage(this.address, nonce, appId, 'server', 'https://server/login', { chainId: this.defaultChainId });
|
|
345
|
-
const signature = await this.account.signMessage({ message });
|
|
346
|
-
const session = await createSessionWithSignature(this.address, message, signature, 'siwe', sessionConfig);
|
|
347
|
-
return {
|
|
348
|
-
address: this.address,
|
|
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 : ''),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
async login() { return this.user(); }
|
|
355
|
-
async restoreSession() { return this.user(); }
|
|
356
|
-
async logout() { }
|
|
357
|
-
async signMessage(message) {
|
|
358
|
-
return this.account.signMessage({ message });
|
|
359
|
-
}
|
|
360
|
-
async signTransaction(_transaction) {
|
|
361
|
-
throw new Error(NO_SOLANA);
|
|
362
|
-
}
|
|
363
|
-
async signAndSubmitTransaction(_transaction, _feePayer) {
|
|
364
|
-
throw new Error(NO_SOLANA);
|
|
365
|
-
}
|
|
366
|
-
async runTransaction(evm, sol, options) {
|
|
367
|
-
var _a;
|
|
368
|
-
if (evm && sol)
|
|
369
|
-
throw new Error('A transaction cannot target EVM and Solana at the same time.');
|
|
370
|
-
if (!evm || !evm.chainId || !evm.to || !evm.data)
|
|
371
|
-
throw new Error('A complete EVM transaction is required');
|
|
372
|
-
if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false)
|
|
373
|
-
throw new Error('EVM keypair writes submit atomically; shouldSubmitTx:false is unsupported.');
|
|
374
|
-
const config = this.chains.get(evm.chainId);
|
|
375
|
-
if (!config)
|
|
376
|
-
throw new Error(`EVM chain ${evm.chainId} is not configured for this wallet client`);
|
|
377
|
-
if (normalizeEvmAddress(evm.to) !== config.runtimeAddress)
|
|
378
|
-
throw new Error('EVM transaction target is not the configured BoundedRuntime');
|
|
379
|
-
if (evm.nonce !== undefined)
|
|
380
|
-
throw new Error('Server-supplied EVM nonces are not accepted');
|
|
381
|
-
try {
|
|
382
|
-
if (BigInt((_a = evm.value) !== null && _a !== void 0 ? _a : '0') !== BigInt(0))
|
|
383
|
-
throw new Error('BoundedRuntime.setDocuments cannot transfer native value');
|
|
384
|
-
}
|
|
385
|
-
catch (error) {
|
|
386
|
-
if (error instanceof Error && error.message.includes('cannot transfer'))
|
|
387
|
-
throw error;
|
|
388
|
-
throw new Error('Invalid EVM transaction value');
|
|
389
|
-
}
|
|
390
|
-
// The shared decoder accepts only canonical ordinary/attested document
|
|
391
|
-
// writes and rejects selector-prefix smuggling, aliases and trailing bytes.
|
|
392
|
-
assertCanonicalBoundedRuntimeWriteCalldata(evm.data);
|
|
393
|
-
// A server key can safely switch between explicitly configured chains: each
|
|
394
|
-
// tx selects its own immutable viem client/RPC/runtime tuple. No untrusted
|
|
395
|
-
// server response may introduce a new chain or RPC endpoint.
|
|
396
|
-
const chain = asViemChain(config);
|
|
397
|
-
const transport = http(config.rpcUrl);
|
|
398
|
-
const wallet = createWalletClient$1({ account: this.account, chain, transport });
|
|
399
|
-
const publicClient = createPublicClient({ chain, transport });
|
|
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
|
-
}
|
|
466
|
-
if (receipt.status !== 'success')
|
|
467
|
-
throw new Error(`EVM transaction ${hash} reverted on-chain`);
|
|
468
|
-
if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER))
|
|
469
|
-
throw new Error('EVM receipt block number exceeds the SDK safe-integer range');
|
|
470
|
-
return {
|
|
471
|
-
outcome: 'confirmed',
|
|
472
|
-
lane: 'evm',
|
|
473
|
-
transactionSignature: hash,
|
|
474
|
-
blockNumber: Number(receipt.blockNumber),
|
|
475
|
-
gasUsed: receipt.gasUsed.toString(),
|
|
476
|
-
data: receipt,
|
|
477
|
-
};
|
|
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
|
-
}
|
|
539
|
-
async getNativeMethods() { return this.account; }
|
|
540
|
-
transactionPolicy() {
|
|
541
|
-
return {
|
|
542
|
-
runtimeAddresses: Object.fromEntries([...this.chains].map(([id, config]) => [String(id), config.runtimeAddress])),
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
250
|
}
|
|
546
251
|
|
|
547
252
|
// #023: strip caller-supplied auth headers so an operation runs as the isolated wallet, not
|
|
@@ -601,12 +306,11 @@ function jwtExpiringSoon(token) {
|
|
|
601
306
|
/* WalletClient */
|
|
602
307
|
/* ------------------------------------------------------------------ */
|
|
603
308
|
class WalletClient {
|
|
604
|
-
constructor(provider, sessionManager, address, config
|
|
309
|
+
constructor(provider, sessionManager, address, config) {
|
|
605
310
|
this.provider = provider;
|
|
606
311
|
this.sessionManager = sessionManager;
|
|
607
312
|
this.address = address;
|
|
608
313
|
this.config = config;
|
|
609
|
-
this.evmTransactionPolicy = evmTransactionPolicy;
|
|
610
314
|
this.live = {
|
|
611
315
|
intent: (roomPath, intent, opts = {}) => live$1.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
|
|
612
316
|
// Route status through the SAME auth merge intent uses so a server-keypair
|
|
@@ -639,10 +343,12 @@ class WalletClient {
|
|
|
639
343
|
}
|
|
640
344
|
/* ---- Auth override helpers ---- */
|
|
641
345
|
buildOverrides() {
|
|
642
|
-
return
|
|
346
|
+
return {
|
|
347
|
+
authProvider: this.provider,
|
|
643
348
|
// Pin every request to this wallet client's snapshotted app + endpoints so
|
|
644
349
|
// a later global init() can never retarget its reads/writes (CTA-05).
|
|
645
|
-
_config: this.config,
|
|
350
|
+
_config: this.config,
|
|
351
|
+
_getAuthHeaders: async () => {
|
|
646
352
|
let s = await this.sessionManager.getSession();
|
|
647
353
|
// Keypair-backed session: if the cached token is expired/near-expiry,
|
|
648
354
|
// re-sign for a fresh one (we always hold the key). Keeps long-lived WS
|
|
@@ -653,9 +359,12 @@ class WalletClient {
|
|
|
653
359
|
s = await this.sessionManager.getSession();
|
|
654
360
|
}
|
|
655
361
|
return { Authorization: `Bearer ${s.idToken}` };
|
|
656
|
-
},
|
|
362
|
+
},
|
|
363
|
+
_clearAuth: async () => {
|
|
657
364
|
this.sessionManager.clearSession();
|
|
658
|
-
},
|
|
365
|
+
},
|
|
366
|
+
_walletAddress: this.address,
|
|
367
|
+
};
|
|
659
368
|
}
|
|
660
369
|
/** #023: strip caller Authorization (any casing) so operations run as the isolated wallet. */
|
|
661
370
|
sanitizeHeaders(headers) {
|
|
@@ -664,7 +373,7 @@ class WalletClient {
|
|
|
664
373
|
mergeSetOverrides(opts) {
|
|
665
374
|
var _a;
|
|
666
375
|
const ovr = this.buildOverrides();
|
|
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,
|
|
376
|
+
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, _config: ovr._config }) });
|
|
668
377
|
}
|
|
669
378
|
mergeReadOverrides(existing) {
|
|
670
379
|
const ovr = this.buildOverrides();
|
|
@@ -783,20 +492,13 @@ class WalletClient {
|
|
|
783
492
|
}
|
|
784
493
|
}
|
|
785
494
|
/**
|
|
786
|
-
* Create a self-contained wallet client bound to one Solana
|
|
495
|
+
* Create a self-contained wallet client bound to one Solana keypair.
|
|
787
496
|
* Each client has its own auth session — no global state is mutated.
|
|
788
497
|
*
|
|
789
498
|
* @example
|
|
790
499
|
* ```ts
|
|
791
500
|
* const vault = await createWalletClient({ keypair: process.env.VAULT_KEY });
|
|
792
501
|
* await vault.set('markets/123', data);
|
|
793
|
-
*
|
|
794
|
-
* const evmVault = await createWalletClient({
|
|
795
|
-
* evmPrivateKey: process.env.EVM_PRIVATE_KEY,
|
|
796
|
-
* chainId: 8453,
|
|
797
|
-
* rpcUrl: process.env.BASE_RPC_URL,
|
|
798
|
-
* runtimeAddress: process.env.BOUNDED_RUNTIME,
|
|
799
|
-
* });
|
|
800
502
|
* ```
|
|
801
503
|
*/
|
|
802
504
|
async function createWalletClient(opts) {
|
|
@@ -812,32 +514,8 @@ async function createWalletClient(opts) {
|
|
|
812
514
|
authApiUrl: config.authApiUrl,
|
|
813
515
|
authMethod: config.authMethod,
|
|
814
516
|
};
|
|
815
|
-
if ('evmPrivateKey' in opts && typeof opts.evmPrivateKey === 'string') {
|
|
816
|
-
const chainConfigs = [];
|
|
817
|
-
if (opts.chains) {
|
|
818
|
-
for (const [rawId, chain] of Object.entries(opts.chains)) {
|
|
819
|
-
const chainId = Number(rawId);
|
|
820
|
-
if (!Number.isSafeInteger(chainId) || chainId <= 0)
|
|
821
|
-
throw new Error(`Invalid EVM chain id in chains map: ${rawId}`);
|
|
822
|
-
chainConfigs.push({ chainId, rpcUrl: chain.rpcUrl, runtimeAddress: chain.runtimeAddress, name: chain.name });
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
if (opts.rpcUrl !== undefined || opts.runtimeAddress !== undefined) {
|
|
826
|
-
if (!opts.rpcUrl || !opts.runtimeAddress)
|
|
827
|
-
throw new Error('Single-chain EVM clients require both rpcUrl and runtimeAddress');
|
|
828
|
-
if (chainConfigs.some((chain) => chain.chainId === opts.chainId))
|
|
829
|
-
throw new Error(`EVM chain ${opts.chainId} is configured twice`);
|
|
830
|
-
chainConfigs.push({ chainId: opts.chainId, rpcUrl: opts.rpcUrl, runtimeAddress: opts.runtimeAddress });
|
|
831
|
-
}
|
|
832
|
-
if (chainConfigs.length === 0)
|
|
833
|
-
throw new Error('EVM wallet clients require an explicit RPC URL and BoundedRuntime address');
|
|
834
|
-
const provider = new EvmKeypairProvider(opts.evmPrivateKey, opts.chainId, chainConfigs);
|
|
835
|
-
const sessionManager = ServerSessionManager.forSessionFactory(() => provider.createBoundedSession(sessionConfig));
|
|
836
|
-
await sessionManager.getSession();
|
|
837
|
-
return new WalletClient(provider, sessionManager, provider.address, config, provider.transactionPolicy());
|
|
838
|
-
}
|
|
839
517
|
if (!('keypair' in opts) || typeof opts.keypair !== 'string') {
|
|
840
|
-
throw new Error('createWalletClient requires
|
|
518
|
+
throw new Error('createWalletClient requires a Solana keypair');
|
|
841
519
|
}
|
|
842
520
|
const kp = parseKeypair(opts.keypair);
|
|
843
521
|
let provider = new SolanaKeypairProvider((_a = config.rpcUrl) !== null && _a !== void 0 ? _a : null, kp);
|
|
@@ -1175,5 +853,5 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1175
853
|
return payload;
|
|
1176
854
|
}
|
|
1177
855
|
|
|
1178
|
-
export { DEFAULT_WEBHOOK_KEYS_URL,
|
|
856
|
+
export { DEFAULT_WEBHOOK_KEYS_URL, InMemoryReplayStore, WalletClient, WebhookVerificationError, aggregate, clearWebhookKeyCache, clearWebhookReplayCache, count, createWalletClient, functions, get, getAuthProvider, getFiles, getIdToken, getMany, init, invokeFunction, live, liveIntent, liveStatus, queryAggregate, runExpression, runExpressionMany, runQuery, runQueryMany, search, set, setFile, setMany, signAndSubmitTransaction, signMessage, signTransaction, subscribe, verifyWebhook };
|
|
1179
857
|
//# sourceMappingURL=index.mjs.map
|