@candledottv/cli 0.11.8 → 0.11.9

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.
Files changed (3) hide show
  1. package/README.md +133 -12
  2. package/dist/index.js +1831 -360
  3. package/package.json +3 -1
package/dist/index.js CHANGED
@@ -1049,6 +1049,9 @@ function createView(arr) {
1049
1049
  function rotr(word, shift) {
1050
1050
  return word << 32 - shift | word >>> shift;
1051
1051
  }
1052
+ function rotl(word, shift) {
1053
+ return word << shift | word >>> 32 - shift >>> 0;
1054
+ }
1052
1055
  function byteSwap(word) {
1053
1056
  return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
1054
1057
  }
@@ -2114,7 +2117,7 @@ var gcd = (a, b) => b === 0 ? a : gcd(b, a % b), radix2carry = (from, to) => fro
2114
2117
  if (s.length > 0 && !re.test(s))
2115
2118
  throw new Error("invalid base64");
2116
2119
  return Uint8Array.fromBase64(s, { alphabet: alphabet2, lastChunkHandling: "strict" });
2117
- }, base64, base64nopad, base64url, base64urlnopad, genBase58 = (abc) => chain(radix(58), alphabet(abc), join3("")), base58, base58flickr, base58xrp, BECH_ALPHABET, POLYMOD_GENERATORS, bech32, bech32m, hasHexBuiltin2, hexBuiltin, hex;
2120
+ }, base64, base64nopad, base64url, base64urlnopad, genBase58 = (abc) => chain(radix(58), alphabet(abc), join3("")), base58, base58flickr, base58xrp, createBase58check = (sha2563) => chain(checksum(4, (data) => sha2563(sha2563(data))), base58), BECH_ALPHABET, POLYMOD_GENERATORS, bech32, bech32m, hasHexBuiltin2, hexBuiltin, hex;
2118
2121
  var init_esm = __esm(() => {
2119
2122
  /*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
2120
2123
  powers = /* @__PURE__ */ (() => {
@@ -3176,7 +3179,18 @@ var init_errors = __esm(() => {
3176
3179
  "LINKED_WALLET_ROOM_UNREADABLE",
3177
3180
  "PROMOTE_ACCOUNT_UNRESOLVED",
3178
3181
  "TRANSFER_SWEEP_PENDING",
3179
- "VAULT_KEY_ALREADY_ENROLLED"
3182
+ "VAULT_KEY_ALREADY_ENROLLED",
3183
+ "EVM_CHAIN_MISMATCH",
3184
+ "EVM_NONCE_STALE",
3185
+ "EVM_TOKEN_UNREADABLE",
3186
+ "EVM_AMOUNT_PRECISION",
3187
+ "EVM_INSUFFICIENT_FOR_FEES",
3188
+ "EVM_DESTINATION_INVALID",
3189
+ "EVM_SELF_TRANSFER",
3190
+ "EVM_RECIPIENT_IS_TOKEN",
3191
+ "EVM_TRANSFER_REVERTED",
3192
+ "TRANSFER_CHAIN_MISMATCH",
3193
+ "SOLANA_COMMAND_EVM_KEY"
3180
3194
  ];
3181
3195
  VaultError = class VaultError extends Error {
3182
3196
  code;
@@ -8972,7 +8986,7 @@ function tokenMintFilters(key) {
8972
8986
  return [{ dataSize: 82 }, memcmp(4, key)];
8973
8987
  }
8974
8988
  function tokenFreezeFilters(key) {
8975
- return [{ dataSize: 82 }, memcmp(46, concat2(COPTION_SOME_U32, key))];
8989
+ return [{ dataSize: 82 }, memcmp(46, concat3(COPTION_SOME_U32, key))];
8976
8990
  }
8977
8991
  function token2022MintFilters(key) {
8978
8992
  const authority = memcmp(4, key);
@@ -8982,7 +8996,7 @@ function token2022MintFilters(key) {
8982
8996
  ];
8983
8997
  }
8984
8998
  function token2022FreezeFilters(key) {
8985
- const authority = memcmp(46, concat2(COPTION_SOME_U32, key));
8999
+ const authority = memcmp(46, concat3(COPTION_SOME_U32, key));
8986
9000
  return [
8987
9001
  [{ dataSize: 82 }, authority],
8988
9002
  [memcmp(165, TOKEN_2022_MINT_ACCOUNT_TYPE), authority]
@@ -8992,7 +9006,7 @@ function keepSetAuthority(hits) {
8992
9006
  return hits.filter((hit) => hit.data.length === COPTION_SOME_U32.length && hit.data.every((byte, i) => byte === COPTION_SOME_U32[i])).map((hit) => hit.pubkey);
8993
9007
  }
8994
9008
  function programUpgradeFilters(key) {
8995
- return [memcmp(0, LOADER_PROGRAM_DATA_TAG), memcmp(12, concat2(COPTION_SOME_U8, key))];
9009
+ return [memcmp(0, LOADER_PROGRAM_DATA_TAG), memcmp(12, concat3(COPTION_SOME_U8, key))];
8996
9010
  }
8997
9011
  function programIdFilters(programData) {
8998
9012
  return [memcmp(0, LOADER_PROGRAM_TAG), memcmp(4, programData)];
@@ -9342,7 +9356,7 @@ function authoritiesJson(result) {
9342
9356
  found: result.found.map(({ address, role, target, program }) => ({ address, role, target, program }))
9343
9357
  };
9344
9358
  }
9345
- var BPF_UPGRADEABLE_LOADER_ID = "BPFLoaderUpgradeab1e11111111111111111111111", STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111", ROLE_GROUP_IDS, PAGINATED_TOKEN_GROUPS, RPC_PAGINATION_REQUIRED = -32600, MAX_V2_PAGES = 10, RPC_METHOD_NOT_FOUND = -32601, REQUESTS_PER_KEY = 9, b642 = (bytes) => base64.encode(bytes), concat2 = (...parts) => {
9359
+ var BPF_UPGRADEABLE_LOADER_ID = "BPFLoaderUpgradeab1e11111111111111111111111", STAKE_PROGRAM_ID = "Stake11111111111111111111111111111111111111", ROLE_GROUP_IDS, PAGINATED_TOKEN_GROUPS, RPC_PAGINATION_REQUIRED = -32600, MAX_V2_PAGES = 10, RPC_METHOD_NOT_FOUND = -32601, REQUESTS_PER_KEY = 9, b642 = (bytes) => base64.encode(bytes), concat3 = (...parts) => {
9346
9360
  const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
9347
9361
  let at = 0;
9348
9362
  for (const part of parts) {
@@ -9464,6 +9478,7 @@ __export(exports_promote_support, {
9464
9478
  confirmPrompt: () => confirmPrompt,
9465
9479
  confirmPromotion: () => confirmPromotion,
9466
9480
  assertNotPinnedDestination: () => assertNotPinnedDestination,
9481
+ assertNotEvmEntry: () => assertNotEvmEntry,
9467
9482
  assertInPlacePreconditions: () => assertInPlacePreconditions,
9468
9483
  assertColdVaultDestination: () => assertColdVaultDestination,
9469
9484
  applyPromotion: () => applyPromotion,
@@ -9471,6 +9486,7 @@ __export(exports_promote_support, {
9471
9486
  CONFIRM_WORD: () => CONFIRM_WORD
9472
9487
  });
9473
9488
  function assertColdVaultDestination(index, destinationLabelOrAddress, opts = {}) {
9489
+ assertNotEvmEntry(index, destinationLabelOrAddress, "this destination");
9474
9490
  const destination = findVaultRoleEntry(index, destinationLabelOrAddress);
9475
9491
  if (destination === undefined) {
9476
9492
  throw new VaultError("PROMOTE_DESTINATION_NOT_COLD", `No vault key matches ${destinationLabelOrAddress}.`, {
@@ -9498,11 +9514,20 @@ function assertColdVaultDestination(index, destinationLabelOrAddress, opts = {})
9498
9514
  }
9499
9515
  return destination;
9500
9516
  }
9517
+ function assertNotEvmEntry(index, labelOrAddress, command) {
9518
+ const named = index.entries.find((entry) => entry.chain === "evm" && (entry.label === labelOrAddress || entry.address.toLowerCase() === labelOrAddress.toLowerCase()));
9519
+ if (named === undefined)
9520
+ return;
9521
+ throw new VaultError("SOLANA_COMMAND_EVM_KEY", `${named.label || named.address} is an EVM key (${named.address}); ${command} works on Solana keys only.`, {
9522
+ suggestion: `Nothing was signed or written. An EVM vault key moves funds with: candle vault transfer <0x address> --from ${named.label || named.address}`
9523
+ });
9524
+ }
9501
9525
  function findVaultRoleEntry(index, labelOrAddress) {
9502
- const byLabel = index.entries.find((entry) => entry.role === "vault" && entry.label !== undefined && entry.label === labelOrAddress);
9526
+ const solana = index.entries.filter((entry) => entry.chain === "solana");
9527
+ const byLabel = solana.find((entry) => entry.role === "vault" && entry.label !== undefined && entry.label === labelOrAddress);
9503
9528
  if (byLabel !== undefined)
9504
9529
  return byLabel;
9505
- return index.entries.find((entry) => entry.address === labelOrAddress);
9530
+ return solana.find((entry) => entry.address === labelOrAddress);
9506
9531
  }
9507
9532
  function findTransferSource(index, labelOrAddress) {
9508
9533
  const byLabel = (role) => index.entries.find((entry) => entry.role === role && entry.label !== undefined && entry.label === labelOrAddress);
@@ -9523,6 +9548,7 @@ function assertNotPinnedDestination(index, subjectAddress) {
9523
9548
  });
9524
9549
  }
9525
9550
  function assertInPlacePreconditions(index, subjectLabel, sweepToLabel, opts) {
9551
+ assertNotEvmEntry(index, subjectLabel, "vault promote");
9526
9552
  const subject = findEntryByLabelOrAddress(index, subjectLabel);
9527
9553
  if (subject === undefined) {
9528
9554
  throw new VaultError("PROMOTE_NOT_VAULT_KEY", `No entry matches ${subjectLabel}.`);
@@ -22973,6 +22999,12 @@ async function findTeeInVault(ctx, address, opts = {}) {
22973
22999
  return { hit: false };
22974
23000
  const opened = await unlockInteractively(ctx, path, raw, { acceptOlderCopy: true });
22975
23001
  const vault = opened.vault;
23002
+ try {
23003
+ assertNotEvmEntry(vault.index, address, "this command");
23004
+ } catch (error) {
23005
+ closeVault(vault);
23006
+ throw error;
23007
+ }
22976
23008
  const entry = vault.index.entries.find((candidate) => candidate.address === address && candidate.role === "tee-wallet");
22977
23009
  if (entry === undefined) {
22978
23010
  closeVault(vault);
@@ -22986,6 +23018,7 @@ function vaultOwnsTeeAddress(vault, address) {
22986
23018
  var init_tee_lookup = __esm(() => {
22987
23019
  init_args();
22988
23020
  init_vault_support();
23021
+ init_promote_support();
22989
23022
  init_store();
22990
23023
  });
22991
23024
 
@@ -38852,7 +38885,7 @@ init_render();
38852
38885
  init_secret_store();
38853
38886
 
38854
38887
  // src/version.ts
38855
- var CLI_VERSION = "0.11.8";
38888
+ var CLI_VERSION = "0.11.9";
38856
38889
 
38857
38890
  // src/commands/auth.ts
38858
38891
  var DEVICE_CODE_PATH = "/api/v1/agent/device/code";
@@ -39247,6 +39280,10 @@ var ENVIRONMENT = [
39247
39280
  description: "Unlocks the encrypted-file backend where no OS keychain exists"
39248
39281
  },
39249
39282
  { name: "CANDLE_SOLANA_RPC_URL", description: "Solana RPC endpoint, when --rpc-url is not given" },
39283
+ {
39284
+ name: "CANDLE_EVM_RPC_URL",
39285
+ description: "EVM RPC endpoint for an EVM vault key, when --rpc-url (transfer) or --evm-rpc-url (list) is not given; without it the built-in Hood RPC is used"
39286
+ },
39250
39287
  {
39251
39288
  name: "CANDLE_FIDO2_HELPER",
39252
39289
  description: "Path to the candle-fido2 helper, when it is not beside the binary"
@@ -39463,7 +39500,7 @@ var HELP = {
39463
39500
  keys: {
39464
39501
  group: "Account",
39465
39502
  summary: "API keys, and the wallets each key may use",
39466
- description: "API keys are minted over the device token and shown exactly once. A key's wallet set and scope decide which wallets an agent holding it may act on.",
39503
+ description: "API keys are minted over the device token and shown exactly once. A key's wallet set and scope decide which wallets an agent holding it may act on. To give a key TEE wallets, move them to it with candle tee rebind; keys wallets set cannot widen a key's set from the CLI.",
39467
39504
  usage: ["candle keys <subcommand> [flags]"],
39468
39505
  rows: [
39469
39506
  {
@@ -39475,8 +39512,14 @@ var HELP = {
39475
39512
  description: "Create an API key; --access mints one of the three levels (read-write-transfer can move funds out of the wallet it runs)"
39476
39513
  },
39477
39514
  { invocation: "revoke <prefix>", description: "Revoke an API key" },
39478
- { invocation: "wallets <prefix>", description: "Wallets an agent profile can use" },
39479
- { invocation: " set <prefix> --wallets <id,id>", description: "Replace the profile's wallet set" },
39515
+ {
39516
+ invocation: "wallets <prefix>",
39517
+ description: "Wallets an agent profile can use. TEE wallets move between keys with candle tee rebind"
39518
+ },
39519
+ {
39520
+ invocation: " set <prefix> --wallets <id,id>",
39521
+ description: "Replace the profile's wallet set; from a key it can only narrow. To move TEE wallets to a key: candle tee rebind"
39522
+ },
39480
39523
  {
39481
39524
  invocation: " scope <prefix> --scope <all|selected>",
39482
39525
  description: "Limit a profile to assigned wallets"
@@ -39487,6 +39530,7 @@ var HELP = {
39487
39530
  "candle keys create --access read-write-transfer --label rebalancer",
39488
39531
  "candle keys create --scopes trade:write --label agent-one",
39489
39532
  "candle keys wallets ck_live_ab12",
39533
+ "candle tee rebind --label-prefix dest- --to-key ck_live_ab12",
39490
39534
  "candle keys revoke ck_live_ab12"
39491
39535
  ],
39492
39536
  env: ENV_API
@@ -39551,12 +39595,12 @@ var HELP = {
39551
39595
  },
39552
39596
  { invocation: "status [--unlock]", description: "What the vault holds, and what opens it" },
39553
39597
  {
39554
- invocation: "list [<filter>] [--balances] [--rpc-url <url>]",
39555
- description: "One line per key: address, label, role, derivation. Prompts for the passphrase"
39598
+ invocation: "list [<filter>] [--balances] [--rpc-url <url>] [--evm-rpc-url <url>]",
39599
+ description: "One line per key: address, label, role, derivation. Prompts for the passphrase. --balances reads SOL over your Solana RPC and ETH (plus USDG on Hood) for EVM keys over --evm-rpc-url, CANDLE_EVM_RPC_URL, or the built-in Hood RPC"
39556
39600
  },
39557
39601
  {
39558
- invocation: "new-key --chain solana [--label <name>] [--count <n>] [--labels-from <file>]",
39559
- description: "Derive the next Solana key, or n of them under one unlock; the name must be free"
39602
+ invocation: "new-key --chain solana|evm [--label <name>] [--count <n>] [--labels-from <file>]",
39603
+ description: "Derive the next Solana key (m/44'/501'/n'/0') or EVM key (m/44'/60'/n'/0/0), or n of them under one unlock; the name must be free"
39560
39604
  },
39561
39605
  {
39562
39606
  invocation: "rename <label|address> <new-label> [--id <entry-id>]",
@@ -39564,8 +39608,8 @@ var HELP = {
39564
39608
  },
39565
39609
  { invocation: "phrase show", description: "Show the 24-word recovery phrase (terminal only)" },
39566
39610
  {
39567
- invocation: "restore --phrase [--own-passphrase] [--count <n>] [--tee-count <k>] [--external-count <e>] [--rpc-url <url>]",
39568
- description: "Rebuild a vault from the recovery phrase; it gets a new passphrase"
39611
+ invocation: "restore --phrase [--own-passphrase] [--count <n>] [--tee-count <k>] [--external-count <e>] [--evm-count <m>] [--rpc-url <url>]",
39612
+ description: "Rebuild a vault from the recovery phrase; it gets a new passphrase. --evm-count derives EVM indices 0..m-1 (default 0, never gap-scanned)"
39569
39613
  },
39570
39614
  {
39571
39615
  invocation: "reconcile-exposure",
@@ -39590,8 +39634,8 @@ var HELP = {
39590
39634
  description: "Rename the Phase 1 store after a verified backup"
39591
39635
  },
39592
39636
  {
39593
- invocation: "transfer <to> --amount <n> --asset SOL|<mint> --from <label> --rpc-url <url>",
39594
- description: "Sign a transfer locally from a vault key or a promoted TEE wallet"
39637
+ invocation: "transfer <to> --amount <n|max> --asset SOL|<mint>|ETH|USDG|<0x token> --from <label> [--rpc-url <url>]",
39638
+ description: "Sign a transfer locally from a vault key or a promoted TEE wallet. From an EVM key: ETH or an ERC-20, on Hood by default (--rpc-url for any EVM chain; the chain id is read from the RPC), exit 0 means depth-confirmed (1 block on Hood, 2 elsewhere), not finalized. A Solana key still needs --rpc-url"
39595
39639
  },
39596
39640
  {
39597
39641
  invocation: "promote --from|--in-place <label> [--sweep-to <label>] [--rpc-url <url>] [--to-key <prefix|label>]",
@@ -39626,7 +39670,11 @@ var HELP = {
39626
39670
  },
39627
39671
  {
39628
39672
  invocation: "--balances",
39629
- description: "list: SOL per matched key, read from your RPC (one request per 100 matched keys). Every matched address goes to that one endpoint together, which links them; tokens are never read"
39673
+ description: "list: SOL per matched key, read from your RPC (one request per 100 matched keys). Every matched address goes to that one endpoint together, which links them; SPL tokens are never read. EVM keys are read over the EVM endpoint: ETH, and USDG on Hood"
39674
+ },
39675
+ {
39676
+ invocation: "--evm-rpc-url <url>",
39677
+ description: "list --balances: the EVM endpoint for EVM keys. Else CANDLE_EVM_RPC_URL, else the built-in Hood RPC; the Solana --rpc-url is never sent an EVM address. The host is printed on stderr before the read"
39630
39678
  },
39631
39679
  {
39632
39680
  invocation: "--labels-from <file>",
@@ -39644,6 +39692,8 @@ var HELP = {
39644
39692
  examples: [
39645
39693
  "candle vault init",
39646
39694
  "candle vault new-key --chain solana --label treasury",
39695
+ "candle vault new-key --chain evm --label hood-cold",
39696
+ "candle vault transfer 0x000000000000000000000000000000000000dEaD --amount 0.5 --asset USDG --from hood-cold",
39647
39697
  "candle vault list cn-s",
39648
39698
  "candle vault rename key-7 treasury-cold",
39649
39699
  "candle vault new-key --chain solana --labels-from ./replacement-names.txt",
@@ -39654,7 +39704,14 @@ var HELP = {
39654
39704
  "candle vault backup --to icloud",
39655
39705
  "CANDLE_CONFIG_DIR=$HOME/t47 candle vault status"
39656
39706
  ],
39657
- env: ["CANDLE_CONFIG_DIR", "CANDLE_FIDO2_HELPER", "CANDLE_ENCLAVE_HELPER", "CANDLE_KEYSTORE_PASSPHRASE"]
39707
+ env: [
39708
+ "CANDLE_CONFIG_DIR",
39709
+ "CANDLE_SOLANA_RPC_URL",
39710
+ "CANDLE_EVM_RPC_URL",
39711
+ "CANDLE_FIDO2_HELPER",
39712
+ "CANDLE_ENCLAVE_HELPER",
39713
+ "CANDLE_KEYSTORE_PASSPHRASE"
39714
+ ]
39658
39715
  },
39659
39716
  tee: {
39660
39717
  group: "Custody, on this machine",
@@ -39684,8 +39741,8 @@ var HELP = {
39684
39741
  description: "Sign locally and move everything to the pinned vault; closes DAMM v2 LP positions after verifying each server-built close (--emergency moves the position NFT instead, with no API)"
39685
39742
  },
39686
39743
  {
39687
- invocation: "rebind <wallet...> --to-key <prefix|label>",
39688
- description: "Move TEE wallets to another key on this account (owner only; funds do not move)"
39744
+ invocation: "rebind <wallet...> --to-key <prefix|label> [--label-prefix <p>]",
39745
+ description: "Move TEE wallets to another key on this account, which is how a key gets TEE wallets. Name them, or --label-prefix for every wallet whose label starts with it (owner only; funds do not move)"
39689
39746
  },
39690
39747
  { invocation: "rebinds [wallet]", description: "List TEE wallet rebinds for this account (owner only)" }
39691
39748
  ],
@@ -39694,7 +39751,8 @@ var HELP = {
39694
39751
  "candle tee new --label AgentOne",
39695
39752
  "candle tee status AgentOneAddress",
39696
39753
  "candle tee sweep AgentOneAddress --rpc-url https://api.mainnet-beta.solana.com",
39697
- "candle tee rebind tr-01 tr-02 --to-key Ab3dEf9h"
39754
+ "candle tee rebind tr-01 tr-02 --to-key Ab3dEf9h",
39755
+ "candle tee rebind --label-prefix dest- --to-key Ab3dEf9h"
39698
39756
  ],
39699
39757
  env: ENV_LOCAL_SIGNING
39700
39758
  },
@@ -42640,6 +42698,1022 @@ zone
42640
42698
  zoo`.split(`
42641
42699
  `);
42642
42700
 
42701
+ // ../../node_modules/@noble/curves/esm/secp256k1.js
42702
+ init_sha2();
42703
+ init__shortw_utils();
42704
+ init_modular();
42705
+ /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
42706
+ var secp256k1_CURVE = {
42707
+ p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),
42708
+ n: BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),
42709
+ h: BigInt(1),
42710
+ a: BigInt(0),
42711
+ b: BigInt(7),
42712
+ Gx: BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
42713
+ Gy: BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")
42714
+ };
42715
+ var secp256k1_ENDO = {
42716
+ beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),
42717
+ basises: [
42718
+ [BigInt("0x3086d221a7d46bcde86c90e49284eb15"), -BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],
42719
+ [BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"), BigInt("0x3086d221a7d46bcde86c90e49284eb15")]
42720
+ ]
42721
+ };
42722
+ var _2n5 = /* @__PURE__ */ BigInt(2);
42723
+ function sqrtMod(y) {
42724
+ const P2 = secp256k1_CURVE.p;
42725
+ const _3n4 = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);
42726
+ const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);
42727
+ const b2 = y * y * y % P2;
42728
+ const b3 = b2 * b2 * y % P2;
42729
+ const b6 = pow2(b3, _3n4, P2) * b3 % P2;
42730
+ const b9 = pow2(b6, _3n4, P2) * b3 % P2;
42731
+ const b11 = pow2(b9, _2n5, P2) * b2 % P2;
42732
+ const b22 = pow2(b11, _11n, P2) * b11 % P2;
42733
+ const b44 = pow2(b22, _22n, P2) * b22 % P2;
42734
+ const b88 = pow2(b44, _44n, P2) * b44 % P2;
42735
+ const b176 = pow2(b88, _88n, P2) * b88 % P2;
42736
+ const b220 = pow2(b176, _44n, P2) * b44 % P2;
42737
+ const b223 = pow2(b220, _3n4, P2) * b3 % P2;
42738
+ const t1 = pow2(b223, _23n, P2) * b22 % P2;
42739
+ const t2 = pow2(t1, _6n, P2) * b2 % P2;
42740
+ const root = pow2(t2, _2n5, P2);
42741
+ if (!Fpk1.eql(Fpk1.sqr(root), y))
42742
+ throw new Error("Cannot find square root");
42743
+ return root;
42744
+ }
42745
+ var Fpk1 = Field(secp256k1_CURVE.p, { sqrt: sqrtMod });
42746
+ var secp256k1 = createCurve({ ...secp256k1_CURVE, Fp: Fpk1, lowS: true, endo: secp256k1_ENDO }, sha256);
42747
+
42748
+ // ../../node_modules/@noble/hashes/esm/sha3.js
42749
+ init__u64();
42750
+ init_utils();
42751
+ var _0n7 = BigInt(0);
42752
+ var _1n7 = BigInt(1);
42753
+ var _2n6 = BigInt(2);
42754
+ var _7n2 = BigInt(7);
42755
+ var _256n = BigInt(256);
42756
+ var _0x71n = BigInt(113);
42757
+ var SHA3_PI = [];
42758
+ var SHA3_ROTL = [];
42759
+ var _SHA3_IOTA = [];
42760
+ for (let round = 0, R = _1n7, x = 1, y = 0;round < 24; round++) {
42761
+ [x, y] = [y, (2 * x + 3 * y) % 5];
42762
+ SHA3_PI.push(2 * (5 * y + x));
42763
+ SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
42764
+ let t = _0n7;
42765
+ for (let j = 0;j < 7; j++) {
42766
+ R = (R << _1n7 ^ (R >> _7n2) * _0x71n) % _256n;
42767
+ if (R & _2n6)
42768
+ t ^= _1n7 << (_1n7 << /* @__PURE__ */ BigInt(j)) - _1n7;
42769
+ }
42770
+ _SHA3_IOTA.push(t);
42771
+ }
42772
+ var IOTAS = split(_SHA3_IOTA, true);
42773
+ var SHA3_IOTA_H = IOTAS[0];
42774
+ var SHA3_IOTA_L = IOTAS[1];
42775
+ var rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s);
42776
+ var rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s);
42777
+ function keccakP(s, rounds = 24) {
42778
+ const B = new Uint32Array(5 * 2);
42779
+ for (let round = 24 - rounds;round < 24; round++) {
42780
+ for (let x = 0;x < 10; x++)
42781
+ B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
42782
+ for (let x = 0;x < 10; x += 2) {
42783
+ const idx1 = (x + 8) % 10;
42784
+ const idx0 = (x + 2) % 10;
42785
+ const B0 = B[idx0];
42786
+ const B1 = B[idx0 + 1];
42787
+ const Th = rotlH(B0, B1, 1) ^ B[idx1];
42788
+ const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
42789
+ for (let y = 0;y < 50; y += 10) {
42790
+ s[x + y] ^= Th;
42791
+ s[x + y + 1] ^= Tl;
42792
+ }
42793
+ }
42794
+ let curH = s[2];
42795
+ let curL = s[3];
42796
+ for (let t = 0;t < 24; t++) {
42797
+ const shift = SHA3_ROTL[t];
42798
+ const Th = rotlH(curH, curL, shift);
42799
+ const Tl = rotlL(curH, curL, shift);
42800
+ const PI = SHA3_PI[t];
42801
+ curH = s[PI];
42802
+ curL = s[PI + 1];
42803
+ s[PI] = Th;
42804
+ s[PI + 1] = Tl;
42805
+ }
42806
+ for (let y = 0;y < 50; y += 10) {
42807
+ for (let x = 0;x < 10; x++)
42808
+ B[x] = s[y + x];
42809
+ for (let x = 0;x < 10; x++)
42810
+ s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
42811
+ }
42812
+ s[0] ^= SHA3_IOTA_H[round];
42813
+ s[1] ^= SHA3_IOTA_L[round];
42814
+ }
42815
+ clean(B);
42816
+ }
42817
+
42818
+ class Keccak extends Hash {
42819
+ constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
42820
+ super();
42821
+ this.pos = 0;
42822
+ this.posOut = 0;
42823
+ this.finished = false;
42824
+ this.destroyed = false;
42825
+ this.enableXOF = false;
42826
+ this.blockLen = blockLen;
42827
+ this.suffix = suffix;
42828
+ this.outputLen = outputLen;
42829
+ this.enableXOF = enableXOF;
42830
+ this.rounds = rounds;
42831
+ anumber(outputLen);
42832
+ if (!(0 < blockLen && blockLen < 200))
42833
+ throw new Error("only keccak-f1600 function is supported");
42834
+ this.state = new Uint8Array(200);
42835
+ this.state32 = u32(this.state);
42836
+ }
42837
+ clone() {
42838
+ return this._cloneInto();
42839
+ }
42840
+ keccak() {
42841
+ swap32IfBE(this.state32);
42842
+ keccakP(this.state32, this.rounds);
42843
+ swap32IfBE(this.state32);
42844
+ this.posOut = 0;
42845
+ this.pos = 0;
42846
+ }
42847
+ update(data) {
42848
+ aexists(this);
42849
+ data = toBytes(data);
42850
+ abytes(data);
42851
+ const { blockLen, state } = this;
42852
+ const len = data.length;
42853
+ for (let pos = 0;pos < len; ) {
42854
+ const take = Math.min(blockLen - this.pos, len - pos);
42855
+ for (let i = 0;i < take; i++)
42856
+ state[this.pos++] ^= data[pos++];
42857
+ if (this.pos === blockLen)
42858
+ this.keccak();
42859
+ }
42860
+ return this;
42861
+ }
42862
+ finish() {
42863
+ if (this.finished)
42864
+ return;
42865
+ this.finished = true;
42866
+ const { state, suffix, pos, blockLen } = this;
42867
+ state[pos] ^= suffix;
42868
+ if ((suffix & 128) !== 0 && pos === blockLen - 1)
42869
+ this.keccak();
42870
+ state[blockLen - 1] ^= 128;
42871
+ this.keccak();
42872
+ }
42873
+ writeInto(out) {
42874
+ aexists(this, false);
42875
+ abytes(out);
42876
+ this.finish();
42877
+ const bufferOut = this.state;
42878
+ const { blockLen } = this;
42879
+ for (let pos = 0, len = out.length;pos < len; ) {
42880
+ if (this.posOut >= blockLen)
42881
+ this.keccak();
42882
+ const take = Math.min(blockLen - this.posOut, len - pos);
42883
+ out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
42884
+ this.posOut += take;
42885
+ pos += take;
42886
+ }
42887
+ return out;
42888
+ }
42889
+ xofInto(out) {
42890
+ if (!this.enableXOF)
42891
+ throw new Error("XOF is not possible for this instance");
42892
+ return this.writeInto(out);
42893
+ }
42894
+ xof(bytes) {
42895
+ anumber(bytes);
42896
+ return this.xofInto(new Uint8Array(bytes));
42897
+ }
42898
+ digestInto(out) {
42899
+ aoutput(out, this);
42900
+ if (this.finished)
42901
+ throw new Error("digest() was already called");
42902
+ this.writeInto(out);
42903
+ this.destroy();
42904
+ return out;
42905
+ }
42906
+ digest() {
42907
+ return this.digestInto(new Uint8Array(this.outputLen));
42908
+ }
42909
+ destroy() {
42910
+ this.destroyed = true;
42911
+ clean(this.state);
42912
+ }
42913
+ _cloneInto(to) {
42914
+ const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
42915
+ to || (to = new Keccak(blockLen, suffix, outputLen, enableXOF, rounds));
42916
+ to.state32.set(this.state32);
42917
+ to.pos = this.pos;
42918
+ to.posOut = this.posOut;
42919
+ to.finished = this.finished;
42920
+ to.rounds = rounds;
42921
+ to.suffix = suffix;
42922
+ to.outputLen = outputLen;
42923
+ to.enableXOF = enableXOF;
42924
+ to.destroyed = this.destroyed;
42925
+ return to;
42926
+ }
42927
+ }
42928
+ var gen = (suffix, blockLen, outputLen) => createHasher(() => new Keccak(blockLen, suffix, outputLen));
42929
+ var keccak_256 = /* @__PURE__ */ (() => gen(1, 136, 256 / 8))();
42930
+
42931
+ // ../../node_modules/@scure/bip32/lib/esm/index.js
42932
+ init_modular();
42933
+ init_hmac();
42934
+
42935
+ // ../../node_modules/@noble/hashes/esm/legacy.js
42936
+ init__md();
42937
+ init_utils();
42938
+ var Rho160 = /* @__PURE__ */ Uint8Array.from([
42939
+ 7,
42940
+ 4,
42941
+ 13,
42942
+ 1,
42943
+ 10,
42944
+ 6,
42945
+ 15,
42946
+ 3,
42947
+ 12,
42948
+ 0,
42949
+ 9,
42950
+ 5,
42951
+ 2,
42952
+ 14,
42953
+ 11,
42954
+ 8
42955
+ ]);
42956
+ var Id160 = /* @__PURE__ */ (() => Uint8Array.from(new Array(16).fill(0).map((_, i) => i)))();
42957
+ var Pi160 = /* @__PURE__ */ (() => Id160.map((i) => (9 * i + 5) % 16))();
42958
+ var idxLR = /* @__PURE__ */ (() => {
42959
+ const L = [Id160];
42960
+ const R = [Pi160];
42961
+ const res = [L, R];
42962
+ for (let i = 0;i < 4; i++)
42963
+ for (let j of res)
42964
+ j.push(j[i].map((k) => Rho160[k]));
42965
+ return res;
42966
+ })();
42967
+ var idxL = /* @__PURE__ */ (() => idxLR[0])();
42968
+ var idxR = /* @__PURE__ */ (() => idxLR[1])();
42969
+ var shifts160 = /* @__PURE__ */ [
42970
+ [11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8],
42971
+ [12, 13, 11, 15, 6, 9, 9, 7, 12, 15, 11, 13, 7, 8, 7, 7],
42972
+ [13, 15, 14, 11, 7, 7, 6, 8, 13, 14, 13, 12, 5, 5, 6, 9],
42973
+ [14, 11, 12, 14, 8, 6, 5, 5, 15, 12, 15, 14, 9, 9, 8, 6],
42974
+ [15, 12, 13, 13, 9, 5, 8, 6, 14, 11, 12, 11, 8, 6, 5, 5]
42975
+ ].map((i) => Uint8Array.from(i));
42976
+ var shiftsL160 = /* @__PURE__ */ idxL.map((idx, i) => idx.map((j) => shifts160[i][j]));
42977
+ var shiftsR160 = /* @__PURE__ */ idxR.map((idx, i) => idx.map((j) => shifts160[i][j]));
42978
+ var Kl160 = /* @__PURE__ */ Uint32Array.from([
42979
+ 0,
42980
+ 1518500249,
42981
+ 1859775393,
42982
+ 2400959708,
42983
+ 2840853838
42984
+ ]);
42985
+ var Kr160 = /* @__PURE__ */ Uint32Array.from([
42986
+ 1352829926,
42987
+ 1548603684,
42988
+ 1836072691,
42989
+ 2053994217,
42990
+ 0
42991
+ ]);
42992
+ function ripemd_f(group, x, y, z) {
42993
+ if (group === 0)
42994
+ return x ^ y ^ z;
42995
+ if (group === 1)
42996
+ return x & y | ~x & z;
42997
+ if (group === 2)
42998
+ return (x | ~y) ^ z;
42999
+ if (group === 3)
43000
+ return x & z | y & ~z;
43001
+ return x ^ (y | ~z);
43002
+ }
43003
+ var BUF_160 = /* @__PURE__ */ new Uint32Array(16);
43004
+
43005
+ class RIPEMD160 extends HashMD {
43006
+ constructor() {
43007
+ super(64, 20, 8, true);
43008
+ this.h0 = 1732584193 | 0;
43009
+ this.h1 = 4023233417 | 0;
43010
+ this.h2 = 2562383102 | 0;
43011
+ this.h3 = 271733878 | 0;
43012
+ this.h4 = 3285377520 | 0;
43013
+ }
43014
+ get() {
43015
+ const { h0, h1, h2, h3, h4 } = this;
43016
+ return [h0, h1, h2, h3, h4];
43017
+ }
43018
+ set(h0, h1, h2, h3, h4) {
43019
+ this.h0 = h0 | 0;
43020
+ this.h1 = h1 | 0;
43021
+ this.h2 = h2 | 0;
43022
+ this.h3 = h3 | 0;
43023
+ this.h4 = h4 | 0;
43024
+ }
43025
+ process(view, offset) {
43026
+ for (let i = 0;i < 16; i++, offset += 4)
43027
+ BUF_160[i] = view.getUint32(offset, true);
43028
+ let al = this.h0 | 0, ar = al, bl = this.h1 | 0, br = bl, cl = this.h2 | 0, cr = cl, dl = this.h3 | 0, dr = dl, el = this.h4 | 0, er = el;
43029
+ for (let group = 0;group < 5; group++) {
43030
+ const rGroup = 4 - group;
43031
+ const hbl = Kl160[group], hbr = Kr160[group];
43032
+ const rl = idxL[group], rr = idxR[group];
43033
+ const sl = shiftsL160[group], sr = shiftsR160[group];
43034
+ for (let i = 0;i < 16; i++) {
43035
+ const tl = rotl(al + ripemd_f(group, bl, cl, dl) + BUF_160[rl[i]] + hbl, sl[i]) + el | 0;
43036
+ al = el, el = dl, dl = rotl(cl, 10) | 0, cl = bl, bl = tl;
43037
+ }
43038
+ for (let i = 0;i < 16; i++) {
43039
+ const tr = rotl(ar + ripemd_f(rGroup, br, cr, dr) + BUF_160[rr[i]] + hbr, sr[i]) + er | 0;
43040
+ ar = er, er = dr, dr = rotl(cr, 10) | 0, cr = br, br = tr;
43041
+ }
43042
+ }
43043
+ this.set(this.h1 + cl + dr | 0, this.h2 + dl + er | 0, this.h3 + el + ar | 0, this.h4 + al + br | 0, this.h0 + bl + cr | 0);
43044
+ }
43045
+ roundClean() {
43046
+ clean(BUF_160);
43047
+ }
43048
+ destroy() {
43049
+ this.destroyed = true;
43050
+ clean(this.buffer);
43051
+ this.set(0, 0, 0, 0, 0);
43052
+ }
43053
+ }
43054
+ var ripemd160 = /* @__PURE__ */ createHasher(() => new RIPEMD160);
43055
+
43056
+ // ../../node_modules/@scure/bip32/lib/esm/index.js
43057
+ init_sha2();
43058
+ init_utils();
43059
+ init_esm();
43060
+ /*! scure-bip32 - MIT License (c) 2022 Patricio Palladino, Paul Miller (paulmillr.com) */
43061
+ var Point = secp256k1.ProjectivePoint;
43062
+ var base58check = createBase58check(sha256);
43063
+ function bytesToNumber(bytes) {
43064
+ abytes(bytes);
43065
+ const h = bytes.length === 0 ? "0" : bytesToHex(bytes);
43066
+ return BigInt("0x" + h);
43067
+ }
43068
+ function numberToBytes(num) {
43069
+ if (typeof num !== "bigint")
43070
+ throw new Error("bigint expected");
43071
+ return hexToBytes(num.toString(16).padStart(64, "0"));
43072
+ }
43073
+ var MASTER_SECRET = utf8ToBytes("Bitcoin seed");
43074
+ var BITCOIN_VERSIONS = { private: 76066276, public: 76067358 };
43075
+ var HARDENED_OFFSET = 2147483648;
43076
+ var hash160 = (data) => ripemd160(sha256(data));
43077
+ var fromU32 = (data) => createView(data).getUint32(0, false);
43078
+ var toU32 = (n) => {
43079
+ if (!Number.isSafeInteger(n) || n < 0 || n > 2 ** 32 - 1) {
43080
+ throw new Error("invalid number, should be from 0 to 2**32-1, got " + n);
43081
+ }
43082
+ const buf = new Uint8Array(4);
43083
+ createView(buf).setUint32(0, n, false);
43084
+ return buf;
43085
+ };
43086
+
43087
+ class HDKey {
43088
+ get fingerprint() {
43089
+ if (!this.pubHash) {
43090
+ throw new Error("No publicKey set!");
43091
+ }
43092
+ return fromU32(this.pubHash);
43093
+ }
43094
+ get identifier() {
43095
+ return this.pubHash;
43096
+ }
43097
+ get pubKeyHash() {
43098
+ return this.pubHash;
43099
+ }
43100
+ get privateKey() {
43101
+ return this.privKeyBytes || null;
43102
+ }
43103
+ get publicKey() {
43104
+ return this.pubKey || null;
43105
+ }
43106
+ get privateExtendedKey() {
43107
+ const priv = this.privateKey;
43108
+ if (!priv) {
43109
+ throw new Error("No private key");
43110
+ }
43111
+ return base58check.encode(this.serialize(this.versions.private, concatBytes(new Uint8Array([0]), priv)));
43112
+ }
43113
+ get publicExtendedKey() {
43114
+ if (!this.pubKey) {
43115
+ throw new Error("No public key");
43116
+ }
43117
+ return base58check.encode(this.serialize(this.versions.public, this.pubKey));
43118
+ }
43119
+ static fromMasterSeed(seed, versions = BITCOIN_VERSIONS) {
43120
+ abytes(seed);
43121
+ if (8 * seed.length < 128 || 8 * seed.length > 512) {
43122
+ throw new Error("HDKey: seed length must be between 128 and 512 bits; 256 bits is advised, got " + seed.length);
43123
+ }
43124
+ const I = hmac(sha512, MASTER_SECRET, seed);
43125
+ return new HDKey({
43126
+ versions,
43127
+ chainCode: I.slice(32),
43128
+ privateKey: I.slice(0, 32)
43129
+ });
43130
+ }
43131
+ static fromExtendedKey(base58key, versions = BITCOIN_VERSIONS) {
43132
+ const keyBuffer = base58check.decode(base58key);
43133
+ const keyView = createView(keyBuffer);
43134
+ const version = keyView.getUint32(0, false);
43135
+ const opt = {
43136
+ versions,
43137
+ depth: keyBuffer[4],
43138
+ parentFingerprint: keyView.getUint32(5, false),
43139
+ index: keyView.getUint32(9, false),
43140
+ chainCode: keyBuffer.slice(13, 45)
43141
+ };
43142
+ const key = keyBuffer.slice(45);
43143
+ const isPriv = key[0] === 0;
43144
+ if (version !== versions[isPriv ? "private" : "public"]) {
43145
+ throw new Error("Version mismatch");
43146
+ }
43147
+ if (isPriv) {
43148
+ return new HDKey({ ...opt, privateKey: key.slice(1) });
43149
+ } else {
43150
+ return new HDKey({ ...opt, publicKey: key });
43151
+ }
43152
+ }
43153
+ static fromJSON(json) {
43154
+ return HDKey.fromExtendedKey(json.xpriv);
43155
+ }
43156
+ constructor(opt) {
43157
+ this.depth = 0;
43158
+ this.index = 0;
43159
+ this.chainCode = null;
43160
+ this.parentFingerprint = 0;
43161
+ if (!opt || typeof opt !== "object") {
43162
+ throw new Error("HDKey.constructor must not be called directly");
43163
+ }
43164
+ this.versions = opt.versions || BITCOIN_VERSIONS;
43165
+ this.depth = opt.depth || 0;
43166
+ this.chainCode = opt.chainCode || null;
43167
+ this.index = opt.index || 0;
43168
+ this.parentFingerprint = opt.parentFingerprint || 0;
43169
+ if (!this.depth) {
43170
+ if (this.parentFingerprint || this.index) {
43171
+ throw new Error("HDKey: zero depth with non-zero index/parent fingerprint");
43172
+ }
43173
+ }
43174
+ if (opt.publicKey && opt.privateKey) {
43175
+ throw new Error("HDKey: publicKey and privateKey at same time.");
43176
+ }
43177
+ if (opt.privateKey) {
43178
+ if (!secp256k1.utils.isValidPrivateKey(opt.privateKey)) {
43179
+ throw new Error("Invalid private key");
43180
+ }
43181
+ this.privKey = typeof opt.privateKey === "bigint" ? opt.privateKey : bytesToNumber(opt.privateKey);
43182
+ this.privKeyBytes = numberToBytes(this.privKey);
43183
+ this.pubKey = secp256k1.getPublicKey(opt.privateKey, true);
43184
+ } else if (opt.publicKey) {
43185
+ this.pubKey = Point.fromHex(opt.publicKey).toRawBytes(true);
43186
+ } else {
43187
+ throw new Error("HDKey: no public or private key provided");
43188
+ }
43189
+ this.pubHash = hash160(this.pubKey);
43190
+ }
43191
+ derive(path) {
43192
+ if (!/^[mM]'?/.test(path)) {
43193
+ throw new Error('Path must start with "m" or "M"');
43194
+ }
43195
+ if (/^[mM]'?$/.test(path)) {
43196
+ return this;
43197
+ }
43198
+ const parts = path.replace(/^[mM]'?\//, "").split("/");
43199
+ let child = this;
43200
+ for (const c of parts) {
43201
+ const m = /^(\d+)('?)$/.exec(c);
43202
+ const m1 = m && m[1];
43203
+ if (!m || m.length !== 3 || typeof m1 !== "string")
43204
+ throw new Error("invalid child index: " + c);
43205
+ let idx = +m1;
43206
+ if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET) {
43207
+ throw new Error("Invalid index");
43208
+ }
43209
+ if (m[2] === "'") {
43210
+ idx += HARDENED_OFFSET;
43211
+ }
43212
+ child = child.deriveChild(idx);
43213
+ }
43214
+ return child;
43215
+ }
43216
+ deriveChild(index) {
43217
+ if (!this.pubKey || !this.chainCode) {
43218
+ throw new Error("No publicKey or chainCode set");
43219
+ }
43220
+ let data = toU32(index);
43221
+ if (index >= HARDENED_OFFSET) {
43222
+ const priv = this.privateKey;
43223
+ if (!priv) {
43224
+ throw new Error("Could not derive hardened child key");
43225
+ }
43226
+ data = concatBytes(new Uint8Array([0]), priv, data);
43227
+ } else {
43228
+ data = concatBytes(this.pubKey, data);
43229
+ }
43230
+ const I = hmac(sha512, this.chainCode, data);
43231
+ const childTweak = bytesToNumber(I.slice(0, 32));
43232
+ const chainCode = I.slice(32);
43233
+ if (!secp256k1.utils.isValidPrivateKey(childTweak)) {
43234
+ throw new Error("Tweak bigger than curve order");
43235
+ }
43236
+ const opt = {
43237
+ versions: this.versions,
43238
+ chainCode,
43239
+ depth: this.depth + 1,
43240
+ parentFingerprint: this.fingerprint,
43241
+ index
43242
+ };
43243
+ try {
43244
+ if (this.privateKey) {
43245
+ const added = mod(this.privKey + childTweak, secp256k1.CURVE.n);
43246
+ if (!secp256k1.utils.isValidPrivateKey(added)) {
43247
+ throw new Error("The tweak was out of range or the resulted private key is invalid");
43248
+ }
43249
+ opt.privateKey = added;
43250
+ } else {
43251
+ const added = Point.fromHex(this.pubKey).add(Point.fromPrivateKey(childTweak));
43252
+ if (added.equals(Point.ZERO)) {
43253
+ throw new Error("The tweak was equal to negative P, which made the result key invalid");
43254
+ }
43255
+ opt.publicKey = added.toRawBytes(true);
43256
+ }
43257
+ return new HDKey(opt);
43258
+ } catch (err) {
43259
+ return this.deriveChild(index + 1);
43260
+ }
43261
+ }
43262
+ sign(hash) {
43263
+ if (!this.privateKey) {
43264
+ throw new Error("No privateKey set!");
43265
+ }
43266
+ abytes(hash, 32);
43267
+ return secp256k1.sign(hash, this.privKey).toCompactRawBytes();
43268
+ }
43269
+ verify(hash, signature) {
43270
+ abytes(hash, 32);
43271
+ abytes(signature, 64);
43272
+ if (!this.publicKey) {
43273
+ throw new Error("No publicKey set!");
43274
+ }
43275
+ let sig;
43276
+ try {
43277
+ sig = secp256k1.Signature.fromCompact(signature);
43278
+ } catch (error) {
43279
+ return false;
43280
+ }
43281
+ return secp256k1.verify(sig, hash, this.publicKey);
43282
+ }
43283
+ wipePrivateData() {
43284
+ this.privKey = undefined;
43285
+ if (this.privKeyBytes) {
43286
+ this.privKeyBytes.fill(0);
43287
+ this.privKeyBytes = undefined;
43288
+ }
43289
+ return this;
43290
+ }
43291
+ toJSON() {
43292
+ return {
43293
+ xpriv: this.privateExtendedKey,
43294
+ xpub: this.publicExtendedKey
43295
+ };
43296
+ }
43297
+ serialize(version, key) {
43298
+ if (!this.chainCode) {
43299
+ throw new Error("No chainCode set");
43300
+ }
43301
+ abytes(key, 33);
43302
+ return concatBytes(toU32(version), new Uint8Array([this.depth]), toU32(this.parentFingerprint), toU32(this.index), this.chainCode, key);
43303
+ }
43304
+ }
43305
+
43306
+ // src/evm-lite.ts
43307
+ var HOOD_CHAIN_ID = 4663;
43308
+ var DEFAULT_HOOD_RPC_URL = "https://rpc.mainnet.chain.robinhood.com";
43309
+ var EVM_RPC_URL_ENV = "CANDLE_EVM_RPC_URL";
43310
+ var HOOD_USDG_ADDRESS = "0x5fc5360d0400a0fd4f2af552add042d716f1d168";
43311
+ var HOOD_USDG_DECIMALS = 6;
43312
+ var NATIVE_DECIMALS = 18;
43313
+ var EVM_DERIVATION_SCHEME = "bip32-secp256k1";
43314
+ var EVM_SECRET_BYTES = 32;
43315
+ function bytesToHex2(bytes) {
43316
+ let out = "";
43317
+ for (const b of bytes)
43318
+ out += b.toString(16).padStart(2, "0");
43319
+ return `0x${out}`;
43320
+ }
43321
+ function hexToBytes2(hex2) {
43322
+ const body = hex2.startsWith("0x") ? hex2.slice(2) : hex2;
43323
+ if (body.length % 2 !== 0 || !/^[0-9a-fA-F]*$/.test(body))
43324
+ throw new Error(`not a hex string: ${hex2}`);
43325
+ const out = new Uint8Array(body.length / 2);
43326
+ for (let i = 0;i < out.length; i++)
43327
+ out[i] = Number.parseInt(body.slice(i * 2, i * 2 + 2), 16);
43328
+ return out;
43329
+ }
43330
+ function hexToBigInt(hex2) {
43331
+ const body = hex2.startsWith("0x") ? hex2.slice(2) : hex2;
43332
+ if (body === "")
43333
+ return 0n;
43334
+ if (!/^[0-9a-fA-F]+$/.test(body))
43335
+ throw new Error(`not a hex quantity: ${hex2}`);
43336
+ return BigInt(`0x${body}`);
43337
+ }
43338
+ function quantity(value) {
43339
+ return `0x${BigInt(value).toString(16)}`;
43340
+ }
43341
+ function uintToMinimalBytes(value) {
43342
+ if (value < 0n)
43343
+ throw new Error("RLP integers are non-negative");
43344
+ if (value === 0n)
43345
+ return new Uint8Array(0);
43346
+ let hex2 = value.toString(16);
43347
+ if (hex2.length % 2 !== 0)
43348
+ hex2 = `0${hex2}`;
43349
+ return hexToBytes2(hex2);
43350
+ }
43351
+ function rlpLength(length, offset) {
43352
+ if (length < 56)
43353
+ return Uint8Array.of(offset + length);
43354
+ const lengthBytes = uintToMinimalBytes(BigInt(length));
43355
+ return concat2(Uint8Array.of(offset + 55 + lengthBytes.length), lengthBytes);
43356
+ }
43357
+ function concat2(...parts) {
43358
+ const out = new Uint8Array(parts.reduce((n, part) => n + part.length, 0));
43359
+ let at = 0;
43360
+ for (const part of parts) {
43361
+ out.set(part, at);
43362
+ at += part.length;
43363
+ }
43364
+ return out;
43365
+ }
43366
+ function rlpEncode(item) {
43367
+ if (item instanceof Uint8Array) {
43368
+ if (item.length === 1 && item[0] < 128)
43369
+ return item;
43370
+ return concat2(rlpLength(item.length, 128), item);
43371
+ }
43372
+ const body = concat2(...item.map(rlpEncode));
43373
+ return concat2(rlpLength(body.length, 192), body);
43374
+ }
43375
+ function toChecksumAddress(address) {
43376
+ const lower = (address.startsWith("0x") ? address.slice(2) : address).toLowerCase();
43377
+ if (!/^[0-9a-f]{40}$/.test(lower))
43378
+ throw new Error(`not an EVM address: ${address}`);
43379
+ const digest = bytesToHex2(keccak_256(new TextEncoder().encode(lower))).slice(2);
43380
+ let out = "0x";
43381
+ for (let i = 0;i < lower.length; i++) {
43382
+ const c = lower[i];
43383
+ out += Number.parseInt(digest[i], 16) >= 8 ? c.toUpperCase() : c;
43384
+ }
43385
+ return out;
43386
+ }
43387
+ function looksLikeEvmAddress(value) {
43388
+ return /^0x[0-9a-fA-F]{40}$/.test(value);
43389
+ }
43390
+ function checkEvmAddress(value) {
43391
+ if (!looksLikeEvmAddress(value))
43392
+ return { ok: false, reason: "not 0x followed by 40 hex characters" };
43393
+ const body = value.slice(2);
43394
+ const checksummed = toChecksumAddress(value);
43395
+ const hasLower = /[a-f]/.test(body);
43396
+ const hasUpper = /[A-F]/.test(body);
43397
+ if (hasLower && hasUpper && checksummed !== value) {
43398
+ return { ok: false, reason: "its mixed-case EIP-55 checksum does not match" };
43399
+ }
43400
+ return { ok: true, address: checksummed };
43401
+ }
43402
+ function sameEvmAddress(a, b) {
43403
+ return a.toLowerCase() === b.toLowerCase();
43404
+ }
43405
+ function evmAddressFromSecret(secret) {
43406
+ if (secret.length !== EVM_SECRET_BYTES) {
43407
+ throw new Error(`expected a ${EVM_SECRET_BYTES}-byte secp256k1 scalar, got ${secret.length}`);
43408
+ }
43409
+ const pub = secp256k1.getPublicKey(secret, false).slice(1);
43410
+ return toChecksumAddress(bytesToHex2(keccak_256(pub)).slice(-40));
43411
+ }
43412
+ function evmDerivationPath(index) {
43413
+ if (!Number.isInteger(index) || index < 0 || index >= 2147483648)
43414
+ throw new Error(`index out of range: ${index}`);
43415
+ return `m/44'/60'/${index}'/0/0`;
43416
+ }
43417
+ function deriveEvmKey(seed, index) {
43418
+ const path = evmDerivationPath(index);
43419
+ const root = HDKey.fromMasterSeed(seed);
43420
+ try {
43421
+ const leaf = root.derive(path);
43422
+ try {
43423
+ if (leaf.privateKey === null)
43424
+ throw new Error("BIP-32 derivation produced no private key");
43425
+ const secret = Uint8Array.from(leaf.privateKey);
43426
+ return { secret, address: evmAddressFromSecret(secret), path };
43427
+ } finally {
43428
+ leaf.wipePrivateData();
43429
+ }
43430
+ } finally {
43431
+ root.wipePrivateData();
43432
+ }
43433
+ }
43434
+ function buildNativeTransfer(input) {
43435
+ return { ...input, to: toChecksumAddress(input.to), data: new Uint8Array(0) };
43436
+ }
43437
+ function buildErc20Transfer(input) {
43438
+ const { token, recipient, amount, ...fees } = input;
43439
+ return { ...fees, to: toChecksumAddress(token), value: 0n, data: encodeErc20Transfer(recipient, amount) };
43440
+ }
43441
+ var ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
43442
+ var ERC20_DECIMALS_SELECTOR = "0x313ce567";
43443
+ var ERC20_SYMBOL_SELECTOR = "0x95d89b41";
43444
+ var ERC20_BALANCE_OF_SELECTOR = "0x70a08231";
43445
+ function abiWord(value) {
43446
+ if (value < 0n || value >= 1n << 256n)
43447
+ throw new Error("uint256 out of range");
43448
+ const out = new Uint8Array(32);
43449
+ out.set(uintToMinimalBytes(value), 32 - uintToMinimalBytes(value).length);
43450
+ return out;
43451
+ }
43452
+ function abiAddress(address) {
43453
+ const out = new Uint8Array(32);
43454
+ out.set(hexToBytes2(address), 12);
43455
+ return out;
43456
+ }
43457
+ function encodeErc20Transfer(recipient, amount) {
43458
+ return concat2(hexToBytes2(ERC20_TRANSFER_SELECTOR), abiAddress(recipient), abiWord(amount));
43459
+ }
43460
+ var TYPE_2 = Uint8Array.of(2);
43461
+ function unsignedFields(tx) {
43462
+ return [
43463
+ uintToMinimalBytes(tx.chainId),
43464
+ uintToMinimalBytes(tx.nonce),
43465
+ uintToMinimalBytes(tx.maxPriorityFeePerGas),
43466
+ uintToMinimalBytes(tx.maxFeePerGas),
43467
+ uintToMinimalBytes(tx.gas),
43468
+ hexToBytes2(tx.to),
43469
+ uintToMinimalBytes(tx.value),
43470
+ tx.data,
43471
+ []
43472
+ ];
43473
+ }
43474
+ function signingPayload(tx) {
43475
+ return keccak_256(concat2(TYPE_2, rlpEncode(unsignedFields(tx))));
43476
+ }
43477
+ function signTransaction(tx, secret) {
43478
+ if (secret.length !== EVM_SECRET_BYTES) {
43479
+ throw new Error(`expected a ${EVM_SECRET_BYTES}-byte secp256k1 scalar, got ${secret.length}`);
43480
+ }
43481
+ const signature = secp256k1.sign(signingPayload(tx), secret, { lowS: true, prehash: false });
43482
+ const yParity = signature.recovery === 1 ? 1 : 0;
43483
+ const raw = concat2(TYPE_2, rlpEncode([
43484
+ ...unsignedFields(tx),
43485
+ uintToMinimalBytes(BigInt(yParity)),
43486
+ uintToMinimalBytes(signature.r),
43487
+ uintToMinimalBytes(signature.s)
43488
+ ]));
43489
+ return { raw, hash: bytesToHex2(keccak_256(raw)), yParity, r: signature.r, s: signature.s };
43490
+ }
43491
+ function formatUnits(raw, decimals) {
43492
+ const negative = raw < 0n;
43493
+ const magnitude = negative ? -raw : raw;
43494
+ const base = 10n ** BigInt(decimals);
43495
+ const whole = magnitude / base;
43496
+ const fraction = decimals === 0 ? "" : (magnitude % base).toString().padStart(decimals, "0").replace(/0+$/, "");
43497
+ const text = fraction === "" ? whole.toString() : `${whole}.${fraction}`;
43498
+ return negative ? `-${text}` : text;
43499
+ }
43500
+ function parseUnits(decimal, decimals) {
43501
+ if (!/^\d+(\.\d+)?$/.test(decimal))
43502
+ return { ok: false, reason: "not-a-number" };
43503
+ const [whole, fraction = ""] = decimal.split(".");
43504
+ if (fraction.length > decimals)
43505
+ return { ok: false, reason: "precision" };
43506
+ const raw = BigInt((whole ?? "0") + fraction.padEnd(decimals, "0"));
43507
+ if (raw === 0n)
43508
+ return { ok: false, reason: "zero" };
43509
+ return { ok: true, raw };
43510
+ }
43511
+
43512
+ class EvmRpcError extends Error {
43513
+ kind;
43514
+ method;
43515
+ rpcCode;
43516
+ constructor(kind, method, message, rpcCode) {
43517
+ super(message);
43518
+ this.name = "EvmRpcError";
43519
+ this.kind = kind;
43520
+ this.method = method;
43521
+ this.rpcCode = rpcCode;
43522
+ }
43523
+ }
43524
+ function decodeAbiString(bytes) {
43525
+ if (bytes.length === 0)
43526
+ return;
43527
+ if (bytes.length >= 64) {
43528
+ const offset = Number(hexToBigInt(bytesToHex2(bytes.subarray(0, 32))));
43529
+ if (offset + 32 <= bytes.length) {
43530
+ const length = Number(hexToBigInt(bytesToHex2(bytes.subarray(offset, offset + 32))));
43531
+ if (offset + 32 + length <= bytes.length) {
43532
+ return new TextDecoder().decode(bytes.subarray(offset + 32, offset + 32 + length));
43533
+ }
43534
+ }
43535
+ }
43536
+ if (bytes.length === 32) {
43537
+ let end = 32;
43538
+ while (end > 0 && bytes[end - 1] === 0)
43539
+ end--;
43540
+ const text = new TextDecoder().decode(bytes.subarray(0, end));
43541
+ return /^[\x20-\x7e]+$/.test(text) ? text : undefined;
43542
+ }
43543
+ return;
43544
+ }
43545
+ function createEvmRpc(url, fetchFn) {
43546
+ let id = 0;
43547
+ async function call(method, params) {
43548
+ id += 1;
43549
+ let res;
43550
+ try {
43551
+ res = await fetchFn(url, {
43552
+ method: "POST",
43553
+ headers: { "content-type": "application/json" },
43554
+ body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
43555
+ });
43556
+ } catch (error) {
43557
+ throw new EvmRpcError("transport", method, `RPC ${method} failed: ${error instanceof Error ? error.message : error}`);
43558
+ }
43559
+ if (!res.ok)
43560
+ throw new EvmRpcError("transport", method, `RPC ${method} failed: HTTP ${res.status}`);
43561
+ let json;
43562
+ try {
43563
+ json = await res.json();
43564
+ } catch {
43565
+ throw new EvmRpcError("transport", method, `RPC ${method} failed: the answer was not JSON`);
43566
+ }
43567
+ if (json.error) {
43568
+ throw new EvmRpcError("rpc", method, `${json.error.message ?? "RPC error"}`, typeof json.error.code === "number" ? json.error.code : undefined);
43569
+ }
43570
+ return json.result;
43571
+ }
43572
+ const asQuantity = (value, method) => {
43573
+ if (typeof value !== "string")
43574
+ throw new EvmRpcError("rpc", method, `RPC ${method} answered without a quantity`);
43575
+ return hexToBigInt(value);
43576
+ };
43577
+ return {
43578
+ async chainId() {
43579
+ return asQuantity(await call("eth_chainId", []), "eth_chainId");
43580
+ },
43581
+ async getTransactionCount(address, tag) {
43582
+ return asQuantity(await call("eth_getTransactionCount", [address, tag]), "eth_getTransactionCount");
43583
+ },
43584
+ async estimateGas(input) {
43585
+ return asQuantity(await call("eth_estimateGas", [
43586
+ {
43587
+ from: input.from,
43588
+ to: input.to,
43589
+ value: quantity(input.value),
43590
+ ...input.data.length > 0 ? { data: bytesToHex2(input.data) } : {}
43591
+ }
43592
+ ]), "eth_estimateGas");
43593
+ },
43594
+ async feeHistory(blockCount, newestBlock, rewardPercentiles) {
43595
+ const r = await call("eth_feeHistory", [
43596
+ quantity(blockCount),
43597
+ newestBlock,
43598
+ rewardPercentiles
43599
+ ]);
43600
+ const base = Array.isArray(r?.baseFeePerGas) ? r.baseFeePerGas : [];
43601
+ if (base.length === 0)
43602
+ throw new EvmRpcError("rpc", "eth_feeHistory", "RPC eth_feeHistory answered no baseFeePerGas");
43603
+ return {
43604
+ baseFeePerGas: base.map((value) => asQuantity(value, "eth_feeHistory")),
43605
+ reward: (Array.isArray(r.reward) ? r.reward : []).map((row) => (Array.isArray(row) ? row : []).map((value) => asQuantity(value, "eth_feeHistory")))
43606
+ };
43607
+ },
43608
+ async maxPriorityFeePerGas() {
43609
+ return asQuantity(await call("eth_maxPriorityFeePerGas", []), "eth_maxPriorityFeePerGas");
43610
+ },
43611
+ async getBalance(address) {
43612
+ return asQuantity(await call("eth_getBalance", [address, "latest"]), "eth_getBalance");
43613
+ },
43614
+ async call(input) {
43615
+ const r = await call("eth_call", [{ to: input.to, data: bytesToHex2(input.data) }, "latest"]);
43616
+ if (typeof r !== "string")
43617
+ throw new EvmRpcError("rpc", "eth_call", "RPC eth_call answered without data");
43618
+ return hexToBytes2(r);
43619
+ },
43620
+ async sendRawTransaction(raw) {
43621
+ const r = await call("eth_sendRawTransaction", [bytesToHex2(raw)]);
43622
+ if (typeof r !== "string") {
43623
+ throw new EvmRpcError("rpc", "eth_sendRawTransaction", "RPC eth_sendRawTransaction answered without a hash");
43624
+ }
43625
+ return r;
43626
+ },
43627
+ async getTransactionReceipt(hash) {
43628
+ const r = await call("eth_getTransactionReceipt", [hash]);
43629
+ if (r === null || r === undefined)
43630
+ return null;
43631
+ const status = asQuantity(r.status, "eth_getTransactionReceipt");
43632
+ return {
43633
+ status: status === 1n ? 1 : 0,
43634
+ blockNumber: asQuantity(r.blockNumber, "eth_getTransactionReceipt"),
43635
+ transactionHash: typeof r.transactionHash === "string" ? r.transactionHash : hash
43636
+ };
43637
+ },
43638
+ async blockNumber() {
43639
+ return asQuantity(await call("eth_blockNumber", []), "eth_blockNumber");
43640
+ },
43641
+ async erc20Decimals(token) {
43642
+ const answer = await this.call({ to: token, data: hexToBytes2(ERC20_DECIMALS_SELECTOR) });
43643
+ if (answer.length !== 32)
43644
+ throw new EvmRpcError("rpc", "eth_call", "the contract did not answer decimals()");
43645
+ const value = hexToBigInt(bytesToHex2(answer));
43646
+ if (value > 255n)
43647
+ throw new EvmRpcError("rpc", "eth_call", "the contract's decimals() is not a uint8");
43648
+ return Number(value);
43649
+ },
43650
+ async erc20Symbol(token) {
43651
+ try {
43652
+ return decodeAbiString(await this.call({ to: token, data: hexToBytes2(ERC20_SYMBOL_SELECTOR) }));
43653
+ } catch (error) {
43654
+ if (error instanceof EvmRpcError && error.kind === "rpc")
43655
+ return;
43656
+ throw error;
43657
+ }
43658
+ },
43659
+ async erc20BalanceOf(token, owner) {
43660
+ const answer = await this.call({
43661
+ to: token,
43662
+ data: concat2(hexToBytes2(ERC20_BALANCE_OF_SELECTOR), abiAddress(owner))
43663
+ });
43664
+ if (answer.length !== 32)
43665
+ throw new EvmRpcError("rpc", "eth_call", "the contract did not answer balanceOf()");
43666
+ return hexToBigInt(bytesToHex2(answer));
43667
+ }
43668
+ };
43669
+ }
43670
+ var FEE_HISTORY_BLOCKS = 10;
43671
+ async function quoteFees(rpc) {
43672
+ const history = await rpc.feeHistory(FEE_HISTORY_BLOCKS, "latest", [50]);
43673
+ const baseFee = history.baseFeePerGas[history.baseFeePerGas.length - 1];
43674
+ let tip;
43675
+ let tipSource;
43676
+ try {
43677
+ tip = await rpc.maxPriorityFeePerGas();
43678
+ tipSource = "eth_maxPriorityFeePerGas";
43679
+ } catch (error) {
43680
+ if (!(error instanceof EvmRpcError) || error.kind !== "rpc")
43681
+ throw error;
43682
+ const rewards = history.reward.map((row) => row[0] ?? 0n).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
43683
+ tip = rewards.length === 0 ? 0n : rewards[Math.floor(rewards.length / 2)];
43684
+ tipSource = "eth_feeHistory";
43685
+ }
43686
+ return { baseFee, maxPriorityFeePerGas: tip, maxFeePerGas: 2n * baseFee + tip, tipSource };
43687
+ }
43688
+ function gasWithHeadroom(estimate) {
43689
+ return (estimate * 12n + 9n) / 10n;
43690
+ }
43691
+ function requiredDepth(chainId) {
43692
+ return chainId === BigInt(HOOD_CHAIN_ID) ? 1 : 2;
43693
+ }
43694
+ function rpcHostOf(url) {
43695
+ return new URL(url).host;
43696
+ }
43697
+ function resolveEvmRpcUrl(flag, envValue, flagName) {
43698
+ const fromFlag = flag?.trim() || undefined;
43699
+ const fromEnv = envValue?.trim() || undefined;
43700
+ const url = fromFlag ?? fromEnv ?? DEFAULT_HOOD_RPC_URL;
43701
+ const source = fromFlag !== undefined ? flagName : fromEnv !== undefined ? EVM_RPC_URL_ENV : undefined;
43702
+ let parsed;
43703
+ try {
43704
+ parsed = new URL(url);
43705
+ } catch {
43706
+ return { error: `${source ?? flagName} is not a valid URL: ${url}` };
43707
+ }
43708
+ const local = parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost";
43709
+ if (parsed.protocol !== "https:" && !(parsed.protocol === "http:" && local)) {
43710
+ return {
43711
+ error: `${source ?? flagName} must be https:// (plain http is allowed only for 127.0.0.1 / localhost).`
43712
+ };
43713
+ }
43714
+ return { url, builtIn: url === DEFAULT_HOOD_RPC_URL };
43715
+ }
43716
+
42643
43717
  // src/vault/ed25519.ts
42644
43718
  init_ed25519();
42645
43719
  init_esm();
@@ -42675,7 +43749,7 @@ var sha5122 = sha512;
42675
43749
 
42676
43750
  // src/vault/slip10.ts
42677
43751
  init_errors();
42678
- var HARDENED_OFFSET = 2147483648;
43752
+ var HARDENED_OFFSET2 = 2147483648;
42679
43753
  var ED25519_SEED_KEY = new TextEncoder().encode("ed25519 seed");
42680
43754
  function masterFromSeed(seed) {
42681
43755
  const i = hmac(sha5122, ED25519_SEED_KEY, seed);
@@ -42686,7 +43760,7 @@ function masterFromSeed(seed) {
42686
43760
  }
42687
43761
  }
42688
43762
  function deriveChild(node, index) {
42689
- if (!Number.isInteger(index) || index < HARDENED_OFFSET || index > 4294967295) {
43763
+ if (!Number.isInteger(index) || index < HARDENED_OFFSET2 || index > 4294967295) {
42690
43764
  throw new VaultError("VAULT_INDEX_INVALID", `SLIP-0010 Ed25519 has no non-hardened children; refusing index ${index}.`);
42691
43765
  }
42692
43766
  const data = new Uint8Array(1 + 32 + 4);
@@ -42713,9 +43787,9 @@ function parsePath(path) {
42713
43787
  if (!hardened) {
42714
43788
  throw new VaultError("VAULT_INDEX_INVALID", `SLIP-0010 Ed25519 has no non-hardened children; ${path} asks for one.`);
42715
43789
  }
42716
- if (index >= HARDENED_OFFSET)
43790
+ if (index >= HARDENED_OFFSET2)
42717
43791
  throw new VaultError("VAULT_INDEX_INVALID", `Derivation index out of range: ${part}`);
42718
- return index + HARDENED_OFFSET;
43792
+ return index + HARDENED_OFFSET2;
42719
43793
  });
42720
43794
  }
42721
43795
  function derivePath(seed, path) {
@@ -42750,6 +43824,10 @@ function solanaExternalPath(index) {
42750
43824
  function evmPath(index) {
42751
43825
  return `m/44'/60'/${assertIndex(index)}'/0/0`;
42752
43826
  }
43827
+ function evmIndexOfPath(path) {
43828
+ const match = /^m\/44'\/60'\/(\d+)'\/0\/0$/.exec(path);
43829
+ return match?.[1] === undefined ? undefined : Number(match[1]);
43830
+ }
42753
43831
  function pathForBranch(branch, index) {
42754
43832
  if (branch === "solanaVault")
42755
43833
  return solanaVaultPath(index);
@@ -42807,6 +43885,15 @@ async function deriveSolanaKey(entropy, path) {
42807
43885
  wipe(seed);
42808
43886
  }
42809
43887
  }
43888
+ async function deriveEvmKeyFromRoot(entropy, index) {
43889
+ const seed = await seedFromEntropy(entropy);
43890
+ try {
43891
+ const derived = deriveEvmKey(seed, index);
43892
+ return { ...derived, secret: ownSecret(derived.secret) };
43893
+ } finally {
43894
+ wipe(seed);
43895
+ }
43896
+ }
42810
43897
  // src/vault/local-sweep.ts
42811
43898
  init_esm();
42812
43899
  init_solana_lite();
@@ -43496,12 +44583,11 @@ async function vaultNewKey(args, ctx) {
43496
44583
  return usage(ctx, `Unexpected argument: ${parsed.positionals[0]}`);
43497
44584
  const chain2 = parsed.values["--chain"];
43498
44585
  if (chain2 === undefined)
43499
- return usage(ctx, "--chain solana is required.");
43500
- if (chain2 === "evm") {
43501
- return usage(ctx, "CHAIN_NOT_OFFERED: EVM keys arrive in Phase 4. This release derives Solana keys only.");
44586
+ return usage(ctx, "--chain solana|evm is required.");
44587
+ if (chain2 !== "solana" && chain2 !== "evm") {
44588
+ return usage(ctx, `Unknown chain: ${chain2}. This release derives Solana and EVM keys (--chain solana|evm).`);
43502
44589
  }
43503
- if (chain2 !== "solana")
43504
- return usage(ctx, `Unknown chain: ${chain2}. This release derives Solana keys only.`);
44590
+ const branch = chain2 === "evm" ? "evm" : "solanaVault";
43505
44591
  if (!refuseEnvPassphrase(ctx))
43506
44592
  return 1;
43507
44593
  if (!requireTty(ctx, "vault new-key"))
@@ -43536,7 +44622,7 @@ async function vaultNewKey(args, ctx) {
43536
44622
  suggestion: "Create a second vault with a fresh root (`candle vault init`) and move the funds across with `candle vault transfer`. There is no flag for this: no fact you could assert would make the old boundary known."
43537
44623
  });
43538
44624
  }
43539
- const clash = labelClash(vault.index, plannedLabels(vault.index.hd, batch, parsed.values["--label"]));
44625
+ const clash = labelClash(vault.index, plannedLabels(vault.index.hd, batch, parsed.values["--label"], branch));
43540
44626
  if (clash !== undefined) {
43541
44627
  return usage(ctx, batch.labels !== undefined ? `A key labelled ${clash} already exists in this vault; every --labels-from name must be new.` : parsed.values["--label"] !== undefined ? `A key labelled ${clash} already exists in this vault; choose another --label.` : `A key labelled ${clash} already exists in this vault; pass --label <name> to choose a different name for this key.`);
43542
44628
  }
@@ -43546,41 +44632,52 @@ async function vaultNewKey(args, ctx) {
43546
44632
  const root = await decryptRoot(vault);
43547
44633
  try {
43548
44634
  for (let made = 0;made < batch.count; made++) {
43549
- const index = nextAllocatableIndex(current.index.hd.nextIndex.solanaVault, current.index.hd.exposedIndexes.solanaVault);
43550
- const derivationPath = solanaVaultPath(index);
43551
- const label = batch.labels?.[made] ?? parsed.values["--label"] ?? `key-${index}`;
44635
+ const index = nextAllocatableIndex(current.index.hd.nextIndex[branch], current.index.hd.exposedIndexes[branch]);
44636
+ const derivationPath = branch === "evm" ? evmPath(index) : solanaVaultPath(index);
44637
+ const label = batch.labels?.[made] ?? parsed.values["--label"] ?? defaultLabel(branch, index);
43552
44638
  let address;
43553
44639
  let keyId;
43554
44640
  let blob;
43555
- const key = await deriveSolanaKey(root, derivationPath);
43556
- try {
43557
- address = key.address;
43558
- keyId = freshKeyId();
43559
- blob = await sealKeyBlob(current, keyId, key.secret64);
43560
- } finally {
43561
- wipe(key.secret64);
44641
+ if (branch === "evm") {
44642
+ const key = await deriveEvmKeyFromRoot(root, index);
44643
+ try {
44644
+ address = key.address;
44645
+ keyId = freshKeyId();
44646
+ blob = await sealKeyBlob(current, keyId, key.secret);
44647
+ } finally {
44648
+ wipe(key.secret);
44649
+ }
44650
+ } else {
44651
+ const key = await deriveSolanaKey(root, derivationPath);
44652
+ try {
44653
+ address = key.address;
44654
+ keyId = freshKeyId();
44655
+ blob = await sealKeyBlob(current, keyId, key.secret64);
44656
+ } finally {
44657
+ wipe(key.secret64);
44658
+ }
43562
44659
  }
43563
44660
  const entry = {
43564
44661
  id: keyId,
43565
- chain: "solana",
43566
- curve: "ed25519",
44662
+ chain: chain2,
44663
+ curve: chain2 === "evm" ? "secp256k1" : "ed25519",
43567
44664
  address,
43568
44665
  label,
43569
44666
  createdAt: new Date(deps.now()).toISOString(),
43570
44667
  role: "vault",
43571
44668
  origin: "derived",
43572
- derivation: { scheme: DERIVATION_SCHEME, path: derivationPath },
44669
+ derivation: { scheme: chain2 === "evm" ? EVM_DERIVATION_SCHEME : DERIVATION_SCHEME, path: derivationPath },
43573
44670
  exposure: { everRemoteExposed: false, everExported: false }
43574
44671
  };
43575
44672
  current = await commitVault(current, {
43576
44673
  index: {
43577
- hd: { ...current.index.hd, nextIndex: { ...current.index.hd.nextIndex, solanaVault: index + 1 } },
44674
+ hd: { ...current.index.hd, nextIndex: { ...current.index.hd.nextIndex, [branch]: index + 1 } },
43578
44675
  entries: [...current.index.entries, entry]
43579
44676
  },
43580
44677
  addKeys: [blob]
43581
44678
  }, deps);
43582
44679
  derived.push({ address, label, path: derivationPath, index, keyId });
43583
- await verifyWrittenFromDisk(current, address, keyId);
44680
+ await verifyWrittenFromDisk(current, address, keyId, chain2);
43584
44681
  last = { address, keyId };
43585
44682
  if (!ctx.json && batchRequested)
43586
44683
  deps.stdout.write(`${address} ${label} ${derivationPath}
@@ -43595,7 +44692,7 @@ async function vaultNewKey(args, ctx) {
43595
44692
  const only = derived[0];
43596
44693
  if (only === undefined || last === undefined)
43597
44694
  throw new VaultError("VAULT_WRITE_FAILED", "No key was derived.");
43598
- await verifyWritten(path, last.address, last.keyId, opened.reopen, ctx);
44695
+ await verifyWritten(path, last.address, last.keyId, opened.reopen, ctx, chain2);
43599
44696
  if (ctx.json) {
43600
44697
  if (batchRequested)
43601
44698
  writeJson(deps, { ok: true, count: derived.length, keys: derived });
@@ -43634,16 +44731,25 @@ ${derived.length} of ${requested} keys were created and ARE in the vault; the va
43634
44731
  ` + `Re-run for the remaining ${requested - derived.length}${derived.length > 0 ? " (with a --labels-from file holding the names that did not land)" : ""}.
43635
44732
  `);
43636
44733
  }
43637
- function plannedLabels(hd, batch, labelFlag) {
44734
+ function plannedLabels(hd, batch, labelFlag, branch = "solanaVault") {
43638
44735
  const labels = [];
43639
- let counter = hd.nextIndex.solanaVault;
44736
+ let counter = hd.nextIndex[branch];
43640
44737
  for (let made = 0;made < batch.count; made++) {
43641
- const index = nextAllocatableIndex(counter, hd.exposedIndexes.solanaVault);
43642
- labels.push(batch.labels?.[made] ?? labelFlag ?? `key-${index}`);
44738
+ const index = nextAllocatableIndex(counter, hd.exposedIndexes[branch]);
44739
+ labels.push(batch.labels?.[made] ?? labelFlag ?? defaultLabel(branch, index));
43643
44740
  counter = index + 1;
43644
44741
  }
43645
44742
  return labels;
43646
44743
  }
44744
+ function defaultLabel(branch, index) {
44745
+ return branch === "evm" ? `evm-${index}` : `key-${index}`;
44746
+ }
44747
+ function addressOfSecret(chain2, secret) {
44748
+ return chain2 === "evm" ? evmAddressFromSecret(secret) : addressFromSecret64(secret);
44749
+ }
44750
+ function sameAddress(chain2, a, b) {
44751
+ return chain2 === "evm" ? sameEvmAddress(a, b) : a === b;
44752
+ }
43647
44753
  function labelClash(index, planned) {
43648
44754
  const taken = new Set(index.entries.map((entry) => entry.label));
43649
44755
  const seen = new Set;
@@ -43660,7 +44766,7 @@ function nextAllocatableIndex(counter, exposed) {
43660
44766
  index++;
43661
44767
  return index;
43662
44768
  }
43663
- async function verifyWrittenFromDisk(vault, address, keyId) {
44769
+ async function verifyWrittenFromDisk(vault, address, keyId, chain2 = "solana") {
43664
44770
  const raw = await readVaultRaw(vault.path);
43665
44771
  if (raw === null) {
43666
44772
  throw new VaultError("VAULT_WRITE_FAILED", `The vault at ${vault.path} could not be read back after the write.`);
@@ -43668,14 +44774,14 @@ async function verifyWrittenFromDisk(vault, address, keyId) {
43668
44774
  const onDisk = { ...vault, raw, file: parseVaultFile(raw) };
43669
44775
  const secret = await decryptKey(onDisk, keyId);
43670
44776
  try {
43671
- if (addressFromSecret64(secret) !== address) {
44777
+ if (!sameAddress(chain2, addressOfSecret(chain2, secret), address)) {
43672
44778
  throw new VaultError("VAULT_VERIFY_FAILED", "The key written to the vault does not produce the address just derived.");
43673
44779
  }
43674
44780
  } finally {
43675
44781
  wipe(secret);
43676
44782
  }
43677
44783
  }
43678
- async function verifyWritten(path, address, keyId, reopen, _ctx) {
44784
+ async function verifyWritten(path, address, keyId, reopen, _ctx, chain2 = "solana") {
43679
44785
  const raw = await readVaultRaw(path);
43680
44786
  if (raw === null)
43681
44787
  throw new VaultError("VAULT_WRITE_FAILED", `The vault at ${path} could not be read back after the write.`);
@@ -43683,7 +44789,7 @@ async function verifyWritten(path, address, keyId, reopen, _ctx) {
43683
44789
  try {
43684
44790
  const secret = await decryptKey(reopened, keyId);
43685
44791
  try {
43686
- if (addressFromSecret64(secret) !== address) {
44792
+ if (!sameAddress(chain2, addressOfSecret(chain2, secret), address)) {
43687
44793
  throw new VaultError("VAULT_VERIFY_FAILED", "The key written to the vault does not produce the address just derived.");
43688
44794
  }
43689
44795
  } finally {
@@ -43898,6 +45004,8 @@ async function externalSweep(args, ctx) {
43898
45004
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
43899
45005
  });
43900
45006
  const vault = hold(opened.vault);
45007
+ assertNotEvmEntry(vault.index, source, "external sweep");
45008
+ assertNotEvmEntry(vault.index, to, "external sweep");
43901
45009
  const sourceEntry = requireExternalEntry(vault.index, source);
43902
45010
  const destination = findVaultRoleEntry(vault.index, to);
43903
45011
  if (destination === undefined || destination.role !== "vault") {
@@ -44001,6 +45109,10 @@ var NO_API_KEY = {
44001
45109
  message: "No API key for this profile.",
44002
45110
  suggestion: "Set CANDLE_API_KEY, or run `candle keys create` and store one."
44003
45111
  };
45112
+ function widenRefusedHint(prefix, walletIds) {
45113
+ const wallets = walletIds.length > 0 ? walletIds.join(" ") : "<wallet...>";
45114
+ return `To move TEE wallets to this key, run: candle tee rebind ${wallets} --to-key ${prefix} ` + "(owner, device token; --label-prefix <p> names many at once). To grant a linked wallet to the key " + "instead, use the agent console's Agents tab in a signed-in session. If `tee rebind` says the wallets " + "are already bound, grant them to the key in the agent console's Agents tab (signed in).";
45115
+ }
44004
45116
  function formatTimestamp2(ms) {
44005
45117
  return ms ? new Date(ms).toISOString().replace("T", " ").slice(0, 16) : "-";
44006
45118
  }
@@ -44096,7 +45208,15 @@ async function keysWalletsSet(args, ctx) {
44096
45208
  env: deps.env
44097
45209
  });
44098
45210
  if (!result.ok) {
44099
- writeFailure(deps, result, { apiUrl, authType: "key" }, json);
45211
+ if (result.code !== "LOOSEN_REQUIRES_SESSION") {
45212
+ writeFailure(deps, result, { apiUrl, authType: "key" }, json);
45213
+ return 1;
45214
+ }
45215
+ const hint = widenRefusedHint(prefix, walletIds);
45216
+ writeFailure(deps, { ...result, uiHint: hint }, { apiUrl, authType: "key" }, json);
45217
+ if (!json)
45218
+ deps.stderr.write(`${hint}
45219
+ `);
44100
45220
  return 1;
44101
45221
  }
44102
45222
  if (json) {
@@ -46667,6 +47787,7 @@ function programNameOf(programId) {
46667
47787
  // src/commands/sign.ts
46668
47788
  init_solana_lite();
46669
47789
  init_errors();
47790
+ init_promote_support();
46670
47791
  init_store();
46671
47792
  init_vault_support();
46672
47793
  async function readInput(ctx, file) {
@@ -46845,6 +47966,7 @@ async function sign2(args, ctx) {
46845
47966
  const vault = hold(opened.vault);
46846
47967
  const named = [];
46847
47968
  for (const requested of lifted.values) {
47969
+ assertNotEvmEntry(vault.index, requested, "candle sign");
46848
47970
  const entry = findExternalEntry(vault.index, requested);
46849
47971
  if (entry === undefined) {
46850
47972
  const other = vault.index.entries.find((candidate) => candidate.label === requested || candidate.address === requested);
@@ -47000,6 +48122,7 @@ async function signMessage2(args, ctx) {
47000
48122
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
47001
48123
  });
47002
48124
  const vault = hold(opened.vault);
48125
+ assertNotEvmEntry(vault.index, requested, "candle sign message");
47003
48126
  const entry = findExternalEntry(vault.index, requested);
47004
48127
  if (entry === undefined) {
47005
48128
  const other = vault.index.entries.find((candidate) => candidate.label === requested || candidate.address === requested);
@@ -47649,11 +48772,11 @@ async function resolveTeeAddress(ctx, _parsed, address, openLegacy, access3 = "r
47649
48772
  } catch (error) {
47650
48773
  if (isUsageError(error)) {
47651
48774
  writeUsageFailure(ctx.deps, error.message, ctx.json);
47652
- return { ok: false, code: 2 };
48775
+ return { ok: false, code: 2, reported: true };
47653
48776
  }
47654
48777
  if (isVaultError(error)) {
47655
48778
  writeLocalFailure(ctx.deps, { code: error.code, message: error.message, ...error.suggestion ? { suggestion: error.suggestion } : {} }, ctx.json);
47656
- return { ok: false, code: error.exitCode };
48779
+ return { ok: false, code: error.exitCode, reported: true };
47657
48780
  }
47658
48781
  throw error;
47659
48782
  }
@@ -48003,7 +49126,7 @@ var sigma16 = _utf8ToBytes("expand 16-byte k");
48003
49126
  var sigma32 = _utf8ToBytes("expand 32-byte k");
48004
49127
  var sigma16_32 = u322(sigma16);
48005
49128
  var sigma32_32 = u322(sigma32);
48006
- function rotl(a, b) {
49129
+ function rotl2(a, b) {
48007
49130
  return a << b | a >>> 32 - b;
48008
49131
  }
48009
49132
  function isAligned322(b) {
@@ -48418,69 +49541,69 @@ function chachaCore(s, k, n, out, cnt, rounds = 20) {
48418
49541
  let x00 = y00, x01 = y01, x02 = y02, x03 = y03, x04 = y04, x05 = y05, x06 = y06, x07 = y07, x08 = y08, x09 = y09, x10 = y10, x11 = y11, x12 = y12, x13 = y13, x14 = y14, x15 = y15;
48419
49542
  for (let r = 0;r < rounds; r += 2) {
48420
49543
  x00 = x00 + x04 | 0;
48421
- x12 = rotl(x12 ^ x00, 16);
49544
+ x12 = rotl2(x12 ^ x00, 16);
48422
49545
  x08 = x08 + x12 | 0;
48423
- x04 = rotl(x04 ^ x08, 12);
49546
+ x04 = rotl2(x04 ^ x08, 12);
48424
49547
  x00 = x00 + x04 | 0;
48425
- x12 = rotl(x12 ^ x00, 8);
49548
+ x12 = rotl2(x12 ^ x00, 8);
48426
49549
  x08 = x08 + x12 | 0;
48427
- x04 = rotl(x04 ^ x08, 7);
49550
+ x04 = rotl2(x04 ^ x08, 7);
48428
49551
  x01 = x01 + x05 | 0;
48429
- x13 = rotl(x13 ^ x01, 16);
49552
+ x13 = rotl2(x13 ^ x01, 16);
48430
49553
  x09 = x09 + x13 | 0;
48431
- x05 = rotl(x05 ^ x09, 12);
49554
+ x05 = rotl2(x05 ^ x09, 12);
48432
49555
  x01 = x01 + x05 | 0;
48433
- x13 = rotl(x13 ^ x01, 8);
49556
+ x13 = rotl2(x13 ^ x01, 8);
48434
49557
  x09 = x09 + x13 | 0;
48435
- x05 = rotl(x05 ^ x09, 7);
49558
+ x05 = rotl2(x05 ^ x09, 7);
48436
49559
  x02 = x02 + x06 | 0;
48437
- x14 = rotl(x14 ^ x02, 16);
49560
+ x14 = rotl2(x14 ^ x02, 16);
48438
49561
  x10 = x10 + x14 | 0;
48439
- x06 = rotl(x06 ^ x10, 12);
49562
+ x06 = rotl2(x06 ^ x10, 12);
48440
49563
  x02 = x02 + x06 | 0;
48441
- x14 = rotl(x14 ^ x02, 8);
49564
+ x14 = rotl2(x14 ^ x02, 8);
48442
49565
  x10 = x10 + x14 | 0;
48443
- x06 = rotl(x06 ^ x10, 7);
49566
+ x06 = rotl2(x06 ^ x10, 7);
48444
49567
  x03 = x03 + x07 | 0;
48445
- x15 = rotl(x15 ^ x03, 16);
49568
+ x15 = rotl2(x15 ^ x03, 16);
48446
49569
  x11 = x11 + x15 | 0;
48447
- x07 = rotl(x07 ^ x11, 12);
49570
+ x07 = rotl2(x07 ^ x11, 12);
48448
49571
  x03 = x03 + x07 | 0;
48449
- x15 = rotl(x15 ^ x03, 8);
49572
+ x15 = rotl2(x15 ^ x03, 8);
48450
49573
  x11 = x11 + x15 | 0;
48451
- x07 = rotl(x07 ^ x11, 7);
49574
+ x07 = rotl2(x07 ^ x11, 7);
48452
49575
  x00 = x00 + x05 | 0;
48453
- x15 = rotl(x15 ^ x00, 16);
49576
+ x15 = rotl2(x15 ^ x00, 16);
48454
49577
  x10 = x10 + x15 | 0;
48455
- x05 = rotl(x05 ^ x10, 12);
49578
+ x05 = rotl2(x05 ^ x10, 12);
48456
49579
  x00 = x00 + x05 | 0;
48457
- x15 = rotl(x15 ^ x00, 8);
49580
+ x15 = rotl2(x15 ^ x00, 8);
48458
49581
  x10 = x10 + x15 | 0;
48459
- x05 = rotl(x05 ^ x10, 7);
49582
+ x05 = rotl2(x05 ^ x10, 7);
48460
49583
  x01 = x01 + x06 | 0;
48461
- x12 = rotl(x12 ^ x01, 16);
49584
+ x12 = rotl2(x12 ^ x01, 16);
48462
49585
  x11 = x11 + x12 | 0;
48463
- x06 = rotl(x06 ^ x11, 12);
49586
+ x06 = rotl2(x06 ^ x11, 12);
48464
49587
  x01 = x01 + x06 | 0;
48465
- x12 = rotl(x12 ^ x01, 8);
49588
+ x12 = rotl2(x12 ^ x01, 8);
48466
49589
  x11 = x11 + x12 | 0;
48467
- x06 = rotl(x06 ^ x11, 7);
49590
+ x06 = rotl2(x06 ^ x11, 7);
48468
49591
  x02 = x02 + x07 | 0;
48469
- x13 = rotl(x13 ^ x02, 16);
49592
+ x13 = rotl2(x13 ^ x02, 16);
48470
49593
  x08 = x08 + x13 | 0;
48471
- x07 = rotl(x07 ^ x08, 12);
49594
+ x07 = rotl2(x07 ^ x08, 12);
48472
49595
  x02 = x02 + x07 | 0;
48473
- x13 = rotl(x13 ^ x02, 8);
49596
+ x13 = rotl2(x13 ^ x02, 8);
48474
49597
  x08 = x08 + x13 | 0;
48475
- x07 = rotl(x07 ^ x08, 7);
49598
+ x07 = rotl2(x07 ^ x08, 7);
48476
49599
  x03 = x03 + x04 | 0;
48477
- x14 = rotl(x14 ^ x03, 16);
49600
+ x14 = rotl2(x14 ^ x03, 16);
48478
49601
  x09 = x09 + x14 | 0;
48479
- x04 = rotl(x04 ^ x09, 12);
49602
+ x04 = rotl2(x04 ^ x09, 12);
48480
49603
  x03 = x03 + x04 | 0;
48481
- x14 = rotl(x14 ^ x03, 8);
49604
+ x14 = rotl2(x14 ^ x03, 8);
48482
49605
  x09 = x09 + x14 | 0;
48483
- x04 = rotl(x04 ^ x09, 7);
49606
+ x04 = rotl2(x04 ^ x09, 7);
48484
49607
  }
48485
49608
  let oi = 0;
48486
49609
  out[oi++] = y00 + x00 | 0;
@@ -48753,7 +49876,7 @@ function i2Osp(n, w) {
48753
49876
  }
48754
49877
  return ret;
48755
49878
  }
48756
- function concat3(a, b) {
49879
+ function concat4(a, b) {
48757
49880
  const ret = new Uint8Array(a.length + b.length);
48758
49881
  ret.set(a, 0);
48759
49882
  ret.set(b, a.length);
@@ -48898,11 +50021,11 @@ class Dhkem {
48898
50021
  const sks = isCryptoKeyPair(params.senderKey) ? params.senderKey.privateKey : params.senderKey;
48899
50022
  const dh1 = new Uint8Array(await this._prim.dh(ke.privateKey, params.recipientPublicKey));
48900
50023
  const dh2 = new Uint8Array(await this._prim.dh(sks, params.recipientPublicKey));
48901
- dh = concat3(dh1, dh2);
50024
+ dh = concat4(dh1, dh2);
48902
50025
  }
48903
50026
  let kemContext;
48904
50027
  if (params.senderKey === undefined) {
48905
- kemContext = concat3(new Uint8Array(enc), new Uint8Array(pkrm));
50028
+ kemContext = concat4(new Uint8Array(enc), new Uint8Array(pkrm));
48906
50029
  } else {
48907
50030
  const pks = isCryptoKeyPair(params.senderKey) ? params.senderKey.publicKey : await this._prim.derivePublicKey(params.senderKey);
48908
50031
  const pksm = await this._prim.serializePublicKey(pks);
@@ -48929,11 +50052,11 @@ class Dhkem {
48929
50052
  } else {
48930
50053
  const dh1 = new Uint8Array(await this._prim.dh(skr, pke));
48931
50054
  const dh2 = new Uint8Array(await this._prim.dh(skr, params.senderPublicKey));
48932
- dh = concat3(dh1, dh2);
50055
+ dh = concat4(dh1, dh2);
48933
50056
  }
48934
50057
  let kemContext;
48935
50058
  if (params.senderPublicKey === undefined) {
48936
- kemContext = concat3(new Uint8Array(params.enc), new Uint8Array(pkrm));
50059
+ kemContext = concat4(new Uint8Array(params.enc), new Uint8Array(pkrm));
48937
50060
  } else {
48938
50061
  const pksm = await this._prim.serializePublicKey(params.senderPublicKey);
48939
50062
  kemContext = new Uint8Array(params.enc.byteLength + pkrm.byteLength + pksm.byteLength);
@@ -50374,241 +51497,9 @@ async function runImportFlow(params) {
50374
51497
 
50375
51498
  // src/wallet-keygen.ts
50376
51499
  import { generateKeyPairSync } from "node:crypto";
50377
-
50378
- // ../../node_modules/@noble/curves/esm/secp256k1.js
50379
- init_sha2();
50380
- init__shortw_utils();
50381
- init_modular();
50382
- /*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
50383
- var secp256k1_CURVE = {
50384
- p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),
50385
- n: BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),
50386
- h: BigInt(1),
50387
- a: BigInt(0),
50388
- b: BigInt(7),
50389
- Gx: BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
50390
- Gy: BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")
50391
- };
50392
- var secp256k1_ENDO = {
50393
- beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),
50394
- basises: [
50395
- [BigInt("0x3086d221a7d46bcde86c90e49284eb15"), -BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],
50396
- [BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"), BigInt("0x3086d221a7d46bcde86c90e49284eb15")]
50397
- ]
50398
- };
50399
- var _2n5 = /* @__PURE__ */ BigInt(2);
50400
- function sqrtMod(y) {
50401
- const P2 = secp256k1_CURVE.p;
50402
- const _3n4 = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);
50403
- const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);
50404
- const b2 = y * y * y % P2;
50405
- const b3 = b2 * b2 * y % P2;
50406
- const b6 = pow2(b3, _3n4, P2) * b3 % P2;
50407
- const b9 = pow2(b6, _3n4, P2) * b3 % P2;
50408
- const b11 = pow2(b9, _2n5, P2) * b2 % P2;
50409
- const b22 = pow2(b11, _11n, P2) * b11 % P2;
50410
- const b44 = pow2(b22, _22n, P2) * b22 % P2;
50411
- const b88 = pow2(b44, _44n, P2) * b44 % P2;
50412
- const b176 = pow2(b88, _88n, P2) * b88 % P2;
50413
- const b220 = pow2(b176, _44n, P2) * b44 % P2;
50414
- const b223 = pow2(b220, _3n4, P2) * b3 % P2;
50415
- const t1 = pow2(b223, _23n, P2) * b22 % P2;
50416
- const t2 = pow2(t1, _6n, P2) * b2 % P2;
50417
- const root = pow2(t2, _2n5, P2);
50418
- if (!Fpk1.eql(Fpk1.sqr(root), y))
50419
- throw new Error("Cannot find square root");
50420
- return root;
50421
- }
50422
- var Fpk1 = Field(secp256k1_CURVE.p, { sqrt: sqrtMod });
50423
- var secp256k1 = createCurve({ ...secp256k1_CURVE, Fp: Fpk1, lowS: true, endo: secp256k1_ENDO }, sha256);
50424
-
50425
- // ../../node_modules/@noble/hashes/esm/sha3.js
50426
- init__u64();
50427
- init_utils();
50428
- var _0n7 = BigInt(0);
50429
- var _1n7 = BigInt(1);
50430
- var _2n6 = BigInt(2);
50431
- var _7n2 = BigInt(7);
50432
- var _256n = BigInt(256);
50433
- var _0x71n = BigInt(113);
50434
- var SHA3_PI = [];
50435
- var SHA3_ROTL = [];
50436
- var _SHA3_IOTA = [];
50437
- for (let round = 0, R = _1n7, x = 1, y = 0;round < 24; round++) {
50438
- [x, y] = [y, (2 * x + 3 * y) % 5];
50439
- SHA3_PI.push(2 * (5 * y + x));
50440
- SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
50441
- let t = _0n7;
50442
- for (let j = 0;j < 7; j++) {
50443
- R = (R << _1n7 ^ (R >> _7n2) * _0x71n) % _256n;
50444
- if (R & _2n6)
50445
- t ^= _1n7 << (_1n7 << /* @__PURE__ */ BigInt(j)) - _1n7;
50446
- }
50447
- _SHA3_IOTA.push(t);
50448
- }
50449
- var IOTAS = split(_SHA3_IOTA, true);
50450
- var SHA3_IOTA_H = IOTAS[0];
50451
- var SHA3_IOTA_L = IOTAS[1];
50452
- var rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s);
50453
- var rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s);
50454
- function keccakP(s, rounds = 24) {
50455
- const B = new Uint32Array(5 * 2);
50456
- for (let round = 24 - rounds;round < 24; round++) {
50457
- for (let x = 0;x < 10; x++)
50458
- B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
50459
- for (let x = 0;x < 10; x += 2) {
50460
- const idx1 = (x + 8) % 10;
50461
- const idx0 = (x + 2) % 10;
50462
- const B0 = B[idx0];
50463
- const B1 = B[idx0 + 1];
50464
- const Th = rotlH(B0, B1, 1) ^ B[idx1];
50465
- const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
50466
- for (let y = 0;y < 50; y += 10) {
50467
- s[x + y] ^= Th;
50468
- s[x + y + 1] ^= Tl;
50469
- }
50470
- }
50471
- let curH = s[2];
50472
- let curL = s[3];
50473
- for (let t = 0;t < 24; t++) {
50474
- const shift = SHA3_ROTL[t];
50475
- const Th = rotlH(curH, curL, shift);
50476
- const Tl = rotlL(curH, curL, shift);
50477
- const PI = SHA3_PI[t];
50478
- curH = s[PI];
50479
- curL = s[PI + 1];
50480
- s[PI] = Th;
50481
- s[PI + 1] = Tl;
50482
- }
50483
- for (let y = 0;y < 50; y += 10) {
50484
- for (let x = 0;x < 10; x++)
50485
- B[x] = s[y + x];
50486
- for (let x = 0;x < 10; x++)
50487
- s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
50488
- }
50489
- s[0] ^= SHA3_IOTA_H[round];
50490
- s[1] ^= SHA3_IOTA_L[round];
50491
- }
50492
- clean(B);
50493
- }
50494
-
50495
- class Keccak extends Hash {
50496
- constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
50497
- super();
50498
- this.pos = 0;
50499
- this.posOut = 0;
50500
- this.finished = false;
50501
- this.destroyed = false;
50502
- this.enableXOF = false;
50503
- this.blockLen = blockLen;
50504
- this.suffix = suffix;
50505
- this.outputLen = outputLen;
50506
- this.enableXOF = enableXOF;
50507
- this.rounds = rounds;
50508
- anumber(outputLen);
50509
- if (!(0 < blockLen && blockLen < 200))
50510
- throw new Error("only keccak-f1600 function is supported");
50511
- this.state = new Uint8Array(200);
50512
- this.state32 = u32(this.state);
50513
- }
50514
- clone() {
50515
- return this._cloneInto();
50516
- }
50517
- keccak() {
50518
- swap32IfBE(this.state32);
50519
- keccakP(this.state32, this.rounds);
50520
- swap32IfBE(this.state32);
50521
- this.posOut = 0;
50522
- this.pos = 0;
50523
- }
50524
- update(data) {
50525
- aexists(this);
50526
- data = toBytes(data);
50527
- abytes(data);
50528
- const { blockLen, state } = this;
50529
- const len = data.length;
50530
- for (let pos = 0;pos < len; ) {
50531
- const take2 = Math.min(blockLen - this.pos, len - pos);
50532
- for (let i = 0;i < take2; i++)
50533
- state[this.pos++] ^= data[pos++];
50534
- if (this.pos === blockLen)
50535
- this.keccak();
50536
- }
50537
- return this;
50538
- }
50539
- finish() {
50540
- if (this.finished)
50541
- return;
50542
- this.finished = true;
50543
- const { state, suffix, pos, blockLen } = this;
50544
- state[pos] ^= suffix;
50545
- if ((suffix & 128) !== 0 && pos === blockLen - 1)
50546
- this.keccak();
50547
- state[blockLen - 1] ^= 128;
50548
- this.keccak();
50549
- }
50550
- writeInto(out) {
50551
- aexists(this, false);
50552
- abytes(out);
50553
- this.finish();
50554
- const bufferOut = this.state;
50555
- const { blockLen } = this;
50556
- for (let pos = 0, len = out.length;pos < len; ) {
50557
- if (this.posOut >= blockLen)
50558
- this.keccak();
50559
- const take2 = Math.min(blockLen - this.posOut, len - pos);
50560
- out.set(bufferOut.subarray(this.posOut, this.posOut + take2), pos);
50561
- this.posOut += take2;
50562
- pos += take2;
50563
- }
50564
- return out;
50565
- }
50566
- xofInto(out) {
50567
- if (!this.enableXOF)
50568
- throw new Error("XOF is not possible for this instance");
50569
- return this.writeInto(out);
50570
- }
50571
- xof(bytes) {
50572
- anumber(bytes);
50573
- return this.xofInto(new Uint8Array(bytes));
50574
- }
50575
- digestInto(out) {
50576
- aoutput(out, this);
50577
- if (this.finished)
50578
- throw new Error("digest() was already called");
50579
- this.writeInto(out);
50580
- this.destroy();
50581
- return out;
50582
- }
50583
- digest() {
50584
- return this.digestInto(new Uint8Array(this.outputLen));
50585
- }
50586
- destroy() {
50587
- this.destroyed = true;
50588
- clean(this.state);
50589
- }
50590
- _cloneInto(to) {
50591
- const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
50592
- to || (to = new Keccak(blockLen, suffix, outputLen, enableXOF, rounds));
50593
- to.state32.set(this.state32);
50594
- to.pos = this.pos;
50595
- to.posOut = this.posOut;
50596
- to.finished = this.finished;
50597
- to.rounds = rounds;
50598
- to.suffix = suffix;
50599
- to.outputLen = outputLen;
50600
- to.enableXOF = enableXOF;
50601
- to.destroyed = this.destroyed;
50602
- return to;
50603
- }
50604
- }
50605
- var gen = (suffix, blockLen, outputLen) => createHasher(() => new Keccak(blockLen, suffix, outputLen));
50606
- var keccak_256 = /* @__PURE__ */ (() => gen(1, 136, 256 / 8))();
50607
-
50608
- // src/wallet-keygen.ts
50609
51500
  init_esm();
50610
51501
  var hex3 = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
50611
- function toChecksumAddress(lowercaseBody) {
51502
+ function toChecksumAddress2(lowercaseBody) {
50612
51503
  const digest = hex3(keccak_256(new TextEncoder().encode(lowercaseBody)));
50613
51504
  let out = "0x";
50614
51505
  for (let i = 0;i < lowercaseBody.length; i++) {
@@ -50629,7 +51520,7 @@ function generateSolana() {
50629
51520
  function generateEvm() {
50630
51521
  const priv = secp256k1.utils.randomPrivateKey();
50631
51522
  const pub = secp256k1.getPublicKey(priv, false).slice(1);
50632
- return { address: toChecksumAddress(hex3(keccak_256(pub)).slice(-40)), privateKey: `0x${hex3(priv)}` };
51523
+ return { address: toChecksumAddress2(hex3(keccak_256(pub)).slice(-40)), privateKey: `0x${hex3(priv)}` };
50633
51524
  }
50634
51525
  function generateWallet(chain2) {
50635
51526
  return chain2 === "solana" ? generateSolana() : generateEvm();
@@ -51372,10 +52263,8 @@ async function addressOwnedByVault(ctx, address) {
51372
52263
  writeUsageFailure(ctx.deps, error.message, ctx.json);
51373
52264
  return "usage";
51374
52265
  }
51375
- writeLocalFailure(ctx.deps, {
51376
- code: "VAULT_UNLOCK_FAILED",
51377
- message: error instanceof Error ? error.message : String(error)
51378
- }, ctx.json);
52266
+ const { isVaultError: isVaultError2 } = await Promise.resolve().then(() => (init_errors(), exports_errors));
52267
+ writeLocalFailure(ctx.deps, isVaultError2(error) ? { code: error.code, message: error.message, ...error.suggestion ? { suggestion: error.suggestion } : {} } : { code: "VAULT_UNLOCK_FAILED", message: error instanceof Error ? error.message : String(error) }, ctx.json);
51379
52268
  return "error";
51380
52269
  }
51381
52270
  }
@@ -53899,12 +54788,32 @@ async function verifyEntry(copy, entry, root, report, observer) {
53899
54788
  if (addressFromSecret64(secret) !== entry.address) {
53900
54789
  fail2(5, "its stored secret does not produce the address the index records", entry.id);
53901
54790
  }
54791
+ } else if (!sameEvmAddress(evmAddressFromSecret(secret), entry.address)) {
54792
+ fail2(5, "its stored secret does not produce the address the index records", entry.id);
53902
54793
  }
53903
54794
  report.addressChecked.push(entry.id);
53904
54795
  if (entry.origin !== "derived" || entry.derivation === undefined) {
53905
54796
  report.notRederived.push(entry.id);
53906
54797
  return;
53907
54798
  }
54799
+ if (entry.derivation.scheme === "bip32-secp256k1") {
54800
+ const index = evmIndexOfPath(entry.derivation.path);
54801
+ if (index === undefined) {
54802
+ fail2(5, `its recorded path ${entry.derivation.path} is not on the EVM branch`, entry.id);
54803
+ }
54804
+ const derivedEvm = await deriveEvmKeyFromRoot(root, index);
54805
+ observer?.onLeafLive?.(2);
54806
+ try {
54807
+ if (!bytesEqual(derivedEvm.secret, secret)) {
54808
+ fail2(5, `it does not re-derive from the root along its recorded path ${entry.derivation.path}`, entry.id);
54809
+ }
54810
+ report.rederived.push(entry.id);
54811
+ } finally {
54812
+ wipe(derivedEvm.secret);
54813
+ observer?.onLeafLive?.(1);
54814
+ }
54815
+ return;
54816
+ }
53908
54817
  if (entry.derivation.scheme !== "slip10-ed25519") {
53909
54818
  report.notRederived.push(entry.id);
53910
54819
  return;
@@ -54440,6 +55349,8 @@ async function vaultDemote(args, ctx) {
54440
55349
  code: 1
54441
55350
  }));
54442
55351
  if (!resolved.ok) {
55352
+ if (resolved.reported)
55353
+ return resolved.code;
54443
55354
  writeLocalFailure(ctx.deps, {
54444
55355
  code: "TEE_WALLET_UNKNOWN",
54445
55356
  message: `${address} is not a TEE wallet in the vault.`,
@@ -56040,6 +56951,7 @@ async function reconcileFundingReceipts(vault, addresses, rpcUrl2, ctx) {
56040
56951
  }
56041
56952
 
56042
56953
  // src/commands/vault-fund.ts
56954
+ init_promote_support();
56043
56955
  init_store();
56044
56956
 
56045
56957
  // src/vault/vault-transfer-sign.ts
@@ -56257,10 +57169,11 @@ async function signAndBroadcastTransfer(input) {
56257
57169
  // src/commands/vault-fund.ts
56258
57170
  init_vault_support();
56259
57171
  async function fundExternal(ctx, opened, vault, external2, input) {
56260
- const vaultKeys = vault.index.entries.filter((entry) => entry.role === "vault");
57172
+ const vaultKeys = vault.index.entries.filter((entry) => entry.role === "vault" && entry.chain === "solana");
56261
57173
  const fromFlag = input.parsed.values["--from"];
56262
57174
  let fromEntry;
56263
57175
  if (fromFlag !== undefined) {
57176
+ assertNotEvmEntry(vault.index, fromFlag, "vault fund");
56264
57177
  fromEntry = vaultKeys.find((entry) => entry.label === fromFlag) ?? vaultKeys.find((entry) => entry.address === fromFlag);
56265
57178
  if (fromEntry === undefined)
56266
57179
  return usage(ctx, `No vault key matches --from ${fromFlag}.`);
@@ -56350,6 +57263,9 @@ async function vaultFund(args, ctx) {
56350
57263
  acceptOlderCopy: parsed.booleans.has("--accept-older-copy")
56351
57264
  });
56352
57265
  const vault = hold(opened.vault);
57266
+ assertNotEvmEntry(vault.index, teeAddress, "vault fund");
57267
+ if (parsed.values["--from"] !== undefined)
57268
+ assertNotEvmEntry(vault.index, parsed.values["--from"], "vault fund");
56353
57269
  const teeEntry = vault.index.entries.find((entry) => entry.address === teeAddress && entry.role === "tee-wallet");
56354
57270
  if (teeEntry === undefined) {
56355
57271
  const external2 = findExternalEntry(vault.index, teeAddress);
@@ -56381,7 +57297,7 @@ async function vaultFund(args, ctx) {
56381
57297
  suggestion: "Nothing was signed. Name the source key with --from <label>, or pin one: candle tee enable <address> --vault <address>"
56382
57298
  });
56383
57299
  }
56384
- const fromEntry = vault.index.entries.find((entry) => entry.address === destination && entry.role === "vault");
57300
+ const fromEntry = vault.index.entries.find((entry) => entry.address === destination && entry.role === "vault" && entry.chain === "solana");
56385
57301
  if (fromEntry === undefined) {
56386
57302
  throw new VaultError("GRANT_DESTINATION_UNRESOLVED", `Pinned destination ${destination} is not a vault key in this vault.`, {
56387
57303
  suggestion: "Nothing was signed. Name the source key with --from <label>; candle vault status lists this vault's keys."
@@ -56838,9 +57754,48 @@ async function readLamports(addresses, rpcUrl2, fetchFn) {
56838
57754
  }
56839
57755
  return { lamports, unavailable, ...failure === undefined ? {} : { failure } };
56840
57756
  }
57757
+ async function readEvmBalances(addresses, rpcUrl2, fetchFn) {
57758
+ const rpc2 = createEvmRpc(rpcUrl2, fetchFn);
57759
+ const read = {
57760
+ host: rpcHostOf(rpcUrl2),
57761
+ chainId: undefined,
57762
+ requests: 0,
57763
+ wei: new Map,
57764
+ usdg: new Map,
57765
+ unavailable: []
57766
+ };
57767
+ try {
57768
+ read.requests += 1;
57769
+ read.chainId = await rpc2.chainId();
57770
+ } catch (error) {
57771
+ read.unavailable.push(...addresses);
57772
+ read.failure = error instanceof Error ? error.message : String(error);
57773
+ return read;
57774
+ }
57775
+ const hood = read.chainId === BigInt(HOOD_CHAIN_ID);
57776
+ for (const address of addresses) {
57777
+ try {
57778
+ read.requests += 1;
57779
+ read.wei.set(address, await rpc2.getBalance(address));
57780
+ if (hood) {
57781
+ read.requests += 1;
57782
+ read.usdg.set(address, await rpc2.erc20BalanceOf(HOOD_USDG_ADDRESS, address));
57783
+ }
57784
+ } catch (error) {
57785
+ read.wei.delete(address);
57786
+ read.usdg.delete(address);
57787
+ read.unavailable.push(address);
57788
+ read.failure ??= error instanceof Error ? error.message : String(error);
57789
+ }
57790
+ }
57791
+ return read;
57792
+ }
57793
+ function evmRequestsPlanned(count) {
57794
+ return `${1 + count} to ${1 + 2 * count}`;
57795
+ }
56841
57796
  async function vaultList(args, ctx) {
56842
57797
  const parsed = parseArgs(args, {
56843
- valueFlags: ["--keystore", "--rpc-url"],
57798
+ valueFlags: ["--keystore", "--rpc-url", "--evm-rpc-url"],
56844
57799
  booleanFlags: ["--balances", "--accept-older-copy"],
56845
57800
  pathFlags: ["--keystore"]
56846
57801
  });
@@ -56856,6 +57811,9 @@ async function vaultList(args, ctx) {
56856
57811
  if (!balances && parsed.values["--rpc-url"] !== undefined) {
56857
57812
  return usage(ctx, "--rpc-url has no effect without --balances; vault list is offline by default.");
56858
57813
  }
57814
+ if (!balances && parsed.values["--evm-rpc-url"] !== undefined) {
57815
+ return usage(ctx, "--evm-rpc-url has no effect without --balances; vault list is offline by default.");
57816
+ }
56859
57817
  const resolvedVault = vaultPathFor(ctx, parsed);
56860
57818
  if ("error" in resolvedVault)
56861
57819
  return usage(ctx, resolvedVault.error);
@@ -56867,6 +57825,10 @@ async function vaultList(args, ctx) {
56867
57825
  return usage(ctx, resolved.error);
56868
57826
  rpcUrl2 = resolved;
56869
57827
  }
57828
+ const evmRpc = resolveEvmRpcUrl(parsed.values["--evm-rpc-url"], deps.env[EVM_RPC_URL_ENV], "--evm-rpc-url");
57829
+ if (balances && "error" in evmRpc)
57830
+ return usage(ctx, evmRpc.error);
57831
+ const evmRpcUrl = "url" in evmRpc ? evmRpc.url : DEFAULT_HOOD_RPC_URL;
56870
57832
  if (!requirePromptStreams(ctx, "vault list"))
56871
57833
  return 1;
56872
57834
  return runVaultCommand(ctx, async ({ hold }) => {
@@ -56892,12 +57854,26 @@ async function vaultList(args, ctx) {
56892
57854
  unavailable = outcome.unavailable;
56893
57855
  failure = outcome.failure;
56894
57856
  }
56895
- const complete = unavailable.length === 0;
57857
+ const solanaComplete = unavailable.length === 0;
56896
57858
  const totalLamports = [...lamports.values()].reduce((sum, value) => sum + value, 0n);
56897
- if (!complete) {
57859
+ if (!solanaComplete) {
56898
57860
  deps.stderr.write(`${unavailable.length} ${unavailable.length === 1 ? "address" : "addresses"} could not be read: ${failure ?? "the RPC did not answer"}. Narrow with a filter, or use your own endpoint with --rpc-url.
56899
57861
  `);
56900
57862
  }
57863
+ const evm = balances ? matched.filter((entry) => entry.chain === "evm") : [];
57864
+ let evmRead;
57865
+ if (evm.length > 0) {
57866
+ const host = rpcHostOf(evmRpcUrl);
57867
+ deps.stderr.write(`Reading ETH (and USDG when the chain is Hood) for ${evm.length} EVM ${evm.length === 1 ? "address" : "addresses"} from ${host}, in ${evmRequestsPlanned(evm.length)} requests. That endpoint sees all ${evm.length} together.
57868
+ `);
57869
+ evmRead = await readEvmBalances(evm.map((entry) => entry.address), evmRpcUrl, deps.fetch);
57870
+ if (evmRead.unavailable.length > 0) {
57871
+ deps.stderr.write(`${evmRead.unavailable.length} EVM ${evmRead.unavailable.length === 1 ? "address" : "addresses"} could not be read: ${evmRead.failure ?? "the RPC did not answer"}. Narrow with a filter, or use your own endpoint with --evm-rpc-url.
57872
+ `);
57873
+ }
57874
+ }
57875
+ const hood = evmRead?.chainId === BigInt(HOOD_CHAIN_ID);
57876
+ const complete = solanaComplete && (evmRead === undefined || evmRead.unavailable.length === 0);
56901
57877
  if (ctx.json) {
56902
57878
  writeJson(deps, {
56903
57879
  ok: true,
@@ -56909,17 +57885,30 @@ async function vaultList(args, ctx) {
56909
57885
  const held = lamports.get(entry.address);
56910
57886
  return {
56911
57887
  ...describeEntry(entry),
56912
- ...balances && entry.chain === "solana" ? { lamports: held === undefined ? null : held.toString() } : {}
57888
+ ...balances && entry.chain === "solana" ? { lamports: held === undefined ? null : held.toString() } : {},
57889
+ ...balances && entry.chain === "evm" ? {
57890
+ wei: evmRead?.wei.get(entry.address)?.toString() ?? null,
57891
+ ...hood ? { usdgRaw: evmRead?.usdg.get(entry.address)?.toString() ?? null } : {}
57892
+ } : {}
56913
57893
  };
56914
57894
  }),
56915
57895
  ...balances && rpcHost !== undefined ? {
56916
57896
  balances: {
56917
57897
  rpcHost,
56918
57898
  requests,
56919
- complete,
57899
+ complete: solanaComplete,
56920
57900
  totalLamports: totalLamports.toString(),
56921
57901
  unavailable
56922
57902
  }
57903
+ } : {},
57904
+ ...evmRead !== undefined ? {
57905
+ evmBalances: {
57906
+ rpcHost: evmRead.host,
57907
+ chainId: evmRead.chainId === undefined ? null : Number(evmRead.chainId),
57908
+ requests: evmRead.requests,
57909
+ complete: evmRead.unavailable.length === 0,
57910
+ unavailable: evmRead.unavailable
57911
+ }
56923
57912
  } : {}
56924
57913
  });
56925
57914
  return complete ? 0 : 3;
@@ -56935,24 +57924,50 @@ async function vaultList(args, ctx) {
56935
57924
  const headers = ["ADDRESS", "LABEL", "ROLE", "DERIVATION"];
56936
57925
  if (balances)
56937
57926
  headers.push("SOL");
57927
+ const evmColumns = evmRead !== undefined;
57928
+ const nativeHeader = hood || evmRead?.chainId === undefined ? "ETH" : `ETH@${evmRead.chainId}`;
57929
+ if (evmColumns)
57930
+ headers.push(nativeHeader);
57931
+ if (evmColumns && hood)
57932
+ headers.push("USDG");
56938
57933
  const rows = matched.map((entry) => {
56939
57934
  const row = [entry.address, entry.label || "(none)", entry.role, entry.derivation?.path ?? "-"];
56940
57935
  if (balances) {
56941
57936
  const held = lamports.get(entry.address);
56942
57937
  row.push(entry.chain !== "solana" ? "-" : held === undefined ? "?" : formatSol3(held));
56943
57938
  }
57939
+ if (evmColumns) {
57940
+ const wei = evmRead?.wei.get(entry.address);
57941
+ row.push(entry.chain !== "evm" ? "-" : wei === undefined ? "?" : formatUnits(wei, NATIVE_DECIMALS));
57942
+ if (hood) {
57943
+ const usdg = evmRead?.usdg.get(entry.address);
57944
+ row.push(entry.chain !== "evm" ? "-" : usdg === undefined ? "?" : formatUnits(usdg, HOOD_USDG_DECIMALS));
57945
+ }
57946
+ }
56944
57947
  return row;
56945
57948
  });
56946
57949
  deps.stdout.write(`
56947
57950
  ${renderTable(headers, rows)}
56948
57951
  `);
56949
57952
  if (balances) {
56950
- deps.stdout.write(complete ? `
57953
+ deps.stdout.write(solanaComplete ? `
56951
57954
  total ${formatSol3(totalLamports)} SOL across ${solana.length} keys
56952
57955
  ` : `
56953
57956
  total ${formatSol3(totalLamports)} SOL across ${solana.length - unavailable.length} of ${solana.length} keys read
56954
57957
  `);
56955
57958
  }
57959
+ if (evmRead !== undefined) {
57960
+ const totalWei = [...evmRead.wei.values()].reduce((sum, value) => sum + value, 0n);
57961
+ const readCount = evm.length - evmRead.unavailable.length;
57962
+ const chain2 = evmRead.chainId === undefined ? "an unread chain" : hood ? "Hood" : `chain id ${evmRead.chainId}`;
57963
+ deps.stdout.write(`total ${formatUnits(totalWei, NATIVE_DECIMALS)} ${nativeHeader} across ${readCount}${readCount === evm.length ? "" : ` of ${evm.length}`} EVM keys${readCount === evm.length ? "" : " read"} on ${chain2}
57964
+ `);
57965
+ if (hood) {
57966
+ const totalUsdg = [...evmRead.usdg.values()].reduce((sum, value) => sum + value, 0n);
57967
+ deps.stdout.write(`total ${formatUnits(totalUsdg, HOOD_USDG_DECIMALS)} USDG across those keys
57968
+ `);
57969
+ }
57970
+ }
56956
57971
  return complete ? 0 : 3;
56957
57972
  });
56958
57973
  }
@@ -57632,6 +58647,7 @@ async function promoteInPlace(ctx, parsed, subjectLabel, toKey) {
57632
58647
  });
57633
58648
  let vault = hold(opened.vault);
57634
58649
  assertRecoverableFactorExists(vault.file.envelopes);
58650
+ assertNotEvmEntry(vault.index, subjectLabel, "vault promote");
57635
58651
  const existing = findEntryByLabelOrAddress(vault.index, subjectLabel);
57636
58652
  if (existing === undefined) {
57637
58653
  throw new VaultError("PROMOTE_NOT_VAULT_KEY", `No entry matches ${subjectLabel}.`, {
@@ -59262,8 +60278,8 @@ function resolveTarget(index, old, id) {
59262
60278
  // src/commands/vault-restore.ts
59263
60279
  init_args();
59264
60280
  init_deps();
59265
- init_solana_lite();
59266
60281
  import { rm as rm3 } from "node:fs/promises";
60282
+ init_solana_lite();
59267
60283
  init_crypto();
59268
60284
  init_errors();
59269
60285
  init_format();
@@ -59275,7 +60291,7 @@ var GAP_LIMIT = 20;
59275
60291
  var SCAN_CEILING = 500;
59276
60292
  async function vaultRestore(args, ctx) {
59277
60293
  const parsed = parseArgs(args, {
59278
- valueFlags: ["--keystore", "--count", "--tee-count", "--external-count", "--rpc-url"],
60294
+ valueFlags: ["--keystore", "--count", "--tee-count", "--external-count", "--evm-count", "--rpc-url"],
59279
60295
  booleanFlags: ["--phrase", "--own-passphrase"],
59280
60296
  pathFlags: ["--keystore"]
59281
60297
  });
@@ -59292,7 +60308,7 @@ async function vaultRestore(args, ctx) {
59292
60308
  }
59293
60309
  if (!requireTty(ctx, "vault restore"))
59294
60310
  return 1;
59295
- const counts = parseCounts(parsed.values["--count"], parsed.values["--tee-count"], parsed.values["--external-count"], parsed.values["--rpc-url"]);
60311
+ const counts = parseCounts(parsed.values["--count"], parsed.values["--tee-count"], parsed.values["--external-count"], parsed.values["--rpc-url"], parsed.values["--evm-count"]);
59296
60312
  if ("error" in counts)
59297
60313
  return usage(ctx, counts.error);
59298
60314
  const { deps } = ctx;
@@ -59392,7 +60408,7 @@ async function discardIncompleteRestore(ctx, path, sidecarExisted) {
59392
60408
  }
59393
60409
  }
59394
60410
  var RESTORED_BRANCHES = ["solanaVault", "solanaTee", "solanaExternal"];
59395
- function parseCounts(count, teeCount, externalCount, rpcUrl2) {
60411
+ function parseCounts(count, teeCount, externalCount, rpcUrl2, evmCount) {
59396
60412
  const parse = (raw, flag) => {
59397
60413
  if (raw === undefined)
59398
60414
  return;
@@ -59410,6 +60426,9 @@ function parseCounts(count, teeCount, externalCount, rpcUrl2) {
59410
60426
  const externalParsed = parse(externalCount, "--external-count");
59411
60427
  if (typeof externalParsed === "object" && externalParsed !== null)
59412
60428
  return externalParsed;
60429
+ const evmParsed = parse(evmCount, "--evm-count");
60430
+ if (typeof evmParsed === "object" && evmParsed !== null)
60431
+ return evmParsed;
59413
60432
  const allOmitted = vaultCount === undefined && teeParsed === undefined && externalParsed === undefined;
59414
60433
  const scan = rpcUrl2 !== undefined;
59415
60434
  const resolve4 = (value) => {
@@ -59424,6 +60443,7 @@ function parseCounts(count, teeCount, externalCount, rpcUrl2) {
59424
60443
  solanaVault,
59425
60444
  solanaTee,
59426
60445
  solanaExternal,
60446
+ evm: evmParsed ?? 0,
59427
60447
  requested: { solanaVault: solanaVault ?? -1, solanaTee: solanaTee ?? -1, solanaExternal: solanaExternal ?? -1 }
59428
60448
  };
59429
60449
  }
@@ -59453,17 +60473,41 @@ async function deriveWithinBounds(ctx, vault, counts, rpc2) {
59453
60473
  ctx.deps.stdout.write(`Gap scan on ${branch} stopped at index ${index - 1} after ${GAP_LIMIT} consecutive indices with no balance, no token account and no signature history.
59454
60474
  `);
59455
60475
  }
60476
+ for (let index = 0;index < counts.evm; index++) {
60477
+ set.entries.push(await deriveOneEvm(vault, root, index));
60478
+ }
59456
60479
  } finally {
59457
60480
  wipe(root);
59458
60481
  }
59459
60482
  for (const entry of set.entries) {
59460
60483
  if (entry.branch === "solanaExternal")
59461
60484
  set.externalByAddress.set(entry.address, entry);
60485
+ else if (entry.branch === "evm")
60486
+ set.byAddress.set(entry.address.toLowerCase(), entry);
59462
60487
  else
59463
60488
  set.byAddress.set(entry.address, entry);
59464
60489
  }
59465
60490
  return set;
59466
60491
  }
60492
+ async function deriveOneEvm(vault, root, index) {
60493
+ const derived = await deriveEvmKeyFromRoot(root, index);
60494
+ try {
60495
+ const keyId = freshKeyId();
60496
+ return {
60497
+ branch: "evm",
60498
+ index,
60499
+ address: derived.address,
60500
+ path: derived.path,
60501
+ keyId,
60502
+ blob: await sealKeyBlob(vault, keyId, derived.secret)
60503
+ };
60504
+ } finally {
60505
+ wipe(derived.secret);
60506
+ }
60507
+ }
60508
+ function lookupKey(address) {
60509
+ return looksLikeEvmAddress(address) ? address.toLowerCase() : address;
60510
+ }
59467
60511
  async function deriveOne(vault, root, branch, index) {
59468
60512
  const path = pathForBranch(branch, index);
59469
60513
  const derived = await deriveSolanaKey(root, path);
@@ -59521,7 +60565,7 @@ This profile acts as account ${read.account}.
59521
60565
  for (const row of read.rows) {
59522
60566
  if (typeof row.address !== "string")
59523
60567
  continue;
59524
- const entry = derived.byAddress.get(row.address);
60568
+ const entry = derived.byAddress.get(lookupKey(row.address));
59525
60569
  if (entry)
59526
60570
  matched.push({ entry, row });
59527
60571
  else if (derived.externalByAddress.has(row.address))
@@ -59536,6 +60580,20 @@ async function writeRestoredIndex(ctx, vault, derived, matches2, counts) {
59536
60580
  const matchByAddress = new Map(matches2.matched.map((match) => [match.entry.address, match.row]));
59537
60581
  const entries = derived.entries.map((entry) => {
59538
60582
  const row = matchByAddress.get(entry.address);
60583
+ if (entry.branch === "evm") {
60584
+ return {
60585
+ id: entry.keyId,
60586
+ chain: "evm",
60587
+ curve: "secp256k1",
60588
+ address: entry.address,
60589
+ label: row?.label ?? `evm-${entry.index}`,
60590
+ createdAt: now,
60591
+ role: "vault",
60592
+ origin: "derived",
60593
+ derivation: { scheme: EVM_DERIVATION_SCHEME, path: entry.path },
60594
+ exposure: { everRemoteExposed: row !== undefined, everExported: false, exposureUnknown: true }
60595
+ };
60596
+ }
59539
60597
  const external2 = entry.branch === "solanaExternal";
59540
60598
  const base = {
59541
60599
  id: entry.keyId,
@@ -59576,14 +60634,14 @@ async function writeRestoredIndex(ctx, vault, derived, matches2, counts) {
59576
60634
  solanaVault: highest.solanaVault + 1,
59577
60635
  solanaTee: highest.solanaTee + 1,
59578
60636
  solanaExternal: highest.solanaExternal + 1,
59579
- evm: 0
60637
+ evm: highest.evm + 1
59580
60638
  },
59581
60639
  rootExported: false,
59582
60640
  exposedIndexes: {
59583
60641
  solanaVault: exposed.solanaVault.sort((a, b) => a - b),
59584
60642
  solanaTee: exposed.solanaTee.sort((a, b) => a - b),
59585
60643
  solanaExternal: exposed.solanaExternal.sort((a, b) => a - b),
59586
- evm: []
60644
+ evm: exposed.evm.sort((a, b) => a - b)
59587
60645
  },
59588
60646
  discovery: {
59589
60647
  restoredAt: now,
@@ -59646,6 +60704,13 @@ Every one of them is recorded with an unknown history and stays that way: this p
59646
60704
  `);
59647
60705
  }
59648
60706
  }
60707
+ if (counts.evm === 0) {
60708
+ deps.stdout.write(` evm: none. If this root has EVM keys, re-run with --evm-count <n>; the EVM branch is never gap-scanned.
60709
+ `);
60710
+ } else {
60711
+ deps.stdout.write(` evm: indices 0 to ${counts.evm - 1}, on m/44'/60'/n'/0/0.
60712
+ `);
60713
+ }
59649
60714
  if ((matches2.externalListed?.length ?? 0) > 0) {
59650
60715
  deps.stdout.write(`
59651
60716
  ${matches2.externalListed?.length} address(es) this account imported are external-branch keys of this root. An external key is never registered with Candle, so this read does not flag them; their history is unknown like every other recovered key's.
@@ -59722,8 +60787,8 @@ async function vaultReconcileExposure(args, ctx) {
59722
60787
  if (recorded !== undefined && recorded !== "" && recorded !== read.account) {
59723
60788
  throw new VaultError("EXPOSURE_ACCOUNT_MISMATCH", `This vault recorded its discovery against account ${recorded}, and this profile acts as ${read.account}.`, { suggestion: "Switch profile and run it again. Nothing was flagged." });
59724
60789
  }
59725
- const listed = new Set(read.rows.map((row) => row.address));
59726
- const entries = vault.index.entries.map((entry) => entry.role !== "external" && listed.has(entry.address) && !entry.exposure.everRemoteExposed ? { ...entry, exposure: { ...entry.exposure, everRemoteExposed: true } } : entry);
60790
+ const listed = new Set(read.rows.map((row) => lookupKey(row.address)));
60791
+ const entries = vault.index.entries.map((entry) => entry.role !== "external" && listed.has(lookupKey(entry.address)) && !entry.exposure.everRemoteExposed ? { ...entry, exposure: { ...entry.exposure, everRemoteExposed: true } } : entry);
59727
60792
  const added = entries.filter((entry, i) => entry !== vault.index.entries[i]);
59728
60793
  const exposedIndexes = { ...vault.index.hd.exposedIndexes };
59729
60794
  for (const entry of entries) {
@@ -60190,6 +61255,375 @@ function describeEnvelope(envelope, facts) {
60190
61255
  // src/commands/vault-transfer.ts
60191
61256
  init_args();
60192
61257
  init_deps();
61258
+ init_errors();
61259
+
61260
+ // src/vault/evm-transfer.ts
61261
+ init_errors();
61262
+ var EVM_RECEIPT_WAIT_MS = 120000;
61263
+ var EVM_RECEIPT_POLL_MS = 2000;
61264
+ function refuse2(code, message, opts = {}) {
61265
+ return new VaultError(code, message, { suggestion: opts.suggestion ?? "Nothing was signed.", ...opts });
61266
+ }
61267
+ function checkEvmDestination(to, from) {
61268
+ if (!to.startsWith("0x")) {
61269
+ throw refuse2("TRANSFER_CHAIN_MISMATCH", `${to} is not an EVM address, and ${from.label || from.address} is an EVM key.`, { suggestion: "Nothing was signed. An EVM key sends to a 0x address; a Solana key sends to a Solana address." });
61270
+ }
61271
+ const checked = checkEvmAddress(to);
61272
+ if (!checked.ok) {
61273
+ throw refuse2("EVM_DESTINATION_INVALID", `${to} is not a valid EVM address: ${checked.reason}.`);
61274
+ }
61275
+ if (sameEvmAddress(checked.address, from.address)) {
61276
+ throw refuse2("EVM_SELF_TRANSFER", `${to} is ${from.label || from.address}'s own address.`);
61277
+ }
61278
+ return checked.address;
61279
+ }
61280
+ function assertSolanaDestination(to, from) {
61281
+ if (!looksLikeEvmAddress(to))
61282
+ return;
61283
+ throw refuse2("TRANSFER_CHAIN_MISMATCH", `${to} is an EVM address, and ${from.label || from.address} is a Solana key.`, { suggestion: "Nothing was signed. A Solana key sends to a Solana address; an EVM key sends to a 0x address." });
61284
+ }
61285
+ async function resolveEvmAsset(rpc2, asset, chainId) {
61286
+ const upper = asset.toUpperCase();
61287
+ if (upper === "ETH")
61288
+ return { kind: "native", symbol: "ETH", decimals: NATIVE_DECIMALS };
61289
+ let token;
61290
+ if (upper === "USDG") {
61291
+ if (chainId !== BigInt(HOOD_CHAIN_ID)) {
61292
+ return {
61293
+ usage: `USDG is named on Hood (chain id ${HOOD_CHAIN_ID}) only; this RPC answered chain id ${chainId}. Name the token by its contract address.`
61294
+ };
61295
+ }
61296
+ token = toChecksumAddress(HOOD_USDG_ADDRESS);
61297
+ } else {
61298
+ const checked = checkEvmAddress(asset);
61299
+ if (!checked.ok)
61300
+ return {
61301
+ usage: `--asset must be ETH, USDG (on Hood), or an ERC-20 contract address: ${asset} is ${checked.reason}.`
61302
+ };
61303
+ token = checked.address;
61304
+ }
61305
+ let decimals;
61306
+ try {
61307
+ decimals = await rpc2.erc20Decimals(token);
61308
+ } catch (error) {
61309
+ throw refuse2("EVM_TOKEN_UNREADABLE", `${token} did not answer decimals(): ${error instanceof Error ? error.message : String(error)}`, { suggestion: "Nothing was signed. Check the contract address, and that this RPC serves the chain it lives on." });
61310
+ }
61311
+ const symbol = upper === "USDG" ? "USDG" : await rpc2.erc20Symbol(token) ?? `${token.slice(0, 10)}…`;
61312
+ return { kind: "erc20", symbol, decimals, token };
61313
+ }
61314
+ function nativeName(hood, chainId) {
61315
+ return hood ? "ETH" : `ETH on chain ${chainId}`;
61316
+ }
61317
+ function evmDisplayLines(plan) {
61318
+ const { tx, asset, hood, chainId } = plan;
61319
+ const native = nativeName(hood, chainId);
61320
+ const lines = [
61321
+ `chain ${chainId}${hood ? " (Hood)" : ""}`,
61322
+ `from ${plan.from.label} ${plan.from.address}`
61323
+ ];
61324
+ if (asset.kind === "native") {
61325
+ lines.push(`to ${tx.to}`);
61326
+ lines.push(`amount ${plan.amount} ${native} = ${plan.amountRaw} wei`);
61327
+ } else {
61328
+ lines.push(`to ${tx.to} (the ${asset.symbol} contract, ${asset.decimals} dp)`);
61329
+ lines.push(`recipient ${plan.recipient} (decoded from transfer(address,uint256))`);
61330
+ lines.push(`amount ${plan.amount} ${asset.symbol} = ${plan.amountRaw} raw`);
61331
+ }
61332
+ lines.push(`gas limit ${tx.gas}`);
61333
+ lines.push(`max fee ${tx.maxFeePerGas} wei/gas (priority ${tx.maxPriorityFeePerGas} wei/gas)`);
61334
+ lines.push(`fee cap ${formatUnits(plan.feeCap, NATIVE_DECIMALS)} ${native} (gas × max fee)`);
61335
+ lines.push(`nonce ${tx.nonce}`);
61336
+ return lines;
61337
+ }
61338
+ async function planEvmTransfer(rpc2, input) {
61339
+ const to = checkEvmDestination(input.to, input.from);
61340
+ const chainId = await rpc2.chainId();
61341
+ if (input.builtIn && chainId !== BigInt(HOOD_CHAIN_ID)) {
61342
+ throw refuse2("EVM_CHAIN_MISMATCH", `The built-in Hood RPC answered chain id ${chainId}, not ${HOOD_CHAIN_ID}.`, {
61343
+ suggestion: "Nothing was signed. Pass --rpc-url for another chain; the built-in endpoint is Hood's only."
61344
+ });
61345
+ }
61346
+ const hood = chainId === BigInt(HOOD_CHAIN_ID);
61347
+ const asset = await resolveEvmAsset(rpc2, input.asset, chainId);
61348
+ if ("usage" in asset)
61349
+ return asset;
61350
+ const max = input.amount.toLowerCase() === "max";
61351
+ if (asset.kind === "erc20" && asset.token !== undefined && sameEvmAddress(to, asset.token)) {
61352
+ throw refuse2("EVM_RECIPIENT_IS_TOKEN", `${to} is the ${asset.symbol} contract itself; sending it its own tokens is a loss.`);
61353
+ }
61354
+ let amountRaw = 0n;
61355
+ if (!max) {
61356
+ const parsed = parseUnits(input.amount, asset.decimals);
61357
+ if (!parsed.ok && parsed.reason === "precision") {
61358
+ throw refuse2("EVM_AMOUNT_PRECISION", `${input.amount} has more decimal places than ${asset.symbol}'s ${asset.decimals}.`);
61359
+ }
61360
+ if (!parsed.ok)
61361
+ return { usage: `--amount must be a positive decimal or max; ${input.amount} is neither.` };
61362
+ amountRaw = parsed.raw;
61363
+ }
61364
+ const nonce = await rpc2.getTransactionCount(input.from.address, "pending");
61365
+ const fees = await quoteFees(rpc2);
61366
+ const nativeBalance = await rpc2.getBalance(input.from.address);
61367
+ if (asset.kind === "erc20" && asset.token !== undefined) {
61368
+ if (max) {
61369
+ amountRaw = await rpc2.erc20BalanceOf(asset.token, input.from.address);
61370
+ if (amountRaw === 0n) {
61371
+ throw new VaultError("VAULT_INDEX_INVALID", `${input.from.label} holds no ${asset.symbol}; there is nothing to send.`);
61372
+ }
61373
+ }
61374
+ const draft2 = buildErc20Transfer({
61375
+ chainId,
61376
+ nonce,
61377
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
61378
+ maxFeePerGas: fees.maxFeePerGas,
61379
+ gas: 0n,
61380
+ token: asset.token,
61381
+ recipient: to,
61382
+ amount: amountRaw
61383
+ });
61384
+ const gas2 = gasWithHeadroom(await estimate(rpc2, input.from.address, draft2));
61385
+ const tx2 = { ...draft2, gas: gas2 };
61386
+ const feeCap2 = gas2 * tx2.maxFeePerGas;
61387
+ if (nativeBalance < feeCap2) {
61388
+ throw refuse2("EVM_INSUFFICIENT_FOR_FEES", `${input.from.label} holds ${formatUnits(nativeBalance, NATIVE_DECIMALS)} ${nativeName(hood, chainId)}, below the fee cap of ${formatUnits(feeCap2, NATIVE_DECIMALS)}.`);
61389
+ }
61390
+ const plan2 = {
61391
+ chainId,
61392
+ hood,
61393
+ from: input.from,
61394
+ asset,
61395
+ recipient: to,
61396
+ amountRaw,
61397
+ amount: formatUnits(amountRaw, asset.decimals),
61398
+ tx: tx2,
61399
+ feeCap: feeCap2,
61400
+ displayLines: []
61401
+ };
61402
+ plan2.displayLines = evmDisplayLines(plan2);
61403
+ return plan2;
61404
+ }
61405
+ const draft = buildNativeTransfer({
61406
+ chainId,
61407
+ nonce,
61408
+ maxPriorityFeePerGas: fees.maxPriorityFeePerGas,
61409
+ maxFeePerGas: fees.maxFeePerGas,
61410
+ gas: 0n,
61411
+ to,
61412
+ value: max ? 0n : amountRaw
61413
+ });
61414
+ if (!max && nativeBalance < amountRaw) {
61415
+ throw refuse2("EVM_INSUFFICIENT_FOR_FEES", `${input.from.label} holds ${formatUnits(nativeBalance, NATIVE_DECIMALS)} ${nativeName(hood, chainId)}, below the amount of ${formatUnits(amountRaw, NATIVE_DECIMALS)}.`);
61416
+ }
61417
+ const gas = gasWithHeadroom(await estimate(rpc2, input.from.address, draft));
61418
+ const feeCap = gas * draft.maxFeePerGas;
61419
+ let value = amountRaw;
61420
+ if (max) {
61421
+ value = nativeBalance - feeCap;
61422
+ if (value <= 0n) {
61423
+ throw refuse2("EVM_INSUFFICIENT_FOR_FEES", `${input.from.label} holds ${formatUnits(nativeBalance, NATIVE_DECIMALS)} ${nativeName(hood, chainId)}, not above the fee cap of ${formatUnits(feeCap, NATIVE_DECIMALS)}; max leaves nothing to send.`);
61424
+ }
61425
+ } else if (nativeBalance < value + feeCap) {
61426
+ throw refuse2("EVM_INSUFFICIENT_FOR_FEES", `${input.from.label} holds ${formatUnits(nativeBalance, NATIVE_DECIMALS)} ${nativeName(hood, chainId)}, below the amount plus the fee cap of ${formatUnits(value + feeCap, NATIVE_DECIMALS)}.`);
61427
+ }
61428
+ const tx = { ...draft, gas, value };
61429
+ const plan = {
61430
+ chainId,
61431
+ hood,
61432
+ from: input.from,
61433
+ asset,
61434
+ recipient: to,
61435
+ amountRaw: value,
61436
+ amount: formatUnits(value, NATIVE_DECIMALS),
61437
+ tx,
61438
+ feeCap,
61439
+ displayLines: []
61440
+ };
61441
+ plan.displayLines = evmDisplayLines(plan);
61442
+ return plan;
61443
+ }
61444
+ async function estimate(rpc2, from, tx) {
61445
+ try {
61446
+ return await rpc2.estimateGas({ from, to: tx.to, value: tx.value, data: tx.data });
61447
+ } catch (error) {
61448
+ throw new VaultError("VAULT_UNREADABLE", `Could not estimate gas: ${error instanceof Error ? error.message : String(error)}`, { suggestion: "Nothing was signed. A revert here usually means the balance does not cover the amount." });
61449
+ }
61450
+ }
61451
+ function displayEvmTransferPlan(ctx, plan) {
61452
+ ctx.deps.stdout.write(`Decoded EVM transfer (local signing only):
61453
+ `);
61454
+ for (const line of plan.displayLines)
61455
+ ctx.deps.stdout.write(` ${line}
61456
+ `);
61457
+ }
61458
+ function evmFactorPrompt(plan) {
61459
+ return `sign transfer of ${plan.amount} ${plan.asset.symbol} to ${plan.recipient}${plan.hood ? " on Hood" : ` on chain ${plan.chainId}`}`;
61460
+ }
61461
+ function judgeReceipt(receipt, head, depth, hash, chainId) {
61462
+ if (receipt.status === 0) {
61463
+ throw new VaultError("EVM_TRANSFER_REVERTED", `Transaction ${hash} reverted in block ${receipt.blockNumber}; the fee was spent.`, {
61464
+ suggestion: "Nothing else was signed. Read the transaction on an explorer before sending again.",
61465
+ details: { hash, chainId: chainId.toString(), blockNumber: receipt.blockNumber.toString(), status: "reverted" }
61466
+ });
61467
+ }
61468
+ const reached = head - receipt.blockNumber + 1n;
61469
+ if (reached < BigInt(depth))
61470
+ return;
61471
+ return {
61472
+ status: "confirmed",
61473
+ exit: 0,
61474
+ blockNumber: receipt.blockNumber,
61475
+ line: `Confirmed ${hash} in block ${receipt.blockNumber}, ${reached} block${reached === 1n ? "" : "s"} deep (depth ${depth}, not finality).`
61476
+ };
61477
+ }
61478
+ async function awaitReceipt(rpc2, ctx, hash, chainId) {
61479
+ const depth = requiredDepth(chainId);
61480
+ const deadline = ctx.deps.now() + EVM_RECEIPT_WAIT_MS;
61481
+ let seen;
61482
+ for (;; ) {
61483
+ try {
61484
+ const receipt = await rpc2.getTransactionReceipt(hash);
61485
+ if (receipt !== null) {
61486
+ seen = receipt;
61487
+ const outcome = judgeReceipt(receipt, await rpc2.blockNumber(), depth, hash, chainId);
61488
+ if (outcome !== undefined)
61489
+ return outcome;
61490
+ }
61491
+ } catch (error) {
61492
+ if (error instanceof VaultError)
61493
+ throw error;
61494
+ }
61495
+ if (ctx.deps.now() >= deadline)
61496
+ break;
61497
+ await ctx.deps.sleep(EVM_RECEIPT_POLL_MS);
61498
+ }
61499
+ if (seen !== undefined) {
61500
+ return {
61501
+ status: "uncertain",
61502
+ exit: 3,
61503
+ blockNumber: seen.blockNumber,
61504
+ line: `Submitted ${hash}: it is in block ${seen.blockNumber} but not yet ${depth} block${depth === 1 ? "" : "s"} deep after ${EVM_RECEIPT_WAIT_MS / 1000} s. Do not resend; check the hash on an explorer.`
61505
+ };
61506
+ }
61507
+ return {
61508
+ status: "uncertain",
61509
+ exit: 3,
61510
+ line: `Submitted ${hash}; no receipt after ${EVM_RECEIPT_WAIT_MS / 1000} s. It may still land: do not resend blindly; check the hash on an explorer first.`
61511
+ };
61512
+ }
61513
+ async function broadcast(rpc2, ctx, raw, hash, chainId) {
61514
+ try {
61515
+ await rpc2.sendRawTransaction(raw);
61516
+ } catch (error) {
61517
+ if (!(error instanceof EvmRpcError))
61518
+ throw error;
61519
+ const message = error.message.toLowerCase();
61520
+ if (error.kind === "transport") {
61521
+ return {
61522
+ status: "uncertain",
61523
+ exit: 3,
61524
+ line: `Submitted ${hash}, but the RPC did not answer eth_sendRawTransaction (${error.message}). It may still land: do not resend blindly; check the hash on an explorer first.`
61525
+ };
61526
+ }
61527
+ if (message.includes("already known")) {
61528
+ return {
61529
+ status: "uncertain",
61530
+ exit: 3,
61531
+ line: `Submitted ${hash}: the RPC already knows it, so it is in flight. Do not resend; check the hash on an explorer.`
61532
+ };
61533
+ }
61534
+ if (message.includes("nonce too low")) {
61535
+ let receipt = null;
61536
+ try {
61537
+ receipt = await rpc2.getTransactionReceipt(hash);
61538
+ } catch {
61539
+ receipt = null;
61540
+ }
61541
+ if (receipt !== null) {
61542
+ let head;
61543
+ try {
61544
+ head = await rpc2.blockNumber();
61545
+ } catch {
61546
+ return awaitReceipt(rpc2, ctx, hash, chainId);
61547
+ }
61548
+ const outcome = judgeReceipt(receipt, head, requiredDepth(chainId), hash, chainId);
61549
+ if (outcome !== undefined)
61550
+ return outcome;
61551
+ return awaitReceipt(rpc2, ctx, hash, chainId);
61552
+ }
61553
+ throw new VaultError("EVM_NONCE_STALE", `The RPC refused ${hash}: nonce too low, and it has no receipt for that hash. Another transaction used this nonce.`, {
61554
+ suggestion: "Nothing was resent. Run the transfer again; it reads the pending nonce afresh. The CLI never re-signs on its own.",
61555
+ details: { hash, chainId: chainId.toString() }
61556
+ });
61557
+ }
61558
+ return {
61559
+ status: "uncertain",
61560
+ exit: 3,
61561
+ line: `Submitted ${hash}, and the RPC answered: ${error.message}. The CLI cannot tell from that whether it is in flight: do not resend blindly; check the hash on an explorer first.`
61562
+ };
61563
+ }
61564
+ return awaitReceipt(rpc2, ctx, hash, chainId);
61565
+ }
61566
+ async function runEvmTransfer(input, rpc2) {
61567
+ const { ctx } = input;
61568
+ const { deps } = ctx;
61569
+ checkEvmDestination(input.to, input.from);
61570
+ deps.stderr.write(`Reading chain id, nonce, fees and balances for ${input.from.label} from ${rpcHostOf(input.rpcUrl)}${input.rpcUrl === DEFAULT_HOOD_RPC_URL ? " (the built-in Hood RPC)" : ""}.
61571
+ `);
61572
+ const planned = await planEvmTransfer(rpc2, input);
61573
+ if ("usage" in planned)
61574
+ return input.usage(planned.usage);
61575
+ const plan = planned;
61576
+ displayEvmTransferPlan(ctx, plan);
61577
+ await input.confirmLastSix(plan.recipient, plan.asset.kind === "erc20" ? "the token recipient" : "the destination");
61578
+ await input.confirmFactor(evmFactorPrompt(plan));
61579
+ const chainIdAgain = await rpc2.chainId();
61580
+ if (chainIdAgain !== plan.chainId) {
61581
+ throw refuse2("EVM_CHAIN_MISMATCH", `The RPC answered chain id ${chainIdAgain} after the factor, but ${plan.chainId} was displayed.`);
61582
+ }
61583
+ const nonceAgain = await rpc2.getTransactionCount(plan.from.address, "pending");
61584
+ if (nonceAgain !== plan.tx.nonce) {
61585
+ throw refuse2("EVM_NONCE_STALE", `The pending nonce is ${nonceAgain} after the factor, but ${plan.tx.nonce} was displayed; another transaction moved it.`, { suggestion: "Nothing was signed. Run the transfer again; it reads the pending nonce afresh." });
61586
+ }
61587
+ const secret = await input.decryptSecret();
61588
+ let signed;
61589
+ try {
61590
+ if (!sameEvmAddress(evmAddressFromSecret(secret), plan.from.address)) {
61591
+ throw new VaultError("VAULT_VERIFY_FAILED", "The decrypted key does not match the planned sender.");
61592
+ }
61593
+ signed = signTransaction(plan.tx, secret);
61594
+ } finally {
61595
+ wipe(secret);
61596
+ }
61597
+ const outcome = await broadcast(rpc2, ctx, signed.raw, signed.hash, plan.chainId);
61598
+ if (ctx.json) {
61599
+ input.writeJson({
61600
+ ok: outcome.status === "confirmed",
61601
+ chainId: Number(plan.chainId),
61602
+ hash: signed.hash,
61603
+ status: outcome.status,
61604
+ ...outcome.blockNumber !== undefined ? { blockNumber: outcome.blockNumber.toString() } : {},
61605
+ depth: requiredDepth(plan.chainId),
61606
+ finalized: false,
61607
+ from: plan.from.address,
61608
+ to: plan.tx.to,
61609
+ recipient: plan.recipient,
61610
+ amount: plan.amount,
61611
+ asset: plan.asset.symbol,
61612
+ amountRaw: plan.amountRaw.toString(),
61613
+ ...plan.asset.token !== undefined ? { token: plan.asset.token } : {},
61614
+ nonce: plan.tx.nonce.toString(),
61615
+ gas: plan.tx.gas.toString(),
61616
+ maxFeePerGas: plan.tx.maxFeePerGas.toString(),
61617
+ maxPriorityFeePerGas: plan.tx.maxPriorityFeePerGas.toString()
61618
+ });
61619
+ } else {
61620
+ deps.stdout.write(`${outcome.line}
61621
+ `);
61622
+ }
61623
+ return outcome.exit;
61624
+ }
61625
+
61626
+ // src/commands/vault-transfer.ts
60193
61627
  init_promote_support();
60194
61628
  init_store();
60195
61629
 
@@ -60272,20 +61706,29 @@ async function vaultTransfer(args, ctx) {
60272
61706
  return usage(ctx, parsed.error);
60273
61707
  const [to, extra] = parsed.positionals;
60274
61708
  if (!to || extra !== undefined) {
60275
- return usage(ctx, "Usage: candle vault transfer <to> --amount <n> --asset SOL|<mint> --from <label> --rpc-url <url>");
61709
+ return usage(ctx, "Usage: candle vault transfer <to> --amount <n|max> --asset SOL|<mint>|ETH|USDG|<0x token> --from <label> [--rpc-url <url>]");
60276
61710
  }
60277
61711
  const amount = parsed.values["--amount"];
60278
61712
  const asset = parsed.values["--asset"];
60279
61713
  const fromLabel = parsed.values["--from"];
60280
- const rpcUrl2 = parsed.values["--rpc-url"];
61714
+ const rpcUrlFlag = parsed.values["--rpc-url"];
60281
61715
  if (!amount)
60282
61716
  return usage(ctx, "--amount <n> is required.");
60283
61717
  if (!asset)
60284
- return usage(ctx, "--asset SOL|<mint> is required.");
61718
+ return usage(ctx, "--asset SOL|<mint>|ETH|USDG|<0x token> is required.");
60285
61719
  if (!fromLabel)
60286
61720
  return usage(ctx, "--from <label> is required.");
60287
- if (!rpcUrl2)
60288
- return usage(ctx, "--rpc-url <url> is required.");
61721
+ if (rpcUrlFlag !== undefined) {
61722
+ try {
61723
+ new URL(rpcUrlFlag);
61724
+ } catch {
61725
+ return usage(ctx, `--rpc-url is not a valid URL: ${rpcUrlFlag}`);
61726
+ }
61727
+ } else {
61728
+ const evmFromEnv = resolveEvmRpcUrl(undefined, ctx.deps.env[EVM_RPC_URL_ENV], "--rpc-url");
61729
+ if ("error" in evmFromEnv)
61730
+ return usage(ctx, evmFromEnv.error);
61731
+ }
60289
61732
  if (!refuseEnvPassphrase(ctx))
60290
61733
  return 1;
60291
61734
  if (!requireTty(ctx, "vault transfer"))
@@ -60306,6 +61749,34 @@ async function vaultTransfer(args, ctx) {
60306
61749
  return usage(ctx, `No vault key or promoted wallet matches --from ${fromLabel}.`);
60307
61750
  }
60308
61751
  assertTransferSigner(fromEntry);
61752
+ if (fromEntry.chain === "evm") {
61753
+ if (fromEntry.role !== "vault") {
61754
+ throw new VaultError("PROMOTE_NOT_VAULT_KEY", `${fromEntry.label || fromEntry.address} is an EVM ${fromEntry.role} entry; this release signs EVM transfers from vault keys only.`, { suggestion: "Nothing was signed." });
61755
+ }
61756
+ const evmRpc = resolveEvmRpcUrl(rpcUrlFlag, ctx.deps.env[EVM_RPC_URL_ENV], "--rpc-url");
61757
+ if ("error" in evmRpc)
61758
+ return usage(ctx, evmRpc.error);
61759
+ const evmRpcUrl = evmRpc.url;
61760
+ const secretRef2 = fromEntry;
61761
+ return runEvmTransfer({
61762
+ ctx,
61763
+ rpcUrl: evmRpcUrl,
61764
+ builtIn: evmRpc.builtIn,
61765
+ from: fromEntry,
61766
+ to,
61767
+ amount,
61768
+ asset,
61769
+ confirmLastSix: (address, what) => confirmLastSix(ctx, address, what),
61770
+ confirmFactor: (what) => opened.confirm(what),
61771
+ decryptSecret: () => decryptKey(vault, secretRef2.id),
61772
+ usage: (line) => usage(ctx, line),
61773
+ writeJson: (value) => writeJson(ctx.deps, value)
61774
+ }, createEvmRpc(evmRpcUrl, ctx.deps.fetch));
61775
+ }
61776
+ assertSolanaDestination(to, fromEntry);
61777
+ const rpcUrl2 = rpcUrlFlag;
61778
+ if (!rpcUrl2)
61779
+ return usage(ctx, "--rpc-url <url> is required for a transfer from a Solana key.");
60309
61780
  const promoted = fromEntry.role === "tee-wallet";
60310
61781
  if (promoted)
60311
61782
  assertNoPendingSweep(fromEntry);