@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.
- package/README.md +133 -12
- package/dist/index.js +1831 -360
- 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,
|
|
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,
|
|
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,
|
|
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),
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
{
|
|
39479
|
-
|
|
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: [
|
|
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
|
|
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 <
|
|
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 >=
|
|
43790
|
+
if (index >= HARDENED_OFFSET2)
|
|
42717
43791
|
throw new VaultError("VAULT_INDEX_INVALID", `Derivation index out of range: ${part}`);
|
|
42718
|
-
return index +
|
|
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
|
|
43501
|
-
return usage(ctx,
|
|
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
|
-
|
|
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
|
|
43550
|
-
const derivationPath = solanaVaultPath(index);
|
|
43551
|
-
const label = batch.labels?.[made] ?? parsed.values["--label"] ??
|
|
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
|
-
|
|
43556
|
-
|
|
43557
|
-
|
|
43558
|
-
|
|
43559
|
-
|
|
43560
|
-
|
|
43561
|
-
|
|
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:
|
|
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,
|
|
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
|
|
44736
|
+
let counter = hd.nextIndex[branch];
|
|
43640
44737
|
for (let made = 0;made < batch.count; made++) {
|
|
43641
|
-
const index = nextAllocatableIndex(counter, hd.exposedIndexes
|
|
43642
|
-
labels.push(batch.labels?.[made] ?? labelFlag ??
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 =
|
|
49544
|
+
x12 = rotl2(x12 ^ x00, 16);
|
|
48422
49545
|
x08 = x08 + x12 | 0;
|
|
48423
|
-
x04 =
|
|
49546
|
+
x04 = rotl2(x04 ^ x08, 12);
|
|
48424
49547
|
x00 = x00 + x04 | 0;
|
|
48425
|
-
x12 =
|
|
49548
|
+
x12 = rotl2(x12 ^ x00, 8);
|
|
48426
49549
|
x08 = x08 + x12 | 0;
|
|
48427
|
-
x04 =
|
|
49550
|
+
x04 = rotl2(x04 ^ x08, 7);
|
|
48428
49551
|
x01 = x01 + x05 | 0;
|
|
48429
|
-
x13 =
|
|
49552
|
+
x13 = rotl2(x13 ^ x01, 16);
|
|
48430
49553
|
x09 = x09 + x13 | 0;
|
|
48431
|
-
x05 =
|
|
49554
|
+
x05 = rotl2(x05 ^ x09, 12);
|
|
48432
49555
|
x01 = x01 + x05 | 0;
|
|
48433
|
-
x13 =
|
|
49556
|
+
x13 = rotl2(x13 ^ x01, 8);
|
|
48434
49557
|
x09 = x09 + x13 | 0;
|
|
48435
|
-
x05 =
|
|
49558
|
+
x05 = rotl2(x05 ^ x09, 7);
|
|
48436
49559
|
x02 = x02 + x06 | 0;
|
|
48437
|
-
x14 =
|
|
49560
|
+
x14 = rotl2(x14 ^ x02, 16);
|
|
48438
49561
|
x10 = x10 + x14 | 0;
|
|
48439
|
-
x06 =
|
|
49562
|
+
x06 = rotl2(x06 ^ x10, 12);
|
|
48440
49563
|
x02 = x02 + x06 | 0;
|
|
48441
|
-
x14 =
|
|
49564
|
+
x14 = rotl2(x14 ^ x02, 8);
|
|
48442
49565
|
x10 = x10 + x14 | 0;
|
|
48443
|
-
x06 =
|
|
49566
|
+
x06 = rotl2(x06 ^ x10, 7);
|
|
48444
49567
|
x03 = x03 + x07 | 0;
|
|
48445
|
-
x15 =
|
|
49568
|
+
x15 = rotl2(x15 ^ x03, 16);
|
|
48446
49569
|
x11 = x11 + x15 | 0;
|
|
48447
|
-
x07 =
|
|
49570
|
+
x07 = rotl2(x07 ^ x11, 12);
|
|
48448
49571
|
x03 = x03 + x07 | 0;
|
|
48449
|
-
x15 =
|
|
49572
|
+
x15 = rotl2(x15 ^ x03, 8);
|
|
48450
49573
|
x11 = x11 + x15 | 0;
|
|
48451
|
-
x07 =
|
|
49574
|
+
x07 = rotl2(x07 ^ x11, 7);
|
|
48452
49575
|
x00 = x00 + x05 | 0;
|
|
48453
|
-
x15 =
|
|
49576
|
+
x15 = rotl2(x15 ^ x00, 16);
|
|
48454
49577
|
x10 = x10 + x15 | 0;
|
|
48455
|
-
x05 =
|
|
49578
|
+
x05 = rotl2(x05 ^ x10, 12);
|
|
48456
49579
|
x00 = x00 + x05 | 0;
|
|
48457
|
-
x15 =
|
|
49580
|
+
x15 = rotl2(x15 ^ x00, 8);
|
|
48458
49581
|
x10 = x10 + x15 | 0;
|
|
48459
|
-
x05 =
|
|
49582
|
+
x05 = rotl2(x05 ^ x10, 7);
|
|
48460
49583
|
x01 = x01 + x06 | 0;
|
|
48461
|
-
x12 =
|
|
49584
|
+
x12 = rotl2(x12 ^ x01, 16);
|
|
48462
49585
|
x11 = x11 + x12 | 0;
|
|
48463
|
-
x06 =
|
|
49586
|
+
x06 = rotl2(x06 ^ x11, 12);
|
|
48464
49587
|
x01 = x01 + x06 | 0;
|
|
48465
|
-
x12 =
|
|
49588
|
+
x12 = rotl2(x12 ^ x01, 8);
|
|
48466
49589
|
x11 = x11 + x12 | 0;
|
|
48467
|
-
x06 =
|
|
49590
|
+
x06 = rotl2(x06 ^ x11, 7);
|
|
48468
49591
|
x02 = x02 + x07 | 0;
|
|
48469
|
-
x13 =
|
|
49592
|
+
x13 = rotl2(x13 ^ x02, 16);
|
|
48470
49593
|
x08 = x08 + x13 | 0;
|
|
48471
|
-
x07 =
|
|
49594
|
+
x07 = rotl2(x07 ^ x08, 12);
|
|
48472
49595
|
x02 = x02 + x07 | 0;
|
|
48473
|
-
x13 =
|
|
49596
|
+
x13 = rotl2(x13 ^ x02, 8);
|
|
48474
49597
|
x08 = x08 + x13 | 0;
|
|
48475
|
-
x07 =
|
|
49598
|
+
x07 = rotl2(x07 ^ x08, 7);
|
|
48476
49599
|
x03 = x03 + x04 | 0;
|
|
48477
|
-
x14 =
|
|
49600
|
+
x14 = rotl2(x14 ^ x03, 16);
|
|
48478
49601
|
x09 = x09 + x14 | 0;
|
|
48479
|
-
x04 =
|
|
49602
|
+
x04 = rotl2(x04 ^ x09, 12);
|
|
48480
49603
|
x03 = x03 + x04 | 0;
|
|
48481
|
-
x14 =
|
|
49604
|
+
x14 = rotl2(x14 ^ x03, 8);
|
|
48482
49605
|
x09 = x09 + x14 | 0;
|
|
48483
|
-
x04 =
|
|
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
|
|
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 =
|
|
50024
|
+
dh = concat4(dh1, dh2);
|
|
48902
50025
|
}
|
|
48903
50026
|
let kemContext;
|
|
48904
50027
|
if (params.senderKey === undefined) {
|
|
48905
|
-
kemContext =
|
|
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 =
|
|
50055
|
+
dh = concat4(dh1, dh2);
|
|
48933
50056
|
}
|
|
48934
50057
|
let kemContext;
|
|
48935
50058
|
if (params.senderPublicKey === undefined) {
|
|
48936
|
-
kemContext =
|
|
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
|
|
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:
|
|
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
|
-
|
|
51376
|
-
|
|
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
|
|
57857
|
+
const solanaComplete = unavailable.length === 0;
|
|
56896
57858
|
const totalLamports = [...lamports.values()].reduce((sum, value) => sum + value, 0n);
|
|
56897
|
-
if (!
|
|
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(
|
|
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:
|
|
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
|
|
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 (
|
|
60288
|
-
|
|
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);
|