@bounded-sh/server 0.0.41 → 0.0.44
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 +34 -4
- package/dist/auth/providers/evm-keypair-provider.d.ts +33 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +183 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +184 -14
- package/dist/index.mjs.map +1 -1
- package/dist/wallet-client.d.ts +30 -6
- package/package.json +20 -15
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, 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,
|
|
1
|
+
import { init as init$1, convertRemainingAccounts, buildSetDocumentsTransaction, normalizeEvmAddress, genAuthNonce, getConfig, genSiweMessage, createSessionWithSignature, assertCanonicalBoundedRuntimeWriteCalldata, live as live$1, set as set$1, setMany as setMany$1, setFile as setFile$1, get as get$1, getMany as getMany$1, getFiles as getFiles$1, search as search$1, runQuery as runQuery$1, runQueryMany as runQueryMany$1, runExpression as runExpression$1, runExpressionMany as runExpressionMany$1, count as count$1, aggregate as aggregate$1, queryAggregate as queryAggregate$1, subscribe as subscribe$1, functions as functions$1, ServerSessionManager, getWebhookKeysUrl } from '@bounded-sh/core';
|
|
2
2
|
export { BoundedDeclineError, DECLINE_EVENT_NAME, FunctionInvokeError, InsufficientBalanceError, LiveIntentError, deriveDecline, getConfig, getWebhookKeysUrl } from '@bounded-sh/core';
|
|
3
3
|
import { Connection, VersionedTransaction, ComputeBudgetProgram, SystemProgram, Keypair } from '@solana/web3.js';
|
|
4
4
|
import bs58 from 'bs58';
|
|
5
5
|
import { Buffer as Buffer$1 } from 'buffer';
|
|
6
6
|
import * as anchor from '@coral-xyz/anchor';
|
|
7
7
|
import nacl from 'tweetnacl';
|
|
8
|
+
import { http, createWalletClient as createWalletClient$1, createPublicClient, defineChain } from 'viem';
|
|
9
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
8
10
|
|
|
9
11
|
async function init(newConfig) {
|
|
10
12
|
await init$1(Object.assign(Object.assign({}, newConfig), { authProvider: null, isServer: true, skipBackendInit: true }));
|
|
@@ -657,6 +659,144 @@ class OffchainAuthProvider {
|
|
|
657
659
|
}
|
|
658
660
|
}
|
|
659
661
|
|
|
662
|
+
const NO_SOLANA = 'An EVM keypair client cannot sign Solana transactions.';
|
|
663
|
+
function normalizeChainConfig(input) {
|
|
664
|
+
var _a;
|
|
665
|
+
if (!Number.isSafeInteger(input.chainId) || input.chainId <= 0)
|
|
666
|
+
throw new Error('EVM chainId must be a positive safe integer');
|
|
667
|
+
let rpc;
|
|
668
|
+
try {
|
|
669
|
+
rpc = new URL(input.rpcUrl);
|
|
670
|
+
}
|
|
671
|
+
catch (_b) {
|
|
672
|
+
throw new Error(`EVM RPC URL is invalid for chain ${input.chainId}`);
|
|
673
|
+
}
|
|
674
|
+
if (rpc.protocol !== 'https:' && !(rpc.protocol === 'http:' && (rpc.hostname === 'localhost' || rpc.hostname === '127.0.0.1' || rpc.hostname === '[::1]'))) {
|
|
675
|
+
throw new Error(`EVM RPC URL must use HTTPS for chain ${input.chainId}`);
|
|
676
|
+
}
|
|
677
|
+
const runtimeAddress = normalizeEvmAddress(input.runtimeAddress);
|
|
678
|
+
if (!runtimeAddress)
|
|
679
|
+
throw new Error(`BoundedRuntime address is invalid for chain ${input.chainId}`);
|
|
680
|
+
return { chainId: input.chainId, rpcUrl: rpc.toString(), runtimeAddress, name: ((_a = input.name) === null || _a === void 0 ? void 0 : _a.trim()) || `EVM ${input.chainId}` };
|
|
681
|
+
}
|
|
682
|
+
function asViemChain(config) {
|
|
683
|
+
return defineChain({
|
|
684
|
+
id: config.chainId,
|
|
685
|
+
name: config.name,
|
|
686
|
+
nativeCurrency: { name: 'Native token', symbol: 'NATIVE', decimals: 18 },
|
|
687
|
+
rpcUrls: { default: { http: [config.rpcUrl] } },
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
class EvmKeypairProvider {
|
|
691
|
+
constructor(privateKey, defaultChainId, chainConfigs) {
|
|
692
|
+
this.chains = new Map();
|
|
693
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(privateKey))
|
|
694
|
+
throw new Error('Invalid EVM private key: expected 0x followed by 64 hex characters.');
|
|
695
|
+
this.account = privateKeyToAccount(privateKey);
|
|
696
|
+
const address = normalizeEvmAddress(this.account.address);
|
|
697
|
+
if (!address)
|
|
698
|
+
throw new Error('Could not derive a valid EVM address from the private key');
|
|
699
|
+
this.address = address;
|
|
700
|
+
for (const raw of chainConfigs) {
|
|
701
|
+
const chain = normalizeChainConfig(raw);
|
|
702
|
+
if (this.chains.has(chain.chainId))
|
|
703
|
+
throw new Error(`Duplicate EVM chain configuration: ${chain.chainId}`);
|
|
704
|
+
this.chains.set(chain.chainId, chain);
|
|
705
|
+
}
|
|
706
|
+
if (!this.chains.has(defaultChainId))
|
|
707
|
+
throw new Error(`Default EVM chain ${defaultChainId} is not configured`);
|
|
708
|
+
this.defaultChainId = defaultChainId;
|
|
709
|
+
}
|
|
710
|
+
user() {
|
|
711
|
+
return { id: this.address, address: null, evmAddress: this.address, provider: this };
|
|
712
|
+
}
|
|
713
|
+
async createBoundedSession() {
|
|
714
|
+
var _a, _b, _c;
|
|
715
|
+
const nonce = await genAuthNonce();
|
|
716
|
+
const { appId } = await getConfig();
|
|
717
|
+
const message = genSiweMessage(this.address, nonce, appId, 'server', 'https://server/login', { chainId: this.defaultChainId });
|
|
718
|
+
const signature = await this.account.signMessage({ message });
|
|
719
|
+
const session = await createSessionWithSignature(this.address, message, signature, 'siwe');
|
|
720
|
+
return {
|
|
721
|
+
address: this.address,
|
|
722
|
+
accessToken: String((_a = session.accessToken) !== null && _a !== void 0 ? _a : ''),
|
|
723
|
+
idToken: String((_b = session.idToken) !== null && _b !== void 0 ? _b : ''),
|
|
724
|
+
refreshToken: String((_c = session.refreshToken) !== null && _c !== void 0 ? _c : ''),
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
async login() { return this.user(); }
|
|
728
|
+
async restoreSession() { return this.user(); }
|
|
729
|
+
async logout() { }
|
|
730
|
+
async signMessage(message) {
|
|
731
|
+
return this.account.signMessage({ message });
|
|
732
|
+
}
|
|
733
|
+
async signTransaction(_transaction) {
|
|
734
|
+
throw new Error(NO_SOLANA);
|
|
735
|
+
}
|
|
736
|
+
async signAndSubmitTransaction(_transaction, _feePayer) {
|
|
737
|
+
throw new Error(NO_SOLANA);
|
|
738
|
+
}
|
|
739
|
+
async runTransaction(evm, sol, options) {
|
|
740
|
+
var _a;
|
|
741
|
+
if (evm && sol)
|
|
742
|
+
throw new Error('A transaction cannot target EVM and Solana at the same time.');
|
|
743
|
+
if (!evm || !evm.chainId || !evm.to || !evm.data)
|
|
744
|
+
throw new Error('A complete EVM transaction is required');
|
|
745
|
+
if ((options === null || options === void 0 ? void 0 : options.shouldSubmitTx) === false)
|
|
746
|
+
throw new Error('EVM keypair writes submit atomically; shouldSubmitTx:false is unsupported.');
|
|
747
|
+
const config = this.chains.get(evm.chainId);
|
|
748
|
+
if (!config)
|
|
749
|
+
throw new Error(`EVM chain ${evm.chainId} is not configured for this wallet client`);
|
|
750
|
+
if (normalizeEvmAddress(evm.to) !== config.runtimeAddress)
|
|
751
|
+
throw new Error('EVM transaction target is not the configured BoundedRuntime');
|
|
752
|
+
if (evm.nonce !== undefined)
|
|
753
|
+
throw new Error('Server-supplied EVM nonces are not accepted');
|
|
754
|
+
try {
|
|
755
|
+
if (BigInt((_a = evm.value) !== null && _a !== void 0 ? _a : '0') !== BigInt(0))
|
|
756
|
+
throw new Error('BoundedRuntime.setDocuments cannot transfer native value');
|
|
757
|
+
}
|
|
758
|
+
catch (error) {
|
|
759
|
+
if (error instanceof Error && error.message.includes('cannot transfer'))
|
|
760
|
+
throw error;
|
|
761
|
+
throw new Error('Invalid EVM transaction value');
|
|
762
|
+
}
|
|
763
|
+
// The shared decoder accepts only canonical ordinary/attested document
|
|
764
|
+
// writes and rejects selector-prefix smuggling, aliases and trailing bytes.
|
|
765
|
+
assertCanonicalBoundedRuntimeWriteCalldata(evm.data);
|
|
766
|
+
// A server key can safely switch between explicitly configured chains: each
|
|
767
|
+
// tx selects its own immutable viem client/RPC/runtime tuple. No untrusted
|
|
768
|
+
// server response may introduce a new chain or RPC endpoint.
|
|
769
|
+
const chain = asViemChain(config);
|
|
770
|
+
const transport = http(config.rpcUrl);
|
|
771
|
+
const wallet = createWalletClient$1({ account: this.account, chain, transport });
|
|
772
|
+
const publicClient = createPublicClient({ chain, transport });
|
|
773
|
+
const hash = await wallet.sendTransaction({
|
|
774
|
+
account: this.account,
|
|
775
|
+
chain,
|
|
776
|
+
to: config.runtimeAddress,
|
|
777
|
+
data: evm.data,
|
|
778
|
+
value: BigInt(0),
|
|
779
|
+
});
|
|
780
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
781
|
+
if (receipt.status !== 'success')
|
|
782
|
+
throw new Error(`EVM transaction ${hash} reverted on-chain`);
|
|
783
|
+
if (receipt.blockNumber > BigInt(Number.MAX_SAFE_INTEGER))
|
|
784
|
+
throw new Error('EVM receipt block number exceeds the SDK safe-integer range');
|
|
785
|
+
return {
|
|
786
|
+
transactionSignature: hash,
|
|
787
|
+
blockNumber: Number(receipt.blockNumber),
|
|
788
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
789
|
+
data: receipt,
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
async getNativeMethods() { return this.account; }
|
|
793
|
+
transactionPolicy() {
|
|
794
|
+
return {
|
|
795
|
+
runtimeAddresses: Object.fromEntries([...this.chains].map(([id, config]) => [String(id), config.runtimeAddress])),
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
|
|
660
800
|
var __rest = (undefined && undefined.__rest) || function (s, e) {
|
|
661
801
|
var t = {};
|
|
662
802
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
@@ -708,10 +848,11 @@ function jwtExpiringSoon(token) {
|
|
|
708
848
|
/* WalletClient */
|
|
709
849
|
/* ------------------------------------------------------------------ */
|
|
710
850
|
class WalletClient {
|
|
711
|
-
constructor(provider, sessionManager, address) {
|
|
851
|
+
constructor(provider, sessionManager, address, evmTransactionPolicy) {
|
|
712
852
|
this.provider = provider;
|
|
713
853
|
this.sessionManager = sessionManager;
|
|
714
854
|
this.address = address;
|
|
855
|
+
this.evmTransactionPolicy = evmTransactionPolicy;
|
|
715
856
|
this.live = {
|
|
716
857
|
intent: (roomPath, intent, opts = {}) => live$1.intent(roomPath, intent, this.mergeLiveIntentOptions(opts)),
|
|
717
858
|
status: (roomPath, opts = {}) => live$1.status(roomPath, Object.assign(Object.assign({}, opts), { headers: this.sanitizeHeaders(opts.headers) })),
|
|
@@ -739,9 +880,7 @@ class WalletClient {
|
|
|
739
880
|
}
|
|
740
881
|
/* ---- Auth override helpers ---- */
|
|
741
882
|
buildOverrides() {
|
|
742
|
-
return {
|
|
743
|
-
authProvider: this.provider,
|
|
744
|
-
_getAuthHeaders: async () => {
|
|
883
|
+
return Object.assign({ authProvider: this.provider, _getAuthHeaders: async () => {
|
|
745
884
|
let s = await this.sessionManager.getSession();
|
|
746
885
|
// Keypair-backed session: if the cached token is expired/near-expiry,
|
|
747
886
|
// re-sign for a fresh one (we always hold the key). Keeps long-lived WS
|
|
@@ -752,12 +891,9 @@ class WalletClient {
|
|
|
752
891
|
s = await this.sessionManager.getSession();
|
|
753
892
|
}
|
|
754
893
|
return { Authorization: `Bearer ${s.idToken}` };
|
|
755
|
-
},
|
|
756
|
-
_clearAuth: async () => {
|
|
894
|
+
}, _clearAuth: async () => {
|
|
757
895
|
this.sessionManager.clearSession();
|
|
758
|
-
},
|
|
759
|
-
_walletAddress: this.address,
|
|
760
|
-
};
|
|
896
|
+
}, _walletAddress: this.address }, (this.evmTransactionPolicy ? { _evmTransactionPolicy: this.evmTransactionPolicy } : {}));
|
|
761
897
|
}
|
|
762
898
|
/** Strip Authorization from caller headers to prevent overriding wallet token */
|
|
763
899
|
sanitizeHeaders(headers) {
|
|
@@ -769,7 +905,7 @@ class WalletClient {
|
|
|
769
905
|
mergeSetOverrides(opts) {
|
|
770
906
|
var _a;
|
|
771
907
|
const ovr = this.buildOverrides();
|
|
772
|
-
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 }) });
|
|
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 }) });
|
|
773
909
|
}
|
|
774
910
|
mergeReadOverrides(existing) {
|
|
775
911
|
const ovr = this.buildOverrides();
|
|
@@ -863,19 +999,53 @@ class WalletClient {
|
|
|
863
999
|
}
|
|
864
1000
|
}
|
|
865
1001
|
/**
|
|
866
|
-
* Create a self-contained wallet client bound to
|
|
1002
|
+
* Create a self-contained wallet client bound to one Solana or EVM keypair.
|
|
867
1003
|
* Each client has its own auth session — no global state is mutated.
|
|
868
1004
|
*
|
|
869
1005
|
* @example
|
|
870
1006
|
* ```ts
|
|
871
1007
|
* const vault = await createWalletClient({ keypair: process.env.VAULT_KEY });
|
|
872
1008
|
* await vault.set('markets/123', data);
|
|
1009
|
+
*
|
|
1010
|
+
* const evmVault = await createWalletClient({
|
|
1011
|
+
* evmPrivateKey: process.env.EVM_PRIVATE_KEY,
|
|
1012
|
+
* chainId: 8453,
|
|
1013
|
+
* rpcUrl: process.env.BASE_RPC_URL,
|
|
1014
|
+
* runtimeAddress: process.env.BOUNDED_RUNTIME,
|
|
1015
|
+
* });
|
|
873
1016
|
* ```
|
|
874
1017
|
*/
|
|
875
1018
|
async function createWalletClient(opts) {
|
|
876
1019
|
var _a;
|
|
877
|
-
const kp = parseKeypair(opts.keypair);
|
|
878
1020
|
const config = await getConfig();
|
|
1021
|
+
if ('evmPrivateKey' in opts && typeof opts.evmPrivateKey === 'string') {
|
|
1022
|
+
const chainConfigs = [];
|
|
1023
|
+
if (opts.chains) {
|
|
1024
|
+
for (const [rawId, chain] of Object.entries(opts.chains)) {
|
|
1025
|
+
const chainId = Number(rawId);
|
|
1026
|
+
if (!Number.isSafeInteger(chainId) || chainId <= 0)
|
|
1027
|
+
throw new Error(`Invalid EVM chain id in chains map: ${rawId}`);
|
|
1028
|
+
chainConfigs.push({ chainId, rpcUrl: chain.rpcUrl, runtimeAddress: chain.runtimeAddress, name: chain.name });
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (opts.rpcUrl !== undefined || opts.runtimeAddress !== undefined) {
|
|
1032
|
+
if (!opts.rpcUrl || !opts.runtimeAddress)
|
|
1033
|
+
throw new Error('Single-chain EVM clients require both rpcUrl and runtimeAddress');
|
|
1034
|
+
if (chainConfigs.some((chain) => chain.chainId === opts.chainId))
|
|
1035
|
+
throw new Error(`EVM chain ${opts.chainId} is configured twice`);
|
|
1036
|
+
chainConfigs.push({ chainId: opts.chainId, rpcUrl: opts.rpcUrl, runtimeAddress: opts.runtimeAddress });
|
|
1037
|
+
}
|
|
1038
|
+
if (chainConfigs.length === 0)
|
|
1039
|
+
throw new Error('EVM wallet clients require an explicit RPC URL and BoundedRuntime address');
|
|
1040
|
+
const provider = new EvmKeypairProvider(opts.evmPrivateKey, opts.chainId, chainConfigs);
|
|
1041
|
+
const sessionManager = ServerSessionManager.forSessionFactory(() => provider.createBoundedSession());
|
|
1042
|
+
await sessionManager.getSession();
|
|
1043
|
+
return new WalletClient(provider, sessionManager, provider.address, provider.transactionPolicy());
|
|
1044
|
+
}
|
|
1045
|
+
if (!('keypair' in opts) || typeof opts.keypair !== 'string') {
|
|
1046
|
+
throw new Error('createWalletClient requires exactly one of keypair or evmPrivateKey');
|
|
1047
|
+
}
|
|
1048
|
+
const kp = parseKeypair(opts.keypair);
|
|
879
1049
|
let provider = new SolanaKeypairProvider((_a = config.rpcUrl) !== null && _a !== void 0 ? _a : null, kp);
|
|
880
1050
|
if (config.chain === 'offchain') {
|
|
881
1051
|
provider = new OffchainAuthProvider(provider);
|
|
@@ -1140,5 +1310,5 @@ async function verifyWebhook(rawBody, headers, opts = {}) {
|
|
|
1140
1310
|
return payload;
|
|
1141
1311
|
}
|
|
1142
1312
|
|
|
1143
|
-
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 };
|
|
1313
|
+
export { DEFAULT_WEBHOOK_KEYS_URL, EvmKeypairProvider, 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 };
|
|
1144
1314
|
//# sourceMappingURL=index.mjs.map
|