@bounded-sh/server 0.0.65 → 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 +12 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +196 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +191 -11
- package/dist/index.mjs.map +1 -1
- package/dist/wallet-client.d.ts +14 -4
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { init as init$1, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl, getConfiguredAppId } from '@bounded-sh/core';
|
|
2
|
-
export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl } from '@bounded-sh/core';
|
|
1
|
+
import { init as init$1, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, 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
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, defineChain } from 'viem';
|
|
7
|
+
import { http, createWalletClient as createWalletClient$1, createPublicClient, keccak256, defineChain } from 'viem';
|
|
8
8
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
9
9
|
|
|
10
10
|
async function init(newConfig) {
|
|
@@ -241,6 +241,48 @@ class OffchainAuthProvider {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
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
|
+
}
|
|
244
286
|
const NO_SOLANA = 'An EVM keypair client cannot sign Solana transactions.';
|
|
245
287
|
function normalizeChainConfig(input) {
|
|
246
288
|
var _a;
|
|
@@ -355,25 +397,145 @@ class EvmKeypairProvider {
|
|
|
355
397
|
const transport = http(config.rpcUrl);
|
|
356
398
|
const wallet = createWalletClient$1({ account: this.account, chain, transport });
|
|
357
399
|
const publicClient = createPublicClient({ chain, transport });
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
+
}
|
|
366
466
|
if (receipt.status !== 'success')
|
|
367
467
|
throw new Error(`EVM transaction ${hash} reverted on-chain`);
|
|
368
468
|
if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER))
|
|
369
469
|
throw new Error('EVM receipt block number exceeds the SDK safe-integer range');
|
|
370
470
|
return {
|
|
471
|
+
outcome: 'confirmed',
|
|
472
|
+
lane: 'evm',
|
|
371
473
|
transactionSignature: hash,
|
|
372
474
|
blockNumber: Number(receipt.blockNumber),
|
|
373
475
|
gasUsed: receipt.gasUsed.toString(),
|
|
374
476
|
data: receipt,
|
|
375
477
|
};
|
|
376
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
|
+
}
|
|
377
539
|
async getNativeMethods() { return this.account; }
|
|
378
540
|
transactionPolicy() {
|
|
379
541
|
return {
|
|
@@ -530,6 +692,24 @@ class WalletClient {
|
|
|
530
692
|
async setFile(path, file, options) {
|
|
531
693
|
return setFile$1(path, file, { _overrides: this.mergeReadOverrides(options === null || options === void 0 ? void 0 : options._overrides) });
|
|
532
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
|
+
}
|
|
533
713
|
async get(path, opts) {
|
|
534
714
|
return get$1(path, Object.assign(Object.assign({}, opts), { _overrides: this.mergeReadOverrides(opts === null || opts === void 0 ? void 0 : opts._overrides) }));
|
|
535
715
|
}
|