@fastxyz/allset-sdk 0.1.10 → 0.1.11

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.
@@ -0,0 +1,47 @@
1
+ /**
2
+ * eip7702.ts — EIP-7702 smartDeposit via AllSet Portal relay
3
+ *
4
+ * Flow:
5
+ * 1. Poll ERC-20 balance until >= minAmount
6
+ * 2. POST /userop/prepare → backend assembles UserOp + paymasterData
7
+ * 3. Sign EIP-7702 authorization (re-delegate EOA to v0.8 impl)
8
+ * 4. Sign UserOperation (EIP-712, v0.8)
9
+ * 5. POST /userop/submit → backend calls Pimlico eth_sendUserOperation
10
+ *
11
+ * Private key never leaves the SDK.
12
+ * Pimlico API key never touches the SDK.
13
+ * Gas is paid in USDC via ERC-20 Paymaster.
14
+ * Chain is inferred from rpcUrl (backend calls eth_chainId) — no hardcoded chain list.
15
+ */
16
+ import { type Address, type Hash, type Hex } from 'viem';
17
+ export interface SmartDepositParams {
18
+ /** EOA private key — stays local, never sent to backend */
19
+ privateKey: Hex;
20
+ /** EVM JSON-RPC URL — used for balance polling and forwarded to backend for chainId detection */
21
+ rpcUrl: string;
22
+ /** AllSet Portal backend base URL, e.g. https://api.allset.xyz */
23
+ allsetApiUrl: string;
24
+ /** ERC-20 token to watch (e.g. USDC on Base) */
25
+ tokenAddress: Address;
26
+ /** Minimum token balance (raw, with decimals) that triggers deposit */
27
+ minAmount: bigint;
28
+ /** AllSet bridge contract address */
29
+ bridgeAddress: Address;
30
+ /** Encoded bridge.deposit(...) calldata from encodeDepositCalldata() */
31
+ depositCalldata: Hex;
32
+ /** Balance poll interval in ms (default: 3000) */
33
+ pollIntervalMs?: number;
34
+ /** Total timeout in ms waiting for balance (default: no timeout) */
35
+ timeoutMs?: number;
36
+ /** Called on each balance check */
37
+ onBalanceCheck?: (balance: bigint) => void;
38
+ }
39
+ export interface SmartDepositResult {
40
+ txHash: Hash;
41
+ userOpHash: Hash;
42
+ userAddress: Address;
43
+ /** Token balance at the time of deposit */
44
+ tokenBalance: bigint;
45
+ }
46
+ export declare function smartDeposit(params: SmartDepositParams): Promise<SmartDepositResult>;
47
+ //# sourceMappingURL=eip7702.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eip7702.d.ts","sourceRoot":"","sources":["../../src/node/eip7702.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAML,KAAK,OAAO,EACZ,KAAK,IAAI,EACT,KAAK,GAAG,EACT,MAAM,MAAM,CAAC;AAgBd,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,UAAU,EAAE,GAAG,CAAC;IAChB,iGAAiG;IACjG,MAAM,EAAE,MAAM,CAAC;IACf,kEAAkE;IAClE,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,YAAY,EAAE,OAAO,CAAC;IACtB,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,qCAAqC;IACrC,aAAa,EAAE,OAAO,CAAC;IACvB,wEAAwE;IACxE,eAAe,EAAE,GAAG,CAAC;IACrB,kDAAkD;IAClD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,IAAI,CAAC;IACb,UAAU,EAAE,IAAI,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,2CAA2C;IAC3C,YAAY,EAAE,MAAM,CAAC;CACtB;AA2HD,wBAAsB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA8I1F"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * eip7702.ts — EIP-7702 smartDeposit via AllSet Portal relay
3
+ *
4
+ * Flow:
5
+ * 1. Poll ERC-20 balance until >= minAmount
6
+ * 2. POST /userop/prepare → backend assembles UserOp + paymasterData
7
+ * 3. Sign EIP-7702 authorization (re-delegate EOA to v0.8 impl)
8
+ * 4. Sign UserOperation (EIP-712, v0.8)
9
+ * 5. POST /userop/submit → backend calls Pimlico eth_sendUserOperation
10
+ *
11
+ * Private key never leaves the SDK.
12
+ * Pimlico API key never touches the SDK.
13
+ * Gas is paid in USDC via ERC-20 Paymaster.
14
+ * Chain is inferred from rpcUrl (backend calls eth_chainId) — no hardcoded chain list.
15
+ */
16
+ import { createPublicClient, encodePacked, http, keccak256, parseAbi, } from 'viem';
17
+ import { privateKeyToAccount } from 'viem/accounts';
18
+ import { getUserOperationTypedData } from 'viem/account-abstraction';
19
+ const ENTRY_POINT_V08 = '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108';
20
+ const ERC20_BALANCEOF_ABI = parseAbi([
21
+ 'function balanceOf(address account) view returns (uint256)',
22
+ ]);
23
+ function sleep(ms) {
24
+ return new Promise((resolve) => setTimeout(resolve, ms));
25
+ }
26
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
27
+ /**
28
+ * Convert the backend's hex-string UserOp to viem's bigint-typed UserOperation<'0.8'>.
29
+ */
30
+ function parseUserOp(raw) {
31
+ return {
32
+ sender: raw.sender,
33
+ nonce: BigInt(raw.nonce),
34
+ callData: raw.callData,
35
+ callGasLimit: BigInt(raw.callGasLimit),
36
+ verificationGasLimit: BigInt(raw.verificationGasLimit),
37
+ preVerificationGas: BigInt(raw.preVerificationGas),
38
+ maxFeePerGas: BigInt(raw.maxFeePerGas),
39
+ maxPriorityFeePerGas: BigInt(raw.maxPriorityFeePerGas),
40
+ ...(raw.paymaster && { paymaster: raw.paymaster }),
41
+ ...(raw.paymasterVerificationGasLimit && {
42
+ paymasterVerificationGasLimit: BigInt(raw.paymasterVerificationGasLimit),
43
+ }),
44
+ ...(raw.paymasterPostOpGasLimit && {
45
+ paymasterPostOpGasLimit: BigInt(raw.paymasterPostOpGasLimit),
46
+ }),
47
+ ...(raw.paymasterData && { paymasterData: raw.paymasterData }),
48
+ ...(raw.factory && { factory: raw.factory }),
49
+ ...(raw.factoryData && { factoryData: raw.factoryData }),
50
+ signature: '0x',
51
+ };
52
+ }
53
+ /**
54
+ * Convert UserOperation<'0.8'> bigint fields → hex strings for JSON serialization.
55
+ * The Go backend expects all numeric fields as 0x-prefixed hex strings.
56
+ */
57
+ function serializeUserOp(op) {
58
+ const toHex = (n) => `0x${n.toString(16)}`;
59
+ return {
60
+ sender: op.sender,
61
+ nonce: toHex(op.nonce),
62
+ callData: op.callData,
63
+ callGasLimit: toHex(op.callGasLimit),
64
+ verificationGasLimit: toHex(op.verificationGasLimit),
65
+ preVerificationGas: toHex(op.preVerificationGas),
66
+ maxFeePerGas: toHex(op.maxFeePerGas),
67
+ maxPriorityFeePerGas: toHex(op.maxPriorityFeePerGas),
68
+ ...(op.paymaster && { paymaster: op.paymaster }),
69
+ ...(op.paymasterVerificationGasLimit !== undefined && {
70
+ paymasterVerificationGasLimit: toHex(op.paymasterVerificationGasLimit),
71
+ }),
72
+ ...(op.paymasterPostOpGasLimit !== undefined && {
73
+ paymasterPostOpGasLimit: toHex(op.paymasterPostOpGasLimit),
74
+ }),
75
+ ...(op.paymasterData && { paymasterData: op.paymasterData }),
76
+ ...(op.factory && { factory: op.factory }),
77
+ ...(op.factoryData && { factoryData: op.factoryData }),
78
+ ...(op.signature && { signature: op.signature }),
79
+ };
80
+ }
81
+ // ─── Main function ─────────────────────────────────────────────────────────────
82
+ export async function smartDeposit(params) {
83
+ const { privateKey, rpcUrl, allsetApiUrl, tokenAddress, minAmount, bridgeAddress, depositCalldata, pollIntervalMs = 3000, timeoutMs, onBalanceCheck, } = params;
84
+ const eoa = privateKeyToAccount(privateKey);
85
+ // No chain object needed — chainId is fetched dynamically from the RPC
86
+ const publicClient = createPublicClient({ transport: http(rpcUrl) });
87
+ // Step 1: Poll ERC-20 balance
88
+ const startTime = Date.now();
89
+ let tokenBalance = 0n;
90
+ while (true) {
91
+ if (timeoutMs && Date.now() - startTime > timeoutMs) {
92
+ throw new Error('smartDeposit: timed out waiting for balance');
93
+ }
94
+ tokenBalance = (await publicClient.readContract({
95
+ address: tokenAddress,
96
+ abi: ERC20_BALANCEOF_ABI,
97
+ functionName: 'balanceOf',
98
+ args: [eoa.address],
99
+ }));
100
+ onBalanceCheck?.(tokenBalance);
101
+ if (tokenBalance >= minAmount)
102
+ break;
103
+ await sleep(pollIntervalMs);
104
+ }
105
+ // Fetch chainId once — used for EIP-7702 auth and UserOp signing
106
+ const chainId = await publicClient.getChainId();
107
+ // Step 2: Build request auth signature (proves caller owns the private key)
108
+ // Backend verifies: ecrecover(hash, authSig) == from
109
+ const timestamp = Math.floor(Date.now() / 1000);
110
+ const msgHash = keccak256(encodePacked(['address', 'address', 'uint256', 'address', 'bytes', 'uint256'], [eoa.address, tokenAddress, minAmount, bridgeAddress, depositCalldata, BigInt(timestamp)]));
111
+ const authSig = await eoa.signMessage({ message: { raw: msgHash } });
112
+ // Step 3: POST /userop/prepare
113
+ const prepareReq = {
114
+ rpcUrl,
115
+ from: eoa.address,
116
+ tokenAddress,
117
+ amount: minAmount.toString(),
118
+ bridgeAddress,
119
+ depositCalldata,
120
+ timestamp,
121
+ authSig,
122
+ };
123
+ const prepareRes = await fetch(`${allsetApiUrl}/userop/prepare`, {
124
+ method: 'POST',
125
+ headers: { 'Content-Type': 'application/json' },
126
+ body: JSON.stringify(prepareReq),
127
+ });
128
+ if (!prepareRes.ok) {
129
+ const err = await prepareRes.text();
130
+ throw new Error(`smartDeposit prepare failed (${prepareRes.status}): ${err}`);
131
+ }
132
+ const prepared = (await prepareRes.json());
133
+ // Step 4: Sign EIP-7702 authorization.
134
+ // We always re-sign to ensure the EOA is delegated to the correct v0.8 impl,
135
+ // even if a prior (possibly outdated) delegation exists.
136
+ let eip7702Auth;
137
+ if (prepared.needsAuthorization) {
138
+ const accountNonce = await publicClient.getTransactionCount({ address: eoa.address });
139
+ const signed = await eoa.signAuthorization({
140
+ address: prepared.delegate7702Address,
141
+ chainId,
142
+ nonce: accountNonce,
143
+ });
144
+ const yParity = signed.yParity ?? 0;
145
+ eip7702Auth = {
146
+ address: prepared.delegate7702Address,
147
+ chainId: `0x${chainId.toString(16)}`,
148
+ nonce: `0x${accountNonce.toString(16)}`,
149
+ yParity: `0x${yParity.toString(16)}`,
150
+ r: `0x${BigInt(signed.r).toString(16).padStart(64, '0')}`,
151
+ s: `0x${BigInt(signed.s).toString(16).padStart(64, '0')}`,
152
+ };
153
+ }
154
+ // Step 5: Parse backend response + sign UserOperation (v0.8 uses EIP-712 typed data)
155
+ const userOpToSign = parseUserOp(prepared.unsignedUserOp);
156
+ // v0.8 requires EIP-712 signTypedData, NOT signMessage/personal_sign
157
+ const typedData = getUserOperationTypedData({
158
+ chainId,
159
+ entryPointAddress: ENTRY_POINT_V08,
160
+ userOperation: { ...userOpToSign, signature: '0x' },
161
+ });
162
+ const signature = await eoa.signTypedData(typedData);
163
+ const signedUserOp = { ...userOpToSign, signature };
164
+ // Step 6: POST /userop/submit
165
+ const serialized = serializeUserOp(signedUserOp);
166
+ if (eip7702Auth) {
167
+ serialized.eip7702Auth = eip7702Auth;
168
+ }
169
+ const submitReq = {
170
+ rpcUrl,
171
+ signedUserOp: serialized,
172
+ };
173
+ const submitRes = await fetch(`${allsetApiUrl}/userop/submit`, {
174
+ method: 'POST',
175
+ headers: { 'Content-Type': 'application/json' },
176
+ body: JSON.stringify(submitReq),
177
+ });
178
+ if (!submitRes.ok) {
179
+ const err = await submitRes.text();
180
+ throw new Error(`smartDeposit submit failed (${submitRes.status}): ${err}`);
181
+ }
182
+ const { txHash, userOpHash: returnedUserOpHash } = (await submitRes.json());
183
+ return {
184
+ txHash,
185
+ userOpHash: returnedUserOpHash,
186
+ userAddress: eoa.address,
187
+ tokenBalance,
188
+ };
189
+ }
@@ -1,4 +1,5 @@
1
1
  export * from '../core/index.js';
2
+ export * from './eip7702.js';
2
3
  export { AllSetProvider, getAllSetDir, getEvmKeysDir as getAllSetEvmKeysDir, ensureAllSetDirs, initUserConfig, } from './provider.js';
3
4
  export { createEvmExecutor, createEvmWallet, getEvmKeysDir, } from './evm-executor.js';
4
5
  export { loadNetworksConfig, getNetworkConfig, getChainConfig, getTokenConfig, clearConfigCache, } from './config.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AAEjC,OAAO,EACL,cAAc,EACd,YAAY,EACZ,aAAa,IAAI,mBAAmB,EACpC,gBAAgB,EAChB,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9F,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,wBAAsB,OAAO,CAC3B,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC,GACxD,UAAU,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC,CAGlD;AAED,wBAAsB,aAAa,CACjC,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,GAC9D,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,CAGxD;AAED,wBAAsB,aAAa,CACjC,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,GAC9D,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,CAGxD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,cAAc,cAAc,CAAC;AAE7B,OAAO,EACL,cAAc,EACd,YAAY,EACZ,aAAa,IAAI,mBAAmB,EACpC,gBAAgB,EAChB,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,aAAa,GACd,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,YAAY,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9F,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAE3D,wBAAsB,OAAO,CAC3B,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC,GACxD,UAAU,CAAC,cAAc,aAAa,EAAE,OAAO,CAAC,CAGlD;AAED,wBAAsB,aAAa,CACjC,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,GAC9D,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,CAGxD;AAED,wBAAsB,aAAa,CACjC,GAAG,IAAI,EAAE,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,GAC9D,UAAU,CAAC,cAAc,aAAa,EAAE,aAAa,CAAC,CAGxD"}
@@ -1,4 +1,5 @@
1
1
  export * from '../core/index.js';
2
+ export * from './eip7702.js';
2
3
  export { AllSetProvider, getAllSetDir, getEvmKeysDir as getAllSetEvmKeysDir, ensureAllSetDirs, initUserConfig, } from './provider.js';
3
4
  export { createEvmExecutor, createEvmWallet, getEvmKeysDir, } from './evm-executor.js';
4
5
  export { loadNetworksConfig, getNetworkConfig, getChainConfig, getTokenConfig, clearConfigCache, } from './config.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fastxyz/allset-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "AllSet SDK for AllSet bridge flows between Fast and EVM testnets",
5
5
  "keywords": [
6
6
  "allset",
@@ -76,6 +76,7 @@
76
76
  "devDependencies": {
77
77
  "@fastxyz/sdk": "^0.2.5",
78
78
  "@types/node": "^22.19.11",
79
+ "permissionless": "^0.3",
79
80
  "tsx": "^4.21.0",
80
81
  "typescript": "^5.9.3"
81
82
  }