@veil-cash/sdk 0.4.0 → 0.6.0

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/index.cjs CHANGED
@@ -375,9 +375,11 @@ var ADDRESSES = {
375
375
  usdcPool: "0x5c50d58E49C59d112680c187De2Bf989d2a91242",
376
376
  usdcQueue: "0x5530241b24504bF05C9a22e95A1F5458888e6a9B",
377
377
  usdcToken: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
378
+ forwarderFactory: "0x2848Fd62293A1ff3b4a897E9FcD0e5962dcc8101",
378
379
  chainId: 8453,
379
380
  relayUrl: "https://veil-relay.up.railway.app"
380
381
  };
382
+ var FORWARDER_CONTRACT_VERSION = "1";
381
383
  var POOL_CONFIG = {
382
384
  eth: {
383
385
  decimals: 18,
@@ -417,6 +419,9 @@ function getQueueAddress(pool) {
417
419
  throw new Error(`Unknown pool: ${pool}`);
418
420
  }
419
421
  }
422
+ function getForwarderFactoryAddress() {
423
+ return getAddresses().forwarderFactory;
424
+ }
420
425
  function getRelayUrl() {
421
426
  return ADDRESSES.relayUrl;
422
427
  }
@@ -1037,6 +1042,170 @@ var ERC20_ABI = [
1037
1042
  type: "function"
1038
1043
  }
1039
1044
  ];
1045
+ var FORWARDER_FACTORY_ABI = [
1046
+ {
1047
+ inputs: [],
1048
+ name: "CONTRACT_VERSION",
1049
+ outputs: [{ internalType: "string", name: "", type: "string" }],
1050
+ stateMutability: "view",
1051
+ type: "function"
1052
+ },
1053
+ {
1054
+ inputs: [
1055
+ { internalType: "bytes32", name: "_salt", type: "bytes32" },
1056
+ { internalType: "bytes", name: "_childDepositKey", type: "bytes" },
1057
+ { internalType: "address", name: "_owner", type: "address" }
1058
+ ],
1059
+ name: "computeAddress",
1060
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1061
+ stateMutability: "view",
1062
+ type: "function"
1063
+ },
1064
+ {
1065
+ inputs: [
1066
+ { internalType: "bytes32", name: "_salt", type: "bytes32" },
1067
+ { internalType: "bytes", name: "_childDepositKey", type: "bytes" },
1068
+ { internalType: "address", name: "_owner", type: "address" }
1069
+ ],
1070
+ name: "deploy",
1071
+ outputs: [{ internalType: "address", name: "forwarder", type: "address" }],
1072
+ stateMutability: "nonpayable",
1073
+ type: "function"
1074
+ },
1075
+ {
1076
+ inputs: [],
1077
+ name: "relayer",
1078
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1079
+ stateMutability: "view",
1080
+ type: "function"
1081
+ },
1082
+ {
1083
+ inputs: [],
1084
+ name: "veilEntry",
1085
+ outputs: [{ internalType: "address payable", name: "", type: "address" }],
1086
+ stateMutability: "view",
1087
+ type: "function"
1088
+ },
1089
+ {
1090
+ inputs: [],
1091
+ name: "usdc",
1092
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1093
+ stateMutability: "view",
1094
+ type: "function"
1095
+ },
1096
+ {
1097
+ inputs: [],
1098
+ name: "owner",
1099
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1100
+ stateMutability: "view",
1101
+ type: "function"
1102
+ }
1103
+ ];
1104
+ var FORWARDER_ABI = [
1105
+ {
1106
+ inputs: [],
1107
+ name: "CONTRACT_VERSION",
1108
+ outputs: [{ internalType: "string", name: "", type: "string" }],
1109
+ stateMutability: "view",
1110
+ type: "function"
1111
+ },
1112
+ {
1113
+ inputs: [
1114
+ { internalType: "address", name: "_token", type: "address" },
1115
+ { internalType: "address", name: "_to", type: "address" },
1116
+ { internalType: "uint256", name: "_amount", type: "uint256" },
1117
+ { internalType: "uint256", name: "_nonce", type: "uint256" },
1118
+ { internalType: "uint256", name: "_deadline", type: "uint256" },
1119
+ { internalType: "bytes", name: "_signature", type: "bytes" }
1120
+ ],
1121
+ name: "withdraw",
1122
+ outputs: [],
1123
+ stateMutability: "nonpayable",
1124
+ type: "function"
1125
+ },
1126
+ {
1127
+ inputs: [{ internalType: "uint256", name: "", type: "uint256" }],
1128
+ name: "usedNonces",
1129
+ outputs: [{ internalType: "bool", name: "", type: "bool" }],
1130
+ stateMutability: "view",
1131
+ type: "function"
1132
+ },
1133
+ {
1134
+ inputs: [],
1135
+ name: "owner",
1136
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1137
+ stateMutability: "view",
1138
+ type: "function"
1139
+ },
1140
+ {
1141
+ inputs: [],
1142
+ name: "factory",
1143
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1144
+ stateMutability: "view",
1145
+ type: "function"
1146
+ },
1147
+ {
1148
+ inputs: [],
1149
+ name: "childDepositKey",
1150
+ outputs: [{ internalType: "bytes", name: "", type: "bytes" }],
1151
+ stateMutability: "view",
1152
+ type: "function"
1153
+ },
1154
+ {
1155
+ inputs: [],
1156
+ name: "entry",
1157
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1158
+ stateMutability: "view",
1159
+ type: "function"
1160
+ },
1161
+ {
1162
+ inputs: [],
1163
+ name: "usdc",
1164
+ outputs: [{ internalType: "address", name: "", type: "address" }],
1165
+ stateMutability: "view",
1166
+ type: "function"
1167
+ },
1168
+ {
1169
+ inputs: [],
1170
+ name: "sweepETH",
1171
+ outputs: [],
1172
+ stateMutability: "nonpayable",
1173
+ type: "function"
1174
+ },
1175
+ {
1176
+ inputs: [],
1177
+ name: "sweepUSDC",
1178
+ outputs: [],
1179
+ stateMutability: "nonpayable",
1180
+ type: "function"
1181
+ },
1182
+ {
1183
+ inputs: [],
1184
+ name: "eip712Domain",
1185
+ outputs: [
1186
+ { internalType: "bytes1", name: "fields", type: "bytes1" },
1187
+ { internalType: "string", name: "name", type: "string" },
1188
+ { internalType: "string", name: "version", type: "string" },
1189
+ { internalType: "uint256", name: "chainId", type: "uint256" },
1190
+ { internalType: "address", name: "verifyingContract", type: "address" },
1191
+ { internalType: "bytes32", name: "salt", type: "bytes32" },
1192
+ { internalType: "uint256[]", name: "extensions", type: "uint256[]" }
1193
+ ],
1194
+ stateMutability: "view",
1195
+ type: "function"
1196
+ },
1197
+ { type: "error", name: "ZeroAddress", inputs: [] },
1198
+ { type: "error", name: "ZeroAmount", inputs: [] },
1199
+ { type: "error", name: "InvalidDepositKey", inputs: [] },
1200
+ { type: "error", name: "NotRelayer", inputs: [] },
1201
+ { type: "error", name: "NoETHBalance", inputs: [] },
1202
+ { type: "error", name: "NoTokenBalance", inputs: [] },
1203
+ { type: "error", name: "TokenApproveFailed", inputs: [] },
1204
+ { type: "error", name: "ETHTransferFailed", inputs: [] },
1205
+ { type: "error", name: "NonceUsed", inputs: [] },
1206
+ { type: "error", name: "Unauthorized", inputs: [] },
1207
+ { type: "error", name: "DeadlineExpired", inputs: [] }
1208
+ ];
1040
1209
 
1041
1210
  // src/deposit.ts
1042
1211
  function buildRegisterTx(depositKey, ownerAddress) {
@@ -1152,12 +1321,12 @@ async function getQueueBalance(options) {
1152
1321
  args: [nonce]
1153
1322
  });
1154
1323
  if (deposit.fallbackReceiver.toLowerCase() === address.toLowerCase()) {
1155
- totalQueueBalance += deposit.amountIn;
1324
+ totalQueueBalance += deposit.shieldAmount;
1156
1325
  pendingDeposits.push({
1157
1326
  nonce: nonce.toString(),
1158
1327
  status: DEPOSIT_STATUS_MAP[deposit.status] || "pending",
1159
- amount: viem.formatUnits(deposit.amountIn, poolConfig.decimals),
1160
- amountWei: deposit.amountIn.toString(),
1328
+ amount: viem.formatUnits(deposit.shieldAmount, poolConfig.decimals),
1329
+ amountWei: deposit.shieldAmount.toString(),
1161
1330
  timestamp: new Date(Number(deposit.timestamp) * 1e3).toISOString()
1162
1331
  });
1163
1332
  }
@@ -1335,7 +1504,10 @@ async function prove(input, circuitName) {
1335
1504
  const result = await snarkjs.groth16.fullProve(
1336
1505
  utils.stringifyBigInts(input),
1337
1506
  wasmPath,
1338
- zkeyPath
1507
+ zkeyPath,
1508
+ void 0,
1509
+ void 0,
1510
+ { singleThread: true }
1339
1511
  );
1340
1512
  const proof = result.proof;
1341
1513
  return "0x" + toFixedHex(proof.pi_a[0]).slice(2) + toFixedHex(proof.pi_a[1]).slice(2) + toFixedHex(proof.pi_b[0][1]).slice(2) + toFixedHex(proof.pi_b[0][0]).slice(2) + toFixedHex(proof.pi_b[1][1]).slice(2) + toFixedHex(proof.pi_b[1][0]).slice(2) + toFixedHex(proof.pi_c[0]).slice(2) + toFixedHex(proof.pi_c[1]).slice(2);
@@ -1488,6 +1660,27 @@ var RelayError = class extends Error {
1488
1660
  this.network = network;
1489
1661
  }
1490
1662
  };
1663
+ async function postRelayJson(endpoint, body, relayUrl) {
1664
+ const url = relayUrl || getRelayUrl();
1665
+ const response = await fetch(`${url}${endpoint}`, {
1666
+ method: "POST",
1667
+ headers: {
1668
+ "Content-Type": "application/json"
1669
+ },
1670
+ body: JSON.stringify(body)
1671
+ });
1672
+ const data = await response.json();
1673
+ if (!response.ok) {
1674
+ const errorData = data;
1675
+ throw new RelayError(
1676
+ errorData.error || errorData.message || "Relay request failed",
1677
+ response.status,
1678
+ errorData.retryAfter,
1679
+ errorData.network
1680
+ );
1681
+ }
1682
+ return data;
1683
+ }
1491
1684
  async function submitRelay(options) {
1492
1685
  const {
1493
1686
  type,
@@ -1507,30 +1700,17 @@ async function submitRelay(options) {
1507
1700
  throw new RelayError("Missing proofArgs or extData", 400);
1508
1701
  }
1509
1702
  const relayUrl = customRelayUrl || getRelayUrl();
1510
- const endpoint = `${relayUrl}/relay/${pool}`;
1511
- const response = await fetch(endpoint, {
1512
- method: "POST",
1513
- headers: {
1514
- "Content-Type": "application/json"
1515
- },
1516
- body: JSON.stringify({
1703
+ const endpoint = `/relay/${pool}`;
1704
+ return postRelayJson(
1705
+ endpoint,
1706
+ {
1517
1707
  type,
1518
1708
  proofArgs,
1519
1709
  extData,
1520
1710
  metadata
1521
- })
1522
- });
1523
- const data = await response.json();
1524
- if (!response.ok) {
1525
- const errorData = data;
1526
- throw new RelayError(
1527
- errorData.error || errorData.message || "Relay request failed",
1528
- response.status,
1529
- errorData.retryAfter,
1530
- errorData.network
1531
- );
1532
- }
1533
- return data;
1711
+ },
1712
+ relayUrl
1713
+ );
1534
1714
  }
1535
1715
  async function checkRelayHealth(relayUrl) {
1536
1716
  const url = relayUrl || getRelayUrl();
@@ -1985,13 +2165,354 @@ async function mergeUtxos(options) {
1985
2165
  recipient: "self"
1986
2166
  };
1987
2167
  }
2168
+ var SUBACCOUNT_CHILD_DOMAIN = "veil-sua-child";
2169
+ var SUBACCOUNT_SALT_DOMAIN = "veil-sua-salt";
2170
+ var ETH_ADDRESS = "0x0000000000000000000000000000000000000000";
2171
+ var DEFAULT_WITHDRAW_DEADLINE_SECONDS = 3600n;
2172
+ var DEFAULT_MAX_NONCE_SCAN = 100n;
2173
+ var MAX_SUBACCOUNT_SLOTS = 3;
2174
+ function createBaseClient(rpcUrl) {
2175
+ return viem.createPublicClient({
2176
+ chain: chains.base,
2177
+ transport: viem.http(rpcUrl)
2178
+ });
2179
+ }
2180
+ function assertPrivateKey(value, label) {
2181
+ if (!/^0x[a-fA-F0-9]{64}$/.test(value)) {
2182
+ throw new Error(`${label} must be a 0x-prefixed 32-byte hex string`);
2183
+ }
2184
+ }
2185
+ function normalizeSlot(slot) {
2186
+ if (!Number.isInteger(slot) || slot < 0) {
2187
+ throw new Error("slot must be a non-negative integer");
2188
+ }
2189
+ if (slot >= MAX_SUBACCOUNT_SLOTS) {
2190
+ throw new Error(`slot must be less than ${MAX_SUBACCOUNT_SLOTS} (supported slots: 0-${MAX_SUBACCOUNT_SLOTS - 1})`);
2191
+ }
2192
+ return slot;
2193
+ }
2194
+ function normalizeAsset(asset) {
2195
+ if (asset !== "eth" && asset !== "usdc") {
2196
+ throw new Error('asset must be "eth" or "usdc"');
2197
+ }
2198
+ return asset;
2199
+ }
2200
+ function normalizeNonce(value) {
2201
+ const nonce = typeof value === "bigint" ? value : BigInt(value);
2202
+ if (nonce < 0n) {
2203
+ throw new Error("nonce must be non-negative");
2204
+ }
2205
+ return nonce;
2206
+ }
2207
+ function normalizeDeadline(deadline) {
2208
+ const nextDeadline = deadline === void 0 ? BigInt(Math.floor(Date.now() / 1e3)) + DEFAULT_WITHDRAW_DEADLINE_SECONDS : typeof deadline === "bigint" ? deadline : BigInt(deadline);
2209
+ if (nextDeadline <= 0n) {
2210
+ throw new Error("deadline must be greater than 0");
2211
+ }
2212
+ return nextDeadline;
2213
+ }
2214
+ function deriveSubaccountChildPrivateKey(rootPrivateKey, slot) {
2215
+ assertPrivateKey(rootPrivateKey, "rootPrivateKey");
2216
+ const normalizedSlot = normalizeSlot(slot);
2217
+ return viem.keccak256(
2218
+ viem.encodePacked(
2219
+ ["bytes32", "string", "uint256"],
2220
+ [rootPrivateKey, SUBACCOUNT_CHILD_DOMAIN, BigInt(normalizedSlot)]
2221
+ )
2222
+ );
2223
+ }
2224
+ function deriveSubaccountSalt(rootPrivateKey, slot) {
2225
+ assertPrivateKey(rootPrivateKey, "rootPrivateKey");
2226
+ const normalizedSlot = normalizeSlot(slot);
2227
+ return viem.keccak256(
2228
+ viem.encodePacked(
2229
+ ["bytes32", "string", "uint256"],
2230
+ [rootPrivateKey, SUBACCOUNT_SALT_DOMAIN, BigInt(normalizedSlot)]
2231
+ )
2232
+ );
2233
+ }
2234
+ function deriveSubaccountChildOwner(childPrivateKey) {
2235
+ assertPrivateKey(childPrivateKey, "childPrivateKey");
2236
+ return accounts.privateKeyToAddress(childPrivateKey);
2237
+ }
2238
+ function deriveSubaccountChildDepositKey(childPrivateKey) {
2239
+ assertPrivateKey(childPrivateKey, "childPrivateKey");
2240
+ return new Keypair(childPrivateKey).depositKey();
2241
+ }
2242
+ async function predictSubaccountForwarder(options) {
2243
+ const publicClient = createBaseClient(options.rpcUrl);
2244
+ return publicClient.readContract({
2245
+ abi: FORWARDER_FACTORY_ABI,
2246
+ address: getForwarderFactoryAddress(),
2247
+ functionName: "computeAddress",
2248
+ args: [options.salt, options.childDepositKey, options.childOwner]
2249
+ });
2250
+ }
2251
+ async function deriveSubaccountSlot(options) {
2252
+ const normalizedSlot = normalizeSlot(options.slot);
2253
+ const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, normalizedSlot);
2254
+ const salt = deriveSubaccountSalt(options.rootPrivateKey, normalizedSlot);
2255
+ const childOwner = deriveSubaccountChildOwner(childPrivateKey);
2256
+ const childDepositKey = deriveSubaccountChildDepositKey(childPrivateKey);
2257
+ const forwarderAddress = await predictSubaccountForwarder({
2258
+ salt,
2259
+ childDepositKey,
2260
+ childOwner,
2261
+ rpcUrl: options.rpcUrl
2262
+ });
2263
+ return {
2264
+ slot: normalizedSlot,
2265
+ childOwner,
2266
+ childDepositKey,
2267
+ salt,
2268
+ forwarderAddress
2269
+ };
2270
+ }
2271
+ async function isSubaccountForwarderDeployed(options) {
2272
+ if (!viem.isAddress(options.forwarderAddress)) {
2273
+ throw new Error("forwarderAddress must be a valid Ethereum address");
2274
+ }
2275
+ const publicClient = createBaseClient(options.rpcUrl);
2276
+ const code = await publicClient.getCode({ address: options.forwarderAddress });
2277
+ return !!code && code !== "0x";
2278
+ }
2279
+ async function deploySubaccountForwarder(options) {
2280
+ const slot = await deriveSubaccountSlot({
2281
+ rootPrivateKey: options.rootPrivateKey,
2282
+ slot: options.slot,
2283
+ rpcUrl: options.rpcUrl
2284
+ });
2285
+ return postRelayJson(
2286
+ "/stealth/deploy",
2287
+ {
2288
+ salt: slot.salt,
2289
+ childDepositKey: slot.childDepositKey,
2290
+ childOwner: slot.childOwner,
2291
+ expectedForwarder: slot.forwarderAddress
2292
+ },
2293
+ options.relayUrl
2294
+ );
2295
+ }
2296
+ async function sweepSubaccountForwarder(options) {
2297
+ const asset = normalizeAsset(options.asset);
2298
+ if (!viem.isAddress(options.forwarderAddress)) {
2299
+ throw new Error("forwarderAddress must be a valid Ethereum address");
2300
+ }
2301
+ return postRelayJson(
2302
+ "/stealth/sweep",
2303
+ {
2304
+ forwarder: options.forwarderAddress,
2305
+ asset
2306
+ },
2307
+ options.relayUrl
2308
+ );
2309
+ }
2310
+ function toQueueStatus(asset, result) {
2311
+ return {
2312
+ asset,
2313
+ queueBalance: result.queueBalance,
2314
+ queueBalanceWei: result.queueBalanceWei,
2315
+ pendingCount: result.pendingCount,
2316
+ pendingDeposits: result.pendingDeposits
2317
+ };
2318
+ }
2319
+ async function getSubaccountStatus(options) {
2320
+ const slot = await deriveSubaccountSlot(options);
2321
+ const publicClient = createBaseClient(options.rpcUrl);
2322
+ const addresses = getAddresses();
2323
+ const [deployed, ethWei, usdcWei, ethQueue, usdcQueue] = await Promise.all([
2324
+ isSubaccountForwarderDeployed({
2325
+ forwarderAddress: slot.forwarderAddress,
2326
+ rpcUrl: options.rpcUrl
2327
+ }),
2328
+ publicClient.getBalance({ address: slot.forwarderAddress }),
2329
+ publicClient.readContract({
2330
+ address: addresses.usdcToken,
2331
+ abi: ERC20_ABI,
2332
+ functionName: "balanceOf",
2333
+ args: [slot.forwarderAddress]
2334
+ }),
2335
+ getQueueBalance({
2336
+ address: slot.forwarderAddress,
2337
+ pool: "eth",
2338
+ rpcUrl: options.rpcUrl
2339
+ }),
2340
+ getQueueBalance({
2341
+ address: slot.forwarderAddress,
2342
+ pool: "usdc",
2343
+ rpcUrl: options.rpcUrl
2344
+ })
2345
+ ]);
2346
+ return {
2347
+ slot,
2348
+ deployed,
2349
+ balances: {
2350
+ eth: {
2351
+ balance: viem.formatEther(ethWei),
2352
+ balanceWei: ethWei.toString()
2353
+ },
2354
+ usdc: {
2355
+ balance: viem.formatUnits(usdcWei, 6),
2356
+ balanceWei: usdcWei.toString()
2357
+ }
2358
+ },
2359
+ queues: {
2360
+ eth: toQueueStatus("eth", ethQueue),
2361
+ usdc: toQueueStatus("usdc", usdcQueue)
2362
+ }
2363
+ };
2364
+ }
2365
+ var WITHDRAW_TYPES = {
2366
+ Withdraw: [
2367
+ { name: "token", type: "address" },
2368
+ { name: "to", type: "address" },
2369
+ { name: "amount", type: "uint256" },
2370
+ { name: "nonce", type: "uint256" },
2371
+ { name: "deadline", type: "uint256" }
2372
+ ]
2373
+ };
2374
+ function buildSubaccountWithdrawTypedData(options) {
2375
+ if (!viem.isAddress(options.forwarderAddress)) {
2376
+ throw new Error("forwarderAddress must be a valid Ethereum address");
2377
+ }
2378
+ if (!viem.isAddress(options.token)) {
2379
+ throw new Error("token must be a valid Ethereum address");
2380
+ }
2381
+ if (!viem.isAddress(options.to)) {
2382
+ throw new Error("to must be a valid Ethereum address");
2383
+ }
2384
+ return {
2385
+ domain: {
2386
+ name: "VeilForwarder",
2387
+ version: FORWARDER_CONTRACT_VERSION,
2388
+ chainId: getAddresses().chainId,
2389
+ verifyingContract: options.forwarderAddress
2390
+ },
2391
+ types: WITHDRAW_TYPES,
2392
+ primaryType: "Withdraw",
2393
+ message: {
2394
+ token: options.token,
2395
+ to: options.to,
2396
+ amount: options.amount,
2397
+ nonce: options.nonce,
2398
+ deadline: options.deadline
2399
+ }
2400
+ };
2401
+ }
2402
+ async function signSubaccountWithdraw(options) {
2403
+ assertPrivateKey(options.childPrivateKey, "childPrivateKey");
2404
+ const account = accounts.privateKeyToAccount(options.childPrivateKey);
2405
+ return account.signTypedData({
2406
+ domain: options.typedData.domain,
2407
+ types: options.typedData.types,
2408
+ primaryType: options.typedData.primaryType,
2409
+ message: options.typedData.message
2410
+ });
2411
+ }
2412
+ async function isSubaccountWithdrawNonceUsed(options) {
2413
+ if (!viem.isAddress(options.forwarderAddress)) {
2414
+ throw new Error("forwarderAddress must be a valid Ethereum address");
2415
+ }
2416
+ const publicClient = createBaseClient(options.rpcUrl);
2417
+ try {
2418
+ return await publicClient.readContract({
2419
+ abi: FORWARDER_ABI,
2420
+ address: options.forwarderAddress,
2421
+ functionName: "usedNonces",
2422
+ args: [normalizeNonce(options.nonce)]
2423
+ });
2424
+ } catch (error) {
2425
+ if (String(error).includes("returned no data")) {
2426
+ throw new Error("Subaccount forwarder is not deployed");
2427
+ }
2428
+ throw error;
2429
+ }
2430
+ }
2431
+ async function findNextSubaccountWithdrawNonce(options) {
2432
+ const startNonce = normalizeNonce(options.startNonce ?? 0n);
2433
+ const maxScan = normalizeNonce(options.maxScan ?? DEFAULT_MAX_NONCE_SCAN);
2434
+ const limit = startNonce + maxScan;
2435
+ let nonce = startNonce;
2436
+ while (await isSubaccountWithdrawNonceUsed({
2437
+ forwarderAddress: options.forwarderAddress,
2438
+ nonce,
2439
+ rpcUrl: options.rpcUrl
2440
+ })) {
2441
+ nonce += 1n;
2442
+ if (nonce > limit) {
2443
+ throw new Error("Unable to find an unused withdraw nonce within the scan limit");
2444
+ }
2445
+ }
2446
+ return nonce;
2447
+ }
2448
+ async function buildSubaccountRecoveryTx(options) {
2449
+ if (!viem.isAddress(options.to)) {
2450
+ throw new Error("to must be a valid Ethereum address");
2451
+ }
2452
+ const asset = normalizeAsset(options.asset);
2453
+ const slot = await deriveSubaccountSlot({
2454
+ rootPrivateKey: options.rootPrivateKey,
2455
+ slot: options.slot,
2456
+ rpcUrl: options.rpcUrl
2457
+ });
2458
+ const deployed = await isSubaccountForwarderDeployed({
2459
+ forwarderAddress: slot.forwarderAddress,
2460
+ rpcUrl: options.rpcUrl
2461
+ });
2462
+ if (!deployed) {
2463
+ throw new Error("Subaccount forwarder is not deployed");
2464
+ }
2465
+ const childPrivateKey = deriveSubaccountChildPrivateKey(options.rootPrivateKey, options.slot);
2466
+ const tokenAddress = asset === "eth" ? ETH_ADDRESS : getAddresses().usdcToken;
2467
+ const amountWei = asset === "eth" ? viem.parseEther(options.amount) : viem.parseUnits(options.amount, 6);
2468
+ const nonce = options.nonce === void 0 ? await findNextSubaccountWithdrawNonce({
2469
+ forwarderAddress: slot.forwarderAddress,
2470
+ rpcUrl: options.rpcUrl
2471
+ }) : normalizeNonce(options.nonce);
2472
+ const deadline = normalizeDeadline(options.deadline);
2473
+ const typedData = buildSubaccountWithdrawTypedData({
2474
+ forwarderAddress: slot.forwarderAddress,
2475
+ token: tokenAddress,
2476
+ to: options.to,
2477
+ amount: amountWei,
2478
+ nonce,
2479
+ deadline
2480
+ });
2481
+ const signature = await signSubaccountWithdraw({
2482
+ childPrivateKey,
2483
+ typedData
2484
+ });
2485
+ return {
2486
+ transaction: {
2487
+ to: slot.forwarderAddress,
2488
+ data: viem.encodeFunctionData({
2489
+ abi: FORWARDER_ABI,
2490
+ functionName: "withdraw",
2491
+ args: [tokenAddress, options.to, amountWei, nonce, deadline, signature]
2492
+ })
2493
+ },
2494
+ forwarderAddress: slot.forwarderAddress,
2495
+ asset,
2496
+ amount: options.amount,
2497
+ amountWei: amountWei.toString(),
2498
+ nonce: nonce.toString(),
2499
+ deadline: deadline.toString(),
2500
+ recipient: options.to,
2501
+ tokenAddress,
2502
+ signature
2503
+ };
2504
+ }
1988
2505
 
1989
2506
  exports.ADDRESSES = ADDRESSES;
1990
2507
  exports.CIRCUIT_CONFIG = CIRCUIT_CONFIG;
1991
2508
  exports.ENTRY_ABI = ENTRY_ABI;
1992
2509
  exports.ERC20_ABI = ERC20_ABI;
1993
2510
  exports.FIELD_SIZE = FIELD_SIZE;
2511
+ exports.FORWARDER_ABI = FORWARDER_ABI;
2512
+ exports.FORWARDER_CONTRACT_VERSION = FORWARDER_CONTRACT_VERSION;
2513
+ exports.FORWARDER_FACTORY_ABI = FORWARDER_FACTORY_ABI;
1994
2514
  exports.Keypair = Keypair;
2515
+ exports.MAX_SUBACCOUNT_SLOTS = MAX_SUBACCOUNT_SLOTS;
1995
2516
  exports.MERKLE_TREE_HEIGHT = MERKLE_TREE_HEIGHT;
1996
2517
  exports.POOL_ABI = POOL_ABI;
1997
2518
  exports.POOL_CONFIG = POOL_CONFIG;
@@ -2006,12 +2527,22 @@ exports.buildDepositTx = buildDepositTx;
2006
2527
  exports.buildDepositUSDCTx = buildDepositUSDCTx;
2007
2528
  exports.buildMerkleTree = buildMerkleTree;
2008
2529
  exports.buildRegisterTx = buildRegisterTx;
2530
+ exports.buildSubaccountRecoveryTx = buildSubaccountRecoveryTx;
2531
+ exports.buildSubaccountWithdrawTypedData = buildSubaccountWithdrawTypedData;
2009
2532
  exports.buildTransferProof = buildTransferProof;
2010
2533
  exports.buildWithdrawProof = buildWithdrawProof;
2011
2534
  exports.checkRecipientRegistration = checkRecipientRegistration;
2012
2535
  exports.checkRelayHealth = checkRelayHealth;
2536
+ exports.deploySubaccountForwarder = deploySubaccountForwarder;
2537
+ exports.deriveSubaccountChildDepositKey = deriveSubaccountChildDepositKey;
2538
+ exports.deriveSubaccountChildOwner = deriveSubaccountChildOwner;
2539
+ exports.deriveSubaccountChildPrivateKey = deriveSubaccountChildPrivateKey;
2540
+ exports.deriveSubaccountSalt = deriveSubaccountSalt;
2541
+ exports.deriveSubaccountSlot = deriveSubaccountSlot;
2542
+ exports.findNextSubaccountWithdrawNonce = findNextSubaccountWithdrawNonce;
2013
2543
  exports.getAddresses = getAddresses;
2014
2544
  exports.getExtDataHash = getExtDataHash;
2545
+ exports.getForwarderFactoryAddress = getForwarderFactoryAddress;
2015
2546
  exports.getMerklePath = getMerklePath;
2016
2547
  exports.getPoolAddress = getPoolAddress;
2017
2548
  exports.getPrivateBalance = getPrivateBalance;
@@ -2019,17 +2550,23 @@ exports.getQueueAddress = getQueueAddress;
2019
2550
  exports.getQueueBalance = getQueueBalance;
2020
2551
  exports.getRelayInfo = getRelayInfo;
2021
2552
  exports.getRelayUrl = getRelayUrl;
2553
+ exports.getSubaccountStatus = getSubaccountStatus;
2554
+ exports.isSubaccountForwarderDeployed = isSubaccountForwarderDeployed;
2555
+ exports.isSubaccountWithdrawNonceUsed = isSubaccountWithdrawNonceUsed;
2022
2556
  exports.mergeUtxos = mergeUtxos;
2023
2557
  exports.packEncryptedMessage = packEncryptedMessage;
2024
2558
  exports.poseidonHash = poseidonHash;
2025
2559
  exports.poseidonHash2 = poseidonHash2;
2560
+ exports.predictSubaccountForwarder = predictSubaccountForwarder;
2026
2561
  exports.prepareTransaction = prepareTransaction;
2027
2562
  exports.prove = prove;
2028
2563
  exports.randomBN = randomBN;
2029
2564
  exports.selectCircuit = selectCircuit;
2030
2565
  exports.selectUtxosForWithdraw = selectUtxosForWithdraw;
2031
2566
  exports.shuffle = shuffle;
2567
+ exports.signSubaccountWithdraw = signSubaccountWithdraw;
2032
2568
  exports.submitRelay = submitRelay;
2569
+ exports.sweepSubaccountForwarder = sweepSubaccountForwarder;
2033
2570
  exports.toBuffer = toBuffer;
2034
2571
  exports.toFixedHex = toFixedHex;
2035
2572
  exports.transfer = transfer;