@trustwallet/cli 0.14.0 → 0.15.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.js CHANGED
@@ -3123,6 +3123,8 @@ __export(dist_exports, {
3123
3123
  COMMON_TOKENS: () => COMMON_TOKENS,
3124
3124
  CommandError: () => CommandError,
3125
3125
  ERC20_ERROR_CODES: () => ERC20_ERROR_CODES,
3126
+ ERC20_TRANSFER_SIG: () => ERC20_TRANSFER_SIG,
3127
+ EVM_ADDRESS_RE: () => EVM_ADDRESS_RE,
3126
3128
  Erc20Error: () => Erc20Error,
3127
3129
  McpResponse: () => McpResponse,
3128
3130
  NATIVE_TOKEN_ADDRESS: () => NATIVE_TOKEN_ADDRESS,
@@ -3144,6 +3146,7 @@ __export(dist_exports, {
3144
3146
  WatchError: () => WatchError,
3145
3147
  X402Error: () => X402Error,
3146
3148
  X402_ERROR_CODES: () => X402_ERROR_CODES,
3149
+ assertEvmAddress: () => assertEvmAddress,
3147
3150
  assertStrictPositiveAmount: () => assertStrictPositiveAmount,
3148
3151
  chainKeyToCoinType: () => chainKeyToCoinType,
3149
3152
  classifyApiError: () => classifyApiError,
@@ -3151,6 +3154,7 @@ __export(dist_exports, {
3151
3154
  classifyWalletLoadError: () => classifyWalletLoadError,
3152
3155
  coinTypeToChainKey: () => coinTypeToChainKey,
3153
3156
  createTwakClient: () => createTwakClient,
3157
+ encodeErc20Transfer: () => encodeErc20Transfer,
3154
3158
  fetchBalance: () => fetchBalance,
3155
3159
  fetchBalances: () => fetchBalances,
3156
3160
  fiatToTokenAmount: () => fiatToTokenAmount,
@@ -3169,13 +3173,18 @@ __export(dist_exports, {
3169
3173
  getSupportedCoinTypes: () => getSupportedCoinTypes,
3170
3174
  isChainSupported: () => isChainSupported,
3171
3175
  isCoinTypeSupported: () => isCoinTypeSupported,
3176
+ isEvmAddress: () => isEvmAddress,
3172
3177
  isNativeTokenId: () => isNativeTokenId,
3173
3178
  isRateLimited: () => isRateLimited,
3179
+ lookupKnownTokenDecimals: () => lookupKnownTokenDecimals,
3174
3180
  parseAmount: () => parseAmount,
3175
3181
  parseHumanAmount: () => parseHumanAmount,
3176
3182
  parseTokenId: () => parseTokenId,
3177
3183
  rankQuotesByBest: () => rankQuotesByBest,
3184
+ requireChain: () => requireChain,
3178
3185
  resolveAddressOrName: () => resolveAddressOrName,
3186
+ resolveAsset: () => resolveAsset,
3187
+ resolveChain: () => resolveChain,
3179
3188
  resolveName: () => resolveName,
3180
3189
  resolveToken: () => resolveToken,
3181
3190
  signRequest: () => signRequest,
@@ -3234,20 +3243,30 @@ function getNativeAddressPlaceholder(chainKey) {
3234
3243
  if (chain2.id === "celo") return CELO_NATIVE_ADDRESS;
3235
3244
  return NATIVE_TOKEN_ADDRESS;
3236
3245
  }
3246
+ function lookupKnownTokenDecimals(chainKey, tokenAddress) {
3247
+ const chain2 = getChainById(chainKey);
3248
+ if (!chain2) return void 0;
3249
+ if (!tokenAddress) return chain2.decimals;
3250
+ const mapKey = getChainMapKey(chainKey) ?? chainKey.toLowerCase();
3251
+ const registry = COMMON_TOKENS[mapKey] ?? {};
3252
+ const entry = Object.values(registry).find(
3253
+ (e2) => e2.address.toLowerCase() === tokenAddress.toLowerCase()
3254
+ );
3255
+ return entry?.decimals;
3256
+ }
3237
3257
  function resolveToken(token, chainKey) {
3238
3258
  const assetIdMatch = token.match(/^c(\d+)(?:_t(.+))?$/);
3239
3259
  if (assetIdMatch) {
3240
3260
  const coinId = parseInt(assetIdMatch[1]);
3241
3261
  const contractAddress = assetIdMatch[2];
3242
- const chain2 = Object.values(CHAINS).find((c3) => c3.coinId === coinId);
3243
- if (chain2) {
3262
+ const found = Object.entries(CHAINS).find(([, c3]) => c3.coinId === coinId);
3263
+ if (found) {
3264
+ const [mapKey2, chain2] = found;
3244
3265
  if (contractAddress) {
3245
- const sym = Object.entries(COMMON_TOKENS[chain2.id] || {}).find(
3246
- ([, addr]) => addr.toLowerCase() === contractAddress.toLowerCase()
3247
- )?.[0]?.replace(".e", "");
3248
- const isBscChain = chain2.id === "smartchain";
3249
- const decimals = isBscChain && sym && BSC_18_DECIMAL_TOKENS.has(sym) ? 18 : TOKEN_DECIMALS[sym ?? ""] ?? 18;
3250
- return { address: contractAddress, decimals };
3266
+ const entry = Object.values(COMMON_TOKENS[mapKey2] || {}).find(
3267
+ (e2) => e2.address.toLowerCase() === contractAddress.toLowerCase()
3268
+ );
3269
+ return { address: contractAddress, decimals: entry?.decimals ?? 18 };
3251
3270
  }
3252
3271
  return {
3253
3272
  address: getNativeAddressPlaceholder(chain2.id),
@@ -3258,11 +3277,9 @@ function resolveToken(token, chainKey) {
3258
3277
  const upperToken = token.toUpperCase();
3259
3278
  const chainConfig = getChainById(chainKey);
3260
3279
  const mapKey = getChainMapKey(chainKey) ?? chainKey.toLowerCase();
3261
- const isBsc = mapKey === "bsc";
3262
3280
  const chainTokens = COMMON_TOKENS[mapKey];
3263
3281
  if (chainTokens?.[upperToken]) {
3264
- const decimals = isBsc && BSC_18_DECIMAL_TOKENS.has(upperToken) ? 18 : TOKEN_DECIMALS[upperToken] ?? 18;
3265
- return { address: chainTokens[upperToken], decimals };
3282
+ return { address: chainTokens[upperToken].address, decimals: chainTokens[upperToken].decimals };
3266
3283
  }
3267
3284
  if (NATIVE_SYMBOLS.has(upperToken) || chainConfig && upperToken === chainConfig.symbol.toUpperCase()) {
3268
3285
  return {
@@ -3271,23 +3288,20 @@ function resolveToken(token, chainKey) {
3271
3288
  };
3272
3289
  }
3273
3290
  if (upperToken.startsWith("USDC")) {
3274
- const addr = chainTokens?.["USDC.e"] || chainTokens?.["USDC"];
3275
- if (!addr) return null;
3276
- return { address: addr, decimals: isBsc ? 18 : 6 };
3291
+ const entry = chainTokens?.["USDC.e"] || chainTokens?.["USDC"];
3292
+ if (!entry) return null;
3293
+ return { address: entry.address, decimals: entry.decimals };
3277
3294
  }
3278
3295
  if (upperToken.startsWith("USDT")) {
3279
- const addr = chainTokens?.["USDT"];
3280
- if (!addr) return null;
3281
- return { address: addr, decimals: isBsc ? 18 : 6 };
3296
+ const entry = chainTokens?.["USDT"];
3297
+ if (!entry) return null;
3298
+ return { address: entry.address, decimals: entry.decimals };
3282
3299
  }
3283
3300
  if (token.startsWith("0x") || token.length > 20) {
3284
3301
  for (const tokens of Object.values(COMMON_TOKENS)) {
3285
- for (const [symbol, addr] of Object.entries(tokens)) {
3286
- if (addr.toLowerCase() === token.toLowerCase()) {
3287
- return {
3288
- address: token,
3289
- decimals: TOKEN_DECIMALS[symbol.replace(".e", "")] ?? 18
3290
- };
3302
+ for (const entry of Object.values(tokens)) {
3303
+ if (entry.address.toLowerCase() === token.toLowerCase()) {
3304
+ return { address: token, decimals: entry.decimals };
3291
3305
  }
3292
3306
  }
3293
3307
  }
@@ -3378,6 +3392,95 @@ function tryClassifyError(err) {
3378
3392
  }
3379
3393
  return classifyApiError(err) ?? classifyStorageError(err) ?? classifyWalletLoadError(err);
3380
3394
  }
3395
+ function formatTokenId(coinId, contractAddress) {
3396
+ if (contractAddress) {
3397
+ return `c${coinId}_t${contractAddress}`;
3398
+ }
3399
+ return `c${coinId}`;
3400
+ }
3401
+ function parseTokenId(id) {
3402
+ const match = id.match(/^c(\d+)(?:_t(.+))?$/);
3403
+ if (!match) {
3404
+ throw new Error(`Invalid token ID format: ${id}`);
3405
+ }
3406
+ const coinId = Number(match[1]);
3407
+ const contractAddress = match[2] || void 0;
3408
+ return { coinId, contractAddress };
3409
+ }
3410
+ function isNativeTokenId(id) {
3411
+ return /^c\d+$/.test(id);
3412
+ }
3413
+ function tokenIdToChain(id) {
3414
+ const { coinId } = parseTokenId(id);
3415
+ const chain2 = getChainByCoinId(coinId);
3416
+ if (!chain2) return void 0;
3417
+ return Object.entries(CHAINS).find(([, c3]) => c3.coinId === coinId)?.[0];
3418
+ }
3419
+ function resolveChain(input2) {
3420
+ if (typeof input2 !== "string" || input2.trim().length === 0) {
3421
+ throw new WalletError(
3422
+ "CHAIN_UNSUPPORTED",
3423
+ 'Chain identifier is required. Examples: "ethereum", "bsc", "solana", "56", "eip155:8453".'
3424
+ );
3425
+ }
3426
+ const lower = input2.trim().toLowerCase();
3427
+ const colonIdx = lower.indexOf(":");
3428
+ if (colonIdx > 0 && colonIdx < lower.length - 1) {
3429
+ const namespace = lower.slice(0, colonIdx);
3430
+ const reference = lower.slice(colonIdx + 1);
3431
+ for (const [key, chain2] of Object.entries(CHAINS)) {
3432
+ if (chain2.namespace === namespace && chain2.chainId.toLowerCase() === reference) {
3433
+ return key;
3434
+ }
3435
+ }
3436
+ throw chainUnsupported(input2);
3437
+ }
3438
+ if (lower.startsWith("c") && lower.length > 1) {
3439
+ const rest = lower.slice(1);
3440
+ const underscoreIdx = rest.indexOf("_");
3441
+ const coinStr = underscoreIdx === -1 ? rest : rest.slice(0, underscoreIdx);
3442
+ if (/^\d+$/.test(coinStr)) {
3443
+ const key = coinTypeToChainKey(Number(coinStr));
3444
+ if (key) return key;
3445
+ throw chainUnsupported(input2);
3446
+ }
3447
+ }
3448
+ if (CHAINS[lower]) return lower;
3449
+ const aliased = ALIASES[lower];
3450
+ if (aliased) return aliased;
3451
+ for (const [key, chain2] of Object.entries(CHAINS)) {
3452
+ if (chain2.id === lower) return key;
3453
+ }
3454
+ if (/^0x[0-9a-f]+$/.test(lower)) {
3455
+ const key = lookupEvm(parseInt(lower, 16).toString());
3456
+ if (key) return key;
3457
+ } else if (/^\d+$/.test(lower)) {
3458
+ const key = lookupEvm(lower);
3459
+ if (key) return key;
3460
+ }
3461
+ throw chainUnsupported(input2);
3462
+ }
3463
+ function lookupEvm(chainId) {
3464
+ for (const [key, chain2] of Object.entries(CHAINS)) {
3465
+ if (chain2.namespace === "eip155" && chain2.chainId === chainId) return key;
3466
+ }
3467
+ return void 0;
3468
+ }
3469
+ function resolveAsset(chain2, tokenAddress) {
3470
+ const chainKey = resolveChain(chain2);
3471
+ const coinId = CHAINS[chainKey].coinId;
3472
+ return { chainKey, coinId, assetId: formatTokenId(coinId, tokenAddress) };
3473
+ }
3474
+ function chainUnsupported(input2) {
3475
+ const suggestions = suggest(input2);
3476
+ const hint = suggestions.length > 0 ? ` Did you mean: ${suggestions.map((s2) => `"${s2}"`).join(", ")}?` : "";
3477
+ return new WalletError("CHAIN_UNSUPPORTED", `Unknown chain: "${input2}".${hint}`);
3478
+ }
3479
+ function suggest(input2) {
3480
+ const lower = input2.trim().toLowerCase();
3481
+ const candidates = [...Object.keys(CHAINS), ...Object.keys(ALIASES)];
3482
+ return candidates.filter((c3) => c3.includes(lower) || lower.includes(c3)).slice(0, 3);
3483
+ }
3381
3484
  function sortQueryParams(query) {
3382
3485
  if (!query) return query;
3383
3486
  const queryParams = new URLSearchParams(query);
@@ -3490,29 +3593,29 @@ async function resolveAddressOrName(value) {
3490
3593
  if (!resolved) throw new Error(`Could not resolve name: ${value}`);
3491
3594
  return resolved;
3492
3595
  }
3493
- function formatTokenId(coinId, contractAddress) {
3494
- if (contractAddress) {
3495
- return `c${coinId}_t${contractAddress}`;
3496
- }
3497
- return `c${coinId}`;
3596
+ function isEvmAddress(value) {
3597
+ return EVM_ADDRESS_RE.test(value);
3498
3598
  }
3499
- function parseTokenId(id) {
3500
- const match = id.match(/^c(\d+)(?:_t(.+))?$/);
3501
- if (!match) {
3502
- throw new Error(`Invalid token ID format: ${id}`);
3599
+ function assertEvmAddress(value, label) {
3600
+ if (!EVM_ADDRESS_RE.test(value)) {
3601
+ throw new WalletError(
3602
+ "VALIDATION_ERROR",
3603
+ `${label} is not a valid EVM address: ${value}. Expected 0x-prefixed 40-hex.`
3604
+ );
3503
3605
  }
3504
- const coinId = Number(match[1]);
3505
- const contractAddress = match[2] || void 0;
3506
- return { coinId, contractAddress };
3507
3606
  }
3508
- function isNativeTokenId(id) {
3509
- return /^c\d+$/.test(id);
3607
+ function encodeErc20Transfer(to3, amount) {
3608
+ assertEvmAddress(to3, "to");
3609
+ const paddedTo = to3.slice(2).toLowerCase().padStart(64, "0");
3610
+ const paddedAmount = amount.toString(16).padStart(64, "0");
3611
+ return `${ERC20_TRANSFER_SIG}${paddedTo}${paddedAmount}`;
3510
3612
  }
3511
- function tokenIdToChain(id) {
3512
- const { coinId } = parseTokenId(id);
3513
- const chain2 = getChainByCoinId(coinId);
3514
- if (!chain2) return void 0;
3515
- return Object.entries(CHAINS).find(([, c3]) => c3.coinId === coinId)?.[0];
3613
+ function requireChain(chainKey) {
3614
+ const chain2 = getChainById(chainKey);
3615
+ if (!chain2) {
3616
+ throw new TwakValidationError(`Unknown chain: ${chainKey}`, { chainKey });
3617
+ }
3618
+ return chain2;
3516
3619
  }
3517
3620
  function sortQueryRaw(query) {
3518
3621
  if (!query) return query;
@@ -3614,7 +3717,7 @@ async function fetchBalance(httpClient, assetId, address, currency = "USD") {
3614
3717
  throw new BalanceError("NETWORK_ERROR", "Could not fetch balance. Please try again later.");
3615
3718
  }
3616
3719
  }
3617
- var import_crypto, NATIVE_TOKEN_ADDRESS, SOLANA_NATIVE_ADDRESS, CHAINS, COMMON_TOKENS, BSC_18_DECIMAL_TOKENS, NATIVE_SYMBOLS, TOKEN_DECIMALS, CELO_NATIVE_ADDRESS, TwakError, TwakApiError, TwakNetworkError, TwakValidationError, CommandError, COMMON_ERROR_CODES, FS_ERRNO_CODES, SWAP_ERROR_CODES, SwapError, TRANSFER_ERROR_CODES, TransferError, WALLET_ERROR_CODES, WalletError, QUERY_ERROR_CODES, QueryError, BALANCE_ERROR_CODES, BalanceError, ALERT_ERROR_CODES, AlertError, ERC20_ERROR_CODES, Erc20Error, WATCH_ERROR_CODES, WatchError, AUTOMATION_ERROR_CODES, AutomationError, X402_ERROR_CODES, X402Error, DECIMAL_AMOUNT_RE, UNSAFE_KEYS, NAMING_API, DEFAULT_GATEWAY_URL, DEFAULT_MARKET_URL, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, DEFAULT_X_ORIGIN, TwakHttpClient, ALLOWED_OFFRAMP_PROVIDERS, McpResponse;
3720
+ var import_crypto, NATIVE_TOKEN_ADDRESS, SOLANA_NATIVE_ADDRESS, CHAINS, COMMON_TOKENS, NATIVE_SYMBOLS, CELO_NATIVE_ADDRESS, TwakError, TwakApiError, TwakNetworkError, TwakValidationError, CommandError, COMMON_ERROR_CODES, FS_ERRNO_CODES, SWAP_ERROR_CODES, SwapError, TRANSFER_ERROR_CODES, TransferError, WALLET_ERROR_CODES, WalletError, QUERY_ERROR_CODES, QueryError, BALANCE_ERROR_CODES, BalanceError, ALERT_ERROR_CODES, AlertError, ERC20_ERROR_CODES, Erc20Error, WATCH_ERROR_CODES, WatchError, AUTOMATION_ERROR_CODES, AutomationError, X402_ERROR_CODES, X402Error, ALIASES, DECIMAL_AMOUNT_RE, UNSAFE_KEYS, NAMING_API, EVM_ADDRESS_RE, ERC20_TRANSFER_SIG, DEFAULT_GATEWAY_URL, DEFAULT_MARKET_URL, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, DEFAULT_X_ORIGIN, TwakHttpClient, ALLOWED_OFFRAMP_PROVIDERS, McpResponse;
3618
3721
  var init_dist = __esm({
3619
3722
  "../core/dist/index.mjs"() {
3620
3723
  "use strict";
@@ -3646,7 +3749,10 @@ var init_dist = __esm({
3646
3749
  explorerUrl: "https://arbiscan.io",
3647
3750
  rpcUrl: "https://arbitrum.rpc.trustwallet.com",
3648
3751
  isEVM: true,
3649
- sampleAccount: "0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac"
3752
+ sampleAccount: "0xecf9ffa7f51e1194f89c25ad8c484f6bfd04e1ac",
3753
+ // Sub-second block times — poll fast, fail fast.
3754
+ receiptPollMs: 500,
3755
+ receiptTimeoutMs: 3e4
3650
3756
  },
3651
3757
  optimism: {
3652
3758
  id: "optimism",
@@ -3659,7 +3765,8 @@ var init_dist = __esm({
3659
3765
  explorerUrl: "https://optimistic.etherscan.io",
3660
3766
  rpcUrl: "https://optimism.rpc.trustwallet.com",
3661
3767
  isEVM: true,
3662
- sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
3768
+ sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
3769
+ receiptPollMs: 1e3
3663
3770
  },
3664
3771
  polygon: {
3665
3772
  id: "polygon",
@@ -3672,7 +3779,8 @@ var init_dist = __esm({
3672
3779
  explorerUrl: "https://polygonscan.com",
3673
3780
  rpcUrl: "https://polygon.rpc.trustwallet.com",
3674
3781
  isEVM: true,
3675
- sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
3782
+ sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
3783
+ receiptPollMs: 1e3
3676
3784
  },
3677
3785
  bsc: {
3678
3786
  id: "smartchain",
@@ -3698,7 +3806,8 @@ var init_dist = __esm({
3698
3806
  explorerUrl: "https://snowtrace.io",
3699
3807
  rpcUrl: "https://avalanche.rpc.trustwallet.com",
3700
3808
  isEVM: true,
3701
- sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
3809
+ sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
3810
+ receiptPollMs: 1e3
3702
3811
  },
3703
3812
  base: {
3704
3813
  id: "base",
@@ -3711,7 +3820,8 @@ var init_dist = __esm({
3711
3820
  explorerUrl: "https://basescan.org",
3712
3821
  rpcUrl: "https://base.rpc.trustwallet.com",
3713
3822
  isEVM: true,
3714
- sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
3823
+ sampleAccount: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
3824
+ receiptPollMs: 1e3
3715
3825
  },
3716
3826
  fantom: {
3717
3827
  id: "fantom",
@@ -3950,72 +4060,72 @@ var init_dist = __esm({
3950
4060
  };
3951
4061
  COMMON_TOKENS = {
3952
4062
  ethereum: {
3953
- USDT: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
3954
- USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
3955
- DAI: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
3956
- WETH: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
3957
- WBTC: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"
4063
+ USDT: { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6 },
4064
+ USDC: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals: 6 },
4065
+ DAI: { address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", decimals: 18 },
4066
+ WETH: { address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", decimals: 18 },
4067
+ WBTC: { address: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", decimals: 8 }
3958
4068
  },
3959
4069
  arbitrum: {
3960
- USDT: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
3961
- USDC: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
3962
- "USDC.e": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
3963
- DAI: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
3964
- WETH: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
3965
- WBTC: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
3966
- ARB: "0x912CE59144191C1204E64559FE8253a0e49E6548"
4070
+ USDT: { address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", decimals: 6 },
4071
+ USDC: { address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimals: 6 },
4072
+ "USDC.e": { address: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", decimals: 6 },
4073
+ DAI: { address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", decimals: 18 },
4074
+ WETH: { address: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", decimals: 18 },
4075
+ WBTC: { address: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f", decimals: 8 },
4076
+ ARB: { address: "0x912CE59144191C1204E64559FE8253a0e49E6548", decimals: 18 }
3967
4077
  },
3968
4078
  polygon: {
3969
- USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
3970
- USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
3971
- "USDC.e": "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
3972
- DAI: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063",
3973
- WMATIC: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
3974
- WETH: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619"
4079
+ USDT: { address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", decimals: 6 },
4080
+ USDC: { address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", decimals: 6 },
4081
+ "USDC.e": { address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174", decimals: 6 },
4082
+ DAI: { address: "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", decimals: 18 },
4083
+ WMATIC: { address: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", decimals: 18 },
4084
+ WETH: { address: "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619", decimals: 18 }
3975
4085
  },
3976
4086
  bsc: {
3977
- USDT: "0x55d398326f99059fF775485246999027B3197955",
3978
- USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
3979
- BUSD: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56",
3980
- DAI: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3",
3981
- WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
3982
- ETH: "0x2170Ed0880ac9A755fd29B2688956BD959F933F8",
3983
- TWT: "0x4B0F1812e5Df2A09796481Ff14017e6005508003"
4087
+ // BEP-20 stables on BSC use 18 decimals (vs the canonical 6 on Ethereum).
4088
+ USDT: { address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18 },
4089
+ USDC: { address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", decimals: 18 },
4090
+ BUSD: { address: "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56", decimals: 18 },
4091
+ DAI: { address: "0x1AF3F329e8BE154074D8769D1FFa4eE058B1DBc3", decimals: 18 },
4092
+ WBNB: { address: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c", decimals: 18 },
4093
+ ETH: { address: "0x2170Ed0880ac9A755fd29B2688956BD959F933F8", decimals: 18 },
4094
+ TWT: { address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003", decimals: 18 }
3984
4095
  },
3985
4096
  base: {
3986
- USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
3987
- USDbC: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA",
3988
- DAI: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb",
3989
- WETH: "0x4200000000000000000000000000000000000006"
4097
+ USDC: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6 },
4098
+ USDbC: { address: "0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CA", decimals: 6 },
4099
+ DAI: { address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", decimals: 18 },
4100
+ WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18 }
3990
4101
  },
3991
4102
  avalanche: {
3992
- USDT: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
3993
- USDC: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
3994
- "USDC.e": "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664",
3995
- DAI: "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70",
3996
- WAVAX: "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7"
4103
+ USDT: { address: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", decimals: 6 },
4104
+ USDC: { address: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", decimals: 6 },
4105
+ "USDC.e": { address: "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664", decimals: 6 },
4106
+ DAI: { address: "0xd586E7F844cEa2F87f50152665BCbc2C279D8d70", decimals: 18 },
4107
+ WAVAX: { address: "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7", decimals: 18 }
3997
4108
  },
3998
4109
  optimism: {
3999
- USDT: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58",
4000
- USDC: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
4001
- "USDC.e": "0x7F5c764cBc14f9669B88837ca1490cCa17c31607",
4002
- DAI: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1",
4003
- WETH: "0x4200000000000000000000000000000000000006",
4004
- OP: "0x4200000000000000000000000000000000000042"
4110
+ USDT: { address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", decimals: 6 },
4111
+ USDC: { address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", decimals: 6 },
4112
+ "USDC.e": { address: "0x7F5c764cBc14f9669B88837ca1490cCa17c31607", decimals: 6 },
4113
+ DAI: { address: "0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1", decimals: 18 },
4114
+ WETH: { address: "0x4200000000000000000000000000000000000006", decimals: 18 },
4115
+ OP: { address: "0x4200000000000000000000000000000000000042", decimals: 18 }
4005
4116
  },
4006
4117
  tron: {
4007
- USDT: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
4008
- USDC: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8"
4118
+ USDT: { address: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t", decimals: 6 },
4119
+ USDC: { address: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8", decimals: 6 }
4009
4120
  },
4010
4121
  solana: {
4011
- USDC: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
4012
- USDT: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
4013
- BONK: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
4014
- JUP: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN",
4015
- WSOL: "So11111111111111111111111111111111111111112"
4122
+ USDC: { address: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", decimals: 6 },
4123
+ USDT: { address: "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB", decimals: 6 },
4124
+ BONK: { address: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263", decimals: 5 },
4125
+ JUP: { address: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN", decimals: 6 },
4126
+ WSOL: { address: "So11111111111111111111111111111111111111112", decimals: 9 }
4016
4127
  }
4017
4128
  };
4018
- BSC_18_DECIMAL_TOKENS = /* @__PURE__ */ new Set(["USDT", "USDC", "BUSD"]);
4019
4129
  NATIVE_SYMBOLS = /* @__PURE__ */ new Set([
4020
4130
  "NATIVE",
4021
4131
  "ETH",
@@ -4045,41 +4155,6 @@ var init_dist = __esm({
4045
4155
  "DOGE"
4046
4156
  // Bitcoin family
4047
4157
  ]);
4048
- TOKEN_DECIMALS = {
4049
- USDC: 6,
4050
- USDT: 6,
4051
- BUSD: 6,
4052
- DAI: 18,
4053
- ETH: 18,
4054
- MATIC: 18,
4055
- BNB: 18,
4056
- AVAX: 18,
4057
- FTM: 18,
4058
- SOL: 9,
4059
- S: 18,
4060
- CELO: 18,
4061
- WETH: 18,
4062
- WMATIC: 18,
4063
- WBNB: 18,
4064
- WAVAX: 18,
4065
- WBTC: 8,
4066
- ARB: 18,
4067
- OP: 18,
4068
- // Non-EVM natives
4069
- TRX: 6,
4070
- APT: 8,
4071
- TON: 9,
4072
- SUI: 9,
4073
- NEAR: 24,
4074
- ATOM: 6,
4075
- BTC: 8,
4076
- LTC: 8,
4077
- DOGE: 8,
4078
- // Solana SPL tokens
4079
- BONK: 5,
4080
- JUP: 6,
4081
- WSOL: 9
4082
- };
4083
4158
  CELO_NATIVE_ADDRESS = "0x471EcE3750Da237f93B8E339c536989b8978a438";
4084
4159
  TwakError = class extends Error {
4085
4160
  constructor(message, code2, details) {
@@ -4266,9 +4341,17 @@ var init_dist = __esm({
4266
4341
  this.name = "X402Error";
4267
4342
  }
4268
4343
  };
4344
+ ALIASES = {
4345
+ "binance-smart-chain": "bsc",
4346
+ matic: "polygon",
4347
+ "arbitrum-one": "arbitrum",
4348
+ op: "optimism"
4349
+ };
4269
4350
  DECIMAL_AMOUNT_RE = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
4270
4351
  UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
4271
4352
  NAMING_API = "https://naming.trustwallet.com/v1/lookup";
4353
+ EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
4354
+ ERC20_TRANSFER_SIG = "0xa9059cbb";
4272
4355
  DEFAULT_GATEWAY_URL = "https://tws.trustwallet.com";
4273
4356
  DEFAULT_MARKET_URL = "https://market.trustwallet.com";
4274
4357
  DEFAULT_TIMEOUT = 3e4;
@@ -4430,7 +4513,7 @@ function getPassword(service, account) {
4430
4513
  throw err;
4431
4514
  }
4432
4515
  }
4433
- var fs, path, os, crypto2, crypto22, import_keyring, crypto3, crypto4, FileStore, ALGORITHM, KEY_LENGTH, IV_LENGTH, SALT_LENGTH, ITERATIONS, SERVICE, WALLET_ACCOUNT, API_SECRET_ACCOUNT, PROBE_ACCOUNT, KeychainManager, CREDENTIALS_FILE, CredentialManager, WALLET_FILE, WalletConfigStore, ALERTS_FILE, AlertStore, AUTOMATIONS_FILE, AutomationStore, SEEN_HASHES_FILE, SeenHashStore, ASSET_CACHE_FILE, AssetCache;
4516
+ var fs, path, os, crypto2, crypto22, import_keyring, crypto3, crypto4, FileStore, ALGORITHM, KEY_LENGTH, IV_LENGTH, SALT_LENGTH, ITERATIONS, SERVICE, WALLET_ACCOUNT, API_SECRET_ACCOUNT, PROBE_ACCOUNT, KeychainManager, CREDENTIALS_FILE, CredentialManager, WALLET_FILE, WalletConfigStore, ALERTS_FILE, AlertStore, AUTOMATIONS_FILE, AutomationStore, ASSET_CACHE_FILE, AssetCache;
4434
4517
  var init_dist2 = __esm({
4435
4518
  "../storage/dist/index.mjs"() {
4436
4519
  "use strict";
@@ -4731,34 +4814,6 @@ var init_dist2 = __esm({
4731
4814
  return automations.filter((a3) => a3.active);
4732
4815
  }
4733
4816
  };
4734
- SEEN_HASHES_FILE = "seen-hashes.json";
4735
- SeenHashStore = class {
4736
- store;
4737
- // In-memory cache prevents duplicate acceptance within the same process
4738
- memCache = /* @__PURE__ */ new Set();
4739
- constructor(config) {
4740
- this.store = new FileStore(config);
4741
- }
4742
- async hasSeen(txHash) {
4743
- const normalized = txHash.toLowerCase();
4744
- if (this.memCache.has(normalized)) return true;
4745
- const records = await this.list();
4746
- return records.some((r3) => r3.txHash === normalized);
4747
- }
4748
- async markSeen(txHash, chainId) {
4749
- const normalized = txHash.toLowerCase();
4750
- this.memCache.add(normalized);
4751
- const records = await this.list();
4752
- records.push({ txHash: normalized, chainId, usedAt: (/* @__PURE__ */ new Date()).toISOString() });
4753
- await this.store.write(SEEN_HASHES_FILE, records);
4754
- }
4755
- async list() {
4756
- return await this.store.read(SEEN_HASHES_FILE) || [];
4757
- }
4758
- async clear() {
4759
- await this.store.remove(SEEN_HASHES_FILE);
4760
- }
4761
- };
4762
4817
  ASSET_CACHE_FILE = "asset-cache.json";
4763
4818
  AssetCache = class {
4764
4819
  store;
@@ -6352,6 +6407,34 @@ var require_source = __commonJS({
6352
6407
  });
6353
6408
 
6354
6409
  // ../wallet-core/dist/index.mjs
6410
+ var dist_exports2 = {};
6411
+ __export(dist_exports2, {
6412
+ AgentWalletProvider: () => AgentWalletProvider,
6413
+ BlockchainClient: () => BlockchainClient,
6414
+ TeeWalletProvider: () => TeeWalletProvider,
6415
+ VerifyCredentialsError: () => VerifyCredentialsError,
6416
+ WalletContext: () => WalletContext,
6417
+ WalletCoreSigner: () => WalletCoreSigner,
6418
+ WalletProvider: () => WalletProvider,
6419
+ _setWalletCore: () => _setWalletCore,
6420
+ applyPolyfills: () => applyPolyfills,
6421
+ buildCoinTypeMap: () => buildCoinTypeMap,
6422
+ chainKeyToDefinedChain: () => chainKeyToDefinedChain,
6423
+ createPendingTransaction: () => createPendingTransaction,
6424
+ getAmberDomainsByCoinId: () => getAmberDomainsByCoinId,
6425
+ getCoinType: () => getCoinType,
6426
+ getWalletCore: () => getWalletCore,
6427
+ humanAmountToWei: () => humanAmountToWei,
6428
+ initTrustSDK: () => initTrustSDK,
6429
+ isInitialized: () => isInitialized,
6430
+ patchNaasSession: () => patchNaasSession,
6431
+ patchNodeStubs: () => patchNodeStubs,
6432
+ registerDeviceAndWallet: () => registerDeviceAndWallet,
6433
+ registerShutdownHandlers: () => registerShutdownHandlers,
6434
+ setPolyfillCredentials: () => setPolyfillCredentials,
6435
+ setPolyfillNaasSessionId: () => setPolyfillNaasSessionId,
6436
+ verifyCredentials: () => verifyCredentials
6437
+ });
6355
6438
  function setPolyfillCredentials(creds) {
6356
6439
  _credentials = creds;
6357
6440
  }
@@ -6590,41 +6673,60 @@ function patchNodeStubs() {
6590
6673
  } catch {
6591
6674
  }
6592
6675
  if (parsedUrl?.hostname === "tws.trustwallet.com" && parsedUrl.pathname === "/v2/devices/fingerprint") {
6593
- return Promise.resolve(new Response(JSON.stringify({
6594
- device_id: _credentials?.accessId || "twak-cli",
6595
- hash_key: _credentials?.hmacSecret || ""
6596
- }), {
6597
- status: 200,
6598
- headers: { "content-type": "application/json" }
6599
- }));
6676
+ return Promise.resolve(
6677
+ new Response(
6678
+ JSON.stringify({
6679
+ device_id: _credentials?.accessId || "twak-cli",
6680
+ hash_key: _credentials?.hmacSecret || ""
6681
+ }),
6682
+ {
6683
+ status: 200,
6684
+ headers: { "content-type": "application/json" }
6685
+ }
6686
+ )
6687
+ );
6600
6688
  }
6601
6689
  if (url.includes("/v2/projects/") && url.includes("/nodes")) {
6602
- return Promise.resolve(new Response(JSON.stringify({
6603
- message: "ok",
6604
- code: 200,
6605
- data: [
6606
- { network: "solana", name: "solana", nodes: [{ url: "https://solana.twnodes.com", weight: "high" }] }
6607
- ]
6608
- }), {
6609
- status: 200,
6610
- headers: { "content-type": "application/json" }
6611
- }));
6690
+ return Promise.resolve(
6691
+ new Response(
6692
+ JSON.stringify({
6693
+ message: "ok",
6694
+ code: 200,
6695
+ data: [
6696
+ {
6697
+ network: "solana",
6698
+ name: "solana",
6699
+ nodes: [{ url: "https://solana.twnodes.com", weight: "high" }]
6700
+ }
6701
+ ]
6702
+ }),
6703
+ {
6704
+ status: 200,
6705
+ headers: { "content-type": "application/json" }
6706
+ }
6707
+ )
6708
+ );
6612
6709
  }
6613
6710
  if (url.includes("/btc-indexer/inscribed/") && url.includes("/available-balance")) {
6614
6711
  const addrMatch = url.match(/\/address\/([^/]+)\//);
6615
6712
  const addr = addrMatch?.[1] ?? "";
6616
- return Promise.resolve(new Response(JSON.stringify({
6617
- address: addr,
6618
- balance: "0",
6619
- totalReceived: "0",
6620
- totalSent: "0",
6621
- unconfirmedBalance: "0",
6622
- unconfirmedTxs: 0,
6623
- txs: 0
6624
- }), {
6625
- status: 200,
6626
- headers: { "content-type": "application/json" }
6627
- }));
6713
+ return Promise.resolve(
6714
+ new Response(
6715
+ JSON.stringify({
6716
+ address: addr,
6717
+ balance: "0",
6718
+ totalReceived: "0",
6719
+ totalSent: "0",
6720
+ unconfirmedBalance: "0",
6721
+ unconfirmedTxs: 0,
6722
+ txs: 0
6723
+ }),
6724
+ {
6725
+ status: 200,
6726
+ headers: { "content-type": "application/json" }
6727
+ }
6728
+ )
6729
+ );
6628
6730
  }
6629
6731
  let resolvedUrl = url;
6630
6732
  if (_naasSessionId && url.includes(".twnodes.com") && !url.includes("/naas/session/")) {
@@ -6659,11 +6761,11 @@ function patchNaasSession(sessionId) {
6659
6761
  };
6660
6762
  }
6661
6763
  function buildCoinTypeMap() {
6662
- const wc2 = getWalletCore();
6764
+ const wc3 = getWalletCore();
6663
6765
  coinTypeByValue = /* @__PURE__ */ new Map();
6664
- for (const key of Object.keys(wc2.CoinType)) {
6766
+ for (const key of Object.keys(wc3.CoinType)) {
6665
6767
  if (key === "values") continue;
6666
- const ct5 = wc2.CoinType[key];
6768
+ const ct5 = wc3.CoinType[key];
6667
6769
  if (ct5 && typeof ct5 === "object" && typeof ct5.value === "number") {
6668
6770
  coinTypeByValue.set(ct5.value, ct5);
6669
6771
  }
@@ -6722,9 +6824,26 @@ function getWalletCore() {
6722
6824
  function isInitialized() {
6723
6825
  return initialized;
6724
6826
  }
6725
- function _setWalletCore(wc2) {
6726
- walletCore = wc2;
6727
- initialized = wc2 !== null;
6827
+ function _setWalletCore(wc3) {
6828
+ walletCore = wc3;
6829
+ initialized = wc3 !== null;
6830
+ }
6831
+ function chainKeyToDefinedChain(chainKey) {
6832
+ let coinId;
6833
+ let canonicalKey;
6834
+ try {
6835
+ ({ coinId, chainKey: canonicalKey } = resolveAsset(chainKey));
6836
+ } catch (err) {
6837
+ if (err instanceof WalletError) {
6838
+ throw new TwakValidationError(err.message, { chainKey });
6839
+ }
6840
+ throw err;
6841
+ }
6842
+ const ct5 = getCoinType(coinId);
6843
+ if (!ct5) {
6844
+ throw new TwakValidationError(`No CoinType mapping for coinId ${coinId}`, { chainKey, coinId });
6845
+ }
6846
+ return { chain: new import_trust_sdk3.DefinedChain(ct5), chainConfig: CHAINS[canonicalKey] };
6728
6847
  }
6729
6848
  function humanAmountToWei(amount, decimals = 18) {
6730
6849
  const parts = amount.split(".");
@@ -6733,6 +6852,13 @@ function humanAmountToWei(amount, decimals = 18) {
6733
6852
  const frac = parts[1].padEnd(decimals, "0").slice(0, decimals);
6734
6853
  return whole * 10n ** BigInt(decimals) + BigInt(frac);
6735
6854
  }
6855
+ function createPendingTransaction(client, chainKey, txHash) {
6856
+ return {
6857
+ txHash,
6858
+ chainKey,
6859
+ wait: (opts) => client.waitForReceipt(chainKey, txHash, opts)
6860
+ };
6861
+ }
6736
6862
  function requireCoinType(coinId) {
6737
6863
  const ct5 = getCoinType(coinId);
6738
6864
  if (!ct5) {
@@ -6742,9 +6868,9 @@ function requireCoinType(coinId) {
6742
6868
  }
6743
6869
  function getCoinDecimals(coinId) {
6744
6870
  try {
6745
- const wc2 = getWalletCore();
6871
+ const wc3 = getWalletCore();
6746
6872
  const ct5 = requireCoinType(coinId);
6747
- return wc2.CoinTypeConfiguration.getDecimals(ct5);
6873
+ return wc3.CoinTypeConfiguration.getDecimals(ct5);
6748
6874
  } catch {
6749
6875
  const chain2 = getChainByCoinId(coinId);
6750
6876
  return chain2?.decimals ?? 18;
@@ -6787,10 +6913,39 @@ function tokenTypeForCoinId(coinId) {
6787
6913
  return import_trust_sdk2.TokenType.Erc20;
6788
6914
  }
6789
6915
  }
6916
+ async function signTransactionBytes(transaction, fee, nonce, hdWallet, ct5) {
6917
+ try {
6918
+ const signingResult = await import_trust_sdk2.BlockchainService.signWithHDWallet(
6919
+ transaction,
6920
+ fee,
6921
+ nonce,
6922
+ hdWallet
6923
+ );
6924
+ return signingResult.output[0];
6925
+ } catch (err) {
6926
+ if (!(err instanceof Error) || !err.message.includes("An operation is not implemented"))
6927
+ throw err;
6928
+ const privateKey = hdWallet.getKeyForCoin(ct5);
6929
+ try {
6930
+ const signingResult = await import_trust_sdk2.BlockchainService.signWithNonce(
6931
+ transaction,
6932
+ fee,
6933
+ nonce,
6934
+ privateKey
6935
+ );
6936
+ return signingResult.output[0];
6937
+ } finally {
6938
+ try {
6939
+ privateKey.data().fill(0);
6940
+ } catch {
6941
+ }
6942
+ }
6943
+ }
6944
+ }
6790
6945
  async function getAmberDomainsByCoinId() {
6791
6946
  if (domainsByCoinId) return domainsByCoinId;
6792
6947
  try {
6793
- const domains = await import_trust_sdk4.AmberService.getDomains();
6948
+ const domains = await import_trust_sdk5.AmberService.getDomains();
6794
6949
  domainsByCoinId = new Map(
6795
6950
  domains.map((d3) => [d3.coin.value, d3.id])
6796
6951
  );
@@ -6890,7 +7045,7 @@ function registerShutdownHandlers(providers, onBeforeExit) {
6890
7045
  process.removeListener("SIGTERM", handler);
6891
7046
  };
6892
7047
  }
6893
- var fs2, path2, import_trust_sdk, import_trust_sdk2, import_trust_sdk3, import_trust_sdk4, import_crypto2, __require, polyfillsApplied, _credentials, _naasSessionId, nodeStubsPatched, NAAS_SESSION_RE, naasSessionPatched, coinTypeByValue, walletCore, initialized, SPONSORED_RPC, BlockchainClient, WalletCoreSigner, domainsByCoinId, GATEWAY_URL, DEVICE_INFO_BODY, VerifyCredentialsError, WALLET_ID_SUFFIX, WALLET_ID_LENGTH, EVM_COIN_IDS, MAINTAINED_COIN_IDS, WalletProvider, AgentWalletProvider, WalletContext, SHUTDOWN_TIMEOUT_MS;
7048
+ var fs2, path2, import_trust_sdk, import_trust_sdk2, import_trust_sdk3, import_trust_sdk4, import_trust_sdk5, import_crypto2, __require, polyfillsApplied, _credentials, _naasSessionId, nodeStubsPatched, NAAS_SESSION_RE, naasSessionPatched, coinTypeByValue, walletCore, initialized, SPONSORED_RPC, RPC_TIMEOUT_MS, RECEIPT_POLL_INTERVAL_MS, RECEIPT_DEFAULT_TIMEOUT_MS, BlockchainClient, WalletCoreSigner, domainsByCoinId, GATEWAY_URL, DEVICE_INFO_BODY, VerifyCredentialsError, WALLET_ID_SUFFIX, WALLET_ID_LENGTH, EVM_COIN_IDS, MAINTAINED_COIN_IDS, WalletProvider, AgentWalletProvider, TeeWalletProvider, WalletContext, SHUTDOWN_TIMEOUT_MS;
6894
7049
  var init_dist3 = __esm({
6895
7050
  "../wallet-core/dist/index.mjs"() {
6896
7051
  "use strict";
@@ -6899,9 +7054,11 @@ var init_dist3 = __esm({
6899
7054
  import_trust_sdk = require("@trustwallet-trust-sdk/trust-sdk");
6900
7055
  import_trust_sdk2 = require("@trustwallet-trust-sdk/trust-sdk");
6901
7056
  init_dist();
6902
- init_dist();
6903
7057
  import_trust_sdk3 = require("@trustwallet-trust-sdk/trust-sdk");
7058
+ init_dist();
7059
+ init_dist();
6904
7060
  import_trust_sdk4 = require("@trustwallet-trust-sdk/trust-sdk");
7061
+ import_trust_sdk5 = require("@trustwallet-trust-sdk/trust-sdk");
6905
7062
  init_dist();
6906
7063
  import_crypto2 = require("crypto");
6907
7064
  init_dist();
@@ -6918,6 +7075,7 @@ var init_dist3 = __esm({
6918
7075
  nodeStubsPatched = false;
6919
7076
  NAAS_SESSION_RE = /\/naas\/session\/[A-Za-z0-9+/_-]+=*\//;
6920
7077
  naasSessionPatched = false;
7078
+ applyPolyfills();
6921
7079
  coinTypeByValue = null;
6922
7080
  walletCore = null;
6923
7081
  initialized = false;
@@ -6925,6 +7083,9 @@ var init_dist3 = __esm({
6925
7083
  20000714: "https://bsc-sponsored.twnodes.com",
6926
7084
  60: "https://ethereum-sponsored.twnodes.com"
6927
7085
  };
7086
+ RPC_TIMEOUT_MS = 15e3;
7087
+ RECEIPT_POLL_INTERVAL_MS = 1500;
7088
+ RECEIPT_DEFAULT_TIMEOUT_MS = 6e4;
6928
7089
  BlockchainClient = class _BlockchainClient {
6929
7090
  signer;
6930
7091
  constructor(signer) {
@@ -7065,97 +7226,9 @@ var init_dist3 = __esm({
7065
7226
  );
7066
7227
  if (transaction7702) transaction = transaction7702;
7067
7228
  const nonce = await import_trust_sdk2.BlockchainService.fetchNonce(account);
7068
- let encodedTx;
7069
- try {
7070
- const signingResult = await import_trust_sdk2.BlockchainService.signWithHDWallet(
7071
- transaction,
7072
- fee,
7073
- nonce,
7074
- params.hdWallet
7075
- );
7076
- encodedTx = signingResult.output[0];
7077
- } catch (err) {
7078
- if (!(err instanceof Error) || !err.message.includes("An operation is not implemented"))
7079
- throw err;
7080
- const privateKey = params.hdWallet.getKeyForCoin(ct5);
7081
- try {
7082
- const signingResult = await import_trust_sdk2.BlockchainService.signWithNonce(
7083
- transaction,
7084
- fee,
7085
- nonce,
7086
- privateKey
7087
- );
7088
- encodedTx = signingResult.output[0];
7089
- } finally {
7090
- try {
7091
- privateKey.data().fill(0);
7092
- } catch {
7093
- }
7094
- }
7095
- }
7096
- const hash = await this.sendTransactionWithSponsorship(chain2, encodedTx, coinId);
7097
- return { hash, chainKey };
7098
- }
7099
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7100
- async approve(params) {
7101
- const { coinId, contractAddress } = parseTokenId(params.assetId);
7102
- const chainKey = coinTypeToChainKey(coinId);
7103
- if (!chainKey) {
7104
- throw new Error(`Unsupported coin type: ${coinId}`);
7105
- }
7106
- if (!contractAddress) {
7107
- throw new Error("Cannot approve native token");
7108
- }
7109
- const ct5 = requireCoinType(coinId);
7110
- const chain2 = new import_trust_sdk2.DefinedChain(ct5);
7111
- const account = this.getAccountFromSigner(chainKey);
7112
- const asset = new import_trust_sdk2.Asset.Token(account, contractAddress, tokenTypeForCoinId(coinId));
7113
- const approveData = this.encodeApproveCalldata(params.spender, params.amount);
7114
- let transaction = new import_trust_sdk2.TransactionSmartContract(
7115
- asset,
7116
- contractAddress,
7117
- "0",
7118
- approveData
7119
- );
7120
- const { fee, transaction7702 } = await this.calculateFeeWithSponsorship(
7121
- transaction,
7122
- account,
7123
- coinId
7124
- );
7125
- if (transaction7702) transaction = transaction7702;
7126
- const nonce = await import_trust_sdk2.BlockchainService.fetchNonce(account);
7127
- let encodedTx;
7128
- try {
7129
- const signingResult = await import_trust_sdk2.BlockchainService.signWithHDWallet(
7130
- transaction,
7131
- fee,
7132
- nonce,
7133
- params.hdWallet
7134
- );
7135
- encodedTx = signingResult.output[0];
7136
- } catch (err) {
7137
- if (!(err instanceof Error) || !err.message.includes("An operation is not implemented"))
7138
- throw err;
7139
- const privateKey = params.hdWallet.getKeyForCoin(ct5);
7140
- try {
7141
- const signingResult = await import_trust_sdk2.BlockchainService.signWithNonce(
7142
- transaction,
7143
- fee,
7144
- nonce,
7145
- privateKey
7146
- );
7147
- encodedTx = signingResult.output[0];
7148
- } finally {
7149
- try {
7150
- privateKey.data().fill(0);
7151
- } catch {
7152
- }
7153
- }
7154
- }
7155
- const hash = await this.sendTransactionWithSponsorship(chain2, encodedTx, coinId);
7156
- return { hash, chainKey };
7229
+ const encodedTx = await signTransactionBytes(transaction, fee, nonce, params.hdWallet, ct5);
7230
+ return this.sendTransactionWithSponsorship(chain2, encodedTx, coinId, chainKey);
7157
7231
  }
7158
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7159
7232
  async executeContract(params) {
7160
7233
  const { coinId } = parseTokenId(params.assetId);
7161
7234
  const chainKey = coinTypeToChainKey(coinId);
@@ -7180,74 +7253,67 @@ var init_dist3 = __esm({
7180
7253
  );
7181
7254
  if (transaction7702) transaction = transaction7702;
7182
7255
  const nonce = await import_trust_sdk2.BlockchainService.fetchNonce(account);
7183
- let encodedTx;
7184
- try {
7185
- const signingResult = await import_trust_sdk2.BlockchainService.signWithHDWallet(
7186
- transaction,
7187
- fee,
7188
- nonce,
7189
- params.hdWallet
7256
+ const encodedTx = await signTransactionBytes(transaction, fee, nonce, params.hdWallet, ct5);
7257
+ return this.sendTransactionWithSponsorship(chain2, encodedTx, coinId, chainKey);
7258
+ }
7259
+ /**
7260
+ * Execute a read-only EVM `eth_call` against the chain's default RPC node.
7261
+ * Returns the raw hex result string from the JSON-RPC envelope, or `'0x'`
7262
+ * when the node omits a result. Errors surface as TwakNetworkError /
7263
+ * TwakValidationError.
7264
+ */
7265
+ async ethCall(chainKey, to3, data) {
7266
+ const rpcUrl = await this.resolveRpcUrl(chainKey);
7267
+ const result = await this.postJsonRpc(rpcUrl, "eth_call", [{ to: to3, data }, "latest"]);
7268
+ return result ?? "0x";
7269
+ }
7270
+ /**
7271
+ * Poll `eth_getTransactionReceipt` until the transaction is mined or the
7272
+ * timeout elapses. Defaults come from the chain's `receiptPollMs` /
7273
+ * `receiptTimeoutMs` config when set, otherwise the global constants
7274
+ * (1.5s poll, 60s timeout). Caller-supplied opts override both.
7275
+ */
7276
+ async waitForReceipt(chainKey, txHash, opts) {
7277
+ const { chain: chain2, chainConfig } = chainKeyToDefinedChain(chainKey);
7278
+ if (chainConfig?.namespace && chainConfig.namespace !== "eip155") {
7279
+ throw new TwakValidationError(
7280
+ `waitForReceipt is EVM-only; ${chainKey} is namespace ${chainConfig.namespace}`,
7281
+ { chainKey, namespace: chainConfig.namespace }
7190
7282
  );
7191
- encodedTx = signingResult.output[0];
7192
- } catch (err) {
7193
- if (!(err instanceof Error) || !err.message.includes("An operation is not implemented"))
7194
- throw err;
7195
- const privateKey = params.hdWallet.getKeyForCoin(ct5);
7196
- try {
7197
- const signingResult = await import_trust_sdk2.BlockchainService.signWithNonce(
7198
- transaction,
7199
- fee,
7200
- nonce,
7201
- privateKey
7202
- );
7203
- encodedTx = signingResult.output[0];
7204
- } finally {
7205
- try {
7206
- privateKey.data().fill(0);
7207
- } catch {
7208
- }
7209
- }
7210
7283
  }
7211
- const hash = await this.sendTransactionWithSponsorship(chain2, encodedTx, coinId);
7212
- return { hash, chainKey };
7213
- }
7214
- async getAllowance(params) {
7215
- const { coinId, contractAddress } = parseTokenId(params.assetId);
7216
- if (!contractAddress) throw new Error("Allowance only applies to ERC-20 tokens");
7217
- const chainKey = coinTypeToChainKey(coinId);
7218
- if (!chainKey) throw new Error(`Unsupported coin type: ${coinId}`);
7219
- const chainDef = getChainByCoinId(coinId);
7220
- if (!chainDef?.isEVM) throw new Error(`Allowance is only supported on EVM chains`);
7221
- const ct5 = requireCoinType(coinId);
7222
- const chain2 = new import_trust_sdk2.DefinedChain(ct5);
7223
- const fnSelector = "dd62ed3e";
7224
- const paddedOwner = params.owner.replace("0x", "").padStart(64, "0");
7225
- const paddedSpender = params.spender.replace("0x", "").padStart(64, "0");
7226
- const calldata = "0x" + fnSelector + paddedOwner + paddedSpender;
7227
- const nodes = await import_trust_sdk2.BlockchainService.getNodes(chain2);
7228
- const rpcUrl = nodes[0]?.url;
7229
- if (!rpcUrl) throw new Error(`No RPC node available for ${chainKey}`);
7230
- const response = await fetch(rpcUrl, {
7231
- method: "POST",
7232
- headers: { "Content-Type": "application/json" },
7233
- body: JSON.stringify({
7234
- jsonrpc: "2.0",
7235
- id: 1,
7236
- method: "eth_call",
7237
- params: [{ to: contractAddress, data: calldata }, "latest"]
7238
- }),
7239
- signal: AbortSignal.timeout(15e3)
7240
- });
7241
- if (!response.ok) {
7242
- const body = await response.text().catch(() => "");
7243
- throw new Error(`RPC HTTP error ${response.status}: ${body.slice(0, 500)}`);
7284
+ const timeoutMs = opts?.timeoutMs ?? chainConfig?.receiptTimeoutMs ?? RECEIPT_DEFAULT_TIMEOUT_MS;
7285
+ const pollMs = opts?.pollMs ?? chainConfig?.receiptPollMs ?? RECEIPT_POLL_INTERVAL_MS;
7286
+ if (timeoutMs <= 0) {
7287
+ throw new TwakValidationError(`waitForReceipt: timeoutMs must be > 0 (got ${timeoutMs})`, {
7288
+ chainKey,
7289
+ timeoutMs
7290
+ });
7244
7291
  }
7245
- const json = await response.json();
7246
- if (json.error) throw new Error(`RPC error: ${json.error.message}`);
7247
- if (!json.result || json.result === "0x") return 0n;
7248
- return BigInt(json.result);
7292
+ if (pollMs <= 0) {
7293
+ throw new TwakValidationError(`waitForReceipt: pollMs must be > 0 (got ${pollMs})`, {
7294
+ chainKey,
7295
+ pollMs
7296
+ });
7297
+ }
7298
+ const rpcUrl = await this.rpcUrlForChain(chain2, chainKey);
7299
+ const deadline = Date.now() + timeoutMs;
7300
+ while (Date.now() < deadline) {
7301
+ const remainingMs = Math.max(1, deadline - Date.now());
7302
+ const result = await this.postJsonRpc(rpcUrl, "eth_getTransactionReceipt", [txHash], {
7303
+ timeoutMs: Math.min(RPC_TIMEOUT_MS, remainingMs)
7304
+ });
7305
+ const receipt = result ?? null;
7306
+ if (receipt) return receipt;
7307
+ await new Promise((resolve2) => setTimeout(resolve2, pollMs));
7308
+ }
7309
+ throw new TwakNetworkError(`Timed out waiting for receipt ${txHash} on ${chainKey}`, {
7310
+ txHash,
7311
+ chainKey
7312
+ });
7313
+ }
7314
+ contract(def) {
7315
+ return def.using(this);
7249
7316
  }
7250
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7251
7317
  async transferRawSolana(params) {
7252
7318
  const ct5 = requireCoinType(501);
7253
7319
  const chain2 = new import_trust_sdk2.DefinedChain(ct5);
@@ -7268,31 +7334,9 @@ var init_dist3 = __esm({
7268
7334
  );
7269
7335
  const fee = await import_trust_sdk2.BlockchainService.calculateFee(trade);
7270
7336
  const nonce = await import_trust_sdk2.BlockchainService.fetchNonce(account);
7271
- let encodedTx;
7272
- try {
7273
- const signingResult = await import_trust_sdk2.BlockchainService.signWithHDWallet(
7274
- trade,
7275
- fee,
7276
- nonce,
7277
- params.hdWallet
7278
- );
7279
- encodedTx = signingResult.output[0];
7280
- } catch (err) {
7281
- if (!(err instanceof Error) || !err.message.includes("An operation is not implemented"))
7282
- throw err;
7283
- const privateKey = params.hdWallet.getKeyForCoin(ct5);
7284
- try {
7285
- const signingResult = await import_trust_sdk2.BlockchainService.signWithNonce(trade, fee, nonce, privateKey);
7286
- encodedTx = signingResult.output[0];
7287
- } finally {
7288
- try {
7289
- privateKey.data().fill(0);
7290
- } catch {
7291
- }
7292
- }
7293
- }
7337
+ const encodedTx = await signTransactionBytes(trade, fee, nonce, params.hdWallet, ct5);
7294
7338
  const hash = await import_trust_sdk2.BlockchainService.sendTransactionV2(chain2, encodedTx);
7295
- return { hash, chainKey: "solana" };
7339
+ return createPendingTransaction(this, "solana", hash);
7296
7340
  }
7297
7341
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7298
7342
  async signMessage(chainKey, message, privateKey) {
@@ -7410,7 +7454,7 @@ var init_dist3 = __esm({
7410
7454
  * Try sending via sponsored RPC first (checks pm_isSponsorable, then
7411
7455
  * eth_sendRawTransaction). Falls back to regular BlockchainService.
7412
7456
  */
7413
- async sendTransactionWithSponsorship(chain2, encodedTx, coinId) {
7457
+ async sendTransactionWithSponsorship(chain2, encodedTx, coinId, chainKey) {
7414
7458
  const sponsoredUrl = this.getSponsoredRpcUrl(coinId);
7415
7459
  if (sponsoredUrl) {
7416
7460
  try {
@@ -7422,23 +7466,17 @@ var init_dist3 = __esm({
7422
7466
  rlpStart = offset;
7423
7467
  }
7424
7468
  const rawRlp = "0x" + Array.from(encodedTx.slice(rlpStart)).map((b3) => b3.toString(16).padStart(2, "0")).join("");
7425
- const resp = await fetch(sponsoredUrl, {
7426
- method: "POST",
7427
- headers: { "Content-Type": "application/json" },
7428
- body: JSON.stringify({
7429
- id: 0,
7430
- jsonrpc: "2.0",
7431
- method: "eth_sendRawTransaction",
7432
- params: [rawRlp]
7433
- }),
7434
- signal: AbortSignal.timeout(3e4)
7469
+ const result = await this.postJsonRpc(sponsoredUrl, "eth_sendRawTransaction", [rawRlp], {
7470
+ timeoutMs: 3e4
7435
7471
  });
7436
- const json = await resp.json();
7437
- if (json.result) return json.result;
7472
+ if (typeof result === "string") {
7473
+ return createPendingTransaction(this, chainKey, result);
7474
+ }
7438
7475
  } catch {
7439
7476
  }
7440
7477
  }
7441
- return import_trust_sdk2.BlockchainService.sendTransactionV2(chain2, encodedTx);
7478
+ const hash = await import_trust_sdk2.BlockchainService.sendTransactionV2(chain2, encodedTx);
7479
+ return createPendingTransaction(this, chainKey, hash);
7442
7480
  }
7443
7481
  // Known stablecoins to try as gas payment tokens when getSupportedTokens returns empty
7444
7482
  static FALLBACK_GAS_TOKENS = {
@@ -7651,8 +7689,8 @@ var init_dist3 = __esm({
7651
7689
  }
7652
7690
  const ct5 = requireCoinType(coinId);
7653
7691
  const chain2 = new import_trust_sdk2.DefinedChain(ct5);
7654
- const wc2 = getWalletCore();
7655
- const derivation = coinId === 0 && address.startsWith("bc1p") ? wc2.Derivation.bitcoinTaproot : coinId === 0 && address.startsWith("bc1") ? wc2.Derivation.bitcoinSegwit : wc2.Derivation.default;
7692
+ const wc3 = getWalletCore();
7693
+ const derivation = coinId === 0 && address.startsWith("bc1p") ? wc3.Derivation.bitcoinTaproot : coinId === 0 && address.startsWith("bc1") ? wc3.Derivation.bitcoinSegwit : wc3.Derivation.default;
7656
7694
  return new import_trust_sdk2.Account(address, derivation, "", "", "", chain2);
7657
7695
  }
7658
7696
  async fetchBitcoinBalanceFromBlockbook(address) {
@@ -7666,11 +7704,61 @@ var init_dist3 = __esm({
7666
7704
  const total = BigInt(confirmed) + BigInt(unconfirmed);
7667
7705
  return { available: confirmed, total: total.toString() };
7668
7706
  }
7669
- encodeApproveCalldata(spender, amount) {
7670
- const fnSelector = "095ea7b3";
7671
- const paddedSpender = spender.replace("0x", "").padStart(64, "0");
7672
- const amountHex = BigInt(amount).toString(16).padStart(64, "0");
7673
- return "0x" + fnSelector + paddedSpender + amountHex;
7707
+ async resolveRpcUrl(chainKey) {
7708
+ const { chain: chain2 } = chainKeyToDefinedChain(chainKey);
7709
+ return this.rpcUrlForChain(chain2, chainKey);
7710
+ }
7711
+ async rpcUrlForChain(chain2, chainKey) {
7712
+ const nodes = await import_trust_sdk2.BlockchainService.getNodes(chain2);
7713
+ const rpcUrl = nodes[0]?.url;
7714
+ if (!rpcUrl) {
7715
+ throw new TwakNetworkError(`No RPC node available for ${chainKey}`, { chainKey });
7716
+ }
7717
+ return rpcUrl;
7718
+ }
7719
+ async postJsonRpc(rpcUrl, method, params, opts) {
7720
+ const timeoutMs = opts?.timeoutMs ?? RPC_TIMEOUT_MS;
7721
+ let response;
7722
+ try {
7723
+ response = await fetch(rpcUrl, {
7724
+ method: "POST",
7725
+ headers: { "Content-Type": "application/json" },
7726
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
7727
+ signal: AbortSignal.timeout(timeoutMs)
7728
+ });
7729
+ } catch (err) {
7730
+ throw new TwakNetworkError(
7731
+ `RPC fetch failed on ${method}: ${err instanceof Error ? err.message : String(err)}`,
7732
+ { method, rpcUrl, timeoutMs }
7733
+ );
7734
+ }
7735
+ if (!response.ok) {
7736
+ const body = await response.text().catch(() => "");
7737
+ const truncated = body.slice(0, 500);
7738
+ throw new TwakNetworkError(`RPC HTTP error ${response.status} on ${method}: ${truncated}`, {
7739
+ status: response.status,
7740
+ method,
7741
+ rpcUrl,
7742
+ body: truncated
7743
+ });
7744
+ }
7745
+ let json;
7746
+ try {
7747
+ json = await response.json();
7748
+ } catch (err) {
7749
+ throw new TwakNetworkError(
7750
+ `RPC response JSON parse failed on ${method}: ${err instanceof Error ? err.message : String(err)}`,
7751
+ { method, rpcUrl }
7752
+ );
7753
+ }
7754
+ if (json.error) {
7755
+ throw new TwakNetworkError(`RPC error on ${method}: ${json.error.message}`, {
7756
+ method,
7757
+ rpcUrl,
7758
+ rpcError: json.error
7759
+ });
7760
+ }
7761
+ return json.result;
7674
7762
  }
7675
7763
  };
7676
7764
  WalletCoreSigner = class _WalletCoreSigner {
@@ -7705,8 +7793,8 @@ var init_dist3 = __esm({
7705
7793
  if (!ct5) {
7706
7794
  throw new Error(`No CoinType mapping for chain: ${chainKey} (coinId: ${coinId})`);
7707
7795
  }
7708
- const chain2 = new import_trust_sdk3.DefinedChain(ct5);
7709
- const accounts = import_trust_sdk3.AccountManagementService.createDefaultAccountsHdWallet(chain2, this.hdWallet);
7796
+ const chain2 = new import_trust_sdk4.DefinedChain(ct5);
7797
+ const accounts = import_trust_sdk4.AccountManagementService.createDefaultAccountsHdWallet(chain2, this.hdWallet);
7710
7798
  if (accounts.length === 0) {
7711
7799
  throw new Error(`No accounts for chain: ${chainKey}`);
7712
7800
  }
@@ -7725,8 +7813,8 @@ var init_dist3 = __esm({
7725
7813
  if (!ct5) {
7726
7814
  throw new Error(`No CoinType mapping for chain: ${chainKey} (coinId: ${coinId})`);
7727
7815
  }
7728
- const chain2 = new import_trust_sdk3.DefinedChain(ct5);
7729
- const accounts = import_trust_sdk3.AccountManagementService.createDefaultAccountsHdWallet(chain2, this.hdWallet);
7816
+ const chain2 = new import_trust_sdk4.DefinedChain(ct5);
7817
+ const accounts = import_trust_sdk4.AccountManagementService.createDefaultAccountsHdWallet(chain2, this.hdWallet);
7730
7818
  if (accounts.length === 0) {
7731
7819
  throw new Error(`No accounts for chain: ${chainKey}`);
7732
7820
  }
@@ -7937,23 +8025,10 @@ var init_dist3 = __esm({
7937
8025
  hdWallet: this.signer.getHDWallet()
7938
8026
  });
7939
8027
  }
7940
- async approveErc20(chainKey, token, spender, amount) {
7941
- await this.ensureConnected();
7942
- this.resetAutoLockTimer();
7943
- const coinId = chainKeyToCoinType(chainKey);
7944
- if (coinId === void 0) throw new Error(`Unsupported chain: ${chainKey}`);
7945
- const result = await this.blockchainClient.approve({
7946
- assetId: `c${coinId}_t${token}`,
7947
- spender,
7948
- amount,
7949
- hdWallet: this.signer.getHDWallet()
7950
- });
7951
- return { txHash: result.hash };
7952
- }
7953
8028
  async sendTransaction(params) {
7954
8029
  await this.ensureConnected();
7955
8030
  this.resetAutoLockTimer();
7956
- const { getChainByEvmId: getChainByEvmId2, chainKeyToCoinType: chainKeyToCoinType4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
8031
+ const { getChainByEvmId: getChainByEvmId2, chainKeyToCoinType: chainKeyToCoinType3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7957
8032
  let assetId;
7958
8033
  if (params.chainId.startsWith("eip155:")) {
7959
8034
  const chain2 = getChainByEvmId2(params.chainId.replace("eip155:", ""));
@@ -7962,7 +8037,7 @@ var init_dist3 = __esm({
7962
8037
  } else if (params.chainId.startsWith("solana:")) {
7963
8038
  assetId = "c501";
7964
8039
  } else {
7965
- const coinId = chainKeyToCoinType4(params.chainId);
8040
+ const coinId = chainKeyToCoinType3(params.chainId);
7966
8041
  if (coinId === void 0) throw new Error(`Unsupported chain: ${params.chainId}`);
7967
8042
  assetId = `c${coinId}`;
7968
8043
  }
@@ -7974,7 +8049,7 @@ var init_dist3 = __esm({
7974
8049
  serializedMessage: params.data,
7975
8050
  hdWallet
7976
8051
  });
7977
- return result2.hash;
8052
+ return result2.txHash;
7978
8053
  }
7979
8054
  if (hasCalldata || hasValue) {
7980
8055
  const result2 = await this.blockchainClient.executeContract({
@@ -7984,7 +8059,7 @@ var init_dist3 = __esm({
7984
8059
  data: params.data || "0x",
7985
8060
  hdWallet
7986
8061
  });
7987
- return result2.hash;
8062
+ return result2.txHash;
7988
8063
  }
7989
8064
  const result = await this.blockchainClient.transfer({
7990
8065
  assetId,
@@ -7992,7 +8067,7 @@ var init_dist3 = __esm({
7992
8067
  amount: params.value || "0",
7993
8068
  hdWallet
7994
8069
  });
7995
- return result.hash;
8070
+ return result.txHash;
7996
8071
  }
7997
8072
  supportsChain(chainId) {
7998
8073
  return isChainSupported(chainId);
@@ -8032,6 +8107,39 @@ var init_dist3 = __esm({
8032
8107
  return this.signer;
8033
8108
  }
8034
8109
  };
8110
+ TeeWalletProvider = class extends WalletProvider {
8111
+ name = "tee";
8112
+ address = null;
8113
+ async getAddress(_chainId) {
8114
+ if (!this.address) throw new Error("TEE wallet not initialized");
8115
+ return this.address;
8116
+ }
8117
+ async getAddresses() {
8118
+ if (!this.address) return [];
8119
+ return [{ address: this.address, chainId: "eip155:1" }];
8120
+ }
8121
+ async signMessage(_chainId, _message) {
8122
+ throw new Error("TEE signing not yet implemented");
8123
+ }
8124
+ async signTypedData(_chainKey, _chainId, _typedDataJson) {
8125
+ throw new Error("TEE signing not yet implemented");
8126
+ }
8127
+ async sendTransaction(_params) {
8128
+ throw new Error("TEE transaction sending not yet implemented");
8129
+ }
8130
+ supportsChain(_chainId) {
8131
+ return false;
8132
+ }
8133
+ async connect(_chains) {
8134
+ throw new Error("TEE wallet connect not yet implemented");
8135
+ }
8136
+ async disconnect() {
8137
+ this.address = null;
8138
+ }
8139
+ isConnected() {
8140
+ return this.address !== null;
8141
+ }
8142
+ };
8035
8143
  WalletContext = class {
8036
8144
  _state = "unbound";
8037
8145
  _wallet;
@@ -8163,13 +8271,13 @@ var init_dist3 = __esm({
8163
8271
  * to avoid forcing wallet-core to depend on it. The caller (typically the
8164
8272
  * MCP action provider) constructs the WC provider and passes it in.
8165
8273
  */
8166
- async beginWalletConnect(wc2, chains) {
8167
- return this.enqueue(() => this.beginWalletConnectImpl(wc2, chains));
8274
+ async beginWalletConnect(wc3, chains) {
8275
+ return this.enqueue(() => this.beginWalletConnectImpl(wc3, chains));
8168
8276
  }
8169
- async beginWalletConnectImpl(wc2, chains) {
8277
+ async beginWalletConnectImpl(wc3, chains) {
8170
8278
  await this.disconnectWcIfAny();
8171
8279
  try {
8172
- this._pairing = await wc2.connect(chains);
8280
+ this._pairing = await wc3.connect(chains);
8173
8281
  } catch (err) {
8174
8282
  this._wallet = void 0;
8175
8283
  this._pendingWc = void 0;
@@ -8183,20 +8291,20 @@ var init_dist3 = __esm({
8183
8291
  throw err;
8184
8292
  }
8185
8293
  this._wallet = void 0;
8186
- this._pendingWc = wc2;
8294
+ this._pendingWc = wc3;
8187
8295
  this._state = "wc-pairing";
8188
8296
  this._lastError = void 0;
8189
8297
  this._peerName = void 0;
8190
- wc2.onSessionEstablished((session) => {
8191
- if (this._pendingWc !== wc2) return;
8192
- this._wallet = wc2;
8298
+ wc3.onSessionEstablished((session) => {
8299
+ if (this._pendingWc !== wc3) return;
8300
+ this._wallet = wc3;
8193
8301
  this._peerName = session.peerName;
8194
8302
  this._state = "wc-connected";
8195
8303
  this._pendingWc = void 0;
8196
8304
  this._pairing = void 0;
8197
8305
  });
8198
- wc2.onSessionEnded((reason) => {
8199
- if (this._wallet !== wc2) return;
8306
+ wc3.onSessionEnded((reason) => {
8307
+ if (this._wallet !== wc3) return;
8200
8308
  this._wallet = void 0;
8201
8309
  this._pairing = void 0;
8202
8310
  this._peerName = void 0;
@@ -8226,35 +8334,6 @@ var init_dist3 = __esm({
8226
8334
  }
8227
8335
  });
8228
8336
 
8229
- // ../blockchain/dist/index.mjs
8230
- var dist_exports2 = {};
8231
- __export(dist_exports2, {
8232
- BlockchainClient: () => BlockchainClient,
8233
- VerifyCredentialsError: () => VerifyCredentialsError,
8234
- WalletCoreSigner: () => WalletCoreSigner,
8235
- _setWalletCore: () => _setWalletCore,
8236
- applyPolyfills: () => applyPolyfills,
8237
- buildCoinTypeMap: () => buildCoinTypeMap,
8238
- getAmberDomainsByCoinId: () => getAmberDomainsByCoinId,
8239
- getCoinType: () => getCoinType,
8240
- getWalletCore: () => getWalletCore,
8241
- humanAmountToWei: () => humanAmountToWei,
8242
- initTrustSDK: () => initTrustSDK,
8243
- isInitialized: () => isInitialized,
8244
- patchNaasSession: () => patchNaasSession,
8245
- patchNodeStubs: () => patchNodeStubs,
8246
- registerDeviceAndWallet: () => registerDeviceAndWallet,
8247
- setPolyfillCredentials: () => setPolyfillCredentials,
8248
- setPolyfillNaasSessionId: () => setPolyfillNaasSessionId,
8249
- verifyCredentials: () => verifyCredentials
8250
- });
8251
- var init_dist4 = __esm({
8252
- "../blockchain/dist/index.mjs"() {
8253
- "use strict";
8254
- init_dist3();
8255
- }
8256
- });
8257
-
8258
8337
  // ../../node_modules/.pnpm/tslib@1.14.1/node_modules/tslib/tslib.es6.js
8259
8338
  var tslib_es6_exports = {};
8260
8339
  __export(tslib_es6_exports, {
@@ -13555,10 +13634,10 @@ var require_boxen = __commonJS({
13555
13634
  }
13556
13635
  return lines.join(NL);
13557
13636
  };
13558
- var isHex = (color) => color.match(/^#(?:[0-f]{3}){1,2}$/i);
13559
- var isColorValid = (color) => typeof color === "string" && (chalk9[color] || isHex(color));
13560
- var getColorFn = (color) => isHex(color) ? chalk9.hex(color) : chalk9[color];
13561
- var getBGColorFn = (color) => isHex(color) ? chalk9.bgHex(color) : chalk9[camelCase(["bg", color])];
13637
+ var isHex2 = (color) => color.match(/^#(?:[0-f]{3}){1,2}$/i);
13638
+ var isColorValid = (color) => typeof color === "string" && (chalk9[color] || isHex2(color));
13639
+ var getColorFn = (color) => isHex2(color) ? chalk9.hex(color) : chalk9[color];
13640
+ var getBGColorFn = (color) => isHex2(color) ? chalk9.bgHex(color) : chalk9[camelCase(["bg", color])];
13562
13641
  module2.exports = (text, options) => {
13563
13642
  options = {
13564
13643
  padding: 0,
@@ -23039,7 +23118,7 @@ function safeExit() {
23039
23118
  // src/config-loader.ts
23040
23119
  var import_chalk2 = __toESM(require_source());
23041
23120
  init_dist();
23042
- init_dist4();
23121
+ init_dist3();
23043
23122
  init_dist2();
23044
23123
  var EMBEDDED_NAAS_SESSION_ID = "Nr8eogRKLULRxGpVkeMqQBoDpGnZxgYjKK7tu6bKxqUn6eZU";
23045
23124
  function resolveNaasSessionId() {
@@ -28267,7 +28346,7 @@ function weierstrassPoints(opts) {
28267
28346
  const CURVE = validatePointOpts(opts);
28268
28347
  const { Fp } = CURVE;
28269
28348
  const Fn3 = Field(CURVE.n, CURVE.nBitLength);
28270
- const toBytes4 = CURVE.toBytes || ((_c2, point, _isCompressed) => {
28349
+ const toBytes5 = CURVE.toBytes || ((_c2, point, _isCompressed) => {
28271
28350
  const a3 = point.toAffine();
28272
28351
  return concatBytes2(Uint8Array.from([4]), Fp.toBytes(a3.x), Fp.toBytes(a3.y));
28273
28352
  });
@@ -28647,7 +28726,7 @@ function weierstrassPoints(opts) {
28647
28726
  toRawBytes(isCompressed = true) {
28648
28727
  abool("isCompressed", isCompressed);
28649
28728
  this.assertValidity();
28650
- return toBytes4(Point, this, isCompressed);
28729
+ return toBytes5(Point, this, isCompressed);
28651
28730
  }
28652
28731
  toHex(isCompressed = true) {
28653
28732
  abool("isCompressed", isCompressed);
@@ -28951,16 +29030,16 @@ function weierstrass(curveDef) {
28951
29030
  throw new Error("options.strict was renamed to lowS");
28952
29031
  if (format !== void 0 && format !== "compact" && format !== "der")
28953
29032
  throw new Error("format must be compact or der");
28954
- const isHex = typeof sg === "string" || isBytes2(sg);
28955
- const isObj = !isHex && !format && typeof sg === "object" && sg !== null && typeof sg.r === "bigint" && typeof sg.s === "bigint";
28956
- if (!isHex && !isObj)
29033
+ const isHex2 = typeof sg === "string" || isBytes2(sg);
29034
+ const isObj = !isHex2 && !format && typeof sg === "object" && sg !== null && typeof sg.r === "bigint" && typeof sg.s === "bigint";
29035
+ if (!isHex2 && !isObj)
28957
29036
  throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance");
28958
29037
  let _sig = void 0;
28959
29038
  let P5;
28960
29039
  try {
28961
29040
  if (isObj)
28962
29041
  _sig = new Signature(sg.r, sg.s);
28963
- if (isHex) {
29042
+ if (isHex2) {
28964
29043
  try {
28965
29044
  if (format !== "compact")
28966
29045
  _sig = Signature.fromDER(sg);
@@ -39097,22 +39176,6 @@ var WalletConnectProvider = class extends WalletProvider {
39097
39176
  }
39098
39177
  });
39099
39178
  }
39100
- async approveErc20(chainKey, token, spender, amount) {
39101
- const chain2 = getChainById(chainKey);
39102
- if (!chain2) throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported chain: ${chainKey}`);
39103
- const caipChainId = `${chain2.namespace}:${chain2.chainId}`;
39104
- const selector = "0x095ea7b3";
39105
- const paddedSpender = spender.replace(/^0x/, "").toLowerCase().padStart(64, "0");
39106
- const paddedAmount = BigInt(amount).toString(16).padStart(64, "0");
39107
- const data = selector + paddedSpender + paddedAmount;
39108
- const txHash = await this.sendTransaction({
39109
- chainId: caipChainId,
39110
- to: token,
39111
- data,
39112
- value: "0x0"
39113
- });
39114
- return { txHash };
39115
- }
39116
39179
  async sendTransaction(params) {
39117
39180
  const session = this.getActiveSession();
39118
39181
  if (!session) throw new WalletError("WC_NOT_CONNECTED", WC_NOT_CONNECTED_MSG);
@@ -39643,11 +39706,19 @@ var import_prompts = require("@inquirer/prompts");
39643
39706
  init_dist2();
39644
39707
  init_dist();
39645
39708
  var PORTAL_URL = "https://portal.trustwallet.com/dashboard/apps";
39709
+ var WC_DASHBOARD_URL = "https://dashboard.walletconnect.com/";
39646
39710
  var defaultPrompter = {
39647
39711
  promptAccessId: () => (0, import_prompts.input)({
39648
39712
  message: "Access ID:",
39649
39713
  validate: (s2) => s2.trim().length > 0 ? true : "Access ID is required"
39650
39714
  }),
39715
+ promptWalletConnectProjectId: async () => {
39716
+ const wc3 = await (0, import_prompts.input)({
39717
+ message: "WalletConnect Project ID (optional):",
39718
+ required: false
39719
+ });
39720
+ return wc3.trim() || void 0;
39721
+ },
39651
39722
  promptHmacSecret: () => (0, import_prompts.password)({
39652
39723
  message: "HMAC secret:",
39653
39724
  mask: "\u25CF",
@@ -39668,19 +39739,13 @@ async function runCredentialsPhase(opts, deps = makeDefaultDeps()) {
39668
39739
  success(`Credentials already configured (access ID ${existing.accessId.slice(0, 8)})`);
39669
39740
  return;
39670
39741
  }
39671
- const isRotation = existing !== null;
39672
39742
  if (opts.nonInteractive) {
39673
39743
  await runNonInteractive(deps);
39674
39744
  } else {
39675
- info(`Get your API Credentials at: ${PORTAL_URL}
39745
+ info(`Get your Trust Wallet API credentials at: ${PORTAL_URL}
39676
39746
  `);
39677
39747
  await runInteractive(deps);
39678
39748
  }
39679
- if (isRotation) {
39680
- warn(
39681
- "Heads up: if you previously hand-pasted `env: { TWAK_HMAC_SECRET: '\u2026' }` into a harness's MCP config, that value is now stale. The canonical twak entry pulls credentials from ~/.twak/credentials.json \u2014 drop any explicit env blocks so the new secret is used."
39682
- );
39683
- }
39684
39749
  }
39685
39750
  async function runNonInteractive(deps) {
39686
39751
  const accessId = deps.env.TWAK_ACCESS_ID?.trim();
@@ -39691,13 +39756,21 @@ async function runNonInteractive(deps) {
39691
39756
  "Non-interactive setup requires TWAK_ACCESS_ID and TWAK_HMAC_SECRET in the environment."
39692
39757
  );
39693
39758
  }
39694
- const result = await deps.credManager.save({ accessId, hmacSecret });
39759
+ const walletConnectProjectId = deps.env.WALLETCONNECT_PROJECT_ID?.trim() || void 0;
39760
+ const result = await deps.credManager.save({ accessId, hmacSecret, walletConnectProjectId });
39695
39761
  success(`Saved access ID ${accessId.slice(0, 8)} to ${storageLocation(result.keychain)}`);
39696
39762
  }
39697
39763
  async function runInteractive(deps) {
39698
39764
  const accessId = (await deps.prompter.promptAccessId()).trim();
39699
39765
  const hmacSecret = (await deps.prompter.promptHmacSecret()).trim();
39700
- const result = await deps.credManager.save({ accessId, hmacSecret });
39766
+ info(
39767
+ `
39768
+ WalletConnect Project ID is only used when signing via WalletConnect in MCP mode.
39769
+ Skip if you only plan to use the local agent wallet. Get one at: ${WC_DASHBOARD_URL}
39770
+ `
39771
+ );
39772
+ const walletConnectProjectId = (await deps.prompter.promptWalletConnectProjectId())?.trim() || void 0;
39773
+ const result = await deps.credManager.save({ accessId, hmacSecret, walletConnectProjectId });
39701
39774
  success(`Saved access ID ${accessId.slice(0, 8)} to ${storageLocation(result.keychain)}`);
39702
39775
  }
39703
39776
  function storageLocation(keychain) {
@@ -40184,6 +40257,16 @@ function isNonInteractive() {
40184
40257
  function printSetupBanner() {
40185
40258
  process.stderr.write("\n" + import_chalk3.default.bold("\u26D3 Trust Wallet Agent Kit \u2014 Setup") + "\n\n");
40186
40259
  }
40260
+ function printNextSteps() {
40261
+ const harnessNames = ALL_HARNESSES.slice(0, 3).map((h4) => h4.displayName).join(", ");
40262
+ info(
40263
+ `
40264
+ Next steps:
40265
+ 1. Restart your harness (${harnessNames}, etc.) so it picks up twak.
40266
+ 2. Try a sample query, e.g. "What is my wallet address on Ethereum?"
40267
+ `
40268
+ );
40269
+ }
40187
40270
  async function runSetup(opts, phases = defaultPhases) {
40188
40271
  const noFlags = !opts.reconfigureCreds && !opts.harness && !opts.wallet;
40189
40272
  if (noFlags || opts.reconfigureCreds) await phases.credentials(opts);
@@ -40206,6 +40289,7 @@ function registerSetupCommand(program3) {
40206
40289
  printSetupBanner();
40207
40290
  await runSetup(opts);
40208
40291
  success("Setup complete.");
40292
+ printNextSteps();
40209
40293
  } catch (err) {
40210
40294
  handleCommandError(err, false, "Setup failed");
40211
40295
  }
@@ -41539,11 +41623,11 @@ function registerTransferCommand(program3) {
41539
41623
  } catch (err) {
41540
41624
  mapBroadcastError(err);
41541
41625
  }
41542
- const explorerUrl = getExplorerTxUrl(result.chainKey, result.hash);
41626
+ const explorerUrl = getExplorerTxUrl(result.chainKey, result.txHash);
41543
41627
  success("Transfer sent!");
41544
41628
  output(
41545
41629
  {
41546
- hash: result.hash,
41630
+ hash: result.txHash,
41547
41631
  chain: result.chainKey,
41548
41632
  from: address,
41549
41633
  to: resolvedTo,
@@ -41562,7 +41646,681 @@ function registerTransferCommand(program3) {
41562
41646
 
41563
41647
  // src/commands/swap.ts
41564
41648
  init_dist();
41565
- init_dist4();
41649
+ init_dist3();
41650
+
41651
+ // ../contracts/dist/index.mjs
41652
+ init_dist();
41653
+ init_dist();
41654
+ init_dist();
41655
+ init_dist();
41656
+ init_dist3();
41657
+ init_dist();
41658
+ var HEX_PATTERN = /^0x[0-9a-fA-F]*$/;
41659
+ function isHex(value) {
41660
+ return HEX_PATTERN.test(value);
41661
+ }
41662
+ function hexToBytes2(hex2) {
41663
+ if (!isHex(hex2)) {
41664
+ throw new TwakValidationError(`Invalid hex string: ${hex2}`, { kind: "invalidHex" });
41665
+ }
41666
+ const clean2 = hex2.slice(2);
41667
+ if (clean2.length % 2 !== 0) {
41668
+ throw new TwakValidationError(`Hex string has odd length: ${hex2}`, { kind: "invalidHex" });
41669
+ }
41670
+ const out = new Uint8Array(clean2.length / 2);
41671
+ for (let i2 = 0; i2 < out.length; i2++) {
41672
+ out[i2] = parseInt(clean2.slice(i2 * 2, i2 * 2 + 2), 16);
41673
+ }
41674
+ return out;
41675
+ }
41676
+ function bytesToHex2(bytes) {
41677
+ let out = "0x";
41678
+ for (let i2 = 0; i2 < bytes.length; i2++) {
41679
+ out += bytes[i2].toString(16).padStart(2, "0");
41680
+ }
41681
+ return out;
41682
+ }
41683
+ function wc2() {
41684
+ return getWalletCore();
41685
+ }
41686
+ var BRIDGE = {
41687
+ createAbiFunction(name2) {
41688
+ const ctor = wc2().EthereumAbiFunction;
41689
+ if (typeof ctor?.createWithString === "function") {
41690
+ return ctor.createWithString(name2);
41691
+ }
41692
+ return new ctor(name2);
41693
+ },
41694
+ encode(fn2) {
41695
+ return wc2().EthereumAbi.encode(fn2);
41696
+ },
41697
+ decodeOutput(fn2, data) {
41698
+ return wc2().EthereumAbi.decodeOutput(fn2, data);
41699
+ },
41700
+ keccak256(data) {
41701
+ return wc2().Hash.keccak256(data);
41702
+ }
41703
+ };
41704
+ function getAbiBridge() {
41705
+ return BRIDGE;
41706
+ }
41707
+ function findAbiFunction(abi, methodName) {
41708
+ const entry = abi.find(
41709
+ (e2) => e2.type === "function" && e2.name === methodName
41710
+ );
41711
+ if (!entry) {
41712
+ throw new TwakValidationError(`Unknown ABI method: ${methodName}`, {
41713
+ kind: "unknownMethod",
41714
+ methodName
41715
+ });
41716
+ }
41717
+ return entry;
41718
+ }
41719
+ function encodeFunctionCall(abi, methodName, args) {
41720
+ const fnDef = findAbiFunction(abi, methodName);
41721
+ if (args.length !== fnDef.inputs.length) {
41722
+ throw new TwakValidationError(
41723
+ `Wrong arg count for ${methodName}: expected ${fnDef.inputs.length}, got ${args.length}`,
41724
+ {
41725
+ kind: "wrongArgCount",
41726
+ methodName,
41727
+ expected: fnDef.inputs.length,
41728
+ actual: args.length
41729
+ }
41730
+ );
41731
+ }
41732
+ const bridge = getAbiBridge();
41733
+ const fn2 = bridge.createAbiFunction(methodName);
41734
+ fnDef.inputs.forEach((param, i2) => {
41735
+ pushParam(fn2, param, args[i2], methodName);
41736
+ });
41737
+ return bytesToHex2(bridge.encode(fn2));
41738
+ }
41739
+ function pushParam(fn2, param, value, methodName) {
41740
+ const type = param.type;
41741
+ const isOutput = false;
41742
+ if (type === "address") {
41743
+ fn2.addParamAddress(toAddressBytes(value, param, methodName), isOutput);
41744
+ return;
41745
+ }
41746
+ if (type === "bool") {
41747
+ if (typeof value !== "boolean") {
41748
+ throw argTypeMismatch(methodName, param, value, "boolean");
41749
+ }
41750
+ fn2.addParamBool(value, isOutput);
41751
+ return;
41752
+ }
41753
+ if (type === "string") {
41754
+ if (typeof value !== "string") {
41755
+ throw argTypeMismatch(methodName, param, value, "string");
41756
+ }
41757
+ fn2.addParamString(value, isOutput);
41758
+ return;
41759
+ }
41760
+ if (type === "bytes") {
41761
+ fn2.addParamBytes(toBytes4(value, param, methodName), isOutput);
41762
+ return;
41763
+ }
41764
+ const bytesNMatch = /^bytes(\d+)$/.exec(type);
41765
+ if (bytesNMatch) {
41766
+ const size3 = Number(bytesNMatch[1]);
41767
+ fn2.addParamBytesFix(toBytesFix(value, size3, param, methodName), size3, isOutput);
41768
+ return;
41769
+ }
41770
+ const intMatch = /^(u?)int(\d*)$/.exec(type);
41771
+ if (intMatch) {
41772
+ const isUnsigned = intMatch[1] === "u";
41773
+ const bits = intMatch[2] ? Number(intMatch[2]) : 256;
41774
+ if (bits < 8 || bits > 256 || bits % 8 !== 0) {
41775
+ throw new TwakValidationError(
41776
+ `Unsupported integer width ${type} for ${methodName}.${param.name} (expected 8..256, multiple of 8)`,
41777
+ { kind: "argTypeMismatch", methodName, paramName: param.name, type }
41778
+ );
41779
+ }
41780
+ const bigValue = toBigInt2(value, param, methodName);
41781
+ if (isUnsigned) {
41782
+ if (bigValue < 0n || bigValue >= 1n << BigInt(bits)) {
41783
+ throw argTypeMismatch(methodName, param, value, `uint${bits} (0 <= x < 2^${bits})`);
41784
+ }
41785
+ } else {
41786
+ const limit = 1n << BigInt(bits - 1);
41787
+ if (bigValue < -limit || bigValue >= limit) {
41788
+ throw argTypeMismatch(
41789
+ methodName,
41790
+ param,
41791
+ value,
41792
+ `int${bits} (-2^${bits - 1} <= x < 2^${bits - 1})`
41793
+ );
41794
+ }
41795
+ }
41796
+ const bytes = bigIntToBytes32(bigValue, isUnsigned);
41797
+ if (isUnsigned) {
41798
+ fn2.addParamUInt256(bytes, isOutput);
41799
+ } else {
41800
+ fn2.addParamInt256(bytes, isOutput);
41801
+ }
41802
+ return;
41803
+ }
41804
+ throw new TwakValidationError(
41805
+ `Unsupported ABI param type "${type}" for ${methodName}.${param.name}`,
41806
+ {
41807
+ kind: "argTypeMismatch",
41808
+ methodName,
41809
+ paramName: param.name,
41810
+ type
41811
+ }
41812
+ );
41813
+ }
41814
+ function toAddressBytes(value, param, methodName) {
41815
+ if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(value)) {
41816
+ throw argTypeMismatch(methodName, param, value, "address (0x + 40 hex)");
41817
+ }
41818
+ return hexToBytes2(value);
41819
+ }
41820
+ function bigIntToBytes32(value, isUnsigned) {
41821
+ let v5 = value;
41822
+ if (!isUnsigned && v5 < 0n) {
41823
+ v5 = (1n << 256n) + v5;
41824
+ }
41825
+ const out = new Uint8Array(32);
41826
+ for (let i2 = 31; i2 >= 0; i2--) {
41827
+ out[i2] = Number(v5 & 0xffn);
41828
+ v5 >>= 8n;
41829
+ }
41830
+ return out;
41831
+ }
41832
+ function toBigInt2(value, param, methodName) {
41833
+ if (typeof value === "bigint") return value;
41834
+ if (typeof value === "number") {
41835
+ if (!Number.isSafeInteger(value)) {
41836
+ throw argTypeMismatch(
41837
+ methodName,
41838
+ param,
41839
+ value,
41840
+ "safe integer (use bigint or string for values >2^53)"
41841
+ );
41842
+ }
41843
+ return BigInt(value);
41844
+ }
41845
+ if (typeof value === "string" && /^-?\d+$/.test(value)) return BigInt(value);
41846
+ if (typeof value === "string" && /^0x[0-9a-fA-F]+$/.test(value)) return BigInt(value);
41847
+ throw argTypeMismatch(methodName, param, value, "integer (bigint | number | numeric string)");
41848
+ }
41849
+ function toBytes4(value, param, methodName) {
41850
+ if (value instanceof Uint8Array) return value;
41851
+ if (typeof value === "string" && /^0x[0-9a-fA-F]*$/.test(value)) {
41852
+ return hexToBytes2(value);
41853
+ }
41854
+ throw argTypeMismatch(methodName, param, value, "bytes (Uint8Array | 0x-prefixed hex)");
41855
+ }
41856
+ function toBytesFix(value, size3, param, methodName) {
41857
+ const bytes = toBytes4(value, param, methodName);
41858
+ if (bytes.length !== size3) {
41859
+ throw new TwakValidationError(
41860
+ `Wrong byte length for ${methodName}.${param.name}: expected ${size3}, got ${bytes.length}`,
41861
+ {
41862
+ kind: "argTypeMismatch",
41863
+ methodName,
41864
+ paramName: param.name,
41865
+ expected: size3,
41866
+ actual: bytes.length
41867
+ }
41868
+ );
41869
+ }
41870
+ return bytes;
41871
+ }
41872
+ function argTypeMismatch(methodName, param, value, expected) {
41873
+ return new TwakValidationError(
41874
+ `Arg type mismatch for ${methodName}.${param.name}: expected ${expected}, got ${typeof value}`,
41875
+ {
41876
+ kind: "argTypeMismatch",
41877
+ methodName,
41878
+ paramName: param.name,
41879
+ expected,
41880
+ actual: typeof value
41881
+ }
41882
+ );
41883
+ }
41884
+ function decodeFunctionResult(abi, methodName, returnHex) {
41885
+ const fnDef = findAbiFunction(abi, methodName);
41886
+ if (fnDef.outputs.length === 0) return void 0;
41887
+ const data = hexToBytes2(returnHex);
41888
+ if (data.length === 0) {
41889
+ throw new TwakValidationError(
41890
+ `Empty return data for ${methodName} \u2014 call likely reverted or contract has no code at address`,
41891
+ { kind: "decodeFailed", methodName }
41892
+ );
41893
+ }
41894
+ const bridge = getAbiBridge();
41895
+ const fn2 = bridge.createAbiFunction(methodName);
41896
+ fnDef.outputs.forEach((param) => addOutputParam(fn2, param, methodName));
41897
+ const ok = bridge.decodeOutput(fn2, data);
41898
+ if (!ok) {
41899
+ throw new TwakValidationError(`decodeOutput failed for ${methodName}`, {
41900
+ kind: "decodeFailed",
41901
+ methodName
41902
+ });
41903
+ }
41904
+ if (fnDef.outputs.length === 1) {
41905
+ return readOutputParam(fn2, fnDef.outputs[0], 0, methodName);
41906
+ }
41907
+ const unnamed = fnDef.outputs.find((p3) => p3.name === "");
41908
+ if (unnamed) {
41909
+ throw new TwakValidationError(
41910
+ `Cannot decode multi-output function ${methodName}: every output must be named in the ABI (found unnamed output of type "${unnamed.type}")`,
41911
+ { kind: "decodeFailed", methodName }
41912
+ );
41913
+ }
41914
+ const result = /* @__PURE__ */ Object.create(null);
41915
+ fnDef.outputs.forEach((param, i2) => {
41916
+ result[param.name] = readOutputParam(fn2, param, i2, methodName);
41917
+ });
41918
+ return result;
41919
+ }
41920
+ function decodeLog(abi, log) {
41921
+ const events = abi.filter((e2) => e2.type === "event");
41922
+ for (const event of events) {
41923
+ if (event.anonymous) {
41924
+ throw new TwakValidationError(
41925
+ `Decoding anonymous events is not supported (${event.name}): they emit no signature topic to match against`,
41926
+ { kind: "unsupportedType", methodName: event.name }
41927
+ );
41928
+ }
41929
+ const topic0 = bytesToHex2(keccak256Utf8(canonicalEventSignature(event)));
41930
+ if (topic0.toLowerCase() !== (log.topics[0] ?? "").toLowerCase()) continue;
41931
+ const indexed = event.inputs.filter((p3) => p3.indexed);
41932
+ const unindexed = event.inputs.filter((p3) => !p3.indexed);
41933
+ const args = {};
41934
+ indexed.forEach((param, i2) => {
41935
+ const topic = log.topics[i2 + 1];
41936
+ if (!topic) {
41937
+ throw new TwakValidationError(`Missing indexed topic for ${event.name}.${param.name}`, {
41938
+ kind: "decodeFailed",
41939
+ methodName: event.name,
41940
+ paramName: param.name
41941
+ });
41942
+ }
41943
+ args[param.name] = decodeIndexedTopic(param, topic);
41944
+ });
41945
+ if (unindexed.length > 0) {
41946
+ const bridge = getAbiBridge();
41947
+ const fn2 = bridge.createAbiFunction(event.name);
41948
+ unindexed.forEach((param) => addOutputParam(fn2, param, event.name));
41949
+ const dataBytes = hexToBytes2(log.data);
41950
+ const ok = bridge.decodeOutput(fn2, dataBytes);
41951
+ if (!ok) {
41952
+ throw new TwakValidationError(`decodeOutput failed for event ${event.name}`, {
41953
+ kind: "decodeFailed",
41954
+ methodName: event.name
41955
+ });
41956
+ }
41957
+ unindexed.forEach((param, i2) => {
41958
+ args[param.name] = readOutputParam(fn2, param, i2, event.name);
41959
+ });
41960
+ }
41961
+ return { eventName: event.name, args };
41962
+ }
41963
+ return null;
41964
+ }
41965
+ function addOutputParam(fn2, param, methodName) {
41966
+ const type = param.type;
41967
+ if (type === "address") {
41968
+ fn2.addParamAddress(new Uint8Array(20), true);
41969
+ return;
41970
+ }
41971
+ if (type === "bool") {
41972
+ fn2.addParamBool(false, true);
41973
+ return;
41974
+ }
41975
+ if (type === "string") {
41976
+ fn2.addParamString("", true);
41977
+ return;
41978
+ }
41979
+ if (/^int\d*$/.test(type)) {
41980
+ throw new TwakValidationError(
41981
+ `Decoding signed integer outputs (${type}) is not supported in v1 \u2014 wallet-core lacks getParamInt*. See contract-abstraction sc-138140 follow-up.`,
41982
+ { kind: "unsupportedType", methodName, paramName: param.name, type }
41983
+ );
41984
+ }
41985
+ if (/^uint\d*$/.test(type)) {
41986
+ fn2.addParamUInt256(new Uint8Array(32), true);
41987
+ return;
41988
+ }
41989
+ throw new TwakValidationError(
41990
+ `Unsupported output type "${type}" for ${methodName}.${param.name}`,
41991
+ { kind: "decodeFailed", methodName, paramName: param.name }
41992
+ );
41993
+ }
41994
+ function readOutputParam(fn2, param, idx, methodName) {
41995
+ const type = param.type;
41996
+ if (type === "address") {
41997
+ const raw = fn2.getParamAddress(idx, true);
41998
+ return normalizeAddress(raw);
41999
+ }
42000
+ if (type === "bool") {
42001
+ return Boolean(fn2.getParamBool(idx, true));
42002
+ }
42003
+ if (type === "string") {
42004
+ return String(fn2.getParamString(idx, true));
42005
+ }
42006
+ if (/^uint\d*$/.test(type)) {
42007
+ const raw = fn2.getParamUInt256(idx, true);
42008
+ return bytesToBigInt(raw);
42009
+ }
42010
+ throw new TwakValidationError(
42011
+ `Unsupported output type "${type}" for ${methodName}.${param.name}`,
42012
+ { kind: "decodeFailed", methodName, paramName: param.name }
42013
+ );
42014
+ }
42015
+ function decodeIndexedTopic(param, topicHex) {
42016
+ const bytes = hexToBytes2(topicHex);
42017
+ if (bytes.length !== 32) {
42018
+ throw new TwakValidationError(
42019
+ `Indexed topic for ${param.name} is not 32 bytes (got ${bytes.length})`,
42020
+ { kind: "decodeFailed", paramName: param.name }
42021
+ );
42022
+ }
42023
+ const type = param.type;
42024
+ if (type === "address") {
42025
+ return normalizeAddress(bytes.slice(12));
42026
+ }
42027
+ if (type === "bool") {
42028
+ return bytes[31] !== 0;
42029
+ }
42030
+ if (/^int\d*$/.test(type)) {
42031
+ throw new TwakValidationError(
42032
+ `Decoding signed integer topics (${type}) is not supported in v1`,
42033
+ { kind: "unsupportedType", paramName: param.name, type }
42034
+ );
42035
+ }
42036
+ if (/^uint\d*$/.test(type)) {
42037
+ return bytesToBigInt(bytes);
42038
+ }
42039
+ if (/^bytes32$/.test(type)) {
42040
+ return bytesToHex2(bytes);
42041
+ }
42042
+ return bytesToHex2(bytes);
42043
+ }
42044
+ function canonicalEventSignature(event) {
42045
+ return `${event.name}(${event.inputs.map((i2) => i2.type).join(",")})`;
42046
+ }
42047
+ function keccak256Utf8(s2) {
42048
+ return getAbiBridge().keccak256(new TextEncoder().encode(s2));
42049
+ }
42050
+ function bytesToBigInt(bytes) {
42051
+ let v5 = 0n;
42052
+ for (let i2 = 0; i2 < bytes.length; i2++) {
42053
+ v5 = v5 << 8n | BigInt(bytes[i2]);
42054
+ }
42055
+ return v5;
42056
+ }
42057
+ function normalizeAddress(raw) {
42058
+ if (typeof raw === "string") {
42059
+ return raw.startsWith("0x") ? raw.toLowerCase() : "0x" + raw.toLowerCase();
42060
+ }
42061
+ if (raw && typeof raw === "object" && "length" in raw) {
42062
+ return bytesToHex2(raw);
42063
+ }
42064
+ throw new TwakValidationError(`Unexpected address shape from wallet-core: ${typeof raw}`, {
42065
+ kind: "decodeFailed"
42066
+ });
42067
+ }
42068
+ function createContractInstance(opts) {
42069
+ const { abi, address, chainKey, client } = opts;
42070
+ if (!EVM_ADDRESS_RE.test(address)) {
42071
+ throw new TwakValidationError(`Invalid EVM contract address: ${address}`, {
42072
+ kind: "invalidAddress",
42073
+ address
42074
+ });
42075
+ }
42076
+ const chainDef = requireChain(chainKey);
42077
+ if (chainDef.namespace !== "eip155") {
42078
+ throw new TwakValidationError(
42079
+ `Contract calls are EVM-only; ${chainKey} is namespace ${chainDef.namespace}`,
42080
+ { chainKey, namespace: chainDef.namespace }
42081
+ );
42082
+ }
42083
+ return {
42084
+ address,
42085
+ chainKey,
42086
+ abi,
42087
+ read: buildReadProxy(abi, address, chainKey, client),
42088
+ write: buildWriteProxy(abi, address, chainKey, client)
42089
+ };
42090
+ }
42091
+ function indexAbiFunctions(abi) {
42092
+ const names = /* @__PURE__ */ new Set();
42093
+ for (const entry of abi) {
42094
+ if (entry.type === "function") {
42095
+ const name2 = entry.name;
42096
+ if (typeof name2 === "string") names.add(name2);
42097
+ }
42098
+ }
42099
+ return names;
42100
+ }
42101
+ function buildReadProxy(abi, address, chainKey, client) {
42102
+ const fnNames = indexAbiFunctions(abi);
42103
+ return new Proxy({}, {
42104
+ get(_2, prop) {
42105
+ if (typeof prop !== "string") return void 0;
42106
+ if (isJsInternal(prop)) return void 0;
42107
+ if (!fnNames.has(prop)) findAbiFunction(abi, prop);
42108
+ return async (args = []) => {
42109
+ const data = encodeFunctionCall(abi, prop, args);
42110
+ const returnHex = await client.ethCall(chainKey, address, data);
42111
+ return decodeFunctionResult(abi, prop, returnHex);
42112
+ };
42113
+ }
42114
+ });
42115
+ }
42116
+ function buildWriteProxy(abi, address, chainKey, client) {
42117
+ const fnNames = indexAbiFunctions(abi);
42118
+ return new Proxy({}, {
42119
+ get(_2, prop) {
42120
+ if (typeof prop !== "string") return void 0;
42121
+ if (isJsInternal(prop)) return void 0;
42122
+ if (!fnNames.has(prop)) findAbiFunction(abi, prop);
42123
+ return async (args = [], signer) => {
42124
+ if (!signer) {
42125
+ throw new TwakValidationError(`Missing signer for write call to ${prop}`, {
42126
+ kind: "missingSigner",
42127
+ methodName: prop
42128
+ });
42129
+ }
42130
+ const data = encodeFunctionCall(abi, prop, args);
42131
+ const txHash = await signer.sendTransaction({
42132
+ chainId: chainKeyToCaip2(chainKey),
42133
+ to: address,
42134
+ data,
42135
+ value: "0x0"
42136
+ });
42137
+ return makeContractPendingTransaction({
42138
+ txHash,
42139
+ abi,
42140
+ address,
42141
+ chainKey,
42142
+ client,
42143
+ methodName: prop
42144
+ });
42145
+ };
42146
+ }
42147
+ });
42148
+ }
42149
+ function makeContractPendingTransaction(opts) {
42150
+ const { txHash, abi, address, chainKey, client, methodName } = opts;
42151
+ return {
42152
+ txHash,
42153
+ async wait(waitOpts) {
42154
+ const receipt = await client.waitForReceipt(chainKey, txHash, {
42155
+ timeoutMs: waitOpts?.timeoutMs
42156
+ });
42157
+ if (receipt.status === "0x0") {
42158
+ throw new TwakError(
42159
+ `Transaction reverted on-chain: ${receipt.transactionHash}`,
42160
+ "TX_FAILED",
42161
+ { txHash: receipt.transactionHash, methodName, receipt }
42162
+ );
42163
+ }
42164
+ return {
42165
+ txHash: receipt.transactionHash,
42166
+ blockNumber: hexToSafeNumber(receipt.blockNumber, "blockNumber"),
42167
+ events: extractEvents(abi, receipt.logs, address)
42168
+ };
42169
+ }
42170
+ };
42171
+ }
42172
+ function extractEvents(abi, logs, contractAddress) {
42173
+ const events = /* @__PURE__ */ Object.create(null);
42174
+ for (const entry of abi) {
42175
+ if (entry.type === "event") {
42176
+ const name2 = entry.name;
42177
+ if (typeof name2 === "string") events[name2] = [];
42178
+ }
42179
+ }
42180
+ const lowerContract = contractAddress.toLowerCase();
42181
+ for (const log of logs) {
42182
+ if (log.address.toLowerCase() !== lowerContract) continue;
42183
+ const decoded = decodeLog(abi, log);
42184
+ if (!decoded) continue;
42185
+ (events[decoded.eventName] ??= []).push(decoded.args);
42186
+ }
42187
+ return events;
42188
+ }
42189
+ function hexToSafeNumber(hex2, fieldName) {
42190
+ let big;
42191
+ try {
42192
+ big = BigInt(hex2);
42193
+ } catch {
42194
+ throw new TwakValidationError(`Malformed hex for ${fieldName}: ${hex2}`, {
42195
+ kind: "invalidHex",
42196
+ fieldName
42197
+ });
42198
+ }
42199
+ if (big > BigInt(Number.MAX_SAFE_INTEGER)) {
42200
+ throw new TwakValidationError(`${fieldName} exceeds safe integer range (${hex2})`, {
42201
+ kind: "unsafeNumber",
42202
+ fieldName
42203
+ });
42204
+ }
42205
+ return Number(big);
42206
+ }
42207
+ function chainKeyToCaip2(chainKey) {
42208
+ const def = requireChain(chainKey);
42209
+ return `${def.namespace}:${def.chainId}`;
42210
+ }
42211
+ var JS_INTERNAL_PROPS = /* @__PURE__ */ new Set([
42212
+ "then",
42213
+ "toJSON",
42214
+ "toString",
42215
+ "inspect",
42216
+ "constructor",
42217
+ "Symbol(nodejs.util.inspect.custom)"
42218
+ ]);
42219
+ function isJsInternal(prop) {
42220
+ return JS_INTERNAL_PROPS.has(prop);
42221
+ }
42222
+ function defineContract(def) {
42223
+ const { id, abi, addresses } = def;
42224
+ return {
42225
+ id,
42226
+ abi,
42227
+ using(client) {
42228
+ return {
42229
+ id,
42230
+ abi,
42231
+ withAddress(address, chainKey) {
42232
+ return createContractInstance({ abi, address, chainKey, client });
42233
+ },
42234
+ bindAt(chainKey) {
42235
+ const address = lookupAddress(id, addresses, chainKey);
42236
+ return createContractInstance({ abi, address, chainKey, client });
42237
+ },
42238
+ resolve(opts) {
42239
+ const address = opts.addressOverride ?? lookupAddress(id, addresses, opts.chainKey);
42240
+ return createContractInstance({ abi, address, chainKey: opts.chainKey, client });
42241
+ }
42242
+ };
42243
+ }
42244
+ };
42245
+ }
42246
+ function lookupAddress(id, addresses, chainKey) {
42247
+ if (!addresses) {
42248
+ throw new TwakValidationError(
42249
+ `Contract "${id}" has no address registry; use .withAddress(address, chainKey)`,
42250
+ { kind: "noAddressRegistry", contractId: id, chainKey }
42251
+ );
42252
+ }
42253
+ const address = addresses[chainKey];
42254
+ if (!address) {
42255
+ throw new TwakValidationError(
42256
+ `Contract "${id}" has no address for chain "${chainKey}". Known: ${Object.keys(addresses).join(", ") || "(none)"}`,
42257
+ { kind: "noAddressForChain", contractId: id, chainKey }
42258
+ );
42259
+ }
42260
+ return address;
42261
+ }
42262
+ var ERC20_ABI = [
42263
+ {
42264
+ type: "function",
42265
+ name: "transfer",
42266
+ inputs: [
42267
+ { name: "to", type: "address" },
42268
+ { name: "amount", type: "uint256" }
42269
+ ],
42270
+ outputs: [{ name: "", type: "bool" }],
42271
+ stateMutability: "nonpayable"
42272
+ },
42273
+ {
42274
+ type: "function",
42275
+ name: "approve",
42276
+ inputs: [
42277
+ { name: "spender", type: "address" },
42278
+ { name: "amount", type: "uint256" }
42279
+ ],
42280
+ outputs: [{ name: "", type: "bool" }],
42281
+ stateMutability: "nonpayable"
42282
+ },
42283
+ {
42284
+ type: "function",
42285
+ name: "allowance",
42286
+ inputs: [
42287
+ { name: "owner", type: "address" },
42288
+ { name: "spender", type: "address" }
42289
+ ],
42290
+ outputs: [{ name: "", type: "uint256" }],
42291
+ stateMutability: "view"
42292
+ },
42293
+ {
42294
+ type: "function",
42295
+ name: "balanceOf",
42296
+ inputs: [{ name: "owner", type: "address" }],
42297
+ outputs: [{ name: "", type: "uint256" }],
42298
+ stateMutability: "view"
42299
+ },
42300
+ {
42301
+ type: "event",
42302
+ name: "Transfer",
42303
+ inputs: [
42304
+ { name: "from", type: "address", indexed: true },
42305
+ { name: "to", type: "address", indexed: true },
42306
+ { name: "value", type: "uint256" }
42307
+ ],
42308
+ anonymous: false
42309
+ },
42310
+ {
42311
+ type: "event",
42312
+ name: "Approval",
42313
+ inputs: [
42314
+ { name: "owner", type: "address", indexed: true },
42315
+ { name: "spender", type: "address", indexed: true },
42316
+ { name: "value", type: "uint256" }
42317
+ ],
42318
+ anonymous: false
42319
+ }
42320
+ ];
42321
+ var erc20 = defineContract({ id: "erc20", abi: ERC20_ABI });
42322
+
42323
+ // src/commands/swap.ts
41566
42324
  var MAX_SLIPPAGE_PCT = 50;
41567
42325
  var WARN_SLIPPAGE_PCT = 5;
41568
42326
  function registerSwapCommand(program3) {
@@ -41736,24 +42494,13 @@ function registerSwapCommand(program3) {
41736
42494
  const fromAssetAddr = firstStep.from.asset.address;
41737
42495
  const isNative = !fromAssetAddr || fromAssetAddr === NATIVE_TOKEN_ADDRESS;
41738
42496
  if (!isNative && chainConfig.coinId) {
41739
- const assetId = formatTokenId(chainConfig.coinId, fromAssetAddr);
41740
42497
  try {
41741
- const allowance = await blockchainClient.getAllowance({
41742
- assetId,
41743
- owner: walletAddress,
41744
- spender: stepApproval.operatorAddress
41745
- });
42498
+ const allowance = await blockchainClient.contract(erc20).withAddress(fromAssetAddr, fromChain).read.allowance([walletAddress, stepApproval.operatorAddress]);
41746
42499
  const requiredAmount = BigInt(stepApproval.minAmount || parsedAmount);
41747
42500
  if (allowance < requiredAmount) {
41748
42501
  info("Sending token approval...");
41749
- const hdWallet = provider.getSigner().getHDWallet();
41750
- const approveResult = await blockchainClient.approve({
41751
- assetId,
41752
- spender: stepApproval.operatorAddress,
41753
- amount: requiredAmount.toString(),
41754
- hdWallet
41755
- });
41756
- info(`Approval tx: ${getExplorerTxUrl(fromChain, approveResult.hash)}`);
42502
+ const approveResult = await blockchainClient.contract(erc20).withAddress(fromAssetAddr, fromChain).write.approve([stepApproval.operatorAddress, requiredAmount], provider);
42503
+ info(`Approval tx: ${getExplorerTxUrl(fromChain, approveResult.txHash)}`);
41757
42504
  const approvalDeadline = Date.now() + 6e4;
41758
42505
  while (Date.now() < approvalDeadline) {
41759
42506
  await new Promise((r3) => setTimeout(r3, 2e3));
@@ -41761,7 +42508,7 @@ function registerSwapCommand(program3) {
41761
42508
  const status = await blockchainClient.findTransactionStatus(
41762
42509
  fromChain,
41763
42510
  walletAddress,
41764
- approveResult.hash
42511
+ approveResult.txHash
41765
42512
  );
41766
42513
  if (status.confirmed) break;
41767
42514
  if (status.failed)
@@ -41870,10 +42617,10 @@ function registerSwapCommand(program3) {
41870
42617
 
41871
42618
  // src/commands/erc20.ts
41872
42619
  init_dist();
41873
- init_dist4();
42620
+ init_dist3();
41874
42621
  function registerErc20Command(program3) {
41875
- const erc20 = program3.command("erc20").description("ERC-20 token operations");
41876
- erc20.command("approve").description("Approve a spender for token spending").requiredOption("--token <assetId>", "Token asset ID (e.g., c60_t0xA0b8...)").requiredOption("--spender <address>", "Spender address").requiredOption("--amount <n>", 'Amount to approve (in smallest unit, or "unlimited")').option(
42622
+ const erc202 = program3.command("erc20").description("ERC-20 token operations");
42623
+ erc202.command("approve").description("Approve a spender for token spending").requiredOption("--token <assetId>", "Token asset ID (e.g., c60_t0xA0b8...)").requiredOption("--spender <address>", "Spender address").requiredOption("--amount <n>", 'Amount to approve (in smallest unit, or "unlimited")').option(
41877
42624
  "--password <password>",
41878
42625
  "Wallet password (falls back to keychain / TWAK_WALLET_PASSWORD)"
41879
42626
  ).option(
@@ -41917,19 +42664,13 @@ function registerErc20Command(program3) {
41917
42664
  }
41918
42665
  info(`Approving ${opts.spender} to spend from ${address} on ${chainKey}...`);
41919
42666
  const client = provider.getBlockchainClient();
41920
- const hdWallet = provider.getSigner().getHDWallet();
41921
- const result = await client.approve({
41922
- assetId: opts.token,
41923
- spender: opts.spender,
41924
- amount,
41925
- hdWallet
41926
- });
41927
- const explorerUrl = getExplorerTxUrl(result.chainKey, result.hash);
42667
+ const pending = await client.contract(erc20).withAddress(contractAddress, chainKey).write.approve([opts.spender, BigInt(amount)], provider);
42668
+ const explorerUrl = getExplorerTxUrl(chainKey, pending.txHash);
41928
42669
  success("Approval sent!");
41929
42670
  output(
41930
42671
  {
41931
- hash: result.hash,
41932
- chain: result.chainKey,
42672
+ hash: pending.txHash,
42673
+ chain: chainKey,
41933
42674
  owner: address,
41934
42675
  spender: opts.spender,
41935
42676
  token: opts.token,
@@ -41943,7 +42684,7 @@ function registerErc20Command(program3) {
41943
42684
  }
41944
42685
  safeExit();
41945
42686
  });
41946
- erc20.command("revoke").description("Revoke token approval (set allowance to 0)").requiredOption("--token <assetId>", "Token asset ID (e.g., c60_t0xA0b8...)").requiredOption("--spender <address>", "Spender address to revoke").option(
42687
+ erc202.command("revoke").description("Revoke token approval (set allowance to 0)").requiredOption("--token <assetId>", "Token asset ID (e.g., c60_t0xA0b8...)").requiredOption("--spender <address>", "Spender address to revoke").option(
41947
42688
  "--password <password>",
41948
42689
  "Wallet password (falls back to keychain / TWAK_WALLET_PASSWORD)"
41949
42690
  ).option("--json", "JSON output").action(async (opts) => {
@@ -41974,19 +42715,13 @@ function registerErc20Command(program3) {
41974
42715
  const address = await provider.getAddress(chainKey);
41975
42716
  info(`Revoking approval for ${opts.spender} on ${chainKey}...`);
41976
42717
  const client = provider.getBlockchainClient();
41977
- const hdWallet = provider.getSigner().getHDWallet();
41978
- const result = await client.approve({
41979
- assetId: opts.token,
41980
- spender: opts.spender,
41981
- amount: "0",
41982
- hdWallet
41983
- });
41984
- const explorerUrl = getExplorerTxUrl(result.chainKey, result.hash);
42718
+ const pending = await client.contract(erc20).withAddress(contractAddress, chainKey).write.approve([opts.spender, 0n], provider);
42719
+ const explorerUrl = getExplorerTxUrl(chainKey, pending.txHash);
41985
42720
  success("Approval revoked!");
41986
42721
  output(
41987
42722
  {
41988
- hash: result.hash,
41989
- chain: result.chainKey,
42723
+ hash: pending.txHash,
42724
+ chain: chainKey,
41990
42725
  owner: address,
41991
42726
  spender: opts.spender,
41992
42727
  token: opts.token,
@@ -41999,7 +42734,7 @@ function registerErc20Command(program3) {
41999
42734
  }
42000
42735
  safeExit();
42001
42736
  });
42002
- erc20.command("allowance").description("Check token allowance").requiredOption("--token <assetId>", "Token asset ID (e.g., c60_t0xA0b8...)").requiredOption("--owner <address>", "Owner address").requiredOption("--spender <address>", "Spender address").option("--json", "JSON output").action(async (opts) => {
42737
+ erc202.command("allowance").description("Check token allowance").requiredOption("--token <assetId>", "Token asset ID (e.g., c60_t0xA0b8...)").requiredOption("--owner <address>", "Owner address").requiredOption("--spender <address>", "Spender address").option("--json", "JSON output").action(async (opts) => {
42003
42738
  try {
42004
42739
  await ensureTrustSDK();
42005
42740
  let coinId;
@@ -42019,10 +42754,10 @@ function registerErc20Command(program3) {
42019
42754
  );
42020
42755
  }
42021
42756
  try {
42022
- const { getWalletCore: getWalletCore2, getCoinType: getCoinType2 } = await Promise.resolve().then(() => (init_dist4(), dist_exports2));
42023
- const wc2 = getWalletCore2();
42757
+ const { getWalletCore: getWalletCore2, getCoinType: getCoinType2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports2));
42758
+ const wc3 = getWalletCore2();
42024
42759
  const ct5 = getCoinType2(coinId);
42025
- if (ct5 && !wc2.CoinTypeExt.validate(ct5, contractAddress)) {
42760
+ if (ct5 && !wc3.CoinTypeExt.validate(ct5, contractAddress)) {
42026
42761
  throw new Erc20Error(
42027
42762
  "VALIDATION_ERROR",
42028
42763
  `Invalid contract address in asset ID: ${contractAddress}`
@@ -42031,12 +42766,15 @@ function registerErc20Command(program3) {
42031
42766
  } catch (err) {
42032
42767
  if (err instanceof CommandError) throw err;
42033
42768
  }
42769
+ const chainKey = coinTypeToChainKey(coinId);
42770
+ if (!chainKey) {
42771
+ throw new Erc20Error(
42772
+ "CHAIN_UNSUPPORTED",
42773
+ `Unsupported coin type in asset ID: ${opts.token}`
42774
+ );
42775
+ }
42034
42776
  const client = new BlockchainClient();
42035
- const allowance = await client.getAllowance({
42036
- assetId: opts.token,
42037
- owner: opts.owner,
42038
- spender: opts.spender
42039
- });
42777
+ const allowance = await client.contract(erc20).withAddress(contractAddress, chainKey).read.allowance([opts.owner, opts.spender]);
42040
42778
  output(
42041
42779
  {
42042
42780
  token: opts.token,
@@ -42072,8 +42810,8 @@ async function resolveTokenLabel(tokenId, service) {
42072
42810
  const chainTokens = COMMON_TOKENS[chainKey];
42073
42811
  if (chainTokens) {
42074
42812
  const normalizedAddr = contractAddress.toLowerCase();
42075
- for (const [sym, addr] of Object.entries(chainTokens)) {
42076
- if (addr.toLowerCase() === normalizedAddr) {
42813
+ for (const [sym, entry] of Object.entries(chainTokens)) {
42814
+ if (entry.address.toLowerCase() === normalizedAddr) {
42077
42815
  await assetCache.set(tokenId, sym);
42078
42816
  return sym;
42079
42817
  }
@@ -42350,7 +43088,7 @@ var TransactionService = class {
42350
43088
  };
42351
43089
 
42352
43090
  // src/commands/query.ts
42353
- init_dist4();
43091
+ init_dist3();
42354
43092
  var import_chalk7 = __toESM(require_source());
42355
43093
  function handleError(err, json) {
42356
43094
  handleCommandError(err, json ?? false, "Query failed");
@@ -42364,7 +43102,7 @@ function registerQueryCommand(program3) {
42364
43102
  const client = await getClient();
42365
43103
  const swapClient = new SwapClient(client);
42366
43104
  const tokens = token.split(",").map((t) => t.trim()).filter(Boolean);
42367
- const resolveChain = (t) => {
43105
+ const resolveChain2 = (t) => {
42368
43106
  if (opts.chain) return opts.chain;
42369
43107
  if (Object.prototype.hasOwnProperty.call(CHAINS, t.toLowerCase())) return t.toLowerCase();
42370
43108
  const bySymbol = Object.entries(CHAINS).find(
@@ -42387,7 +43125,7 @@ function registerQueryCommand(program3) {
42387
43125
  const assetIds = [];
42388
43126
  const tokenMeta = [];
42389
43127
  for (const t of tokens) {
42390
- const chain3 = resolveChain(t);
43128
+ const chain3 = resolveChain2(t);
42391
43129
  const info2 = resolveToken(t, chain3);
42392
43130
  if (!info2) {
42393
43131
  warn(`Unknown token: ${t.toUpperCase()}`);
@@ -42432,7 +43170,7 @@ function registerQueryCommand(program3) {
42432
43170
  }
42433
43171
  return;
42434
43172
  }
42435
- const chain2 = resolveChain(token);
43173
+ const chain2 = resolveChain2(token);
42436
43174
  const tokenInfo = resolveToken(token, chain2);
42437
43175
  let price = null;
42438
43176
  if (tokenInfo) {
@@ -42695,10 +43433,10 @@ function registerQueryCommand(program3) {
42695
43433
  if (!chain2) throw new QueryError("CHAIN_UNSUPPORTED", `Unknown coin ID: ${coinId}`);
42696
43434
  try {
42697
43435
  await ensureTrustSDK();
42698
- const { getWalletCore: getWalletCore2, getCoinType: getCoinType2 } = await Promise.resolve().then(() => (init_dist4(), dist_exports2));
42699
- const wc2 = getWalletCore2();
43436
+ const { getWalletCore: getWalletCore2, getCoinType: getCoinType2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports2));
43437
+ const wc3 = getWalletCore2();
42700
43438
  const ct5 = getCoinType2(coinId);
42701
- if (ct5 && !wc2.CoinTypeExt.validate(ct5, opts.address)) {
43439
+ if (ct5 && !wc3.CoinTypeExt.validate(ct5, opts.address)) {
42702
43440
  throw new QueryError("VALIDATION_ERROR", `Invalid address format for ${chain2.name}`);
42703
43441
  }
42704
43442
  } catch (err) {
@@ -42777,11 +43515,11 @@ function registerQueryCommand(program3) {
42777
43515
  const coinId = parseInt(coinIdStr, 10);
42778
43516
  try {
42779
43517
  await ensureTrustSDK();
42780
- const { getWalletCore: getWalletCore2, getCoinType: getCoinType2 } = await Promise.resolve().then(() => (init_dist4(), dist_exports2));
42781
- const wc2 = getWalletCore2();
43518
+ const { getWalletCore: getWalletCore2, getCoinType: getCoinType2 } = await Promise.resolve().then(() => (init_dist3(), dist_exports2));
43519
+ const wc3 = getWalletCore2();
42782
43520
  const ct5 = getCoinType2(coinId);
42783
43521
  if (ct5) {
42784
- const isValid = wc2.CoinTypeExt.validate(ct5, opts.address);
43522
+ const isValid = wc3.CoinTypeExt.validate(ct5, opts.address);
42785
43523
  if (!isValid) {
42786
43524
  const chainInfo = getChainByCoinId(coinId);
42787
43525
  const label = chainInfo?.name ?? `coin ${coinId}`;
@@ -42940,6 +43678,7 @@ init_dist();
42940
43678
  var import_zod5 = require("zod");
42941
43679
  init_dist();
42942
43680
  var import_zod6 = require("zod");
43681
+ init_dist();
42943
43682
  var import_zod7 = require("zod");
42944
43683
  init_dist();
42945
43684
  var import_zod8 = require("zod");
@@ -42950,6 +43689,7 @@ var import_zod10 = require("zod");
42950
43689
  init_dist3();
42951
43690
  init_dist();
42952
43691
  var import_zod11 = require("zod");
43692
+ init_dist();
42953
43693
  var import_zod12 = require("zod");
42954
43694
  init_dist();
42955
43695
  var import_zod13 = require("zod");
@@ -43383,7 +44123,6 @@ async function payAndFetch(args) {
43383
44123
  blockchainClient: bc2,
43384
44124
  chainKey,
43385
44125
  chainId: evmChainId,
43386
- coinId: CHAINS[chainKey].coinId,
43387
44126
  owner: fromAddress,
43388
44127
  tokenAddress,
43389
44128
  requiredAmount: BigInt(amount),
@@ -43433,12 +44172,8 @@ async function payAndFetch(args) {
43433
44172
  }
43434
44173
  var MAX_UINT256_DECIMAL = (2n ** 256n - 1n).toString();
43435
44174
  async function ensurePermit2Allowance(args) {
43436
- const assetId = `c${args.coinId}_t${args.tokenAddress}`;
43437
- const allowance = await args.blockchainClient.getAllowance({
43438
- assetId,
43439
- owner: args.owner,
43440
- spender: PERMIT2_ADDRESS
43441
- });
44175
+ const token = args.blockchainClient.contract(erc20).withAddress(args.tokenAddress, args.chainKey);
44176
+ const allowance = await token.read.allowance([args.owner, PERMIT2_ADDRESS]);
43442
44177
  if (allowance >= args.requiredAmount) return null;
43443
44178
  const ctx = {
43444
44179
  token: args.tokenAddress,
@@ -43449,19 +44184,18 @@ async function ensurePermit2Allowance(args) {
43449
44184
  };
43450
44185
  const approved = await args.confirmApproval?.(ctx) === true;
43451
44186
  if (!approved) {
44187
+ const assetId = formatTokenId(CHAINS[args.chainKey].coinId, args.tokenAddress);
43452
44188
  throw new X402Error(
43453
44189
  "PERMIT2_APPROVAL_REQUIRED",
43454
- `Permit2 needs a one-time approval for ${args.tokenAddress} on ${args.chainKey}. Run: twak erc20 approve --asset ${assetId} --spender ${PERMIT2_ADDRESS} --amount unlimited`
44190
+ `Permit2 needs a one-time approval for ${args.tokenAddress} on ${args.chainKey}. Run: twak erc20 approve --token ${assetId} --spender ${PERMIT2_ADDRESS} --amount unlimited`
43455
44191
  );
43456
44192
  }
43457
- const { txHash } = await args.walletProvider.approveErc20(
43458
- args.chainKey,
43459
- args.tokenAddress,
43460
- PERMIT2_ADDRESS,
43461
- MAX_UINT256_DECIMAL
44193
+ const pending = await token.write.approve(
44194
+ [PERMIT2_ADDRESS, BigInt(MAX_UINT256_DECIMAL)],
44195
+ args.walletProvider
43462
44196
  );
43463
- await waitForTxConfirmation(args.blockchainClient, args.chainKey, args.owner, txHash);
43464
- return { token: args.tokenAddress, chain: args.chainKey, txHash };
44197
+ await waitForTxConfirmation(args.blockchainClient, args.chainKey, args.owner, pending.txHash);
44198
+ return { token: args.tokenAddress, chain: args.chainKey, txHash: pending.txHash };
43465
44199
  }
43466
44200
  var APPROVE_TIMEOUT_MS = 6e4;
43467
44201
  var APPROVE_POLL_MS = 2e3;
@@ -43812,6 +44546,8 @@ var ActionRegistry = class {
43812
44546
  return this.providers;
43813
44547
  }
43814
44548
  };
44549
+ var CHAIN_DESC = 'Chain (e.g., "ethereum", "bsc", "solana"). Accepts chain key, CAIP-2 ("eip155:1"), EVM chainId ("56", "0x38"), aliases ("matic", "op"), and asset IDs ("c60").';
44550
+ var TOKEN_ADDR_DESC = "Token contract address (ERC-20 / SPL). Omit for the chain native coin (ETH, BNB, SOL, etc.).";
43815
44551
  var BalanceActionProvider = class extends ActionProvider {
43816
44552
  name = "balance";
43817
44553
  description = "Check token balances and holdings";
@@ -43821,25 +44557,14 @@ var BalanceActionProvider = class extends ActionProvider {
43821
44557
  getBalance() {
43822
44558
  return {
43823
44559
  name: "get_balance",
43824
- description: 'Read the on-chain balance of a single asset for a wallet address. Pass `chainKey` as one of the supported chain keys: "ethereum", "bsc", "base", "polygon", "arbitrum", "optimism", "avalanche", "fantom", "linea", "scroll", "zksync", "blast", "sonic", "celo", "aurora", "solana", "bitcoin", "litecoin", "dogecoin", "tron", "cosmos", "near", "aptos", "ton", "sui". Omit `tokenAddress` for the chain\'s native coin (ETH, BNB, SOL, BTC, ...). Provide `tokenAddress` to read an ERC-20 / SPL token (e.g. tokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" with chainKey="base" for USDC on Base; tokenAddress="0x55d398326f99059fF775485246999027B3197955" with chainKey="bsc" for USDT on BSC).',
44560
+ description: 'Read the on-chain balance of a single asset for a wallet address. Omit `tokenAddress` for the chain native coin (ETH, BNB, SOL, BTC, ...). Provide `tokenAddress` to read an ERC-20 / SPL token (e.g. tokenAddress="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" with chain="base" for USDC on Base).',
43825
44561
  schema: import_zod3.z.object({
43826
44562
  address: import_zod3.z.string().describe("Wallet address on the specified chain."),
43827
- chainKey: import_zod3.z.string().describe(
43828
- 'Chain key (e.g. "ethereum", "bsc", "base", "polygon", "solana"). Lowercase, no spaces. See the action description for the full supported list.'
43829
- ),
43830
- tokenAddress: import_zod3.z.string().trim().min(1).optional().describe(
43831
- "Optional. ERC-20 or SPL token contract address on the chosen chain. Omit (or pass undefined) to read the chain's native coin balance. Empty or whitespace-only strings are rejected."
43832
- )
44563
+ chain: import_zod3.z.string().describe(CHAIN_DESC),
44564
+ tokenAddress: import_zod3.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC)
43833
44565
  }),
43834
44566
  invoke: async (ctx, input2) => {
43835
- const coinId = chainKeyToCoinType(input2.chainKey);
43836
- if (coinId === void 0) {
43837
- throw new WalletError(
43838
- "CHAIN_UNSUPPORTED",
43839
- `Unsupported chain: "${input2.chainKey}". Supported keys include "ethereum", "bsc", "base", "polygon", "arbitrum", "optimism", "avalanche", "solana", "bitcoin" and others.`
43840
- );
43841
- }
43842
- const assetId = formatTokenId(coinId, input2.tokenAddress);
44567
+ const { assetId } = resolveAsset(input2.chain, input2.tokenAddress);
43843
44568
  return fetchBalance(ctx.httpClient, assetId, input2.address);
43844
44569
  }
43845
44570
  };
@@ -43847,26 +44572,32 @@ var BalanceActionProvider = class extends ActionProvider {
43847
44572
  getTokenHoldings() {
43848
44573
  return {
43849
44574
  name: "get_token_holdings",
43850
- description: "Get ERC-20 token holdings for an address",
44575
+ description: "Get ERC-20 / SPL token holdings for a wallet address on a chain. Returns the list of tokens the address holds. Omit `tokenAddress` to list all token holdings on the chain.",
43851
44576
  schema: import_zod3.z.object({
43852
- address: import_zod3.z.string().describe("Wallet address"),
43853
- coin: import_zod3.z.coerce.number().describe("SLIP44 coin ID")
44577
+ address: import_zod3.z.string().describe("Wallet address on the specified chain."),
44578
+ chain: import_zod3.z.string().describe(CHAIN_DESC),
44579
+ tokenAddress: import_zod3.z.string().trim().min(1).optional().describe(
44580
+ "Token contract address (ERC-20 / SPL). Omit to list all token holdings on the chain; provide an address to filter to that single token."
44581
+ )
43854
44582
  }),
43855
44583
  invoke: async (ctx, input2) => {
44584
+ const { chainKey, coinId } = resolveAsset(input2.chain);
43856
44585
  const response = await ctx.httpClient.gateway(
43857
44586
  "POST",
43858
44587
  "/v1/assets",
43859
44588
  {
43860
44589
  version: 2,
43861
44590
  includeDisabledAssets: false,
43862
- assets: [{ address: input2.address, coin: input2.coin }]
44591
+ assets: [{ address: input2.address, coin: coinId }]
43863
44592
  }
43864
44593
  );
43865
44594
  const holdings = Array.isArray(response) ? response : response.docs ?? response.data ?? [];
44595
+ const tokenFilter = input2.tokenAddress?.toLowerCase();
43866
44596
  const tokens = [];
43867
44597
  for (const h4 of holdings) {
43868
44598
  if (Array.isArray(h4.tokens)) {
43869
44599
  for (const t of h4.tokens) {
44600
+ if (tokenFilter && String(t.contract).toLowerCase() !== tokenFilter) continue;
43870
44601
  tokens.push({
43871
44602
  symbol: t.symbol ?? t.contract,
43872
44603
  contract: t.contract,
@@ -43878,7 +44609,7 @@ var BalanceActionProvider = class extends ActionProvider {
43878
44609
  }
43879
44610
  }
43880
44611
  }
43881
- return { address: input2.address, coin: input2.coin, tokens };
44612
+ return { address: input2.address, chain: chainKey, tokens };
43882
44613
  }
43883
44614
  };
43884
44615
  }
@@ -43888,7 +44619,9 @@ var BalanceActionProvider = class extends ActionProvider {
43888
44619
  description: "Search for tokens by name, symbol, or address",
43889
44620
  schema: import_zod3.z.object({
43890
44621
  query: import_zod3.z.string().describe("Search query"),
43891
- networks: import_zod3.z.string().optional().describe("Comma-separated coin IDs to filter"),
44622
+ chains: import_zod3.z.string().optional().describe(
44623
+ `Comma-separated list of chains to restrict the search. Each entry accepts the same forms as the canonical \`chain\` input. ${CHAIN_DESC}`
44624
+ ),
43892
44625
  limit: import_zod3.z.coerce.number().optional().default(10).describe("Max results")
43893
44626
  }),
43894
44627
  invoke: async (ctx, input2) => {
@@ -43898,52 +44631,41 @@ var BalanceActionProvider = class extends ActionProvider {
43898
44631
  version: "24",
43899
44632
  limit: (input2.limit || 10).toString()
43900
44633
  });
43901
- if (input2.networks) params.set("networks", input2.networks);
44634
+ const chainsInput = input2.chains;
44635
+ if (chainsInput) {
44636
+ const coinIds = chainsInput.split(",").map((c3) => c3.trim()).filter((c3) => c3.length > 0).map((c3) => resolveAsset(c3).coinId.toString()).join(",");
44637
+ if (coinIds) params.set("networks", coinIds);
44638
+ }
43902
44639
  return ctx.httpClient.gateway("GET", "/v1/search/assets", void 0, params.toString());
43903
44640
  }
43904
44641
  };
43905
44642
  }
43906
44643
  };
43907
- var ERC20_TRANSFER_SIG = "0xa9059cbb";
43908
44644
  var NATIVE_TOKEN_PLACEHOLDER = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
43909
44645
  var DEFAULT_MAX_TRANSFER_USD2 = 1e4;
43910
- function encodeErc20Transfer(to3, amount) {
43911
- const paddedTo = to3.slice(2).padStart(64, "0");
43912
- const paddedAmount = amount.toString(16).padStart(64, "0");
43913
- return `${ERC20_TRANSFER_SIG}${paddedTo}${paddedAmount}`;
43914
- }
43915
- function extractEvmChainId(chainId) {
43916
- if (chainId.startsWith("eip155:")) return chainId.split(":")[1];
43917
- return chainId;
43918
- }
43919
- function toCaip2(chain2) {
43920
- let caip2;
43921
- if (chain2.includes(":")) {
43922
- caip2 = chain2;
43923
- } else {
43924
- const def = getChainById(chain2);
43925
- if (!def) {
43926
- throw new TransferError(
43927
- "CHAIN_UNSUPPORTED",
43928
- `Unknown chain: ${chain2}. Use a chain key like ethereum, bsc, polygon, arbitrum, optimism, base, or a CAIP-2 id like eip155:1.`
43929
- );
43930
- }
43931
- caip2 = `${def.namespace}:${def.chainId}`;
43932
- }
43933
- if (!caip2.startsWith("eip155:")) {
44646
+ function resolveEvmChain(chain2) {
44647
+ const chainKey = resolveChain(chain2);
44648
+ const def = requireChain(chainKey);
44649
+ if (def.namespace !== "eip155") {
43934
44650
  throw new TransferError(
43935
44651
  "CHAIN_UNSUPPORTED",
43936
- `Transfer is only supported for EVM chains (eip155:*). Got: ${chain2}`
44652
+ `Transfer is only supported for EVM chains. Got: ${chain2}`
43937
44653
  );
43938
44654
  }
43939
- return caip2;
44655
+ return { chainKey, caip2: `eip155:${def.chainId}` };
43940
44656
  }
43941
44657
  var TransferActionProvider = class extends ActionProvider {
43942
- constructor(walletContext, priceLookup, maxTransferUsd = DEFAULT_MAX_TRANSFER_USD2) {
44658
+ // `blockchainClient` is the 4th (optional) arg, not the 1st, to avoid
44659
+ // churning the ~14 existing test sites that already pass only walletContext
44660
+ // (+ optional priceLookup / maxTransferUsd). TokenActionProvider uses
44661
+ // blockchainClient as its 1st arg — that asymmetry is intentional for this
44662
+ // PR; normalize in a follow-up once the integration branch settles.
44663
+ constructor(walletContext, priceLookup, maxTransferUsd = DEFAULT_MAX_TRANSFER_USD2, blockchainClient) {
43943
44664
  super();
43944
44665
  this.walletContext = walletContext;
43945
44666
  this.priceLookup = priceLookup;
43946
44667
  this.maxTransferUsd = maxTransferUsd;
44668
+ this.blockchainClient = blockchainClient;
43947
44669
  }
43948
44670
  name = "transfer";
43949
44671
  description = "Transfer tokens to another address";
@@ -43955,24 +44677,28 @@ var TransferActionProvider = class extends ActionProvider {
43955
44677
  name: "transfer",
43956
44678
  description: 'Transfer native or ERC-20 tokens to a destination address on any EVM chain. Resolves ENS for the "to" field, applies a USD safety cap, and works with both the local agent wallet and an external WalletConnect-paired wallet.',
43957
44679
  schema: import_zod4.z.object({
43958
- chain: import_zod4.z.string().describe(
43959
- 'Chain name (e.g., ethereum, bsc, polygon, arbitrum, base). CAIP-2 ids like "eip155:1" also accepted.'
43960
- ),
44680
+ chain: import_zod4.z.string().describe(CHAIN_DESC),
43961
44681
  to: import_zod4.z.string().describe("Destination address or ENS name (e.g., vitalik.eth)"),
43962
44682
  amount: import_zod4.z.string().describe('Amount in human-readable format (e.g., "1.5", "0.01")'),
43963
- token: import_zod4.z.string().optional().describe("ERC-20 token contract address (omit for native token)"),
43964
- decimals: import_zod4.z.coerce.number().optional().default(18).describe("Token decimals (default 18, use 6 for USDC/USDT)")
44683
+ tokenAddress: import_zod4.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44684
+ decimals: import_zod4.z.coerce.number().int().min(0).max(36).optional().describe(
44685
+ "Token decimals (non-negative integer). Omit for native coins and for tokens twak recognizes \u2014 decimals are derived from `COMMON_TOKENS` per chain (BSC stables are 18, USDC on Ethereum is 6, etc.). REQUIRED when tokenAddress is a contract twak does not recognize; look up the value on the token contract."
44686
+ )
43965
44687
  }),
43966
44688
  invoke: async (_ctx, input2) => {
43967
44689
  const signer = this.walletContext.activeWallet();
43968
- const caip2 = toCaip2(input2.chain);
44690
+ const { chainKey, caip2 } = resolveEvmChain(input2.chain);
44691
+ const tokenAddress = input2.tokenAddress?.toLowerCase() === NATIVE_TOKEN_PLACEHOLDER.toLowerCase() ? void 0 : input2.tokenAddress;
44692
+ const decimals = input2.decimals ?? lookupKnownTokenDecimals(chainKey, tokenAddress);
44693
+ if (decimals === void 0) {
44694
+ throw new TransferError(
44695
+ "VALIDATION_ERROR",
44696
+ `Cannot derive decimals for ${tokenAddress} on ${chainKey}. Provide \`decimals\` explicitly.`
44697
+ );
44698
+ }
43969
44699
  if (this.priceLookup) {
43970
44700
  try {
43971
- const evmId2 = extractEvmChainId(caip2);
43972
- const chain22 = getChainById(evmId2) ?? getChainByEvmId(evmId2);
43973
- const chainKey = chain22 ? coinTypeToChainKey(chain22.coinId) ?? evmId2 : caip2;
43974
- const tokenAddress = input2.token || "";
43975
- const price = await this.priceLookup.getTokenPrice(chainKey, tokenAddress);
44701
+ const price = await this.priceLookup.getTokenPrice(chainKey, tokenAddress ?? "");
43976
44702
  if (price && price > 0) {
43977
44703
  const usdValue = parseFloat(input2.amount) * price;
43978
44704
  if (usdValue > this.maxTransferUsd) {
@@ -43987,18 +44713,18 @@ var TransferActionProvider = class extends ActionProvider {
43987
44713
  }
43988
44714
  }
43989
44715
  const resolvedTo = await resolveAddressOrName(input2.to);
43990
- const atomicAmount = parseAmount(input2.amount, input2.decimals ?? 18);
43991
- const isErc20 = input2.token && input2.token !== NATIVE_TOKEN_PLACEHOLDER;
44716
+ const atomicAmount = parseAmount(input2.amount, decimals);
44717
+ const isErc20 = Boolean(tokenAddress);
43992
44718
  let txHash;
43993
- const evmId = extractEvmChainId(caip2);
43994
44719
  if (isErc20) {
43995
- const calldata = encodeErc20Transfer(resolvedTo, BigInt(atomicAmount));
43996
- txHash = await signer.sendTransaction({
43997
- chainId: caip2,
43998
- to: input2.token,
43999
- data: calldata,
44000
- value: "0x0"
44001
- });
44720
+ if (!this.blockchainClient) {
44721
+ throw new TransferError(
44722
+ "WALLET_NOT_FOUND",
44723
+ "BlockchainClient not available \u2014 ERC-20 transfer requires a configured blockchain client"
44724
+ );
44725
+ }
44726
+ const pending = await this.blockchainClient.contract(erc20).withAddress(tokenAddress, chainKey).write.transfer([resolvedTo, BigInt(atomicAmount)], signer);
44727
+ txHash = pending.txHash;
44002
44728
  } else {
44003
44729
  txHash = await signer.sendTransaction({
44004
44730
  chainId: caip2,
@@ -44007,8 +44733,7 @@ var TransferActionProvider = class extends ActionProvider {
44007
44733
  value: "0x" + BigInt(atomicAmount).toString(16)
44008
44734
  });
44009
44735
  }
44010
- const chain2 = getChainByEvmId(evmId);
44011
- const explorerUrl = getExplorerTxUrl(chain2?.id ?? caip2, txHash);
44736
+ const explorerUrl = getExplorerTxUrl(chainKey, txHash);
44012
44737
  return {
44013
44738
  success: true,
44014
44739
  txHash,
@@ -44077,9 +44802,9 @@ var SwapActionProvider = class extends ActionProvider {
44077
44802
  name: "swap",
44078
44803
  description: "Execute a token swap via Trust Wallet (supports all chains including cross-chain)",
44079
44804
  schema: import_zod5.z.object({
44080
- fromChain: import_zod5.z.string().describe("Source chain (e.g., ethereum, polygon, solana)"),
44805
+ fromChain: import_zod5.z.string().describe(`Source chain. ${CHAIN_DESC}`),
44081
44806
  fromToken: import_zod5.z.string().describe("Source token symbol or address"),
44082
- toChain: import_zod5.z.string().describe("Destination chain"),
44807
+ toChain: import_zod5.z.string().describe(`Destination chain. ${CHAIN_DESC}`),
44083
44808
  toToken: import_zod5.z.string().describe("Destination token symbol or address"),
44084
44809
  amount: import_zod5.z.string().describe("Human-readable amount to swap"),
44085
44810
  slippage: import_zod5.z.string().optional().default("1").describe("Slippage tolerance % (max 50)")
@@ -44089,6 +44814,8 @@ var SwapActionProvider = class extends ActionProvider {
44089
44814
  throw new SwapError("WALLET_NOT_FOUND", "Swap client not configured");
44090
44815
  }
44091
44816
  const signer = this.walletContext.activeWallet();
44817
+ const fromChain = resolveChain(input2.fromChain);
44818
+ const toChain = resolveChain(input2.toChain);
44092
44819
  const slippage = parseFloat(input2.slippage ?? "1");
44093
44820
  if (isNaN(slippage) || slippage <= 0 || slippage > 50) {
44094
44821
  return {
@@ -44097,28 +44824,28 @@ var SwapActionProvider = class extends ActionProvider {
44097
44824
  message: `Slippage must be greater than 0% and at most 50%. Got: ${input2.slippage}`
44098
44825
  };
44099
44826
  }
44100
- const fromTokenInfo = resolveToken(input2.fromToken, input2.fromChain);
44827
+ const fromTokenInfo = resolveToken(input2.fromToken, fromChain);
44101
44828
  if (!fromTokenInfo) {
44102
44829
  return {
44103
44830
  success: false,
44104
44831
  code: "TOKEN_NOT_FOUND",
44105
- message: `Unknown token: ${input2.fromToken} on ${input2.fromChain}`
44832
+ message: `Unknown token: ${input2.fromToken} on ${fromChain}`
44106
44833
  };
44107
44834
  }
44108
- const toTokenInfo = resolveToken(input2.toToken, input2.toChain);
44835
+ const toTokenInfo = resolveToken(input2.toToken, toChain);
44109
44836
  if (!toTokenInfo) {
44110
44837
  return {
44111
44838
  success: false,
44112
44839
  code: "TOKEN_NOT_FOUND",
44113
- message: `Unknown token: ${input2.toToken} on ${input2.toChain}`
44840
+ message: `Unknown token: ${input2.toToken} on ${toChain}`
44114
44841
  };
44115
44842
  }
44116
44843
  const parsedAmount = parseAmount(input2.amount, fromTokenInfo.decimals);
44117
- const address = await signer.getAddress(input2.fromChain);
44844
+ const address = await signer.getAddress(fromChain);
44118
44845
  const quote = await this.swapClient.getQuote({
44119
- fromChain: input2.fromChain,
44846
+ fromChain,
44120
44847
  fromToken: fromTokenInfo.address,
44121
- toChain: input2.toChain,
44848
+ toChain,
44122
44849
  toToken: toTokenInfo.address,
44123
44850
  amount: parsedAmount,
44124
44851
  walletAddress: address,
@@ -44128,18 +44855,13 @@ var SwapActionProvider = class extends ActionProvider {
44128
44855
  return { success: false, code: "NO_ROUTES", message: "No swap routes found" };
44129
44856
  }
44130
44857
  const route = quote.routes[0];
44131
- const chainConfig = getChainById(input2.fromChain);
44858
+ const chainConfig = getChainById(fromChain);
44132
44859
  const transactions = await this.swapClient.getSwapTransactions(route);
44133
44860
  let txHash = "";
44134
44861
  for (const txData of transactions) {
44135
44862
  txHash = await this.sendSwapTx(txData, chainConfig, signer);
44136
44863
  const isApproval = txData.transaction.type === "approval" || txData.transaction.type === "revokeApproval";
44137
- await this.pollConfirmation(
44138
- input2.fromChain,
44139
- address,
44140
- txHash,
44141
- isApproval ? 6e4 : 12e4
44142
- );
44864
+ await this.pollConfirmation(fromChain, address, txHash, isApproval ? 6e4 : 12e4);
44143
44865
  }
44144
44866
  const firstStep = route.steps[0];
44145
44867
  const lastStep = route.steps[route.steps.length - 1];
@@ -44148,7 +44870,7 @@ var SwapActionProvider = class extends ActionProvider {
44148
44870
  firstStep.from.asset.metadata.decimals
44149
44871
  );
44150
44872
  const outputAmt = formatAmount(lastStep.to.amount, lastStep.to.asset.metadata.decimals);
44151
- const explorerUrl = getExplorerTxUrl(input2.fromChain, txHash);
44873
+ const explorerUrl = getExplorerTxUrl(fromChain, txHash);
44152
44874
  return {
44153
44875
  success: true,
44154
44876
  txHash,
@@ -44164,9 +44886,9 @@ var SwapActionProvider = class extends ActionProvider {
44164
44886
  name: "get_swap_quote",
44165
44887
  description: "Get a swap quote without executing",
44166
44888
  schema: import_zod5.z.object({
44167
- fromChain: import_zod5.z.string().describe("Source chain"),
44889
+ fromChain: import_zod5.z.string().describe(`Source chain. ${CHAIN_DESC}`),
44168
44890
  fromToken: import_zod5.z.string().describe("Source token symbol or address"),
44169
- toChain: import_zod5.z.string().describe("Destination chain"),
44891
+ toChain: import_zod5.z.string().describe(`Destination chain. ${CHAIN_DESC}`),
44170
44892
  toToken: import_zod5.z.string().describe("Destination token symbol or address"),
44171
44893
  amount: import_zod5.z.string().describe("Human-readable amount")
44172
44894
  }),
@@ -44174,30 +44896,32 @@ var SwapActionProvider = class extends ActionProvider {
44174
44896
  if (!this.swapClient) {
44175
44897
  throw new SwapError("WALLET_NOT_FOUND", "Swap client required");
44176
44898
  }
44177
- const fromTokenInfo = resolveToken(input2.fromToken, input2.fromChain);
44899
+ const fromChain = resolveChain(input2.fromChain);
44900
+ const toChain = resolveChain(input2.toChain);
44901
+ const fromTokenInfo = resolveToken(input2.fromToken, fromChain);
44178
44902
  if (!fromTokenInfo) {
44179
44903
  return {
44180
44904
  success: false,
44181
44905
  code: "TOKEN_NOT_FOUND",
44182
- message: `Unknown token: ${input2.fromToken} on ${input2.fromChain}`
44906
+ message: `Unknown token: ${input2.fromToken} on ${fromChain}`
44183
44907
  };
44184
44908
  }
44185
- const toTokenInfo = resolveToken(input2.toToken, input2.toChain);
44909
+ const toTokenInfo = resolveToken(input2.toToken, toChain);
44186
44910
  if (!toTokenInfo) {
44187
44911
  return {
44188
44912
  success: false,
44189
44913
  code: "TOKEN_NOT_FOUND",
44190
- message: `Unknown token: ${input2.toToken} on ${input2.toChain}`
44914
+ message: `Unknown token: ${input2.toToken} on ${toChain}`
44191
44915
  };
44192
44916
  }
44193
44917
  const parsedAmount = parseAmount(input2.amount, fromTokenInfo.decimals);
44194
44918
  const quote = await this.swapClient.getQuote({
44195
- fromChain: input2.fromChain,
44919
+ fromChain,
44196
44920
  fromToken: fromTokenInfo.address,
44197
- toChain: input2.toChain,
44921
+ toChain,
44198
44922
  toToken: toTokenInfo.address,
44199
44923
  amount: parsedAmount,
44200
- walletAddress: getSampleAccount(input2.fromChain)
44924
+ walletAddress: getSampleAccount(fromChain)
44201
44925
  });
44202
44926
  if (quote.routes.length === 0) {
44203
44927
  return { success: false, code: "NO_ROUTES", message: "No routes found" };
@@ -44222,22 +44946,23 @@ var SwapActionProvider = class extends ActionProvider {
44222
44946
  name: "get_token_price",
44223
44947
  description: "Get current USD price for a token",
44224
44948
  schema: import_zod5.z.object({
44225
- chain: import_zod5.z.string().describe("Chain name (e.g., ethereum)"),
44949
+ chain: import_zod5.z.string().describe(CHAIN_DESC),
44226
44950
  token: import_zod5.z.string().describe("Token symbol or address")
44227
44951
  }),
44228
44952
  invoke: async (_ctx, input2) => {
44229
44953
  if (!this.swapClient) {
44230
44954
  throw new SwapError("WALLET_NOT_FOUND", "Swap client required");
44231
44955
  }
44232
- const tokenInfo = resolveToken(input2.token, input2.chain);
44956
+ const chain2 = resolveChain(input2.chain);
44957
+ const tokenInfo = resolveToken(input2.token, chain2);
44233
44958
  if (!tokenInfo) {
44234
44959
  return {
44235
44960
  success: false,
44236
44961
  code: "TOKEN_NOT_FOUND",
44237
- message: `Unknown token: ${input2.token} on ${input2.chain}`
44962
+ message: `Unknown token: ${input2.token} on ${chain2}`
44238
44963
  };
44239
44964
  }
44240
- const price = await this.swapClient.getTokenPrice(input2.chain, tokenInfo.address);
44965
+ const price = await this.swapClient.getTokenPrice(chain2, tokenInfo.address);
44241
44966
  if (price === null) {
44242
44967
  return {
44243
44968
  success: false,
@@ -44248,7 +44973,7 @@ var SwapActionProvider = class extends ActionProvider {
44248
44973
  return {
44249
44974
  success: true,
44250
44975
  token: input2.token.toUpperCase(),
44251
- chain: input2.chain,
44976
+ chain: chain2,
44252
44977
  priceUsd: price
44253
44978
  };
44254
44979
  }
@@ -44271,26 +44996,37 @@ var SecurityActionProvider = class extends ActionProvider {
44271
44996
  validateTransaction() {
44272
44997
  return {
44273
44998
  name: "validate_transaction",
44274
- description: "Validate an address or transaction for security",
44999
+ description: "Validate an address or transaction for security. Provide `chain` (and optional `tokenAddress`) to scope the validation to a specific asset; omit both for a generic address check.",
44275
45000
  schema: import_zod6.z.object({
44276
45001
  address: import_zod6.z.string().describe("Address to validate"),
44277
- assetId: import_zod6.z.string().optional().describe("Asset ID"),
45002
+ chain: import_zod6.z.string().optional().describe(CHAIN_DESC),
45003
+ tokenAddress: import_zod6.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44278
45004
  type: import_zod6.z.string().optional().describe("Validation type")
45005
+ }).refine((data) => data.tokenAddress === void 0 || data.chain !== void 0, {
45006
+ path: ["tokenAddress"],
45007
+ message: "tokenAddress requires chain to be provided."
44279
45008
  }),
44280
45009
  invoke: async (ctx, input2) => {
44281
- return this.getService(ctx).validate(input2);
45010
+ const assetId = input2.chain !== void 0 ? resolveAsset(input2.chain, input2.tokenAddress).assetId : void 0;
45011
+ return this.getService(ctx).validate({
45012
+ address: input2.address,
45013
+ assetId,
45014
+ type: input2.type
45015
+ });
44282
45016
  }
44283
45017
  };
44284
45018
  }
44285
45019
  getAssetInfo() {
44286
45020
  return {
44287
45021
  name: "get_asset_info",
44288
- description: "Get detailed information about an asset",
45022
+ description: "Get detailed information about an asset (name, symbol, price, market cap). Omit `tokenAddress` for the chain native coin.",
44289
45023
  schema: import_zod6.z.object({
44290
- assetId: import_zod6.z.string().describe("Asset ID (e.g., c60_t0xA0b8...)")
45024
+ chain: import_zod6.z.string().describe(CHAIN_DESC),
45025
+ tokenAddress: import_zod6.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC)
44291
45026
  }),
44292
45027
  invoke: async (ctx, input2) => {
44293
- return this.getService(ctx).getAssetInfo(input2.assetId);
45028
+ const { assetId } = resolveAsset(input2.chain, input2.tokenAddress);
45029
+ return this.getService(ctx).getAssetInfo(assetId);
44294
45030
  }
44295
45031
  };
44296
45032
  }
@@ -44314,13 +45050,14 @@ var HistoryActionProvider = class extends ActionProvider {
44314
45050
  description: "Get transaction history for an address",
44315
45051
  schema: import_zod7.z.object({
44316
45052
  address: import_zod7.z.string().describe("Wallet address"),
44317
- chain: import_zod7.z.string().optional().describe("Chain name"),
45053
+ chain: import_zod7.z.string().optional().describe(CHAIN_DESC),
44318
45054
  limit: import_zod7.z.coerce.number().optional().default(20).describe("Max results"),
44319
45055
  from: import_zod7.z.string().optional().describe("Start date (ISO)"),
44320
45056
  to: import_zod7.z.string().optional().describe("End date (ISO)")
44321
45057
  }),
44322
45058
  invoke: async (ctx, input2) => {
44323
- return this.getService(ctx).getHistory(input2);
45059
+ const chain2 = input2.chain !== void 0 ? resolveChain(input2.chain) : void 0;
45060
+ return this.getService(ctx).getHistory({ ...input2, chain: chain2 });
44324
45061
  }
44325
45062
  };
44326
45063
  }
@@ -44330,11 +45067,12 @@ var HistoryActionProvider = class extends ActionProvider {
44330
45067
  description: "Get details of a specific transaction by hash",
44331
45068
  schema: import_zod7.z.object({
44332
45069
  hash: import_zod7.z.string().describe("Transaction hash"),
44333
- chain: import_zod7.z.string().describe("Chain name")
45070
+ chain: import_zod7.z.string().describe(CHAIN_DESC)
44334
45071
  }),
44335
45072
  invoke: async (ctx, input2) => {
44336
- const details = await this.getService(ctx).getByHash(input2.hash, input2.chain);
44337
- const explorer = getExplorerTxUrl(input2.chain, input2.hash);
45073
+ const chain2 = resolveChain(input2.chain);
45074
+ const details = await this.getService(ctx).getByHash(input2.hash, chain2);
45075
+ const explorer = getExplorerTxUrl(chain2, input2.hash);
44338
45076
  return { ...details, explorer: explorer || void 0 };
44339
45077
  }
44340
45078
  };
@@ -44345,11 +45083,12 @@ var HistoryActionProvider = class extends ActionProvider {
44345
45083
  description: "Get swap transaction history",
44346
45084
  schema: import_zod7.z.object({
44347
45085
  address: import_zod7.z.string().describe("Wallet address"),
44348
- chain: import_zod7.z.string().optional().describe("Chain name"),
45086
+ chain: import_zod7.z.string().optional().describe(CHAIN_DESC),
44349
45087
  limit: import_zod7.z.coerce.number().optional().default(20).describe("Max results")
44350
45088
  }),
44351
45089
  invoke: async (ctx, input2) => {
44352
- return this.getService(ctx).getSwapHistory(input2);
45090
+ const chain2 = input2.chain !== void 0 ? resolveChain(input2.chain) : void 0;
45091
+ return this.getService(ctx).getSwapHistory({ ...input2, chain: chain2 });
44353
45092
  }
44354
45093
  };
44355
45094
  }
@@ -44365,38 +45104,30 @@ var AlertActionProvider = class extends ActionProvider {
44365
45104
  this.swapClient = swapClient;
44366
45105
  }
44367
45106
  getActions() {
44368
- return [
44369
- this.createAlert(),
44370
- this.listAlerts(),
44371
- this.checkAlerts(),
44372
- this.deleteAlert()
44373
- ];
45107
+ return [this.createAlert(), this.listAlerts(), this.checkAlerts(), this.deleteAlert()];
44374
45108
  }
44375
45109
  createAlert() {
44376
45110
  return {
44377
45111
  name: "create_alert",
44378
- description: "Create a price alert for a token. Triggers when price goes above or below a target.",
45112
+ description: "Create a price alert. Triggers when the asset price crosses a target. Omit `tokenAddress` to watch the chain native coin (ETH, BNB, SOL, etc.).",
44379
45113
  schema: import_zod8.z.object({
44380
- tokenId: import_zod8.z.string().describe('Token ID (e.g., "c60" for ETH, "c20000714_t0x55d3..." for BSC USDT)'),
44381
- chainKey: import_zod8.z.string().describe('Chain key (e.g., "ethereum", "bsc")'),
45114
+ chain: import_zod8.z.string().describe(CHAIN_DESC),
45115
+ tokenAddress: import_zod8.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44382
45116
  condition: import_zod8.z.enum(["above", "below"]).describe("Alert when price goes above or below target"),
44383
45117
  targetPrice: import_zod8.z.coerce.number().positive().describe("Target price in USD")
44384
45118
  }),
44385
45119
  invoke: async (_ctx, input2) => {
44386
- try {
44387
- parseTokenId(input2.tokenId);
44388
- } catch {
44389
- return { error: `Invalid tokenId format: ${input2.tokenId}. Expected format: "c60" or "c60_t0xABC..."` };
44390
- }
45120
+ const { chainKey, assetId: tokenId } = resolveAsset(input2.chain, input2.tokenAddress);
44391
45121
  const alert = await this.alertStore.create({
44392
- tokenId: input2.tokenId,
44393
- chainKey: input2.chainKey,
45122
+ tokenId,
45123
+ chainKey,
44394
45124
  condition: input2.condition,
44395
45125
  targetPrice: input2.targetPrice
44396
45126
  });
45127
+ const label = input2.tokenAddress ? `${input2.tokenAddress} on ${chainKey}` : `${requireChain(chainKey).symbol} on ${chainKey}`;
44397
45128
  return {
44398
45129
  id: alert.id,
44399
- message: `Alert created: notify when ${input2.tokenId} goes ${input2.condition} $${input2.targetPrice}`
45130
+ message: `Alert created: notify when ${label} goes ${input2.condition} $${input2.targetPrice}`
44400
45131
  };
44401
45132
  }
44402
45133
  };
@@ -44425,10 +45156,7 @@ var AlertActionProvider = class extends ActionProvider {
44425
45156
  for (const alert of activeAlerts) {
44426
45157
  if (!this.swapClient) continue;
44427
45158
  const { contractAddress } = parseTokenId(alert.tokenId);
44428
- const price = await this.swapClient.getTokenPrice(
44429
- alert.chainKey,
44430
- contractAddress || ""
44431
- );
45159
+ const price = await this.swapClient.getTokenPrice(alert.chainKey, contractAddress || "");
44432
45160
  if (price === null) continue;
44433
45161
  const shouldTrigger = alert.condition === "above" && price >= alert.targetPrice || alert.condition === "below" && price <= alert.targetPrice;
44434
45162
  if (shouldTrigger) {
@@ -44483,12 +45211,13 @@ var AgentWalletActionProvider = class extends ActionProvider {
44483
45211
  name: "get_address",
44484
45212
  description: "Get the active wallet's address for a specific chain.",
44485
45213
  schema: import_zod9.z.object({
44486
- chainKey: import_zod9.z.string().describe('Chain key (e.g., "ethereum", "bsc", "solana")')
45214
+ chain: import_zod9.z.string().describe(CHAIN_DESC)
44487
45215
  }),
44488
45216
  invoke: async (_ctx, input2) => {
45217
+ const chainKey = resolveChain(input2.chain);
44489
45218
  const wallet = this.requireWallet();
44490
- const address = await wallet.getAddress(input2.chainKey);
44491
- return { chainKey: input2.chainKey, address };
45219
+ const address = await wallet.getAddress(chainKey);
45220
+ return { chain: chainKey, address };
44492
45221
  }
44493
45222
  };
44494
45223
  }
@@ -44509,21 +45238,17 @@ var AgentWalletActionProvider = class extends ActionProvider {
44509
45238
  name: "wallet_balance",
44510
45239
  description: "Read the native-coin balance (BNB on BSC, ETH on Ethereum / Base / Arbitrum, SOL on Solana, etc.) for the active wallet on a specific chain. For ERC-20 / SPL token balances, use `get_balance`.",
44511
45240
  schema: import_zod9.z.object({
44512
- chainKey: import_zod9.z.string().describe('Chain key (e.g., "ethereum", "bsc")')
45241
+ chain: import_zod9.z.string().describe(CHAIN_DESC)
44513
45242
  }),
44514
45243
  invoke: async (_ctx, input2) => {
45244
+ const { chainKey, assetId } = resolveAsset(input2.chain);
44515
45245
  const wallet = this.requireWallet();
44516
- const address = await wallet.getAddress(input2.chainKey);
45246
+ const address = await wallet.getAddress(chainKey);
44517
45247
  if (!this.blockchainClient) {
44518
- return { chainKey: input2.chainKey, address, balance: "BlockchainClient not available" };
45248
+ return { chain: chainKey, address, balance: "BlockchainClient not available" };
44519
45249
  }
44520
- const coinType = chainKeyToCoinType(input2.chainKey);
44521
- if (coinType === void 0) {
44522
- throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported chain: ${input2.chainKey}`);
44523
- }
44524
- const assetId = formatTokenId(coinType);
44525
45250
  const balance = await this.blockchainClient.getBalance(assetId, address);
44526
- return { chainKey: input2.chainKey, address, balance };
45251
+ return { chain: chainKey, address, balance };
44527
45252
  }
44528
45253
  };
44529
45254
  }
@@ -44548,17 +45273,19 @@ var TokenActionProvider = class extends ActionProvider {
44548
45273
  tokenBalance() {
44549
45274
  return {
44550
45275
  name: "token_balance",
44551
- description: "Get balance for a specific token or native asset by asset ID",
45276
+ description: "Get balance for a specific token or native asset on a chain.",
44552
45277
  schema: import_zod10.z.object({
44553
- assetId: import_zod10.z.string().describe('Token ID (e.g., "c60" for ETH, "c20000714_t0x55d3..." for BSC USDT)'),
45278
+ chain: import_zod10.z.string().describe(CHAIN_DESC),
45279
+ tokenAddress: import_zod10.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44554
45280
  address: import_zod10.z.string().describe("Wallet address to check balance for")
44555
45281
  }),
44556
45282
  invoke: async (_ctx, input2) => {
44557
45283
  if (!this.blockchainClient) {
44558
45284
  throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44559
45285
  }
44560
- const balance = await this.blockchainClient.getBalance(input2.assetId, input2.address);
44561
- return { assetId: input2.assetId, address: input2.address, balance };
45286
+ const { chainKey, assetId } = resolveAsset(input2.chain, input2.tokenAddress);
45287
+ const balance = await this.blockchainClient.getBalance(assetId, input2.address);
45288
+ return { chain: chainKey, address: input2.address, balance };
44562
45289
  }
44563
45290
  };
44564
45291
  }
@@ -44567,54 +45294,60 @@ var TokenActionProvider = class extends ActionProvider {
44567
45294
  name: "transfer_token",
44568
45295
  description: "Transfer a token or native asset to a destination address. Works in both local and WalletConnect modes for EVM chains; non-EVM chains (e.g. Solana) require local wallet mode.",
44569
45296
  schema: import_zod10.z.object({
44570
- assetId: import_zod10.z.string().describe("Token ID to transfer"),
45297
+ chain: import_zod10.z.string().describe(CHAIN_DESC),
45298
+ tokenAddress: import_zod10.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44571
45299
  to: import_zod10.z.string().describe("Destination address"),
44572
- amount: import_zod10.z.string().describe("Amount to transfer (in smallest unit)"),
45300
+ amount: import_zod10.z.string().describe('Amount in human-readable format (e.g., "1.5", "0.01")'),
45301
+ decimals: import_zod10.z.coerce.number().int().min(0).max(36).optional().describe(
45302
+ "Token decimals (non-negative integer). Omit for native coins and for tokens twak recognizes \u2014 decimals are derived from `COMMON_TOKENS` per chain (BSC stables are 18, USDC on Ethereum is 6, etc.). REQUIRED when tokenAddress is a contract twak does not recognize; look up the value on the token contract."
45303
+ ),
44573
45304
  memo: import_zod10.z.string().optional().describe("Optional memo/note")
44574
45305
  }),
44575
45306
  invoke: async (_ctx, input2) => {
44576
45307
  const wallet = this.walletContext.activeWallet();
45308
+ const { chainKey, assetId } = resolveAsset(input2.chain, input2.tokenAddress);
45309
+ const decimals = input2.decimals ?? lookupKnownTokenDecimals(chainKey, input2.tokenAddress);
45310
+ if (decimals === void 0) {
45311
+ throw new WalletError(
45312
+ "VALIDATION_ERROR",
45313
+ `Cannot derive decimals for ${input2.tokenAddress} on ${chainKey}. Provide \`decimals\` explicitly.`
45314
+ );
45315
+ }
44577
45316
  if (wallet instanceof AgentWalletProvider) {
44578
45317
  if (!this.blockchainClient) {
44579
45318
  throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44580
45319
  }
44581
45320
  const hdWallet = wallet.getSigner().getHDWallet();
44582
45321
  const result = await this.blockchainClient.transfer({
44583
- assetId: input2.assetId,
45322
+ assetId,
44584
45323
  to: input2.to,
44585
45324
  amount: input2.amount,
45325
+ decimals,
44586
45326
  memo: input2.memo,
44587
45327
  hdWallet
44588
45328
  });
44589
- return { hash: result.hash, chainKey: result.chainKey };
44590
- }
44591
- const { coinId, contractAddress } = parseTokenId(input2.assetId);
44592
- const chainKey = coinTypeToChainKey(coinId);
44593
- if (!chainKey) {
44594
- throw new WalletError("CHAIN_UNSUPPORTED", `Unknown coin type: ${coinId}`);
45329
+ return { hash: result.txHash, chain: result.chainKey };
44595
45330
  }
44596
- const chain2 = getChainById(chainKey);
44597
- if (!chain2 || chain2.namespace !== "eip155") {
45331
+ const chain2 = requireChain(chainKey);
45332
+ if (chain2.namespace !== "eip155") {
44598
45333
  throw new WalletError(
44599
45334
  "CHAIN_UNSUPPORTED",
44600
45335
  `transfer_token on ${chainKey} is not supported in WalletConnect mode (EVM-only). Switch to local wallet mode for ${chainKey} transfers.`
44601
45336
  );
44602
45337
  }
45338
+ if (input2.tokenAddress) assertEvmAddress(input2.tokenAddress, "tokenAddress");
44603
45339
  const caipChainId = `${chain2.namespace}:${chain2.chainId}`;
45340
+ const atomicAmount = parseAmount(input2.amount, decimals);
44604
45341
  let txHash;
44605
- if (contractAddress) {
44606
- const selector = "0xa9059cbb";
44607
- const paddedTo = input2.to.replace(/^0x/, "").toLowerCase().padStart(64, "0");
44608
- const paddedAmount = BigInt(input2.amount).toString(16).padStart(64, "0");
44609
- const data = selector + paddedTo + paddedAmount;
44610
- txHash = await wallet.sendTransaction({
44611
- chainId: caipChainId,
44612
- to: contractAddress,
44613
- data,
44614
- value: "0x0"
44615
- });
45342
+ if (input2.tokenAddress) {
45343
+ if (!this.blockchainClient) {
45344
+ throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
45345
+ }
45346
+ const pending = await this.blockchainClient.contract(erc20).withAddress(input2.tokenAddress, chainKey).write.transfer([input2.to, BigInt(atomicAmount)], wallet);
45347
+ txHash = pending.txHash;
44616
45348
  } else {
44617
- const valueHex = "0x" + BigInt(input2.amount).toString(16);
45349
+ assertEvmAddress(input2.to, "to");
45350
+ const valueHex = "0x" + BigInt(atomicAmount).toString(16);
44618
45351
  txHash = await wallet.sendTransaction({
44619
45352
  chainId: caipChainId,
44620
45353
  to: input2.to,
@@ -44622,80 +45355,77 @@ var TokenActionProvider = class extends ActionProvider {
44622
45355
  value: valueHex
44623
45356
  });
44624
45357
  }
44625
- return { hash: txHash, chainKey };
45358
+ return { hash: txHash, chain: chainKey };
44626
45359
  }
44627
45360
  };
44628
45361
  }
44629
45362
  approveToken() {
44630
45363
  return {
44631
45364
  name: "approve_token",
44632
- description: `Approve a spender to use tokens on your behalf (ERC-20 approve). SAFETY: when amount is "unlimited" (or the MaxUint256 decimal string), you MUST surface the risk to the user and get their explicit consent before calling this tool \u2014 unlimited approvals grant the spender access to ALL of the user's tokens of this type, including future transfers, and have been used in many drainer attacks. Prefer a finite amount equal to the immediate need. Use revoke_token_approval (or amount: "0") to revoke an existing approval.`,
45365
+ description: `Approve a spender to use ERC-20 tokens on your behalf. SAFETY: when amount is "unlimited" (or the MaxUint256 decimal string), you MUST surface the risk to the user and get their explicit consent before calling this tool \u2014 unlimited approvals grant the spender access to ALL of the user's tokens of this type, including future transfers, and have been used in many drainer attacks. Prefer a finite amount equal to the immediate need. Use revoke_token_approval (or amount: "0") to revoke an existing approval.`,
44633
45366
  schema: import_zod10.z.object({
44634
- assetId: import_zod10.z.string().describe("Token ID to approve"),
45367
+ chain: import_zod10.z.string().describe(CHAIN_DESC),
45368
+ tokenAddress: import_zod10.z.string().trim().min(1).describe("ERC-20 token contract address."),
44635
45369
  spender: import_zod10.z.string().describe("Spender address to approve"),
44636
45370
  amount: import_zod10.z.string().describe('Amount to approve (in smallest unit, or "unlimited")')
44637
45371
  }),
44638
45372
  invoke: async (_ctx, input2) => {
44639
45373
  const wallet = this.walletContext.activeWallet();
44640
- const { coinId, contractAddress } = parseTokenId(input2.assetId);
44641
- if (!contractAddress) {
44642
- throw new WalletError("VALIDATION_ERROR", "Cannot approve native token (no contract).");
44643
- }
44644
- const chainKey = coinTypeToChainKey(coinId);
44645
- if (!chainKey) {
44646
- throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported coin type: ${coinId}`);
44647
- }
44648
- const chain2 = getChainById(chainKey);
44649
- if (!chain2 || chain2.namespace !== "eip155") {
45374
+ const chainKey = resolveChain(input2.chain);
45375
+ const chain2 = requireChain(chainKey);
45376
+ if (chain2.namespace !== "eip155") {
44650
45377
  throw new WalletError(
44651
45378
  "CHAIN_UNSUPPORTED",
44652
45379
  `approve_token on ${chainKey} is not supported \u2014 ERC-20 approvals are EVM-only.`
44653
45380
  );
44654
45381
  }
45382
+ assertEvmAddress(input2.tokenAddress, "tokenAddress");
45383
+ assertEvmAddress(input2.spender, "spender");
45384
+ if (!this.blockchainClient) {
45385
+ throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
45386
+ }
44655
45387
  const amount = input2.amount === "unlimited" ? "115792089237316195423570985008687907853269984665640564039457584007913129639935" : input2.amount;
44656
- const result = await wallet.approveErc20(chainKey, contractAddress, input2.spender, amount);
44657
- return { hash: result.txHash, chainKey };
45388
+ const pending = await this.blockchainClient.contract(erc20).withAddress(input2.tokenAddress, chainKey).write.approve([input2.spender, BigInt(amount)], wallet);
45389
+ return { hash: pending.txHash, chain: chainKey };
44658
45390
  }
44659
45391
  };
44660
45392
  }
44661
45393
  revokeTokenApproval() {
44662
45394
  return {
44663
45395
  name: "revoke_token_approval",
44664
- description: 'Revoke an ERC-20 token approval by setting the spender allowance to 0. Use this to remove a previously-granted allowance \u2014 especially important after unlimited approvals or any approval you no longer need. Applies only to ERC-20 tokens (asset IDs of form "c{coinId}_t{contract}"); native tokens have no approvals to revoke. This is the inverse of approve_token.',
45396
+ description: "Revoke an ERC-20 token approval by setting the spender allowance to 0. Use this to remove a previously-granted allowance \u2014 especially important after unlimited approvals or any approval you no longer need. This is the inverse of approve_token.",
44665
45397
  schema: import_zod10.z.object({
44666
- assetId: import_zod10.z.string().describe(
44667
- 'ERC-20 token ID to revoke approval for (e.g., "c60_t0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" for USDC on Ethereum)'
44668
- ),
45398
+ chain: import_zod10.z.string().describe(CHAIN_DESC),
45399
+ tokenAddress: import_zod10.z.string().trim().min(1).describe("ERC-20 token contract address whose allowance you want to revoke."),
44669
45400
  spender: import_zod10.z.string().describe("Spender address whose allowance should be set to 0")
44670
45401
  }),
44671
45402
  invoke: async (_ctx, input2) => {
44672
45403
  const wallet = this.walletContext.activeWallet();
44673
- const { coinId, contractAddress } = parseTokenId(input2.assetId);
44674
- if (!contractAddress) {
44675
- throw new WalletError("VALIDATION_ERROR", "Native tokens have no approval to revoke.");
44676
- }
44677
- const chainKey = coinTypeToChainKey(coinId);
44678
- if (!chainKey) {
44679
- throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported coin type: ${coinId}`);
44680
- }
44681
- const chain2 = getChainById(chainKey);
44682
- if (!chain2 || chain2.namespace !== "eip155") {
45404
+ const chainKey = resolveChain(input2.chain);
45405
+ const chain2 = requireChain(chainKey);
45406
+ if (chain2.namespace !== "eip155") {
44683
45407
  throw new WalletError(
44684
45408
  "CHAIN_UNSUPPORTED",
44685
45409
  `revoke_token_approval on ${chainKey} is not supported \u2014 ERC-20 approvals are EVM-only.`
44686
45410
  );
44687
45411
  }
44688
- const result = await wallet.approveErc20(chainKey, contractAddress, input2.spender, "0");
44689
- return { hash: result.txHash, chainKey };
45412
+ assertEvmAddress(input2.tokenAddress, "tokenAddress");
45413
+ assertEvmAddress(input2.spender, "spender");
45414
+ if (!this.blockchainClient) {
45415
+ throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
45416
+ }
45417
+ const pending = await this.blockchainClient.contract(erc20).withAddress(input2.tokenAddress, chainKey).write.approve([input2.spender, 0n], wallet);
45418
+ return { hash: pending.txHash, chain: chainKey };
44690
45419
  }
44691
45420
  };
44692
45421
  }
44693
45422
  checkAllowance() {
44694
45423
  return {
44695
45424
  name: "check_allowance",
44696
- description: "Check the token allowance for a spender address",
45425
+ description: "Check the ERC-20 allowance for a spender address.",
44697
45426
  schema: import_zod10.z.object({
44698
- assetId: import_zod10.z.string().describe("Token ID to check allowance for"),
45427
+ chain: import_zod10.z.string().describe(CHAIN_DESC),
45428
+ tokenAddress: import_zod10.z.string().trim().min(1).describe("ERC-20 token contract address."),
44699
45429
  owner: import_zod10.z.string().describe("Token owner address"),
44700
45430
  spender: import_zod10.z.string().describe("Spender address to check")
44701
45431
  }),
@@ -44703,13 +45433,11 @@ var TokenActionProvider = class extends ActionProvider {
44703
45433
  if (!this.blockchainClient) {
44704
45434
  throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44705
45435
  }
44706
- const allowance = await this.blockchainClient.getAllowance({
44707
- assetId: input2.assetId,
44708
- owner: input2.owner,
44709
- spender: input2.spender
44710
- });
45436
+ const chainKey = resolveChain(input2.chain);
45437
+ const allowance = await this.blockchainClient.contract(erc20).withAddress(input2.tokenAddress, chainKey).read.allowance([input2.owner, input2.spender]);
44711
45438
  return {
44712
- assetId: input2.assetId,
45439
+ chain: chainKey,
45440
+ tokenAddress: input2.tokenAddress,
44713
45441
  owner: input2.owner,
44714
45442
  spender: input2.spender,
44715
45443
  allowance: allowance.toString()
@@ -44747,17 +45475,19 @@ var MarketActionProvider = class extends ActionProvider {
44747
45475
  checkTokenRisk() {
44748
45476
  return {
44749
45477
  name: "check_token_risk",
44750
- description: "Check security and risk information for a token (honeypot detection, audit status, freeze authority)",
45478
+ description: "Check security and risk information for a token (honeypot detection, audit status, freeze authority). Omit `tokenAddress` to check the chain native coin.",
44751
45479
  schema: import_zod11.z.object({
44752
- assetId: import_zod11.z.string().describe("Asset ID (e.g., c60_t0xA0b8... for ERC-20, c60 for native)")
45480
+ chain: import_zod11.z.string().describe(CHAIN_DESC),
45481
+ tokenAddress: import_zod11.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC)
44753
45482
  }),
44754
45483
  invoke: async (ctx, input2) => {
44755
- const status = await this.getService(ctx).checkCoinStatus(input2.assetId);
45484
+ const { assetId } = resolveAsset(input2.chain, input2.tokenAddress);
45485
+ const status = await this.getService(ctx).checkCoinStatus(assetId);
44756
45486
  const riskLevel = status.securityInfo?.riskLevel || status.solanaSecurityInfo?.riskLevel || "unknown";
44757
45487
  const isHoneypot = status.securityInfo?.isHoneypot ?? false;
44758
45488
  return {
44759
45489
  success: true,
44760
- assetId: input2.assetId,
45490
+ assetId,
44761
45491
  name: status.name,
44762
45492
  symbol: status.symbol,
44763
45493
  isActive: status.isActive,
@@ -44773,32 +45503,58 @@ var MarketActionProvider = class extends ActionProvider {
44773
45503
  };
44774
45504
  }
44775
45505
  };
44776
- var ERC20_TRANSFER_SIG2 = "0xa9059cbb";
44777
- var EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
44778
45506
  var DEFAULT_MAX_OFFRAMP_USD = 1e4;
44779
- function encodeErc20Transfer2(to3, amount) {
44780
- const paddedTo = to3.slice(2).padStart(64, "0");
44781
- const paddedAmount = amount.toString(16).padStart(64, "0");
44782
- return `${ERC20_TRANSFER_SIG2}${paddedTo}${paddedAmount}`;
45507
+ function validateOfframpInput(input2, chainKey, nativeDecimals) {
45508
+ if (!isEvmAddress(input2.depositAddress)) {
45509
+ throw new TransferError(
45510
+ "VALIDATION_ERROR",
45511
+ `depositAddress is not a valid EVM address: ${input2.depositAddress}. Double-check what the provider displayed.`
45512
+ );
45513
+ }
45514
+ if (input2.tokenAddress && !isEvmAddress(input2.tokenAddress)) {
45515
+ throw new TransferError("VALIDATION_ERROR", `Malformed tokenAddress: ${input2.tokenAddress}`);
45516
+ }
45517
+ let decimals;
45518
+ if (input2.tokenAddress) {
45519
+ if (input2.decimals === void 0) {
45520
+ throw new TransferError(
45521
+ "VALIDATION_ERROR",
45522
+ `decimals is required for ERC-20 token ${input2.tokenAddress} on ${chainKey}. Pass the token's decimals explicitly (6 for USDC/USDT, 18 for DAI, etc.) \u2014 silently defaulting to 18 could over-pay the provider by orders of magnitude.`
45523
+ );
45524
+ }
45525
+ decimals = input2.decimals;
45526
+ } else {
45527
+ decimals = nativeDecimals;
45528
+ }
45529
+ try {
45530
+ assertStrictPositiveAmount(input2.cryptoAmount, decimals);
45531
+ } catch (err) {
45532
+ throw new TransferError("VALIDATION_ERROR", err instanceof Error ? err.message : String(err));
45533
+ }
45534
+ return decimals;
44783
45535
  }
44784
- function buildFollowUp(cryptoAsset, address, direction, paymentMethod) {
44785
- if (!cryptoAsset) return void 0;
44786
- let parsed;
45536
+ async function checkOfframpUsdCap(priceLookup, maxUsd, chainKey, tokenAddress, cryptoAmount) {
44787
45537
  try {
44788
- parsed = parseTokenId(cryptoAsset);
45538
+ const tokenInfo = tokenAddress ? resolveToken(tokenAddress, chainKey) : void 0;
45539
+ const lookupAddress2 = tokenInfo?.address ?? tokenAddress ?? "";
45540
+ const price = await priceLookup.getTokenPrice(chainKey, lookupAddress2);
45541
+ if (price && price > 0) {
45542
+ const usdValue = parseFloat(cryptoAmount) * price;
45543
+ if (usdValue > maxUsd) {
45544
+ return {
45545
+ code: "USD_LIMIT_EXCEEDED",
45546
+ message: `Off-ramp payout value ~$${usdValue.toFixed(2)} USD exceeds safety limit of $${maxUsd}. Use the CLI with --max-usd ${Math.ceil(usdValue)} or --skip-safety-check to proceed.`
45547
+ };
45548
+ }
45549
+ }
44789
45550
  } catch {
44790
- return void 0;
44791
- }
44792
- const isErc20 = Boolean(parsed.contractAddress);
44793
- const action = isErc20 ? "get_token_holdings" : "get_balance";
44794
- let args;
44795
- if (action === "get_balance") {
44796
- const chainKey = coinTypeToChainKey(parsed.coinId);
44797
- if (!chainKey) return void 0;
44798
- args = parsed.contractAddress ? { address, chainKey, tokenAddress: parsed.contractAddress } : { address, chainKey };
44799
- } else {
44800
- args = { address, coin: parsed.coinId };
44801
45551
  }
45552
+ return void 0;
45553
+ }
45554
+ function buildFollowUp(chain2, tokenAddress, address, direction, paymentMethod) {
45555
+ if (!chain2) return void 0;
45556
+ const action = tokenAddress ? "get_token_holdings" : "get_balance";
45557
+ const args = tokenAddress ? { address, chain: chain2, tokenAddress } : { address, chain: chain2 };
44802
45558
  if (direction === "onramp") {
44803
45559
  return {
44804
45560
  action,
@@ -44855,11 +45611,15 @@ var OnrampActionProvider = class extends ActionProvider {
44855
45611
  // TransferActionProvider for the off-ramp payout path (confirm_offramp_transfer).
44856
45612
  // Off-ramp sends crypto to a third-party deposit address, so the cap is
44857
45613
  // strictly more important here than for peer-to-peer transfers.
44858
- constructor(walletContext, priceLookup, maxTransferUsd = DEFAULT_MAX_OFFRAMP_USD) {
45614
+ // `blockchainClient` is the 4th (optional) arg, not the 1st — see
45615
+ // TransferActionProvider for the rationale (avoid churning ~70 test sites).
45616
+ // Normalize across action providers in a follow-up.
45617
+ constructor(walletContext, priceLookup, maxTransferUsd = DEFAULT_MAX_OFFRAMP_USD, blockchainClient) {
44859
45618
  super();
44860
45619
  this.walletContext = walletContext;
44861
45620
  this.priceLookup = priceLookup;
44862
45621
  this.maxTransferUsd = maxTransferUsd;
45622
+ this.blockchainClient = blockchainClient;
44863
45623
  }
44864
45624
  name = "onramp";
44865
45625
  description = "Fiat on-ramp and off-ramp \u2014 buy crypto with fiat (USD, EUR, etc.) or sell crypto for fiat payouts. On-ramp: two-step flow (get quote \u2192 get checkout URL), provider sends crypto to the destination address. Off-ramp: three-step flow (get quote \u2192 get checkout URL \u2192 confirm_offramp_transfer). The off-ramp checkout URL runs in a plain browser which has no access to wallet keys, so the payout transaction must be broadcast natively via confirm_offramp_transfer once the user has read the provider's deposit address and exact amount. Destination/source address auto-derives from the configured wallet using the asset's chain. IMPORTANT: TWAK has no background polling or completion webhook for ramps. Tool responses include a `followUp` hint telling the agent exactly which balance tool to poll (with args and timing) to observe completion.";
@@ -44872,19 +45632,12 @@ var OnrampActionProvider = class extends ActionProvider {
44872
45632
  this.confirmOfframpTransfer()
44873
45633
  ];
44874
45634
  }
44875
- async resolveAddress(providedAddress, cryptoAsset) {
45635
+ async resolveAddress(providedAddress, chainKey) {
44876
45636
  if (providedAddress) return providedAddress;
44877
- if (!cryptoAsset) {
44878
- throw new TransferError(
44879
- "VALIDATION_ERROR",
44880
- "walletAddress not provided and cryptoAsset is missing \u2014 cannot derive chain. Pass walletAddress or cryptoAsset."
44881
- );
44882
- }
44883
- const chainKey = tokenIdToChain(cryptoAsset);
44884
45637
  if (!chainKey) {
44885
45638
  throw new TransferError(
44886
- "CHAIN_UNSUPPORTED",
44887
- `Cannot determine chain for asset ${cryptoAsset}. Pass walletAddress explicitly.`
45639
+ "VALIDATION_ERROR",
45640
+ "walletAddress not provided and chain is missing \u2014 cannot derive address. Pass walletAddress or chain."
44888
45641
  );
44889
45642
  }
44890
45643
  return this.walletContext.activeWallet().getAddress(chainKey);
@@ -44896,15 +45649,15 @@ var OnrampActionProvider = class extends ActionProvider {
44896
45649
  schema: import_zod12.z.object({
44897
45650
  fiatAmount: import_zod12.z.coerce.number().positive().describe("Amount of fiat currency to spend (e.g., 100 for $100)."),
44898
45651
  fiatCurrency: import_zod12.z.string().length(3).default("USD").describe("Fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
44899
- cryptoAsset: import_zod12.z.string().describe(
44900
- "Target crypto asset ID. Format: c<coinId> for native coins (e.g., c60 for ETH, c0 for BTC, c501 for SOL) or c<coinId>_t<contract> for tokens (e.g., c60_t0xA0b8... for USDC on Ethereum)."
44901
- ),
45652
+ chain: import_zod12.z.string().describe(CHAIN_DESC),
45653
+ tokenAddress: import_zod12.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44902
45654
  walletAddress: import_zod12.z.string().optional().describe(
44903
- "Destination wallet address where the purchased crypto will be sent. OMIT to auto-derive from the configured wallet using cryptoAsset's chain \u2014 this is the safe default and prevents sending funds to a stranger by mistake. Only supply this when operating on behalf of a different user."
45655
+ "Destination wallet address where the purchased crypto will be sent. OMIT to auto-derive from the configured wallet using the chain \u2014 this is the safe default and prevents sending funds to a stranger by mistake. Only supply this when operating on behalf of a different user."
44904
45656
  )
44905
45657
  }),
44906
45658
  invoke: async (ctx, input2) => {
44907
- const address = await this.resolveAddress(input2.walletAddress, input2.cryptoAsset);
45659
+ const { chainKey, assetId } = resolveAsset(input2.chain, input2.tokenAddress);
45660
+ const address = await this.resolveAddress(input2.walletAddress, chainKey);
44908
45661
  const query = new URLSearchParams({
44909
45662
  amount: input2.fiatAmount.toString(),
44910
45663
  currency: input2.fiatCurrency,
@@ -44912,7 +45665,7 @@ var OnrampActionProvider = class extends ActionProvider {
44912
45665
  });
44913
45666
  const result = await ctx.httpClient.gateway(
44914
45667
  "GET",
44915
- `/v1/buycrypto/quote/${encodeURIComponent(input2.cryptoAsset)}`,
45668
+ `/v1/buycrypto/quote/${encodeURIComponent(assetId)}`,
44916
45669
  void 0,
44917
45670
  query.toString()
44918
45671
  );
@@ -44922,9 +45675,10 @@ var OnrampActionProvider = class extends ActionProvider {
44922
45675
  success: true,
44923
45676
  summary: summarizeOnrampQuotes(quotes),
44924
45677
  address,
44925
- cryptoAsset: input2.cryptoAsset,
45678
+ chain: chainKey,
45679
+ tokenAddress: input2.tokenAddress,
44926
45680
  quotes,
44927
- nextAction: quotes.length > 0 ? "Call get_onramp_url with the chosen quoteId to receive a browser redirect URL for checkout. quotes[0] is the best deal (lowest vsBestPct)." : "No quotes available \u2014 try a different fiatAmount or cryptoAsset."
45681
+ nextAction: quotes.length > 0 ? "Call get_onramp_url with the chosen quoteId to receive a browser redirect URL for checkout. quotes[0] is the best deal (lowest vsBestPct)." : "No quotes available \u2014 try a different fiatAmount or chain/tokenAddress."
44928
45682
  };
44929
45683
  }
44930
45684
  };
@@ -44936,14 +45690,16 @@ var OnrampActionProvider = class extends ActionProvider {
44936
45690
  schema: import_zod12.z.object({
44937
45691
  quoteId: import_zod12.z.string().describe("Quote ID returned by get_onramp_quote."),
44938
45692
  walletAddress: import_zod12.z.string().optional().describe(
44939
- "Destination wallet address. OMIT to auto-derive from the configured wallet \u2014 requires cryptoAsset so the chain can be determined. Only supply this when operating on behalf of a different user."
45693
+ "Destination wallet address. OMIT to auto-derive from the configured wallet \u2014 requires chain so the address can be derived. Only supply this when operating on behalf of a different user."
44940
45694
  ),
44941
- cryptoAsset: import_zod12.z.string().optional().describe(
44942
- "Crypto asset ID that was used for the quote (e.g., c60 for ETH). Required when walletAddress is omitted so the chain can be derived for address lookup."
44943
- )
45695
+ chain: import_zod12.z.string().optional().describe(
45696
+ `${CHAIN_DESC} Required when walletAddress is omitted so the address can be derived.`
45697
+ ),
45698
+ tokenAddress: import_zod12.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC)
44944
45699
  }),
44945
45700
  invoke: async (ctx, input2) => {
44946
- const address = await this.resolveAddress(input2.walletAddress, input2.cryptoAsset);
45701
+ const chainKey = input2.chain !== void 0 ? resolveAsset(input2.chain).chainKey : void 0;
45702
+ const address = await this.resolveAddress(input2.walletAddress, chainKey);
44947
45703
  const query = new URLSearchParams({ address });
44948
45704
  let result;
44949
45705
  try {
@@ -44966,7 +45722,7 @@ var OnrampActionProvider = class extends ActionProvider {
44966
45722
  throw err;
44967
45723
  }
44968
45724
  const url = result.url ?? result.redirect_url;
44969
- const followUp = url ? buildFollowUp(input2.cryptoAsset, address, "onramp") : void 0;
45725
+ const followUp = url ? buildFollowUp(chainKey, input2.tokenAddress, address, "onramp") : void 0;
44970
45726
  return {
44971
45727
  success: Boolean(url),
44972
45728
  address,
@@ -44983,24 +45739,24 @@ var OnrampActionProvider = class extends ActionProvider {
44983
45739
  description: "Step 1 of selling crypto for fiat: get quotes from available providers for converting a crypto amount into fiat. Returns an array of quotes each with a quoteId \u2014 pass one to get_offramp_url to generate the checkout redirect for completing KYC and order creation. The provider page cannot sign the payout (no key access in the browser); the agent must broadcast it natively via confirm_offramp_transfer once the deposit address is known.",
44984
45740
  schema: import_zod12.z.object({
44985
45741
  cryptoAmount: import_zod12.z.coerce.number().positive().describe("Amount of crypto to sell (e.g., 0.1 for 0.1 ETH)."),
44986
- cryptoAsset: import_zod12.z.string().describe(
44987
- "Crypto asset ID to sell. Format: c<coinId> for native coins (e.g., c60 for ETH, c0 for BTC) or c<coinId>_t<contract> for tokens."
44988
- ),
45742
+ chain: import_zod12.z.string().describe(CHAIN_DESC),
45743
+ tokenAddress: import_zod12.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC),
44989
45744
  fiatCurrency: import_zod12.z.string().length(3).default("USD").describe("Target fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
44990
45745
  paymentMethod: import_zod12.z.enum(["ANY", "bank_transfer", "card"]).optional().default("ANY").describe(
44991
45746
  'Payout method. Default "ANY" is a sentinel that matches any provider \u2014 recommended unless you specifically need "card" or "bank_transfer". Sending an unrecognised value (or `bank_transfer` in regions without bank-payout coverage) silently returns an empty quotes array.'
44992
45747
  ),
44993
45748
  walletAddress: import_zod12.z.string().optional().describe(
44994
- "Source wallet address holding the crypto to be sold. OMIT to auto-derive from the configured wallet using cryptoAsset's chain \u2014 this is the safe default. Only supply this when operating on behalf of a different user."
45749
+ "Source wallet address holding the crypto to be sold. OMIT to auto-derive from the configured wallet using the chain \u2014 this is the safe default. Only supply this when operating on behalf of a different user."
44995
45750
  )
44996
45751
  }),
44997
45752
  invoke: async (ctx, input2) => {
44998
- const address = await this.resolveAddress(input2.walletAddress, input2.cryptoAsset);
45753
+ const { chainKey, assetId } = resolveAsset(input2.chain, input2.tokenAddress);
45754
+ const address = await this.resolveAddress(input2.walletAddress, chainKey);
44999
45755
  const query = new URLSearchParams({
45000
45756
  amount: input2.cryptoAmount.toString(),
45001
45757
  currency: input2.fiatCurrency,
45002
45758
  // iOS sends `asset_id` as both path segment AND query param. Mirroring that.
45003
- asset_id: input2.cryptoAsset,
45759
+ asset_id: assetId,
45004
45760
  address,
45005
45761
  // The HTTP gateway uses `payment_method` for both buy and sell, mirroring the
45006
45762
  // iOS production client. The sell proto declares `payout_method` but the
@@ -45012,7 +45768,7 @@ var OnrampActionProvider = class extends ActionProvider {
45012
45768
  });
45013
45769
  const result = await ctx.httpClient.gateway(
45014
45770
  "GET",
45015
- `/v1/sellcrypto/quote/${encodeURIComponent(input2.cryptoAsset)}`,
45771
+ `/v1/sellcrypto/quote/${encodeURIComponent(assetId)}`,
45016
45772
  void 0,
45017
45773
  query.toString()
45018
45774
  );
@@ -45022,9 +45778,10 @@ var OnrampActionProvider = class extends ActionProvider {
45022
45778
  success: true,
45023
45779
  summary: summarizeOfframpQuotes(quotes),
45024
45780
  address,
45025
- cryptoAsset: input2.cryptoAsset,
45781
+ chain: chainKey,
45782
+ tokenAddress: input2.tokenAddress,
45026
45783
  quotes,
45027
- nextAction: quotes.length > 0 ? "Call get_offramp_url with the chosen quoteId to receive a browser redirect URL for the sale. quotes[0] is the best deal (lowest vsBestPct)." : "No quotes available \u2014 try a different cryptoAmount, cryptoAsset, or paymentMethod."
45784
+ nextAction: quotes.length > 0 ? "Call get_offramp_url with the chosen quoteId to receive a browser redirect URL for the sale. quotes[0] is the best deal (lowest vsBestPct)." : "No quotes available \u2014 try a different cryptoAmount, chain/tokenAddress, or paymentMethod."
45028
45785
  };
45029
45786
  }
45030
45787
  };
@@ -45036,14 +45793,16 @@ var OnrampActionProvider = class extends ActionProvider {
45036
45793
  schema: import_zod12.z.object({
45037
45794
  quoteId: import_zod12.z.string().describe("Quote ID returned by get_offramp_quote."),
45038
45795
  walletAddress: import_zod12.z.string().optional().describe(
45039
- "Source wallet address. OMIT to auto-derive from the configured wallet \u2014 requires cryptoAsset so the chain can be determined. Only supply this when operating on behalf of a different user."
45796
+ "Source wallet address. OMIT to auto-derive from the configured wallet \u2014 requires chain so the address can be derived. Only supply this when operating on behalf of a different user."
45040
45797
  ),
45041
- cryptoAsset: import_zod12.z.string().optional().describe(
45042
- "Crypto asset ID that was used for the quote (e.g., c60 for ETH). Required when walletAddress is omitted so the chain can be derived for address lookup."
45043
- )
45798
+ chain: import_zod12.z.string().optional().describe(
45799
+ `${CHAIN_DESC} Required when walletAddress is omitted so the address can be derived.`
45800
+ ),
45801
+ tokenAddress: import_zod12.z.string().trim().min(1).optional().describe(TOKEN_ADDR_DESC)
45044
45802
  }),
45045
45803
  invoke: async (ctx, input2) => {
45046
- const address = await this.resolveAddress(input2.walletAddress, input2.cryptoAsset);
45804
+ const chainKey = input2.chain !== void 0 ? resolveAsset(input2.chain).chainKey : void 0;
45805
+ const address = await this.resolveAddress(input2.walletAddress, chainKey);
45047
45806
  const query = new URLSearchParams({ address });
45048
45807
  let result;
45049
45808
  try {
@@ -45066,7 +45825,7 @@ var OnrampActionProvider = class extends ActionProvider {
45066
45825
  throw err;
45067
45826
  }
45068
45827
  const url = result.url ?? result.redirect_url;
45069
- const followUp = url ? buildFollowUp(input2.cryptoAsset, address, "offramp") : void 0;
45828
+ const followUp = url ? buildFollowUp(chainKey, input2.tokenAddress, address, "offramp") : void 0;
45070
45829
  return {
45071
45830
  success: Boolean(url),
45072
45831
  address,
@@ -45081,7 +45840,7 @@ var OnrampActionProvider = class extends ActionProvider {
45081
45840
  confirmOfframpTransfer() {
45082
45841
  return {
45083
45842
  name: "confirm_offramp_transfer",
45084
- description: "Step 3 of selling crypto for fiat: sign and broadcast the payout transaction to the provider's deposit address. Call this AFTER the user has completed KYC in the browser and can read the deposit address + exact amount the provider displayed. Works with any wallet provider that implements sendTransaction (the local agent wallet, or a WalletConnect-paired external wallet). EVM-only \u2014 for BTC / Solana / Cosmos off-ramps use the `twak onramp sell-confirm` CLI command instead. Returns a tx hash on success; fiat payout from the provider then arrives separately (no on-chain signal for that step).",
45843
+ description: "Step 3 of selling crypto for fiat: sign and broadcast the payout transaction to the provider's deposit address. Call this AFTER the user has completed KYC in the browser and can read the deposit address + exact amount the provider displayed. Works with any wallet provider that implements sendTransaction (the local agent wallet, or a WalletConnect-paired external wallet). EVM-only \u2014 non-EVM chains (BTC, Solana, Cosmos, etc.) are not supported by this action. Returns a tx hash on success; fiat payout from the provider then arrives separately (no on-chain signal for that step).",
45085
45844
  schema: import_zod12.z.object({
45086
45845
  depositAddress: import_zod12.z.string().describe(
45087
45846
  "The deposit address displayed by the provider after KYC. This is a third-party address \u2014 verify it matches what the provider shows before calling."
@@ -45089,8 +45848,9 @@ var OnrampActionProvider = class extends ActionProvider {
45089
45848
  cryptoAmount: import_zod12.z.string().describe(
45090
45849
  'Exact amount to send in human-readable format (e.g., "0.1" for 0.1 ETH). Must match the amount the provider displays; under-sending will invalidate the sell order.'
45091
45850
  ),
45092
- cryptoAsset: import_zod12.z.string().describe(
45093
- "Crypto asset being sold. Format: c<coinId> for native coins (e.g., c60 for ETH) or c<coinId>_t<contract> for ERC-20 tokens."
45851
+ chain: import_zod12.z.string().describe(CHAIN_DESC),
45852
+ tokenAddress: import_zod12.z.string().trim().min(1).optional().describe(
45853
+ "ERC-20 token contract address. Omit for the chain native coin. This action is EVM-only \u2014 non-EVM tokens are rejected."
45094
45854
  ),
45095
45855
  decimals: import_zod12.z.coerce.number().optional().describe(
45096
45856
  "Token decimals. REQUIRED for ERC-20 assets (pass 6 for USDC/USDT, 18 for DAI, etc.) \u2014 omitting it will be rejected rather than silently defaulting to 18, because a wrong-decimals encode can over-pay the provider by orders of magnitude. Ignored for native coins."
@@ -45104,82 +45864,37 @@ var OnrampActionProvider = class extends ActionProvider {
45104
45864
  }),
45105
45865
  invoke: async (_ctx, input2) => {
45106
45866
  const signer = this.walletContext.activeWallet();
45107
- let parsed;
45108
- try {
45109
- parsed = parseTokenId(input2.cryptoAsset);
45110
- } catch {
45111
- throw new TransferError("VALIDATION_ERROR", `Invalid cryptoAsset: ${input2.cryptoAsset}`);
45112
- }
45113
- const chainKey = coinTypeToChainKey(parsed.coinId);
45114
- if (!chainKey) {
45867
+ const { chainKey } = resolveAsset(input2.chain);
45868
+ const chainDef = requireChain(chainKey);
45869
+ if (chainDef.namespace !== "eip155") {
45115
45870
  throw new TransferError(
45116
45871
  "CHAIN_UNSUPPORTED",
45117
- `Unsupported coin type in cryptoAsset: ${input2.cryptoAsset}`
45118
- );
45119
- }
45120
- const chainDef = getChainById(chainKey);
45121
- if (!chainDef || chainDef.namespace !== "eip155") {
45122
- throw new TransferError(
45123
- "CHAIN_UNSUPPORTED",
45124
- `confirm_offramp_transfer is EVM-only. For ${chainKey} off-ramps use the CLI: twak onramp sell-confirm --asset ${input2.cryptoAsset} --to <deposit> --amount <n>${input2.memo ? " --memo <tag>" : ""}.`
45872
+ `confirm_offramp_transfer is EVM-only. ${chainKey} is not supported.`
45125
45873
  );
45126
45874
  }
45127
45875
  const caip2 = `eip155:${chainDef.chainId}`;
45128
- if (!EVM_ADDRESS_RE.test(input2.depositAddress)) {
45129
- throw new TransferError(
45130
- "VALIDATION_ERROR",
45131
- `depositAddress is not a valid EVM address: ${input2.depositAddress}. Double-check what the provider displayed.`
45132
- );
45133
- }
45134
- if (parsed.contractAddress && !EVM_ADDRESS_RE.test(parsed.contractAddress)) {
45135
- throw new TransferError(
45136
- "VALIDATION_ERROR",
45137
- `Malformed token contract in cryptoAsset: ${parsed.contractAddress}`
45138
- );
45139
- }
45140
- if (parsed.contractAddress && input2.decimals === void 0) {
45141
- throw new TransferError(
45142
- "VALIDATION_ERROR",
45143
- `decimals is required for ERC-20 asset ${input2.cryptoAsset}. Pass the token's decimals explicitly (6 for USDC/USDT, 18 for DAI, etc.) \u2014 silently defaulting to 18 could over-pay the provider by orders of magnitude.`
45144
- );
45145
- }
45146
- const decimals = parsed.contractAddress ? input2.decimals : 18;
45147
- try {
45148
- assertStrictPositiveAmount(input2.cryptoAmount, decimals);
45149
- } catch (err) {
45150
- throw new TransferError(
45151
- "VALIDATION_ERROR",
45152
- err instanceof Error ? err.message : String(err)
45153
- );
45154
- }
45876
+ const decimals = validateOfframpInput(input2, chainKey, chainDef.decimals);
45155
45877
  if (this.priceLookup) {
45156
- try {
45157
- const tokenInfo = resolveToken(input2.cryptoAsset, chainKey);
45158
- const tokenAddress = tokenInfo?.address ?? parsed.contractAddress ?? "";
45159
- const price = await this.priceLookup.getTokenPrice(chainKey, tokenAddress);
45160
- if (price && price > 0) {
45161
- const usdValue = parseFloat(input2.cryptoAmount) * price;
45162
- if (usdValue > this.maxTransferUsd) {
45163
- return {
45164
- success: false,
45165
- code: "USD_LIMIT_EXCEEDED",
45166
- message: `Off-ramp payout value ~$${usdValue.toFixed(2)} USD exceeds safety limit of $${this.maxTransferUsd}. Use the CLI with --max-usd ${Math.ceil(usdValue)} or --skip-safety-check to proceed.`
45167
- };
45168
- }
45169
- }
45170
- } catch {
45171
- }
45878
+ const cap = await checkOfframpUsdCap(
45879
+ this.priceLookup,
45880
+ this.maxTransferUsd,
45881
+ chainKey,
45882
+ input2.tokenAddress,
45883
+ input2.cryptoAmount
45884
+ );
45885
+ if (cap) return { success: false, ...cap };
45172
45886
  }
45173
45887
  const atomic = parseAmount(input2.cryptoAmount, decimals);
45174
45888
  let txHash;
45175
- if (parsed.contractAddress) {
45176
- const calldata = encodeErc20Transfer2(input2.depositAddress, BigInt(atomic));
45177
- txHash = await signer.sendTransaction({
45178
- chainId: caip2,
45179
- to: parsed.contractAddress,
45180
- data: calldata,
45181
- value: "0x0"
45182
- });
45889
+ if (input2.tokenAddress) {
45890
+ if (!this.blockchainClient) {
45891
+ throw new TransferError(
45892
+ "WALLET_NOT_FOUND",
45893
+ "BlockchainClient not available \u2014 off-ramp token payout requires a configured blockchain client"
45894
+ );
45895
+ }
45896
+ const pending = await this.blockchainClient.contract(erc20).withAddress(input2.tokenAddress, chainKey).write.transfer([input2.depositAddress, BigInt(atomic)], signer);
45897
+ txHash = pending.txHash;
45183
45898
  } else {
45184
45899
  txHash = await signer.sendTransaction({
45185
45900
  chainId: caip2,
@@ -45193,13 +45908,13 @@ var OnrampActionProvider = class extends ActionProvider {
45193
45908
  success: true,
45194
45909
  txHash,
45195
45910
  chain: chainKey,
45911
+ tokenAddress: input2.tokenAddress,
45196
45912
  to: input2.depositAddress,
45197
45913
  amount: input2.cryptoAmount,
45198
- asset: input2.cryptoAsset,
45199
45914
  memoIgnored: input2.memo ? `A memo was supplied (${input2.memo}) but EVM transactions have no memo field \u2014 it was ignored. If the provider requires a memo, they likely expect a non-EVM chain.` : void 0,
45200
45915
  quoteId: input2.quoteId,
45201
45916
  explorer: explorerUrl,
45202
- summary: `Off-ramp payout of ${input2.cryptoAmount} ${input2.cryptoAsset} broadcast to ${input2.depositAddress} on ${chainKey}. Fiat payout from the provider arrives separately.`
45917
+ summary: `Off-ramp payout of ${input2.cryptoAmount}${input2.tokenAddress ? ` ${input2.tokenAddress.slice(0, 10)}\u2026` : ` ${chainDef.symbol}`} broadcast to ${input2.depositAddress} on ${chainKey}. Fiat payout from the provider arrives separately.`
45203
45918
  };
45204
45919
  }
45205
45920
  };
@@ -45231,7 +45946,7 @@ var AutomateActionProvider = class extends ActionProvider {
45231
45946
  type: import_zod13.z.enum(["dca", "limit"]).describe("Automation type: dca (recurring) or limit (price trigger)"),
45232
45947
  fromToken: import_zod13.z.string().describe("Source token symbol or address"),
45233
45948
  toToken: import_zod13.z.string().describe("Destination token symbol or address"),
45234
- chain: import_zod13.z.string().default("ethereum").describe("Chain name"),
45949
+ chain: import_zod13.z.string().default("ethereum").describe(CHAIN_DESC),
45235
45950
  amount: import_zod13.z.string().describe("Amount of fromToken per execution"),
45236
45951
  intervalMs: import_zod13.z.coerce.number().optional().describe("Interval in milliseconds for DCA (e.g., 86400000 for daily)"),
45237
45952
  targetPrice: import_zod13.z.coerce.number().optional().describe("Target price in USD for limit orders"),
@@ -45252,11 +45967,12 @@ var AutomateActionProvider = class extends ActionProvider {
45252
45967
  message: "Limit orders require targetPrice and condition"
45253
45968
  };
45254
45969
  }
45970
+ const chainKey = resolveChain(input2.chain);
45255
45971
  const record2 = await this.automationStore.create({
45256
45972
  type: input2.type,
45257
45973
  fromToken: input2.fromToken,
45258
45974
  toToken: input2.toToken,
45259
- chain: input2.chain,
45975
+ chain: chainKey,
45260
45976
  amount: input2.amount,
45261
45977
  intervalMs: input2.intervalMs,
45262
45978
  targetPrice: input2.targetPrice,
@@ -45432,7 +46148,7 @@ var X402ActionProvider = class extends ActionProvider {
45432
46148
  x402Quote() {
45433
46149
  return {
45434
46150
  name: "x402_quote",
45435
- description: 'Preview the payment requirements at an x402 payment-gated endpoint WITHOUT signing or paying. Returns the full list of payment options (each entry has chain, asset address, tokenName, amount, transferMethod, requiresApproval, and a pre-formatted `description` string). CRITICAL \u2014 after this returns, ALWAYS surface ALL entries in `accepts[]` to the user (as a short numbered list: chain \xB7 token \xB7 amount \xB7 method \xB7 "preferred" tag if applicable) and ask which to use BEFORE calling x402_request. Do not silently pick the `preferred` entry just because it is marked preferred \u2014 that is the picker default if no override is passed, but the user deserves to see every option (other chains may have cheaper gas, the user may hold balance only on a specific chain, etc.). The only exception: if you have already done a balance check and only one entry is viable, you may proceed with that one after noting the others as "unavailable (no balance)". IMPORTANT: each entry in `accepts[]` is an INDEPENDENT payment option \u2014 a unique (chain, token, transfer method) triple. Do NOT merge entries by chain (e.g. "BSC USDC via EIP-3009 or Permit2" is wrong if BSC USDC is only offered via Permit2 and a separate BSC entry for a different token offers EIP-3009). Read each entry as its own row; use the per-entry `description` field verbatim when surfacing options to the user. Once the user picks, call x402_request with the agreed maxPaymentAtomic plus optionally preferNetwork/preferMethod/preferAsset to disambiguate. INTERPRETING `requiresApproval`: `false` (EIP-3009, e.g. USDC on Base / Ethereum) \u2014 gasless. Sign the payment and you are done. No setup, no on-chain tx for the buyer, ever. `true` (Permit2, e.g. USDC on BSC) \u2014 the industry-standard Permit2 flow used by Uniswap, 1inch, and most major DeFi apps. THE FIRST x402_request to a Permit2 endpoint for a given token+chain broadcasts a one-time `approve(Permit2, MAX_UINT256)` tx (the buyer pays the gas \u2014 usually a few cents on BSC / Base, more on Ethereum mainnet). The allowance granted is unlimited for the canonical Permit2 contract at 0x000000000022D473030F116dDEE9F6B43aC78BA3. After that, every future Permit2 payment for that token is signature-only (gasless). Revocable any time via `twak erc20 revoke`. The per-call spend is still capped at the request `maxPaymentAtomic` \u2014 unlimited allowance does NOT mean unlimited per-call spend. When `requiresApproval: true` AND the buyer has not used Permit2 for that token on that chain before, the agent SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) before calling x402_request. BALANCE CHECK \u2014 AFTER this quote returns and BEFORE recommending any route or showing the menu to the user, check the wallet balance for every viable entry in `accepts`. For each entry: call `get_address(chainKey)` to find the wallet address on that chain, then `get_balance` for the entry\'s asset (or `wallet_balance(chainKey)` for native coins). Filter or annotate the menu \u2014 only recommend routes where the wallet has enough of the asset to cover `amount`. If a Permit2 entry needs a first-time approval (`requiresApproval: true`), also confirm the wallet holds some native chain gas (gas amount varies by chain). If no viable route has sufficient balance, tell the user upfront \u2014 do not present a menu that includes routes they cannot pay. This avoids the failure mode where the agent recommends a chain the user has no balance on and the user finds out only after committing. No wallet required \u2014 read-only HTTPS + 402 parse. HTTPS-only; private IPs and non-HTTPS schemes are rejected.',
46151
+ description: 'Preview the payment requirements at an x402 payment-gated endpoint WITHOUT signing or paying. Returns the full list of payment options (each entry has chain, asset address, tokenName, amount, transferMethod, requiresApproval, and a pre-formatted `description` string). CRITICAL \u2014 after this returns, ALWAYS surface ALL entries in `accepts[]` to the user (as a short numbered list: chain \xB7 token \xB7 amount \xB7 method \xB7 "preferred" tag if applicable) and ask which to use BEFORE calling x402_request. Do not silently pick the `preferred` entry just because it is marked preferred \u2014 that is the picker default if no override is passed, but the user deserves to see every option (other chains may have cheaper gas, the user may hold balance only on a specific chain, etc.). The only exception: if you have already done a balance check and only one entry is viable, you may proceed with that one after noting the others as "unavailable (no balance)". IMPORTANT: each entry in `accepts[]` is an INDEPENDENT payment option \u2014 a unique (chain, token, transfer method) triple. Do NOT merge entries by chain (e.g. "BSC USDC via EIP-3009 or Permit2" is wrong if BSC USDC is only offered via Permit2 and a separate BSC entry for a different token offers EIP-3009). Read each entry as its own row; use the per-entry `description` field verbatim when surfacing options to the user. Once the user picks, call x402_request with the agreed maxPaymentAtomic plus optionally preferNetwork/preferMethod/preferAsset to disambiguate. INTERPRETING `requiresApproval`: `false` (EIP-3009, e.g. USDC on Base / Ethereum) \u2014 gasless. Sign the payment and you are done. No setup, no on-chain tx for the buyer, ever. `true` (Permit2, e.g. USDC on BSC) \u2014 the industry-standard Permit2 flow used by Uniswap, 1inch, and most major DeFi apps. THE FIRST x402_request to a Permit2 endpoint for a given token+chain broadcasts a one-time `approve(Permit2, MAX_UINT256)` tx (the buyer pays the gas \u2014 usually a few cents on BSC / Base, more on Ethereum mainnet). The allowance granted is unlimited for the canonical Permit2 contract at 0x000000000022D473030F116dDEE9F6B43aC78BA3. After that, every future Permit2 payment for that token is signature-only (gasless). Revocable any time via `twak erc20 revoke`. The per-call spend is still capped at the request `maxPaymentAtomic` \u2014 unlimited allowance does NOT mean unlimited per-call spend. When `requiresApproval: true` AND the buyer has not used Permit2 for that token on that chain before, the agent SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) before calling x402_request. BALANCE CHECK \u2014 AFTER this quote returns and BEFORE recommending any route or showing the menu to the user, check the wallet balance for every viable entry in `accepts`. For each entry: call `get_address(chain)` to find the wallet address on that chain, then `get_balance` for the entry\'s asset (or `wallet_balance(chain)` for native coins). Filter or annotate the menu \u2014 only recommend routes where the wallet has enough of the asset to cover `amount`. If a Permit2 entry needs a first-time approval (`requiresApproval: true`), also confirm the wallet holds some native chain gas (gas amount varies by chain). If no viable route has sufficient balance, tell the user upfront \u2014 do not present a menu that includes routes they cannot pay. This avoids the failure mode where the agent recommends a chain the user has no balance on and the user finds out only after committing. No wallet required \u2014 read-only HTTPS + 402 parse. HTTPS-only; private IPs and non-HTTPS schemes are rejected.',
45436
46152
  schema: import_zod14.z.object({
45437
46153
  url: import_zod14.z.string().describe(
45438
46154
  "Target endpoint URL. Must be HTTPS. Resolves via DNS; private/local IPs are rejected."
@@ -45448,7 +46164,7 @@ var X402ActionProvider = class extends ActionProvider {
45448
46164
  x402Request() {
45449
46165
  return {
45450
46166
  name: "x402_request",
45451
- description: "Make an HTTP request to an x402 payment-gated endpoint. Handles the full flow: sends the initial request, parses any 402 challenge, signs an EIP-3009 or Permit2 payment authorization with the agent wallet, retries with the X-Payment header. PAYMENT METHODS: x402 endpoints offer one or more options in their 402 response. EIP-3009 is gasless for the buyer (no setup, no approval) \u2014 preferred when available, e.g. USDC on Base / Ethereum. Permit2 needs a one-time approve(Permit2, MAX) tx on the chain (the buyer pays the gas \u2014 usually a few cents on BSC / Base, more on Ethereum mainnet), then every future Permit2 payment for that token is signature-only. Permit2 is required when the token does not support EIP-3009 \u2014 e.g. USDC on BSC. SAFETY \u2014 autoApprove decision: When autoApprove=true (default), this action silently broadcasts the one-time Permit2 approval if your wallet has not yet approved the canonical Permit2 contract for that token. The approval is to the same Permit2 contract Uniswap, 1inch, and most major DeFi apps use \u2014 it is not exotic infrastructure. The approval alone does not move funds; each subsequent payment still needs a fresh signature. Revocable any time via `twak erc20 revoke`. For first-time use of an endpoint you SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) and get their go-ahead before calling. After the approval lands, subsequent Permit2 payments for that token are free of further approvals. Set autoApprove=false to surface code: 'PERMIT2_APPROVAL_REQUIRED' instead of broadcasting \u2014 useful when the user is not around or you want to handle the approval out-of-band. maxPaymentAtomic is the user's pre-commitment to a maximum payment in the asset's smallest unit (e.g. \"10000\" = 0.01 USDC at 6 decimals). If the server demands more than this cap, the call returns code: 'PAYMENT_AMOUNT_EXCEEDED' \u2014 DO NOT retry with a higher cap without asking the user. BALANCE CHECK \u2014 Before calling this action, verify the wallet holds enough of the chosen asset on the chosen chain. Call `get_address(chainKey)` to find the wallet address, then `get_balance` for the asset (or `wallet_balance(chainKey)` for native coins). For Permit2 first-time use, also confirm the wallet holds some native chain gas (amount varies by chain). Surface insufficient balance to the user BEFORE calling this action \u2014 a failed payment mid-flow can leave the wallet in a half-state (e.g. Permit2 approval broadcast, then the transfer signature fails). The balance check should already have happened during the `x402_quote` flow that produced the menu; redo it here only if you skipped quote or the balance might have changed since. WALLET \u2014 Signs through whichever wallet mode is active for this session (local by default; switchable via `switch_wallet_mode`). If mode='local', signs in-process (no external prompt). If mode='walletconnect', signs over the WalletConnect relay \u2014 the user must approve each signature on their paired wallet. To retrieve the active wallet's address for a chain (e.g. to call `wallet_balance` / `get_balance`), call `get_address` with the chain key (e.g. \"bsc\", \"base\"), or `list_addresses` for all chains at once. HTTPS-only; non-HTTPS schemes and private IPs are rejected before any payment signing.",
46167
+ description: "Make an HTTP request to an x402 payment-gated endpoint. Handles the full flow: sends the initial request, parses any 402 challenge, signs an EIP-3009 or Permit2 payment authorization with the agent wallet, retries with the X-Payment header. PAYMENT METHODS: x402 endpoints offer one or more options in their 402 response. EIP-3009 is gasless for the buyer (no setup, no approval) \u2014 preferred when available, e.g. USDC on Base / Ethereum. Permit2 needs a one-time approve(Permit2, MAX) tx on the chain (the buyer pays the gas \u2014 usually a few cents on BSC / Base, more on Ethereum mainnet), then every future Permit2 payment for that token is signature-only. Permit2 is required when the token does not support EIP-3009 \u2014 e.g. USDC on BSC. SAFETY \u2014 autoApprove decision: When autoApprove=true (default), this action silently broadcasts the one-time Permit2 approval if your wallet has not yet approved the canonical Permit2 contract for that token. The approval is to the same Permit2 contract Uniswap, 1inch, and most major DeFi apps use \u2014 it is not exotic infrastructure. The approval alone does not move funds; each subsequent payment still needs a fresh signature. Revocable any time via `twak erc20 revoke`. For first-time use of an endpoint you SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) and get their go-ahead before calling. After the approval lands, subsequent Permit2 payments for that token are free of further approvals. Set autoApprove=false to surface code: 'PERMIT2_APPROVAL_REQUIRED' instead of broadcasting \u2014 useful when the user is not around or you want to handle the approval out-of-band. maxPaymentAtomic is the user's pre-commitment to a maximum payment in the asset's smallest unit (e.g. \"10000\" = 0.01 USDC at 6 decimals). If the server demands more than this cap, the call returns code: 'PAYMENT_AMOUNT_EXCEEDED' \u2014 DO NOT retry with a higher cap without asking the user. BALANCE CHECK \u2014 Before calling this action, verify the wallet holds enough of the chosen asset on the chosen chain. Call `get_address(chain)` to find the wallet address, then `get_balance` for the asset (or `wallet_balance(chain)` for native coins). For Permit2 first-time use, also confirm the wallet holds some native chain gas (amount varies by chain). Surface insufficient balance to the user BEFORE calling this action \u2014 a failed payment mid-flow can leave the wallet in a half-state (e.g. Permit2 approval broadcast, then the transfer signature fails). The balance check should already have happened during the `x402_quote` flow that produced the menu; redo it here only if you skipped quote or the balance might have changed since. WALLET \u2014 Signs through whichever wallet mode is active for this session (local by default; switchable via `switch_wallet_mode`). If mode='local', signs in-process (no external prompt). If mode='walletconnect', signs over the WalletConnect relay \u2014 the user must approve each signature on their paired wallet. To retrieve the active wallet's address for a chain (e.g. to call `wallet_balance` / `get_balance`), call `get_address` with the chain key (e.g. \"bsc\", \"base\"), or `list_addresses` for all chains at once. HTTPS-only; non-HTTPS schemes and private IPs are rejected before any payment signing.",
45452
46168
  schema: import_zod14.z.object({
45453
46169
  url: import_zod14.z.string().describe(
45454
46170
  "Target endpoint URL. Must be HTTPS. Resolves via DNS; private/local IPs are rejected before any payment."
@@ -45572,8 +46288,8 @@ var WalletModeActionProvider = class extends ActionProvider {
45572
46288
  "WalletConnect is not configured. Set WALLETCONNECT_PROJECT_ID (env var or `twak init --wc-project-id <id>`) and restart the MCP server."
45573
46289
  );
45574
46290
  }
45575
- const wc2 = this.walletConnectFactory();
45576
- await this.walletContext.beginWalletConnect(wc2);
46291
+ const wc3 = this.walletConnectFactory();
46292
+ await this.walletContext.beginWalletConnect(wc3);
45577
46293
  return this.snapshotResponse(this.walletContext.snapshot());
45578
46294
  }
45579
46295
  };
@@ -50224,7 +50940,6 @@ var StdioServerTransport = class {
50224
50940
  init_dist();
50225
50941
  var import_http = require("http");
50226
50942
  var import_crypto7 = require("crypto");
50227
- init_dist2();
50228
50943
  init_dist();
50229
50944
  var SERVER_INSTRUCTIONS = `twak signs blockchain operations through one of two wallet modes:
50230
50945
 
@@ -50309,263 +51024,18 @@ var TwakMcpServer = class {
50309
51024
  await this.server.connect(transport);
50310
51025
  }
50311
51026
  };
50312
- var CAIP2_TO_CHAIN = {
50313
- "eip155:1": "ethereum",
50314
- "eip155:56": "smartchain",
50315
- "eip155:137": "polygon",
50316
- "eip155:42161": "arbitrum",
50317
- "eip155:8453": "base",
50318
- "eip155:10": "optimism",
50319
- "eip155:43114": "avalanche"
50320
- };
50321
- var CAIP2_TO_BC_CHAIN = {
50322
- "eip155:1": "ethereum",
50323
- "eip155:56": "bsc",
50324
- "eip155:137": "polygon",
50325
- "eip155:42161": "arbitrum",
50326
- "eip155:8453": "base",
50327
- "eip155:10": "optimism",
50328
- "eip155:43114": "avalanche"
50329
- };
50330
- var RPC_REGISTRY = {
50331
- "eip155:1": {
50332
- rpc: "https://eth.llamarpc.com",
50333
- native: "eth",
50334
- nativeDecimals: 18,
50335
- tokens: {
50336
- usdc: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", decimals: 6 },
50337
- usdt: { address: "0xdAC17F958D2ee523a2206206994597C13D831ec7", decimals: 6 },
50338
- dai: { address: "0x6B175474E89094C44Da98b954EedeAC495271d0F", decimals: 18 }
50339
- }
50340
- },
50341
- "eip155:56": {
50342
- rpc: "https://bsc-rpc.publicnode.com",
50343
- native: "bnb",
50344
- nativeDecimals: 18,
50345
- tokens: {
50346
- usdt: { address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18 },
50347
- usdc: { address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", decimals: 18 },
50348
- twt: { address: "0x4B0F1812e5Df2A09796481Ff14017e6005508003", decimals: 18 }
50349
- }
50350
- },
50351
- "eip155:137": {
50352
- rpc: "https://rpc-mainnet.matic.quiknode.pro",
50353
- native: "matic",
50354
- nativeDecimals: 18,
50355
- tokens: {
50356
- usdc: { address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", decimals: 6 },
50357
- usdt: { address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F", decimals: 6 }
50358
- }
50359
- },
50360
- "eip155:42161": {
50361
- rpc: "https://arb1.arbitrum.io/rpc",
50362
- native: "eth",
50363
- nativeDecimals: 18,
50364
- tokens: {
50365
- usdc: { address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimals: 6 },
50366
- usdt: { address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", decimals: 6 }
50367
- }
50368
- },
50369
- "eip155:8453": {
50370
- rpc: "https://mainnet.base.org",
50371
- native: "eth",
50372
- nativeDecimals: 18,
50373
- tokens: {
50374
- usdc: { address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", decimals: 6 },
50375
- dai: { address: "0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb", decimals: 18 }
50376
- }
50377
- },
50378
- "eip155:10": {
50379
- rpc: "https://mainnet.optimism.io",
50380
- native: "eth",
50381
- nativeDecimals: 18,
50382
- tokens: {
50383
- usdc: { address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", decimals: 6 },
50384
- usdt: { address: "0x94b008aA00579c1307B0EF2c499aD98a8ce58e58", decimals: 6 }
50385
- }
50386
- },
50387
- "eip155:43114": {
50388
- rpc: "https://api.avax.network/ext/bc/C/rpc",
50389
- native: "avax",
50390
- nativeDecimals: 18,
50391
- tokens: {
50392
- usdc: { address: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", decimals: 6 },
50393
- usdt: { address: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", decimals: 6 }
50394
- }
50395
- }
50396
- };
50397
- var TRANSFER_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
50398
- function parseTokenAmount(amount, decimals) {
50399
- const [int, frac = ""] = amount.split(".");
50400
- const fracNorm = frac.slice(0, decimals).padEnd(decimals, "0");
50401
- return BigInt(int || "0") * 10n ** BigInt(decimals) + BigInt(fracNorm || "0");
50402
- }
50403
- async function jsonRpc(rpcUrl, method, params) {
50404
- const res = await fetch(rpcUrl, {
50405
- method: "POST",
50406
- headers: { "Content-Type": "application/json" },
50407
- body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params })
50408
- });
50409
- if (!res.ok) throw new Error(`RPC HTTP ${res.status}`);
50410
- const data = await res.json();
50411
- if (data.error) throw new Error(data.error.message);
50412
- return data.result;
50413
- }
50414
- async function verifyViaRPC(txHash, config, chainId) {
50415
- const chain2 = RPC_REGISTRY[chainId];
50416
- if (!chain2) return { valid: false, error: `No RPC available for chain ${chainId}` };
50417
- const assetKey = config.asset.toLowerCase();
50418
- const isNative = assetKey === chain2.native;
50419
- const tokenInfo = isNative ? null : chain2.tokens[assetKey];
50420
- if (!isNative && !tokenInfo) {
50421
- return { valid: false, error: `Unsupported asset "${config.asset}" on chain ${chainId}` };
50422
- }
50423
- const decimals = isNative ? chain2.nativeDecimals : tokenInfo.decimals;
50424
- const expectedAmount = parseTokenAmount(config.amount, decimals);
50425
- const recipientLower = config.recipient.toLowerCase();
50426
- let receipt;
50427
- try {
50428
- receipt = await jsonRpc(chain2.rpc, "eth_getTransactionReceipt", [txHash]);
50429
- } catch (err) {
50430
- const msg = err instanceof Error ? err.message : String(err);
50431
- if (msg === "no response" || msg.toLowerCase().includes("not found")) {
50432
- return { valid: false, error: "Transaction not found or not yet mined" };
50433
- }
50434
- return { valid: false, error: `RPC error: ${msg}` };
50435
- }
50436
- if (!receipt) return { valid: false, error: "Transaction not found or not yet mined" };
50437
- if (receipt.status !== "0x1") return { valid: false, error: "Transaction reverted on-chain" };
50438
- if (isNative) {
50439
- const tx = await jsonRpc(chain2.rpc, "eth_getTransactionByHash", [txHash]);
50440
- if (!tx) return { valid: false, error: "Transaction data not found" };
50441
- if (config.recipient && tx.to?.toLowerCase() !== recipientLower) {
50442
- return { valid: false, error: "Payment recipient mismatch" };
50443
- }
50444
- if (BigInt(tx.value || "0x0") < expectedAmount) {
50445
- return { valid: false, error: "Insufficient native payment" };
50446
- }
50447
- return { valid: true, method: "rpc" };
50448
- }
50449
- const tokenAddr = tokenInfo.address.toLowerCase();
50450
- const recipientPadded = recipientLower.replace("0x", "").padStart(64, "0");
50451
- const transferLog = receipt.logs?.find(
50452
- (log) => log.address.toLowerCase() === tokenAddr && log.topics[0] === TRANSFER_TOPIC && (!config.recipient || log.topics[2]?.toLowerCase() === `0x${recipientPadded}`)
50453
- );
50454
- if (!transferLog) return { valid: false, error: "No matching Transfer event found" };
50455
- if (BigInt(transferLog.data) < expectedAmount) {
50456
- return { valid: false, error: "Insufficient ERC-20 payment" };
50457
- }
50458
- return { valid: true, method: "rpc" };
50459
- }
50460
- async function verifyPayment(rawHeader, config, httpClient, seenHashes, blockchainClient) {
50461
- const header = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;
50462
- let proof;
50463
- try {
50464
- proof = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
50465
- } catch {
50466
- return { valid: false, error: "Invalid payment header: expected base64-encoded JSON" };
50467
- }
50468
- if (!proof.txHash || typeof proof.txHash !== "string") {
50469
- return { valid: false, error: "Missing txHash in payment proof" };
50470
- }
50471
- const txHash = proof.txHash;
50472
- if (await seenHashes.hasSeen(txHash)) {
50473
- return { valid: false, error: "Transaction hash already used (replay attack detected)" };
50474
- }
50475
- if (typeof proof.chainId === "string" && proof.chainId.toLowerCase() !== config.chainId.toLowerCase()) {
50476
- return {
50477
- valid: false,
50478
- error: `Payment chain mismatch \u2014 merchant configured ${config.chainId}, proof claims ${proof.chainId}`
50479
- };
50480
- }
50481
- const chainId = config.chainId.toLowerCase();
50482
- const chainKey = CAIP2_TO_CHAIN[chainId];
50483
- if (!chainKey) {
50484
- return { valid: false, error: `Unsupported chain: ${chainId}` };
50485
- }
50486
- const bcChainKey = CAIP2_TO_BC_CHAIN[chainId];
50487
- if (blockchainClient && bcChainKey) {
50488
- try {
50489
- const status = await blockchainClient.findTransactionStatus(
50490
- bcChainKey,
50491
- config.recipient || "0x0000000000000000000000000000000000000000",
50492
- txHash
50493
- );
50494
- if (status.failed) {
50495
- return {
50496
- valid: false,
50497
- error: `Transaction reverted on-chain${status.error ? `: ${status.error}` : ""}`
50498
- };
50499
- }
50500
- if (status.confirmed) {
50501
- const rpcResult2 = await verifyViaRPC(txHash, config, chainId);
50502
- if (rpcResult2.valid) {
50503
- await seenHashes.markSeen(txHash, chainId);
50504
- }
50505
- return rpcResult2;
50506
- }
50507
- } catch {
50508
- }
50509
- }
50510
- const txService = new TransactionService(httpClient);
50511
- try {
50512
- const tx = await txService.getByHash(txHash, chainKey);
50513
- if (!tx) return { valid: false, error: "Transaction not found" };
50514
- if (tx.status === "pending" || tx.status === "failed") {
50515
- return { valid: false, error: `Transaction is not confirmed (status: ${tx.status})` };
50516
- }
50517
- if (config.recipient && tx.to?.toLowerCase() !== config.recipient.toLowerCase()) {
50518
- return { valid: false, error: "Payment recipient mismatch" };
50519
- }
50520
- const chain2 = RPC_REGISTRY[chainId];
50521
- const assetKey = config.asset.toLowerCase();
50522
- const isNative = chain2 && assetKey === chain2.native;
50523
- const tokenInfo = chain2 && !isNative ? chain2.tokens[assetKey] : void 0;
50524
- const decimals = isNative ? chain2.nativeDecimals : tokenInfo?.decimals ?? 18;
50525
- const expectedAtomic = parseTokenAmount(config.amount, decimals);
50526
- if (BigInt(tx.amount) < expectedAtomic) {
50527
- return {
50528
- valid: false,
50529
- error: `Insufficient payment: received ${tx.amount}, expected ${config.amount}`
50530
- };
50531
- }
50532
- await seenHashes.markSeen(txHash, chainId);
50533
- return { valid: true, method: "txhub" };
50534
- } catch (err) {
50535
- const is403 = err instanceof Error && err.message.includes("403");
50536
- if (!is403) {
50537
- return {
50538
- valid: false,
50539
- error: `Failed to fetch transaction: ${err instanceof Error ? err.message : String(err)}`
50540
- };
50541
- }
50542
- }
50543
- const rpcResult = await verifyViaRPC(txHash, config, chainId);
50544
- if (rpcResult.valid) {
50545
- await seenHashes.markSeen(txHash, chainId);
50546
- }
50547
- return rpcResult;
50548
- }
50549
51027
  var TwakRestServer = class {
50550
51028
  registry;
50551
51029
  context;
50552
51030
  config;
50553
- seenHashes;
50554
51031
  httpServer = null;
50555
51032
  constructor(registry, context, config = {}) {
50556
51033
  this.registry = registry;
50557
51034
  this.context = context;
50558
- this.seenHashes = new SeenHashStore();
50559
51035
  this.config = {
50560
51036
  port: config.port ?? 3e3,
50561
51037
  host: config.host ?? "127.0.0.1",
50562
- apiKey: config.apiKey,
50563
- paymentRequired: config.paymentRequired ?? false,
50564
- paymentAsset: config.paymentAsset ?? "usdc",
50565
- paymentAmount: config.paymentAmount ?? "0.01",
50566
- paymentChainId: config.paymentChainId ?? "eip155:8453",
50567
- paymentRecipient: config.paymentRecipient ?? "",
50568
- blockchainClient: config.blockchainClient ?? void 0
51038
+ apiKey: config.apiKey
50569
51039
  };
50570
51040
  }
50571
51041
  start() {
@@ -50589,11 +51059,6 @@ var TwakRestServer = class {
50589
51059
  } else {
50590
51060
  console.error("WARNING: No API key configured \u2014 REST server is unauthenticated");
50591
51061
  }
50592
- if (this.config.paymentRequired) {
50593
- console.error(
50594
- `x402 payment required: ${this.config.paymentAmount} ${this.config.paymentAsset} on ${this.config.paymentChainId}`
50595
- );
50596
- }
50597
51062
  resolve2();
50598
51063
  });
50599
51064
  });
@@ -50623,7 +51088,7 @@ var TwakRestServer = class {
50623
51088
  } catch {
50624
51089
  }
50625
51090
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
50626
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
51091
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
50627
51092
  if (req.method === "OPTIONS") {
50628
51093
  res.writeHead(204);
50629
51094
  res.end();
@@ -50662,52 +51127,6 @@ var TwakRestServer = class {
50662
51127
  return;
50663
51128
  }
50664
51129
  const actionName = match[1];
50665
- if (this.config.paymentRequired) {
50666
- const paymentHeader = req.headers["x-payment"];
50667
- if (!paymentHeader) {
50668
- res.setHeader(
50669
- "WWW-Authenticate",
50670
- `x402 realm="twak", asset="${this.config.paymentAsset}", amount="${this.config.paymentAmount}", chain="${this.config.paymentChainId}"`
50671
- );
50672
- res.writeHead(402);
50673
- res.end(
50674
- JSON.stringify({
50675
- error: "payment_required",
50676
- message: `This endpoint requires payment of ${this.config.paymentAmount} ${this.config.paymentAsset}`,
50677
- asset: this.config.paymentAsset,
50678
- amount: this.config.paymentAmount,
50679
- chainId: this.config.paymentChainId,
50680
- recipient: this.config.paymentRecipient || void 0
50681
- })
50682
- );
50683
- return;
50684
- }
50685
- if (this.config.paymentRecipient) {
50686
- const result = await verifyPayment(
50687
- paymentHeader,
50688
- {
50689
- asset: this.config.paymentAsset,
50690
- amount: this.config.paymentAmount,
50691
- chainId: this.config.paymentChainId,
50692
- recipient: this.config.paymentRecipient
50693
- },
50694
- this.context.httpClient,
50695
- this.seenHashes,
50696
- this.config.blockchainClient
50697
- );
50698
- if (!result.valid) {
50699
- res.writeHead(402);
50700
- res.end(
50701
- JSON.stringify({
50702
- error: "payment_verification_failed",
50703
- message: result.error ?? "Payment could not be verified"
50704
- })
50705
- );
50706
- return;
50707
- }
50708
- }
50709
- console.error(`[x402] Payment verified for ${actionName}`);
50710
- }
50711
51130
  const action = this.registry.getAction(actionName);
50712
51131
  if (!action) {
50713
51132
  res.writeHead(404);
@@ -50747,14 +51166,6 @@ function registerServeCommand(program3) {
50747
51166
  program3.command("serve").description("Start MCP server (stdio) or REST API server for AI agent connection").option("--rest", "Start REST HTTP server instead of MCP stdio server").option("--port <n>", "Port for REST server", "3000").option(
50748
51167
  "--host <host>",
50749
51168
  "Host to bind REST server (default: 127.0.0.1; use 0.0.0.0 to expose externally)"
50750
- ).option("--x402", "Require x402 micropayment for REST endpoints").option("--payment-amount <n>", "Payment amount required per request", "0.01").option("--payment-asset <asset>", "Payment asset (usdc, eth, twt, bnb, matic, ...)", "usdc").option(
50751
- "--payment-chain <chainId>",
50752
- "Payment chain ID (e.g. eip155:56 for BSC/TWT, eip155:8453 for Base)",
50753
- "eip155:8453"
50754
- ).option(
50755
- "--payment-recipient <address>",
50756
- "Address to receive payments (required for on-chain verification)",
50757
- ""
50758
51169
  ).option("--password <password>", "Wallet password (falls back to keychain)").option("--auto-lock <minutes>", "Auto-lock wallet after N minutes of inactivity").option("--wc-project-id <id>", "WalletConnect project ID (or set WALLETCONNECT_PROJECT_ID)").action(async (opts) => {
50759
51170
  await ensureTrustSDK();
50760
51171
  const client = await getClient();
@@ -50791,6 +51202,7 @@ function registerServeCommand(program3) {
50791
51202
  const walletContext = new WalletContext();
50792
51203
  if (agentWallet) {
50793
51204
  await walletContext.bindLocal(agentWallet);
51205
+ blockchainClient.setSigner(agentWallet.getSigner());
50794
51206
  }
50795
51207
  const registry = new ActionRegistry();
50796
51208
  registry.register(new BalanceActionProvider());
@@ -50800,12 +51212,16 @@ function registerServeCommand(program3) {
50800
51212
  registry.register(new SwapActionProvider(swapClient, walletContext, blockchainClient));
50801
51213
  registry.register(new SecurityActionProvider());
50802
51214
  registry.register(new HistoryActionProvider());
50803
- registry.register(new TransferActionProvider(walletContext, swapClient));
51215
+ registry.register(
51216
+ new TransferActionProvider(walletContext, swapClient, void 0, blockchainClient)
51217
+ );
50804
51218
  registry.register(new AlertActionProvider(alertStore, swapClient));
50805
51219
  registry.register(new AgentWalletActionProvider(blockchainClient, walletContext));
50806
51220
  registry.register(new TokenActionProvider(blockchainClient, walletContext));
50807
51221
  registry.register(new MarketActionProvider());
50808
- registry.register(new OnrampActionProvider(walletContext, swapClient));
51222
+ registry.register(
51223
+ new OnrampActionProvider(walletContext, swapClient, void 0, blockchainClient)
51224
+ );
50809
51225
  registry.register(
50810
51226
  new AutomateActionProvider(automationStore, swapClient, walletContext, blockchainClient)
50811
51227
  );
@@ -50817,13 +51233,7 @@ function registerServeCommand(program3) {
50817
51233
  const restServer = new TwakRestServer(registry, context, {
50818
51234
  port: parseInt(opts.port),
50819
51235
  host: opts.host,
50820
- apiKey: client.hmacSecret,
50821
- paymentRequired: opts.x402,
50822
- paymentAsset: opts.paymentAsset,
50823
- paymentAmount: opts.paymentAmount,
50824
- paymentChainId: opts.paymentChain,
50825
- paymentRecipient: opts.paymentRecipient,
50826
- blockchainClient
51236
+ apiKey: client.hmacSecret
50827
51237
  });
50828
51238
  registerShutdownHandlers([agentWallet], async () => {
50829
51239
  await walletContext.reset();
@@ -51240,7 +51650,7 @@ ${url}`);
51240
51650
  } catch (err) {
51241
51651
  mapBroadcastError(err);
51242
51652
  }
51243
- const explorerUrl = getExplorerTxUrl(result.chainKey, result.hash);
51653
+ const explorerUrl = getExplorerTxUrl(result.chainKey, result.txHash);
51244
51654
  success("Off-ramp payout broadcast!");
51245
51655
  if (!opts.json) {
51246
51656
  info(
@@ -51249,7 +51659,7 @@ ${url}`);
51249
51659
  }
51250
51660
  output(
51251
51661
  {
51252
- hash: result.hash,
51662
+ hash: result.txHash,
51253
51663
  chain: result.chainKey,
51254
51664
  from: address,
51255
51665
  to: opts.to,
@@ -51751,7 +52161,7 @@ var crypto7 = __toESM(require("crypto"));
51751
52161
  // package.json
51752
52162
  var package_default = {
51753
52163
  name: "@trustwallet/cli",
51754
- version: "0.14.0",
52164
+ version: "0.15.0",
51755
52165
  description: "TWAK CLI \u2014 command-line interface for Trust Wallet Agent Kit",
51756
52166
  main: "dist/index.js",
51757
52167
  types: "dist/index.d.ts",
@@ -51786,7 +52196,7 @@ var package_default = {
51786
52196
  },
51787
52197
  devDependencies: {
51788
52198
  "@twak/actions": "workspace:*",
51789
- "@twak/blockchain": "workspace:*",
52199
+ "@twak/contracts": "workspace:*",
51790
52200
  "@twak/core": "workspace:*",
51791
52201
  "@twak/mcp": "workspace:*",
51792
52202
  "@twak/security": "workspace:*",
@@ -51919,7 +52329,7 @@ function registerTelemetryCommand(program3) {
51919
52329
  // src/commands/watch.ts
51920
52330
  init_dist();
51921
52331
  init_dist2();
51922
- init_dist4();
52332
+ init_dist3();
51923
52333
  function ts3() {
51924
52334
  return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
51925
52335
  }