@vultisig/cli 2.22.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -0
- package/dist/index.js +1488 -1346
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1687,7 +1687,7 @@ function createHasher(hashCons) {
|
|
|
1687
1687
|
hashC.create = () => hashCons();
|
|
1688
1688
|
return hashC;
|
|
1689
1689
|
}
|
|
1690
|
-
function
|
|
1690
|
+
function randomBytes2(bytesLength = 32) {
|
|
1691
1691
|
if (crypto2 && typeof crypto2.getRandomValues === "function") {
|
|
1692
1692
|
return crypto2.getRandomValues(new Uint8Array(bytesLength));
|
|
1693
1693
|
}
|
|
@@ -4467,7 +4467,7 @@ function getHash(hash) {
|
|
|
4467
4467
|
return {
|
|
4468
4468
|
hash,
|
|
4469
4469
|
hmac: (key, ...msgs) => hmac(hash, key, concatBytes(...msgs)),
|
|
4470
|
-
randomBytes:
|
|
4470
|
+
randomBytes: randomBytes2
|
|
4471
4471
|
};
|
|
4472
4472
|
}
|
|
4473
4473
|
function createCurve(curveDef, defHash) {
|
|
@@ -4700,7 +4700,7 @@ function challenge(...args) {
|
|
|
4700
4700
|
function schnorrGetPublicKey(privateKey) {
|
|
4701
4701
|
return schnorrGetExtPubKey(privateKey).bytes;
|
|
4702
4702
|
}
|
|
4703
|
-
function schnorrSign(message, privateKey, auxRand =
|
|
4703
|
+
function schnorrSign(message, privateKey, auxRand = randomBytes2(32)) {
|
|
4704
4704
|
const m = ensureBytes("message", message);
|
|
4705
4705
|
const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey);
|
|
4706
4706
|
const a = ensureBytes("auxRand", auxRand, 32);
|
|
@@ -5855,12 +5855,20 @@ function displayPortfolio(portfolio, currency, _raw = false) {
|
|
|
5855
5855
|
printResult(chalk2.cyan("| ") + chalk2.bold.green(totalDisplay) + chalk2.cyan(" |"));
|
|
5856
5856
|
printResult(chalk2.cyan("+----------------------------------------+\n"));
|
|
5857
5857
|
printResult(chalk2.bold("Chain Breakdown:\n"));
|
|
5858
|
-
const table = portfolio.chainBalances.
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5858
|
+
const table = portfolio.chainBalances.flatMap(({ chain, balance, value, tokens }) => [
|
|
5859
|
+
{
|
|
5860
|
+
Chain: chain,
|
|
5861
|
+
Amount: balance.formattedAmount,
|
|
5862
|
+
Symbol: balance.symbol,
|
|
5863
|
+
Value: value ? `${value.amount} ${value.currency.toUpperCase()}` : "N/A"
|
|
5864
|
+
},
|
|
5865
|
+
...(tokens ?? []).map((token) => ({
|
|
5866
|
+
Chain: chain,
|
|
5867
|
+
Amount: token.balance?.formattedAmount ?? "-",
|
|
5868
|
+
Symbol: token.balance?.symbol ?? token.tokenId,
|
|
5869
|
+
Value: `${token.value.amount} ${token.value.currency.toUpperCase()}`
|
|
5870
|
+
}))
|
|
5871
|
+
]);
|
|
5864
5872
|
printTable(table);
|
|
5865
5873
|
}
|
|
5866
5874
|
function displayAddresses(addresses) {
|
|
@@ -6085,6 +6093,16 @@ async function executeBalance(ctx2, options = {}) {
|
|
|
6085
6093
|
const spinner = createSpinner("Loading balances...");
|
|
6086
6094
|
const raw = options.raw ?? false;
|
|
6087
6095
|
if (options.chain) {
|
|
6096
|
+
if (options.includeTokens) {
|
|
6097
|
+
const balances = await vault.balances([options.chain], true);
|
|
6098
|
+
spinner.succeed("Balances loaded");
|
|
6099
|
+
if (isJsonOutput()) {
|
|
6100
|
+
outputJson({ chain: options.chain, balances });
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
displayBalancesTable(balances, raw);
|
|
6104
|
+
return;
|
|
6105
|
+
}
|
|
6088
6106
|
const balance = await vault.balance(options.chain);
|
|
6089
6107
|
spinner.succeed("Balance loaded");
|
|
6090
6108
|
if (isJsonOutput()) {
|
|
@@ -6115,7 +6133,6 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
6115
6133
|
}
|
|
6116
6134
|
const currencyName = fiatCurrencyNameRecord2[currency];
|
|
6117
6135
|
const spinner = createSpinner(`Loading portfolio in ${currencyName}...`);
|
|
6118
|
-
const totalValue = await vault.getTotalValue(currency);
|
|
6119
6136
|
const chains = vault.chains;
|
|
6120
6137
|
const results = await Promise.all(
|
|
6121
6138
|
chains.map(async (chain) => {
|
|
@@ -6125,12 +6142,34 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
6125
6142
|
} catch (err) {
|
|
6126
6143
|
return { failure: { chain, stage: "balance", error: conciseError(err) } };
|
|
6127
6144
|
}
|
|
6145
|
+
let values;
|
|
6128
6146
|
try {
|
|
6129
|
-
|
|
6130
|
-
return { entry: { chain, balance, value } };
|
|
6147
|
+
values = await vault.getValues(chain, currency);
|
|
6131
6148
|
} catch (err) {
|
|
6132
6149
|
return { entry: { chain, balance }, failure: { chain, stage: "value", error: conciseError(err) } };
|
|
6133
6150
|
}
|
|
6151
|
+
const { native, ...tokenValues } = values;
|
|
6152
|
+
if (!native) {
|
|
6153
|
+
try {
|
|
6154
|
+
const retried = await vault.getValue(chain, void 0, currency);
|
|
6155
|
+
return { entry: { chain, balance, value: retried, tokens: await withAmounts(tokenValues) } };
|
|
6156
|
+
} catch (err) {
|
|
6157
|
+
return {
|
|
6158
|
+
entry: { chain, balance, tokens: await withAmounts(tokenValues) },
|
|
6159
|
+
failure: { chain, stage: "value", error: conciseError(err) }
|
|
6160
|
+
};
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
return { entry: { chain, balance, value: native, tokens: await withAmounts(tokenValues) } };
|
|
6164
|
+
async function withAmounts(byTokenId) {
|
|
6165
|
+
return Promise.all(
|
|
6166
|
+
Object.entries(byTokenId).map(async ([tokenId, value]) => ({
|
|
6167
|
+
tokenId,
|
|
6168
|
+
value,
|
|
6169
|
+
balance: await vault.balance(chain, tokenId).catch(() => void 0)
|
|
6170
|
+
}))
|
|
6171
|
+
);
|
|
6172
|
+
}
|
|
6134
6173
|
})
|
|
6135
6174
|
);
|
|
6136
6175
|
const chainBalances = [];
|
|
@@ -6147,6 +6186,11 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
6147
6186
|
["Check your internet connection", "Retry in a few moments"]
|
|
6148
6187
|
);
|
|
6149
6188
|
}
|
|
6189
|
+
const total = chainBalances.reduce(
|
|
6190
|
+
(sum, entry) => sum + parseFloat(entry.value?.amount ?? "0") + (entry.tokens ?? []).reduce((tokenSum, token) => tokenSum + parseFloat(token.value.amount), 0),
|
|
6191
|
+
0
|
|
6192
|
+
);
|
|
6193
|
+
const totalValue = { amount: total.toFixed(2), currency, lastUpdated: Date.now() };
|
|
6150
6194
|
const portfolio = { totalValue, chainBalances };
|
|
6151
6195
|
spinner.succeed("Portfolio loaded");
|
|
6152
6196
|
if (isJsonOutput()) {
|
|
@@ -6324,8 +6368,8 @@ import {
|
|
|
6324
6368
|
writeFileSync,
|
|
6325
6369
|
writeSync
|
|
6326
6370
|
} from "node:fs";
|
|
6327
|
-
import { homedir } from "node:os";
|
|
6328
6371
|
import { dirname, join } from "node:path";
|
|
6372
|
+
import { getVultisigConfigDir } from "@vultisig/client-shared";
|
|
6329
6373
|
|
|
6330
6374
|
// src/agent/agentErrors.ts
|
|
6331
6375
|
import { VaultError as VaultError2, VaultErrorCode as VaultErrorCode2, VaultImportError as VaultImportError2, VaultImportErrorCode as VaultImportErrorCode2 } from "@vultisig/sdk";
|
|
@@ -6560,8 +6604,7 @@ function nowMs() {
|
|
|
6560
6604
|
function journalPath() {
|
|
6561
6605
|
const explicit = process.env.VULTISIG_BROADCAST_JOURNAL_PATH;
|
|
6562
6606
|
if (explicit && explicit.trim()) return explicit;
|
|
6563
|
-
|
|
6564
|
-
return join(dir, "broadcasts.jsonl");
|
|
6607
|
+
return join(getVultisigConfigDir(), "broadcasts.jsonl");
|
|
6565
6608
|
}
|
|
6566
6609
|
function normalize(v, canonicalizeEmptyCalldata = false) {
|
|
6567
6610
|
const normalized = (v ?? "").trim().toLowerCase();
|
|
@@ -7289,7 +7332,7 @@ async function discoverTokens(ctx2, chain) {
|
|
|
7289
7332
|
});
|
|
7290
7333
|
}
|
|
7291
7334
|
const allTokens = vault.getTokens(chain);
|
|
7292
|
-
spinner.succeed(`
|
|
7335
|
+
spinner.succeed(`Now tracking ${newTokens.length} new token(s) on ${chain}`);
|
|
7293
7336
|
if (isJsonOutput()) {
|
|
7294
7337
|
outputJson({
|
|
7295
7338
|
chain,
|
|
@@ -7306,6 +7349,9 @@ async function discoverTokens(ctx2, chain) {
|
|
|
7306
7349
|
printResult(` ${d.ticker} (${d.contractAddress})`);
|
|
7307
7350
|
}
|
|
7308
7351
|
info(chalk4.gray(`
|
|
7352
|
+
Saved to this vault \u2014 tracked tokens count toward portfolio and balance --tokens.`));
|
|
7353
|
+
info(chalk4.gray(`Use --remove <tokenId> to stop tracking one.`));
|
|
7354
|
+
info(chalk4.gray(`
|
|
7309
7355
|
${allTokens.length} total token(s) tracked on ${chain}`));
|
|
7310
7356
|
}
|
|
7311
7357
|
async function listTokens(ctx2, chain) {
|
|
@@ -7378,6 +7424,51 @@ async function executeSend(ctx2, params) {
|
|
|
7378
7424
|
}
|
|
7379
7425
|
return sendTransaction(vault, params);
|
|
7380
7426
|
}
|
|
7427
|
+
async function previewDryRun(vault, params, dryResult, to, destinationTag) {
|
|
7428
|
+
const balance = await vault.balance(params.chain, params.tokenId);
|
|
7429
|
+
const hasInsufficientBalance = parseFloat(dryResult.total) > parseFloat(balance.formattedAmount);
|
|
7430
|
+
const isTokenSend = balance.tokenId !== void 0;
|
|
7431
|
+
const feeBalance = isTokenSend ? await vault.balance(params.chain).catch(() => void 0) : void 0;
|
|
7432
|
+
const warnings = [];
|
|
7433
|
+
if (hasInsufficientBalance) {
|
|
7434
|
+
warnings.push(`Insufficient balance: you have ${balance.formattedAmount} ${balance.symbol}`);
|
|
7435
|
+
}
|
|
7436
|
+
if (isTokenSend && feeBalance === void 0) {
|
|
7437
|
+
warnings.push(`Could not check your ${dryResult.feeSymbol} balance for the network fee`);
|
|
7438
|
+
} else if (feeBalance && parseFloat(dryResult.fee) > parseFloat(feeBalance.formattedAmount)) {
|
|
7439
|
+
warnings.push(
|
|
7440
|
+
`Insufficient ${dryResult.feeSymbol} for the network fee: you have ${feeBalance.formattedAmount} ${dryResult.feeSymbol}, the fee is ${dryResult.fee}`
|
|
7441
|
+
);
|
|
7442
|
+
}
|
|
7443
|
+
const result = {
|
|
7444
|
+
dryRun: true,
|
|
7445
|
+
chain: params.chain,
|
|
7446
|
+
to,
|
|
7447
|
+
amount: params.amount,
|
|
7448
|
+
symbol: balance.symbol,
|
|
7449
|
+
fee: dryResult.fee,
|
|
7450
|
+
feeSymbol: dryResult.feeSymbol,
|
|
7451
|
+
total: dryResult.total,
|
|
7452
|
+
balance: balance.formattedAmount,
|
|
7453
|
+
destinationTag,
|
|
7454
|
+
...warnings.length > 0 ? { warning: warnings.join(". ") } : {}
|
|
7455
|
+
};
|
|
7456
|
+
if (isJsonOutput()) {
|
|
7457
|
+
outputJson(result);
|
|
7458
|
+
return result;
|
|
7459
|
+
}
|
|
7460
|
+
info(`
|
|
7461
|
+
Dry-run preview:`);
|
|
7462
|
+
info(` Chain: ${result.chain}`);
|
|
7463
|
+
info(` To: ${result.to}`);
|
|
7464
|
+
info(` Amount: ${result.amount} ${result.symbol}`);
|
|
7465
|
+
if (result.destinationTag !== void 0) info(` Destination tag: ${result.destinationTag}`);
|
|
7466
|
+
info(` Fee: ${result.fee} ${result.feeSymbol}`);
|
|
7467
|
+
info(` Total: ${result.total} ${result.symbol}`);
|
|
7468
|
+
info(` Balance: ${result.balance} ${result.symbol}`);
|
|
7469
|
+
if (result.warning) warn(` Warning: ${result.warning}`);
|
|
7470
|
+
return result;
|
|
7471
|
+
}
|
|
7381
7472
|
async function sendTransaction(vault, params) {
|
|
7382
7473
|
if (!params.dryRun && !params.yes && isNonInteractive()) {
|
|
7383
7474
|
throw new ConfirmationRequiredError(
|
|
@@ -7406,37 +7497,7 @@ async function sendTransaction(vault, params) {
|
|
|
7406
7497
|
prepareSpinner.succeed("Transaction prepared");
|
|
7407
7498
|
if (!dryResult.dryRun) throw new Error("unreachable");
|
|
7408
7499
|
if (params.dryRun) {
|
|
7409
|
-
|
|
7410
|
-
const hasInsufficientBalance = parseFloat(dryResult.total) > parseFloat(balance2.formattedAmount);
|
|
7411
|
-
const result = {
|
|
7412
|
-
dryRun: true,
|
|
7413
|
-
chain: params.chain,
|
|
7414
|
-
to,
|
|
7415
|
-
amount: params.amount,
|
|
7416
|
-
symbol: balance2.symbol,
|
|
7417
|
-
fee: dryResult.fee,
|
|
7418
|
-
total: dryResult.total,
|
|
7419
|
-
balance: balance2.formattedAmount,
|
|
7420
|
-
destinationTag
|
|
7421
|
-
};
|
|
7422
|
-
if (hasInsufficientBalance) {
|
|
7423
|
-
result.warning = `Insufficient balance: you have ${balance2.formattedAmount} ${balance2.symbol}`;
|
|
7424
|
-
}
|
|
7425
|
-
if (isJsonOutput()) {
|
|
7426
|
-
outputJson(result);
|
|
7427
|
-
} else {
|
|
7428
|
-
info(`
|
|
7429
|
-
Dry-run preview:`);
|
|
7430
|
-
info(` Chain: ${result.chain}`);
|
|
7431
|
-
info(` To: ${result.to}`);
|
|
7432
|
-
info(` Amount: ${result.amount} ${result.symbol}`);
|
|
7433
|
-
if (result.destinationTag !== void 0) info(` Destination tag: ${result.destinationTag}`);
|
|
7434
|
-
info(` Fee: ${result.fee} ${result.symbol}`);
|
|
7435
|
-
info(` Total: ${result.total} ${result.symbol}`);
|
|
7436
|
-
info(` Balance: ${result.balance} ${result.symbol}`);
|
|
7437
|
-
if (result.warning) warn(` Warning: ${result.warning}`);
|
|
7438
|
-
}
|
|
7439
|
-
return result;
|
|
7500
|
+
return previewDryRun(vault, params, dryResult, to, destinationTag);
|
|
7440
7501
|
}
|
|
7441
7502
|
let gas;
|
|
7442
7503
|
try {
|
|
@@ -8890,1337 +8951,1485 @@ Please specify more characters of the vault ID.`
|
|
|
8890
8951
|
throw new Error(`Vault not found: "${idOrName}"`);
|
|
8891
8952
|
}
|
|
8892
8953
|
|
|
8893
|
-
//
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
const
|
|
8898
|
-
|
|
8899
|
-
if (isJsonOutput()) {
|
|
8900
|
-
outputJson({ swapChains: [...chains] });
|
|
8901
|
-
return chains;
|
|
8902
|
-
}
|
|
8903
|
-
displaySwapChains(chains);
|
|
8904
|
-
return chains;
|
|
8905
|
-
}
|
|
8906
|
-
async function executeSwapQuote(ctx2, options) {
|
|
8907
|
-
const vault = await ctx2.ensureActiveVault();
|
|
8908
|
-
const isMax = options.amount === "max";
|
|
8909
|
-
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
8910
|
-
throw new Error("Invalid amount");
|
|
8911
|
-
}
|
|
8912
|
-
const spinner = createSpinner("Getting swap quote...");
|
|
8913
|
-
const result = await vault.swap({
|
|
8914
|
-
fromChain: options.fromChain,
|
|
8915
|
-
fromSymbol: options.fromToken || "",
|
|
8916
|
-
toChain: options.toChain,
|
|
8917
|
-
toSymbol: options.toToken || "",
|
|
8918
|
-
amount: isMax ? "max" : String(options.amount),
|
|
8919
|
-
dryRun: true
|
|
8920
|
-
});
|
|
8921
|
-
if (!result.dryRun) throw new Error("unreachable");
|
|
8922
|
-
spinner.succeed("Quote received");
|
|
8923
|
-
const quote = result.quote;
|
|
8924
|
-
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(options.amount);
|
|
8925
|
-
if (isJsonOutput()) {
|
|
8926
|
-
outputJson({
|
|
8927
|
-
fromChain: options.fromChain,
|
|
8928
|
-
toChain: options.toChain,
|
|
8929
|
-
amount: isMax ? "max" : options.amount,
|
|
8930
|
-
isMax,
|
|
8931
|
-
quote
|
|
8932
|
-
});
|
|
8933
|
-
return quote;
|
|
8934
|
-
}
|
|
8935
|
-
const feeBalance = await vault.balance(options.fromChain);
|
|
8936
|
-
const discountTier = await vault.getDiscountTier();
|
|
8937
|
-
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
8938
|
-
fromDecimals: quote.fromCoin.decimals,
|
|
8939
|
-
toDecimals: quote.toCoin.decimals,
|
|
8940
|
-
feeDecimals: feeBalance.decimals,
|
|
8941
|
-
feeSymbol: feeBalance.symbol,
|
|
8942
|
-
discountTier
|
|
8943
|
-
});
|
|
8944
|
-
info('\nTo execute this swap, use the "swap" command');
|
|
8945
|
-
return quote;
|
|
8946
|
-
}
|
|
8947
|
-
function validateSwapAmount(amount) {
|
|
8948
|
-
if (amount === "max") return;
|
|
8949
|
-
if (isNaN(amount) || amount <= 0) throw new Error("Invalid amount");
|
|
8950
|
-
}
|
|
8951
|
-
function getSwapAmountString(amount) {
|
|
8952
|
-
return amount === "max" ? "max" : String(amount);
|
|
8953
|
-
}
|
|
8954
|
-
function toSwapRequest(options, amount, dryRun) {
|
|
8955
|
-
return {
|
|
8956
|
-
fromChain: options.fromChain,
|
|
8957
|
-
fromSymbol: options.fromToken || "",
|
|
8958
|
-
toChain: options.toChain,
|
|
8959
|
-
toSymbol: options.toToken || "",
|
|
8960
|
-
amount,
|
|
8961
|
-
...options.slippage !== void 0 && { slippageTolerance: options.slippage },
|
|
8962
|
-
...dryRun && { dryRun: true }
|
|
8963
|
-
};
|
|
8964
|
-
}
|
|
8965
|
-
function toDryRunResult(options, quote, fromAmountRaw) {
|
|
8966
|
-
const result = {
|
|
8967
|
-
dryRun: true,
|
|
8968
|
-
fromChain: String(options.fromChain),
|
|
8969
|
-
fromToken: quote.fromCoin.ticker,
|
|
8970
|
-
toChain: String(options.toChain),
|
|
8971
|
-
toToken: quote.toCoin.ticker,
|
|
8972
|
-
inputAmount: fromAmountRaw,
|
|
8973
|
-
...options.amount === "max" && { isMax: true },
|
|
8974
|
-
estimatedOutput: formatBigintAmount(quote.estimatedOutput, quote.toCoin.decimals),
|
|
8975
|
-
provider: quote.provider
|
|
8976
|
-
};
|
|
8977
|
-
if (quote.estimatedOutputFiat != null) result.estimatedOutputFiat = parseFloat(quote.estimatedOutputFiat.toFixed(2));
|
|
8978
|
-
if (quote.requiresApproval) result.requiresApproval = true;
|
|
8979
|
-
if (quote.warnings?.length) result.warnings = [...quote.warnings];
|
|
8980
|
-
return result;
|
|
8954
|
+
// ../../node_modules/viem/_esm/accounts/utils/publicKeyToAddress.js
|
|
8955
|
+
init_getAddress();
|
|
8956
|
+
init_keccak256();
|
|
8957
|
+
function publicKeyToAddress(publicKey) {
|
|
8958
|
+
const address = keccak256(`0x${publicKey.substring(4)}`).substring(26);
|
|
8959
|
+
return checksumAddress(`0x${address}`);
|
|
8981
8960
|
}
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8961
|
+
|
|
8962
|
+
// ../../node_modules/viem/_esm/utils/signature/recoverPublicKey.js
|
|
8963
|
+
init_isHex();
|
|
8964
|
+
init_size();
|
|
8965
|
+
init_fromHex();
|
|
8966
|
+
init_toHex();
|
|
8967
|
+
async function recoverPublicKey({ hash, signature }) {
|
|
8968
|
+
const hashHex = isHex(hash) ? hash : toHex(hash);
|
|
8969
|
+
const { secp256k1: secp256k12 } = await Promise.resolve().then(() => (init_secp256k1(), secp256k1_exports));
|
|
8970
|
+
const signature_ = (() => {
|
|
8971
|
+
if (typeof signature === "object" && "r" in signature && "s" in signature) {
|
|
8972
|
+
const { r, s, v, yParity } = signature;
|
|
8973
|
+
const yParityOrV2 = Number(yParity ?? v);
|
|
8974
|
+
const recoveryBit2 = toRecoveryBit(yParityOrV2);
|
|
8975
|
+
return new secp256k12.Signature(hexToBigInt(r), hexToBigInt(s)).addRecoveryBit(recoveryBit2);
|
|
8976
|
+
}
|
|
8977
|
+
const signatureHex = isHex(signature) ? signature : toHex(signature);
|
|
8978
|
+
if (size(signatureHex) !== 65)
|
|
8979
|
+
throw new Error("invalid signature length");
|
|
8980
|
+
const yParityOrV = hexToNumber(`0x${signatureHex.slice(130)}`);
|
|
8981
|
+
const recoveryBit = toRecoveryBit(yParityOrV);
|
|
8982
|
+
return secp256k12.Signature.fromCompact(signatureHex.substring(2, 130)).addRecoveryBit(recoveryBit);
|
|
8983
|
+
})();
|
|
8984
|
+
const publicKey = signature_.recoverPublicKey(hashHex.substring(2)).toHex(false);
|
|
8985
|
+
return `0x${publicKey}`;
|
|
8991
8986
|
}
|
|
8992
|
-
function
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8987
|
+
function toRecoveryBit(yParityOrV) {
|
|
8988
|
+
if (yParityOrV === 0 || yParityOrV === 1)
|
|
8989
|
+
return yParityOrV;
|
|
8990
|
+
if (yParityOrV === 27)
|
|
8991
|
+
return 0;
|
|
8992
|
+
if (yParityOrV === 28)
|
|
8993
|
+
return 1;
|
|
8994
|
+
throw new Error("Invalid yParityOrV value");
|
|
8997
8995
|
}
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
}
|
|
9003
|
-
const confirmed = await confirmSwap();
|
|
9004
|
-
if (!confirmed) {
|
|
9005
|
-
throw new ConfirmationRequiredError("Swap declined at the confirmation prompt");
|
|
9006
|
-
}
|
|
8996
|
+
|
|
8997
|
+
// ../../node_modules/viem/_esm/utils/signature/recoverAddress.js
|
|
8998
|
+
async function recoverAddress({ hash, signature }) {
|
|
8999
|
+
return publicKeyToAddress(await recoverPublicKey({ hash, signature }));
|
|
9007
9000
|
}
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9001
|
+
|
|
9002
|
+
// ../../node_modules/viem/_esm/utils/signature/hashTypedData.js
|
|
9003
|
+
init_encodeAbiParameters();
|
|
9004
|
+
init_concat();
|
|
9005
|
+
init_toHex();
|
|
9006
|
+
init_keccak256();
|
|
9007
|
+
|
|
9008
|
+
// ../../node_modules/viem/_esm/utils/typedData.js
|
|
9009
|
+
init_abi();
|
|
9010
|
+
init_address();
|
|
9011
|
+
|
|
9012
|
+
// ../../node_modules/viem/_esm/errors/typedData.js
|
|
9013
|
+
init_stringify();
|
|
9014
|
+
init_base();
|
|
9015
|
+
var InvalidDomainError = class extends BaseError {
|
|
9016
|
+
constructor({ domain }) {
|
|
9017
|
+
super(`Invalid domain "${stringify(domain)}".`, {
|
|
9018
|
+
metaMessages: ["Must be a valid EIP-712 domain."]
|
|
9019
|
+
});
|
|
9014
9020
|
}
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
if (options.dryRun) {
|
|
9023
|
-
const result = toDryRunResult(options, quote, fromAmountRaw);
|
|
9024
|
-
if (isJsonOutput()) {
|
|
9025
|
-
outputJson(result);
|
|
9026
|
-
} else {
|
|
9027
|
-
displayDryRunResult(result);
|
|
9028
|
-
}
|
|
9029
|
-
return result;
|
|
9021
|
+
};
|
|
9022
|
+
var InvalidPrimaryTypeError = class extends BaseError {
|
|
9023
|
+
constructor({ primaryType, types }) {
|
|
9024
|
+
super(`Invalid primary type \`${primaryType}\` must be one of \`${JSON.stringify(Object.keys(types))}\`.`, {
|
|
9025
|
+
docsPath: "/api/glossary/Errors#typeddatainvalidprimarytypeerror",
|
|
9026
|
+
metaMessages: ["Check that the primary type is a key in `types`."]
|
|
9027
|
+
});
|
|
9030
9028
|
}
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
feeDecimals: feeBalance.decimals,
|
|
9038
|
-
feeSymbol: feeBalance.symbol,
|
|
9039
|
-
discountTier
|
|
9029
|
+
};
|
|
9030
|
+
var InvalidStructTypeError = class extends BaseError {
|
|
9031
|
+
constructor({ type }) {
|
|
9032
|
+
super(`Struct type "${type}" is invalid.`, {
|
|
9033
|
+
metaMessages: ["Struct type must not be a Solidity type."],
|
|
9034
|
+
name: "InvalidStructTypeError"
|
|
9040
9035
|
});
|
|
9041
9036
|
}
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
|
|
9049
|
-
|
|
9050
|
-
|
|
9051
|
-
|
|
9052
|
-
|
|
9053
|
-
|
|
9054
|
-
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
|
|
9058
|
-
|
|
9059
|
-
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9037
|
+
};
|
|
9038
|
+
|
|
9039
|
+
// ../../node_modules/viem/_esm/utils/typedData.js
|
|
9040
|
+
init_isAddress();
|
|
9041
|
+
init_size();
|
|
9042
|
+
init_toHex();
|
|
9043
|
+
init_regex();
|
|
9044
|
+
function validateTypedData(parameters) {
|
|
9045
|
+
const { domain, message, primaryType, types } = parameters;
|
|
9046
|
+
const validateData = (struct, data) => {
|
|
9047
|
+
for (const param of struct) {
|
|
9048
|
+
const { name, type } = param;
|
|
9049
|
+
const value = data[name];
|
|
9050
|
+
const integerMatch = type.match(integerRegex);
|
|
9051
|
+
if (integerMatch && (typeof value === "number" || typeof value === "bigint")) {
|
|
9052
|
+
const [_type, base, size_] = integerMatch;
|
|
9053
|
+
numberToHex(value, {
|
|
9054
|
+
signed: base === "int",
|
|
9055
|
+
size: Number.parseInt(size_, 10) / 8
|
|
9056
|
+
});
|
|
9057
|
+
}
|
|
9058
|
+
if (type === "address" && typeof value === "string" && !isAddress(value))
|
|
9059
|
+
throw new InvalidAddressError2({ address: value });
|
|
9060
|
+
const bytesMatch = type.match(bytesRegex);
|
|
9061
|
+
if (bytesMatch) {
|
|
9062
|
+
const [_type, size_] = bytesMatch;
|
|
9063
|
+
if (size_ && size(value) !== Number.parseInt(size_, 10))
|
|
9064
|
+
throw new BytesSizeMismatchError({
|
|
9065
|
+
expectedSize: Number.parseInt(size_, 10),
|
|
9066
|
+
givenSize: size(value)
|
|
9067
|
+
});
|
|
9068
|
+
}
|
|
9069
|
+
const struct2 = types[type];
|
|
9070
|
+
if (struct2) {
|
|
9071
|
+
validateReference(type);
|
|
9072
|
+
validateData(struct2, value);
|
|
9073
|
+
}
|
|
9073
9074
|
}
|
|
9074
|
-
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9075
|
+
};
|
|
9076
|
+
if (types.EIP712Domain && domain) {
|
|
9077
|
+
if (typeof domain !== "object")
|
|
9078
|
+
throw new InvalidDomainError({ domain });
|
|
9079
|
+
validateData(types.EIP712Domain, domain);
|
|
9080
|
+
}
|
|
9081
|
+
if (primaryType !== "EIP712Domain") {
|
|
9082
|
+
if (types[primaryType])
|
|
9083
|
+
validateData(types[primaryType], message);
|
|
9084
|
+
else
|
|
9085
|
+
throw new InvalidPrimaryTypeError({ primaryType, types });
|
|
9080
9086
|
}
|
|
9081
9087
|
}
|
|
9088
|
+
function getTypesForEIP712Domain({ domain }) {
|
|
9089
|
+
return [
|
|
9090
|
+
typeof domain?.name === "string" && { name: "name", type: "string" },
|
|
9091
|
+
domain?.version && { name: "version", type: "string" },
|
|
9092
|
+
(typeof domain?.chainId === "number" || typeof domain?.chainId === "bigint") && {
|
|
9093
|
+
name: "chainId",
|
|
9094
|
+
type: "uint256"
|
|
9095
|
+
},
|
|
9096
|
+
domain?.verifyingContract && {
|
|
9097
|
+
name: "verifyingContract",
|
|
9098
|
+
type: "address"
|
|
9099
|
+
},
|
|
9100
|
+
domain?.salt && { name: "salt", type: "bytes32" }
|
|
9101
|
+
].filter(Boolean);
|
|
9102
|
+
}
|
|
9103
|
+
function validateReference(type) {
|
|
9104
|
+
if (type === "address" || type === "bool" || type === "string" || type.startsWith("bytes") || type.startsWith("uint") || type.startsWith("int"))
|
|
9105
|
+
throw new InvalidStructTypeError({ type });
|
|
9106
|
+
}
|
|
9082
9107
|
|
|
9083
|
-
//
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9108
|
+
// ../../node_modules/viem/_esm/utils/signature/hashTypedData.js
|
|
9109
|
+
function hashTypedData(parameters) {
|
|
9110
|
+
const { domain = {}, message, primaryType } = parameters;
|
|
9111
|
+
const types = {
|
|
9112
|
+
EIP712Domain: getTypesForEIP712Domain({ domain }),
|
|
9113
|
+
...parameters.types
|
|
9114
|
+
};
|
|
9115
|
+
validateTypedData({
|
|
9116
|
+
domain,
|
|
9117
|
+
message,
|
|
9118
|
+
primaryType,
|
|
9119
|
+
types
|
|
9120
|
+
});
|
|
9121
|
+
const parts = ["0x1901"];
|
|
9122
|
+
if (domain)
|
|
9123
|
+
parts.push(hashDomain({
|
|
9124
|
+
domain,
|
|
9125
|
+
types
|
|
9126
|
+
}));
|
|
9127
|
+
if (primaryType !== "EIP712Domain")
|
|
9128
|
+
parts.push(hashStruct({
|
|
9129
|
+
data: message,
|
|
9130
|
+
primaryType,
|
|
9131
|
+
types
|
|
9132
|
+
}));
|
|
9133
|
+
return keccak256(concat(parts));
|
|
9134
|
+
}
|
|
9135
|
+
function hashDomain({ domain, types }) {
|
|
9136
|
+
return hashStruct({
|
|
9137
|
+
data: domain,
|
|
9138
|
+
primaryType: "EIP712Domain",
|
|
9139
|
+
types
|
|
9140
|
+
});
|
|
9141
|
+
}
|
|
9142
|
+
function hashStruct({ data, primaryType, types }) {
|
|
9143
|
+
const encoded = encodeData({
|
|
9144
|
+
data,
|
|
9145
|
+
primaryType,
|
|
9146
|
+
types
|
|
9147
|
+
});
|
|
9148
|
+
return keccak256(encoded);
|
|
9149
|
+
}
|
|
9150
|
+
function encodeData({ data, primaryType, types }) {
|
|
9151
|
+
const encodedTypes = [{ type: "bytes32" }];
|
|
9152
|
+
const encodedValues = [hashType({ primaryType, types })];
|
|
9153
|
+
for (const field of types[primaryType]) {
|
|
9154
|
+
const [type, value] = encodeField({
|
|
9155
|
+
types,
|
|
9156
|
+
name: field.name,
|
|
9157
|
+
type: field.type,
|
|
9158
|
+
value: data[field.name]
|
|
9159
|
+
});
|
|
9160
|
+
encodedTypes.push(type);
|
|
9161
|
+
encodedValues.push(value);
|
|
9103
9162
|
}
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
const
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9163
|
+
return encodeAbiParameters(encodedTypes, encodedValues);
|
|
9164
|
+
}
|
|
9165
|
+
function hashType({ primaryType, types }) {
|
|
9166
|
+
const encodedHashType = toHex(encodeType({ primaryType, types }));
|
|
9167
|
+
return keccak256(encodedHashType);
|
|
9168
|
+
}
|
|
9169
|
+
function encodeType({ primaryType, types }) {
|
|
9170
|
+
let result = "";
|
|
9171
|
+
const unsortedDeps = findTypeDependencies({ primaryType, types });
|
|
9172
|
+
unsortedDeps.delete(primaryType);
|
|
9173
|
+
const deps = [primaryType, ...Array.from(unsortedDeps).sort()];
|
|
9174
|
+
for (const type of deps) {
|
|
9175
|
+
result += `${type}(${types[type].map(({ name, type: t }) => `${t} ${name}`).join(",")})`;
|
|
9111
9176
|
}
|
|
9112
|
-
|
|
9113
|
-
+ Currency preference set to ${currency.toUpperCase()} (${currencyName})`);
|
|
9114
|
-
return currency;
|
|
9177
|
+
return result;
|
|
9115
9178
|
}
|
|
9116
|
-
|
|
9117
|
-
const
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
if (isJsonOutput()) {
|
|
9122
|
-
outputJson({ server: status });
|
|
9123
|
-
return status;
|
|
9124
|
-
}
|
|
9125
|
-
printResult(chalk6.cyan("\nServer Status:\n"));
|
|
9126
|
-
printResult(chalk6.bold("Fast Vault Server:"));
|
|
9127
|
-
printResult(` Online: ${status.fastVault.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9128
|
-
if (status.fastVault.latency) {
|
|
9129
|
-
printResult(` Latency: ${status.fastVault.latency}ms`);
|
|
9130
|
-
}
|
|
9131
|
-
printResult(chalk6.bold("\nMessage Relay:"));
|
|
9132
|
-
printResult(` Online: ${status.messageRelay.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9133
|
-
if (status.messageRelay.latency) {
|
|
9134
|
-
printResult(` Latency: ${status.messageRelay.latency}ms`);
|
|
9135
|
-
}
|
|
9136
|
-
return status;
|
|
9137
|
-
} catch (err) {
|
|
9138
|
-
spinner.fail("Failed to check server status");
|
|
9139
|
-
error(`
|
|
9140
|
-
x ${err.message}`);
|
|
9141
|
-
throw err;
|
|
9179
|
+
function findTypeDependencies({ primaryType: primaryType_, types }, results = /* @__PURE__ */ new Set()) {
|
|
9180
|
+
const match = primaryType_.match(/^\w*/u);
|
|
9181
|
+
const primaryType = match?.[0];
|
|
9182
|
+
if (results.has(primaryType) || types[primaryType] === void 0) {
|
|
9183
|
+
return results;
|
|
9142
9184
|
}
|
|
9185
|
+
results.add(primaryType);
|
|
9186
|
+
for (const field of types[primaryType]) {
|
|
9187
|
+
findTypeDependencies({ primaryType: field.type, types }, results);
|
|
9188
|
+
}
|
|
9189
|
+
return results;
|
|
9143
9190
|
}
|
|
9144
|
-
|
|
9145
|
-
if (
|
|
9146
|
-
|
|
9147
|
-
|
|
9148
|
-
|
|
9149
|
-
|
|
9150
|
-
if (!chain) {
|
|
9151
|
-
prompts.push({
|
|
9152
|
-
type: "select",
|
|
9153
|
-
name: "chain",
|
|
9154
|
-
message: "Select chain:",
|
|
9155
|
-
choices: Object.values(Chain9)
|
|
9156
|
-
});
|
|
9157
|
-
}
|
|
9158
|
-
if (!address) {
|
|
9159
|
-
prompts.push({
|
|
9160
|
-
type: "input",
|
|
9161
|
-
name: "address",
|
|
9162
|
-
message: "Enter address:",
|
|
9163
|
-
validate: (input) => input.trim() !== "" || "Address is required"
|
|
9164
|
-
});
|
|
9165
|
-
}
|
|
9166
|
-
if (!name) {
|
|
9167
|
-
prompts.push({
|
|
9168
|
-
type: "input",
|
|
9169
|
-
name: "name",
|
|
9170
|
-
message: "Enter name/label:",
|
|
9171
|
-
validate: (input) => input.trim() !== "" || "Name is required"
|
|
9172
|
-
});
|
|
9173
|
-
}
|
|
9174
|
-
if (prompts.length > 0) {
|
|
9175
|
-
const answers = await prompt(prompts);
|
|
9176
|
-
chain = chain || answers.chain;
|
|
9177
|
-
address = address || answers.address?.trim();
|
|
9178
|
-
name = name || answers.name?.trim();
|
|
9179
|
-
}
|
|
9180
|
-
const spinner2 = createSpinner("Adding address to address book...");
|
|
9181
|
-
const entry = {
|
|
9182
|
-
chain,
|
|
9183
|
-
address,
|
|
9184
|
-
name,
|
|
9185
|
-
source: "saved",
|
|
9186
|
-
dateAdded: Date.now()
|
|
9187
|
-
};
|
|
9188
|
-
await ctx2.sdk.addAddressBookEntry([entry]);
|
|
9189
|
-
spinner2.succeed("Address added");
|
|
9190
|
-
if (isJsonOutput()) {
|
|
9191
|
-
outputJson({ added: entry });
|
|
9192
|
-
return [];
|
|
9193
|
-
}
|
|
9194
|
-
success(`
|
|
9195
|
-
+ Added ${name} (${chain}: ${address})`);
|
|
9196
|
-
return [];
|
|
9197
|
-
}
|
|
9198
|
-
if (options.remove) {
|
|
9199
|
-
const spinner2 = createSpinner("Removing address from address book...");
|
|
9200
|
-
await ctx2.sdk.removeAddressBookEntry([{ address: options.remove, chain: options.chain }]);
|
|
9201
|
-
spinner2.succeed("Address removed");
|
|
9202
|
-
if (isJsonOutput()) {
|
|
9203
|
-
outputJson({ removed: { address: options.remove, chain: options.chain } });
|
|
9204
|
-
return [];
|
|
9205
|
-
}
|
|
9206
|
-
success(`
|
|
9207
|
-
+ Removed ${options.remove}`);
|
|
9208
|
-
return [];
|
|
9209
|
-
}
|
|
9210
|
-
const spinner = createSpinner("Loading address book...");
|
|
9211
|
-
const addressBook = await ctx2.sdk.getAddressBook(options.chain);
|
|
9212
|
-
spinner.succeed("Address book loaded");
|
|
9213
|
-
const allEntries = [...addressBook.saved, ...addressBook.vaults];
|
|
9214
|
-
if (isJsonOutput()) {
|
|
9215
|
-
outputJson({ addressBook: allEntries, chain: options.chain });
|
|
9216
|
-
return allEntries;
|
|
9191
|
+
function encodeField({ types, name, type, value }) {
|
|
9192
|
+
if (types[type] !== void 0) {
|
|
9193
|
+
return [
|
|
9194
|
+
{ type: "bytes32" },
|
|
9195
|
+
keccak256(encodeData({ data: value, primaryType: type, types }))
|
|
9196
|
+
];
|
|
9217
9197
|
}
|
|
9218
|
-
if (
|
|
9219
|
-
|
|
9220
|
-
|
|
9221
|
-
|
|
9222
|
-
|
|
9223
|
-
|
|
9224
|
-
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
Address: entry.address,
|
|
9230
|
-
Source: entry.source
|
|
9198
|
+
if (type === "bytes")
|
|
9199
|
+
return [{ type: "bytes32" }, keccak256(value)];
|
|
9200
|
+
if (type === "string")
|
|
9201
|
+
return [{ type: "bytes32" }, keccak256(toHex(value))];
|
|
9202
|
+
if (type.lastIndexOf("]") === type.length - 1) {
|
|
9203
|
+
const parsedType = type.slice(0, type.lastIndexOf("["));
|
|
9204
|
+
const typeValuePairs = value.map((item) => encodeField({
|
|
9205
|
+
name,
|
|
9206
|
+
type: parsedType,
|
|
9207
|
+
types,
|
|
9208
|
+
value: item
|
|
9231
9209
|
}));
|
|
9232
|
-
|
|
9233
|
-
|
|
9210
|
+
return [
|
|
9211
|
+
{ type: "bytes32" },
|
|
9212
|
+
keccak256(encodeAbiParameters(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v)))
|
|
9213
|
+
];
|
|
9234
9214
|
}
|
|
9235
|
-
return
|
|
9215
|
+
return [{ type }, value];
|
|
9236
9216
|
}
|
|
9237
9217
|
|
|
9238
|
-
//
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
}
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9252
|
-
|
|
9253
|
-
|
|
9218
|
+
// ../../node_modules/viem/_esm/errors/unit.js
|
|
9219
|
+
init_base();
|
|
9220
|
+
var InvalidDecimalNumberError = class extends BaseError {
|
|
9221
|
+
constructor({ value }) {
|
|
9222
|
+
super(`Number \`${value}\` is not a valid decimal number.`, {
|
|
9223
|
+
name: "InvalidDecimalNumberError"
|
|
9224
|
+
});
|
|
9225
|
+
}
|
|
9226
|
+
};
|
|
9227
|
+
|
|
9228
|
+
// ../../node_modules/viem/_esm/utils/unit/parseUnits.js
|
|
9229
|
+
function parseUnits(value, decimals) {
|
|
9230
|
+
if (!/^(-?)([0-9]*)\.?([0-9]*)$/.test(value))
|
|
9231
|
+
throw new InvalidDecimalNumberError({ value });
|
|
9232
|
+
let [integer, fraction = "0"] = value.split(".");
|
|
9233
|
+
const negative = integer.startsWith("-");
|
|
9234
|
+
if (negative)
|
|
9235
|
+
integer = integer.slice(1);
|
|
9236
|
+
fraction = fraction.replace(/(0+)$/, "");
|
|
9237
|
+
if (decimals === 0) {
|
|
9238
|
+
if (Math.round(Number(`.${fraction}`)) === 1)
|
|
9239
|
+
integer = `${BigInt(integer) + 1n}`;
|
|
9240
|
+
fraction = "";
|
|
9241
|
+
} else if (fraction.length > decimals) {
|
|
9242
|
+
const [left, unit, right] = [
|
|
9243
|
+
fraction.slice(0, decimals - 1),
|
|
9244
|
+
fraction.slice(decimals - 1, decimals),
|
|
9245
|
+
fraction.slice(decimals)
|
|
9246
|
+
];
|
|
9247
|
+
const rounded = Math.round(Number(`${unit}.${right}`));
|
|
9248
|
+
if (rounded > 9)
|
|
9249
|
+
fraction = `${BigInt(left) + BigInt(1)}0`.padStart(left.length + 1, "0");
|
|
9250
|
+
else
|
|
9251
|
+
fraction = `${left}${rounded}`;
|
|
9252
|
+
if (fraction.length > decimals) {
|
|
9253
|
+
fraction = fraction.slice(1);
|
|
9254
|
+
integer = `${BigInt(integer) + 1n}`;
|
|
9254
9255
|
}
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
return
|
|
9256
|
+
fraction = fraction.slice(0, decimals);
|
|
9257
|
+
} else {
|
|
9258
|
+
fraction = fraction.padEnd(decimals, "0");
|
|
9259
|
+
}
|
|
9260
|
+
return BigInt(`${negative ? "-" : ""}${integer}${fraction}`);
|
|
9260
9261
|
}
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
9268
|
-
|
|
9269
|
-
|
|
9270
|
-
outputJson({ thorAddress, balances: filtered });
|
|
9271
|
-
return;
|
|
9262
|
+
|
|
9263
|
+
// ../../node_modules/viem/_esm/index.js
|
|
9264
|
+
init_formatUnits();
|
|
9265
|
+
|
|
9266
|
+
// ../../packages/core/chain/dist/amount/toChainAmount.js
|
|
9267
|
+
var ChainAmountParseError = class extends Error {
|
|
9268
|
+
name = "ChainAmountParseError";
|
|
9269
|
+
constructor(message) {
|
|
9270
|
+
super(message);
|
|
9272
9271
|
}
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9276
|
-
|
|
9272
|
+
};
|
|
9273
|
+
var SCIENTIFIC_DECIMAL = /^([+-]?)(?:(\d+)\.?(\d*)|\.(\d+))[eE]([+-]?\d+)$/i;
|
|
9274
|
+
var MAX_SCALE_ABS = 10000n;
|
|
9275
|
+
var padFractionDigits = (frac, totalLen) => {
|
|
9276
|
+
const need = totalLen - BigInt(frac.length);
|
|
9277
|
+
if (need <= 0n) {
|
|
9278
|
+
return frac;
|
|
9277
9279
|
}
|
|
9278
|
-
|
|
9279
|
-
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
}
|
|
9287
|
-
|
|
9288
|
-
const
|
|
9289
|
-
|
|
9280
|
+
if (need <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
9281
|
+
return `${"0".repeat(Number(need))}${frac}`;
|
|
9282
|
+
}
|
|
9283
|
+
let out = frac;
|
|
9284
|
+
while (BigInt(out.length) < totalLen) {
|
|
9285
|
+
out = `0${out}`;
|
|
9286
|
+
}
|
|
9287
|
+
return out;
|
|
9288
|
+
};
|
|
9289
|
+
var expandScientificNotationToDecimalString = (s) => {
|
|
9290
|
+
const m = SCIENTIFIC_DECIMAL.exec(s.trim());
|
|
9291
|
+
if (!m) {
|
|
9292
|
+
throw new ChainAmountParseError(`Invalid amount: "${s}"`);
|
|
9293
|
+
}
|
|
9294
|
+
const signNeg = m[1] === "-";
|
|
9295
|
+
let digitStr;
|
|
9296
|
+
let fracLen;
|
|
9297
|
+
if (m[4] !== void 0) {
|
|
9298
|
+
digitStr = m[4];
|
|
9299
|
+
fracLen = digitStr.length;
|
|
9300
|
+
} else {
|
|
9301
|
+
digitStr = `${m[2] ?? ""}${m[3] ?? ""}`;
|
|
9302
|
+
fracLen = (m[3] ?? "").length;
|
|
9303
|
+
}
|
|
9304
|
+
if (!/^\d+$/.test(digitStr)) {
|
|
9305
|
+
throw new ChainAmountParseError(`Invalid amount: "${s}"`);
|
|
9306
|
+
}
|
|
9307
|
+
const expStr = m[5] ?? "";
|
|
9308
|
+
if (expStr === "" || expStr === "+" || expStr === "-") {
|
|
9309
|
+
throw new ChainAmountParseError(`Invalid amount: "${s}"`);
|
|
9310
|
+
}
|
|
9311
|
+
const allDigits = BigInt(digitStr);
|
|
9312
|
+
const exp = BigInt(expStr);
|
|
9313
|
+
const scale = exp - BigInt(fracLen);
|
|
9314
|
+
const scaleAbs = scale < 0n ? -scale : scale;
|
|
9315
|
+
if (scaleAbs > MAX_SCALE_ABS) {
|
|
9316
|
+
throw new ChainAmountParseError(`Amount exponent out of supported range: "${s}"`);
|
|
9317
|
+
}
|
|
9318
|
+
let absResult;
|
|
9319
|
+
if (scale >= 0n) {
|
|
9320
|
+
const mult = 10n ** scale;
|
|
9321
|
+
absResult = (allDigits * mult).toString();
|
|
9322
|
+
} else {
|
|
9323
|
+
const k = -scale;
|
|
9324
|
+
const divisor = 10n ** k;
|
|
9325
|
+
const intPart = allDigits / divisor;
|
|
9326
|
+
const rem = allDigits % divisor;
|
|
9327
|
+
const frac = padFractionDigits(rem.toString(), k);
|
|
9328
|
+
absResult = intPart === 0n ? `0.${frac}` : `${intPart.toString()}.${frac}`;
|
|
9329
|
+
}
|
|
9330
|
+
if (signNeg && allDigits !== 0n) {
|
|
9331
|
+
return `-${absResult}`;
|
|
9332
|
+
}
|
|
9333
|
+
return absResult;
|
|
9334
|
+
};
|
|
9335
|
+
var formatNumberAmount = (amount) => {
|
|
9336
|
+
const str2 = amount.toString();
|
|
9337
|
+
return /[eE]/.test(str2) ? expandScientificNotationToDecimalString(str2) : str2;
|
|
9338
|
+
};
|
|
9339
|
+
var truncateToDecimals = (s, decimals) => {
|
|
9340
|
+
const dotIdx = s.indexOf(".");
|
|
9341
|
+
if (dotIdx === -1)
|
|
9342
|
+
return s;
|
|
9343
|
+
if (decimals === 0)
|
|
9344
|
+
return s.slice(0, dotIdx);
|
|
9345
|
+
const fracPart = s.slice(dotIdx + 1);
|
|
9346
|
+
if (fracPart.length <= decimals)
|
|
9347
|
+
return s;
|
|
9348
|
+
return `${s.slice(0, dotIdx + 1)}${fracPart.slice(0, decimals)}`;
|
|
9349
|
+
};
|
|
9350
|
+
var toChainAmount = (amount, decimals) => {
|
|
9351
|
+
if (typeof amount === "string") {
|
|
9352
|
+
const trimmed = amount.trim();
|
|
9353
|
+
if (!trimmed) {
|
|
9354
|
+
throw new ChainAmountParseError("Amount cannot be empty");
|
|
9355
|
+
}
|
|
9356
|
+
if (/[eE]/.test(trimmed)) {
|
|
9357
|
+
const expanded = expandScientificNotationToDecimalString(trimmed);
|
|
9358
|
+
return parseUnits(truncateToDecimals(expanded, decimals), decimals);
|
|
9359
|
+
}
|
|
9360
|
+
return parseUnits(truncateToDecimals(trimmed, decimals), decimals);
|
|
9361
|
+
}
|
|
9362
|
+
return parseUnits(truncateToDecimals(formatNumberAmount(amount), decimals), decimals);
|
|
9363
|
+
};
|
|
9364
|
+
|
|
9365
|
+
// src/commands/swap.ts
|
|
9366
|
+
async function executeSwapChains(ctx2) {
|
|
9367
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9368
|
+
const spinner = createSpinner("Loading supported swap chains...");
|
|
9369
|
+
const chains = await vault.getSupportedSwapChains();
|
|
9370
|
+
spinner.succeed("Swap chains loaded");
|
|
9290
9371
|
if (isJsonOutput()) {
|
|
9291
|
-
outputJson({
|
|
9292
|
-
return;
|
|
9372
|
+
outputJson({ swapChains: [...chains] });
|
|
9373
|
+
return chains;
|
|
9293
9374
|
}
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
printTable(
|
|
9297
|
-
routes.map((r) => ({
|
|
9298
|
-
name: r.name,
|
|
9299
|
-
from: r.from,
|
|
9300
|
-
to: r.to,
|
|
9301
|
-
liquidity: r.liquidity,
|
|
9302
|
-
description: r.description
|
|
9303
|
-
}))
|
|
9304
|
-
);
|
|
9375
|
+
displaySwapChains(chains);
|
|
9376
|
+
return chains;
|
|
9305
9377
|
}
|
|
9306
|
-
async function
|
|
9378
|
+
async function executeSwapQuote(ctx2, options) {
|
|
9379
|
+
const isMax = options.amount === "max";
|
|
9380
|
+
const amount = normalizeSwapAmount(options.amount);
|
|
9307
9381
|
const vault = await ctx2.ensureActiveVault();
|
|
9308
|
-
const
|
|
9309
|
-
const
|
|
9310
|
-
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
if (isJsonOutput()) {
|
|
9315
|
-
outputJson({ thorAddress, inboundAddresses: inbound });
|
|
9316
|
-
return;
|
|
9317
|
-
}
|
|
9318
|
-
info(`THORChain address: ${thorAddress}`);
|
|
9319
|
-
printResult("Provide an L1 asset to get a chain-specific inbound address + memo.");
|
|
9320
|
-
printResult("Example: vultisig rujira deposit --asset BTC.BTC --amount 100000");
|
|
9321
|
-
printResult("");
|
|
9322
|
-
printTable(
|
|
9323
|
-
inbound.map((a) => ({
|
|
9324
|
-
chain: a.chain,
|
|
9325
|
-
address: a.address,
|
|
9326
|
-
halted: a.halted,
|
|
9327
|
-
globalTradingPaused: a.global_trading_paused,
|
|
9328
|
-
chainTradingPaused: a.chain_trading_paused
|
|
9329
|
-
}))
|
|
9330
|
-
);
|
|
9331
|
-
return;
|
|
9332
|
-
}
|
|
9333
|
-
const amount = options.amount ?? "1";
|
|
9334
|
-
const spinner = createSpinner("Preparing deposit instructions...");
|
|
9335
|
-
const prepared = await client.deposit.prepare({
|
|
9336
|
-
fromAsset: options.asset,
|
|
9382
|
+
const spinner = createSpinner("Getting swap quote...");
|
|
9383
|
+
const result = await vault.swap({
|
|
9384
|
+
fromChain: options.fromChain,
|
|
9385
|
+
fromSymbol: options.fromToken || "",
|
|
9386
|
+
toChain: options.toChain,
|
|
9387
|
+
toSymbol: options.toToken || "",
|
|
9337
9388
|
amount,
|
|
9338
|
-
|
|
9339
|
-
affiliate: options.affiliate,
|
|
9340
|
-
affiliateBps: options.affiliateBps
|
|
9389
|
+
dryRun: true
|
|
9341
9390
|
});
|
|
9342
|
-
|
|
9391
|
+
if (!result.dryRun) throw new Error("unreachable");
|
|
9392
|
+
spinner.succeed("Quote received");
|
|
9393
|
+
const quote = result.quote;
|
|
9394
|
+
const semanticAmount = isMax ? amount : normalizeSwapAmount(amount, quote.fromCoin.decimals);
|
|
9395
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : semanticAmount;
|
|
9343
9396
|
if (isJsonOutput()) {
|
|
9344
|
-
outputJson({
|
|
9345
|
-
|
|
9397
|
+
outputJson({
|
|
9398
|
+
fromChain: options.fromChain,
|
|
9399
|
+
toChain: options.toChain,
|
|
9400
|
+
amount: semanticAmount,
|
|
9401
|
+
isMax,
|
|
9402
|
+
quote
|
|
9403
|
+
});
|
|
9404
|
+
return quote;
|
|
9346
9405
|
}
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9406
|
+
const feeBalance = await vault.balance(options.fromChain);
|
|
9407
|
+
const discountTier = await vault.getDiscountTier();
|
|
9408
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
9409
|
+
fromDecimals: quote.fromCoin.decimals,
|
|
9410
|
+
toDecimals: quote.toCoin.decimals,
|
|
9411
|
+
feeDecimals: feeBalance.decimals,
|
|
9412
|
+
feeSymbol: feeBalance.symbol,
|
|
9413
|
+
discountTier
|
|
9414
|
+
});
|
|
9415
|
+
info('\nTo execute this swap, use the "swap" command');
|
|
9416
|
+
return quote;
|
|
9417
|
+
}
|
|
9418
|
+
var SWAP_AMOUNT_CANONICAL_DECIMALS = 1e4;
|
|
9419
|
+
function normalizeSwapAmount(amount, decimals = SWAP_AMOUNT_CANONICAL_DECIMALS) {
|
|
9420
|
+
if (amount === "max") return amount;
|
|
9421
|
+
try {
|
|
9422
|
+
const chainAmount = toChainAmount(amount, decimals);
|
|
9423
|
+
if (chainAmount <= 0n) throw new Error("Invalid amount");
|
|
9424
|
+
return formatUnits(chainAmount, decimals);
|
|
9425
|
+
} catch {
|
|
9426
|
+
throw new Error("Invalid amount");
|
|
9356
9427
|
}
|
|
9357
9428
|
}
|
|
9358
|
-
|
|
9429
|
+
function toSwapRequest(options, amount, dryRun) {
|
|
9430
|
+
return {
|
|
9431
|
+
fromChain: options.fromChain,
|
|
9432
|
+
fromSymbol: options.fromToken || "",
|
|
9433
|
+
toChain: options.toChain,
|
|
9434
|
+
toSymbol: options.toToken || "",
|
|
9435
|
+
amount,
|
|
9436
|
+
...options.slippage !== void 0 && { slippageTolerance: options.slippage },
|
|
9437
|
+
...dryRun && { dryRun: true }
|
|
9438
|
+
};
|
|
9439
|
+
}
|
|
9440
|
+
function toDryRunResult(options, quote, fromAmountRaw) {
|
|
9441
|
+
const result = {
|
|
9442
|
+
dryRun: true,
|
|
9443
|
+
fromChain: String(options.fromChain),
|
|
9444
|
+
fromToken: quote.fromCoin.ticker,
|
|
9445
|
+
toChain: String(options.toChain),
|
|
9446
|
+
toToken: quote.toCoin.ticker,
|
|
9447
|
+
inputAmount: fromAmountRaw,
|
|
9448
|
+
...options.amount === "max" && { isMax: true },
|
|
9449
|
+
estimatedOutput: formatBigintAmount(quote.estimatedOutput, quote.toCoin.decimals),
|
|
9450
|
+
provider: quote.provider
|
|
9451
|
+
};
|
|
9452
|
+
if (quote.estimatedOutputFiat != null) result.estimatedOutputFiat = parseFloat(quote.estimatedOutputFiat.toFixed(2));
|
|
9453
|
+
if (quote.requiresApproval) result.requiresApproval = true;
|
|
9454
|
+
if (quote.warnings?.length) result.warnings = [...quote.warnings];
|
|
9455
|
+
return result;
|
|
9456
|
+
}
|
|
9457
|
+
function displayDryRunResult(result) {
|
|
9458
|
+
info(`
|
|
9459
|
+
Dry-run preview:`);
|
|
9460
|
+
info(` From: ${result.inputAmount} ${result.fromToken} (${result.fromChain})`);
|
|
9461
|
+
info(` To: ${result.estimatedOutput} ${result.toToken} (${result.toChain})`);
|
|
9462
|
+
info(` Provider: ${result.provider}`);
|
|
9463
|
+
if (result.estimatedOutputFiat != null) info(` Est. value (USD): $${result.estimatedOutputFiat}`);
|
|
9464
|
+
if (result.requiresApproval) info(` Requires approval: yes`);
|
|
9465
|
+
if (result.warnings?.length) result.warnings.forEach((w) => warn(` Warning: ${w}`));
|
|
9466
|
+
}
|
|
9467
|
+
function refuseSwapWhenNonInteractive() {
|
|
9468
|
+
throw new ConfirmationRequiredError(
|
|
9469
|
+
"Swap requires confirmation.",
|
|
9470
|
+
"Pass --yes to confirm, or --dry-run to preview without signing."
|
|
9471
|
+
);
|
|
9472
|
+
}
|
|
9473
|
+
async function confirmSwapIfNeeded(options) {
|
|
9474
|
+
if (options.yes) return;
|
|
9475
|
+
if (isNonInteractive()) {
|
|
9476
|
+
refuseSwapWhenNonInteractive();
|
|
9477
|
+
}
|
|
9478
|
+
const confirmed = await confirmSwap();
|
|
9479
|
+
if (!confirmed) {
|
|
9480
|
+
throw new ConfirmationRequiredError("Swap declined at the confirmation prompt");
|
|
9481
|
+
}
|
|
9482
|
+
}
|
|
9483
|
+
async function executeSwap(ctx2, options) {
|
|
9484
|
+
const amountStr = normalizeSwapAmount(options.amount);
|
|
9359
9485
|
const vault = await ctx2.ensureActiveVault();
|
|
9360
9486
|
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9361
|
-
|
|
9362
|
-
"Swap requires confirmation.",
|
|
9363
|
-
"Pass --yes to confirm, or --dry-run to preview."
|
|
9364
|
-
);
|
|
9487
|
+
refuseSwapWhenNonInteractive();
|
|
9365
9488
|
}
|
|
9366
|
-
const
|
|
9367
|
-
const
|
|
9368
|
-
|
|
9369
|
-
const quote = await client.swap.getQuote({
|
|
9370
|
-
fromAsset: options.fromAsset,
|
|
9371
|
-
toAsset: options.toAsset,
|
|
9372
|
-
amount: options.amount,
|
|
9373
|
-
destination,
|
|
9374
|
-
slippageBps: options.slippageBps
|
|
9375
|
-
});
|
|
9489
|
+
const quoteSpinner = createSpinner("Getting swap quote...");
|
|
9490
|
+
const dryResult = await vault.swap(toSwapRequest(options, amountStr, true));
|
|
9491
|
+
if (!dryResult.dryRun) throw new Error("unreachable");
|
|
9376
9492
|
quoteSpinner.succeed("Quote received");
|
|
9493
|
+
const quote = dryResult.quote;
|
|
9494
|
+
const semanticAmount = options.amount === "max" ? amountStr : normalizeSwapAmount(amountStr, quote.fromCoin.decimals);
|
|
9495
|
+
const fromAmountRaw = options.amount === "max" ? formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals) : semanticAmount;
|
|
9496
|
+
const fromAmountDisplay = options.amount === "max" ? `${fromAmountRaw} (max)` : fromAmountRaw;
|
|
9377
9497
|
if (options.dryRun) {
|
|
9498
|
+
const result = toDryRunResult(options, quote, fromAmountRaw);
|
|
9378
9499
|
if (isJsonOutput()) {
|
|
9379
|
-
outputJson(
|
|
9500
|
+
outputJson(result);
|
|
9380
9501
|
} else {
|
|
9381
|
-
|
|
9382
|
-
printResult(` From: ${options.fromAsset}`);
|
|
9383
|
-
printResult(` To: ${options.toAsset}`);
|
|
9384
|
-
printResult(` Amount (in): ${options.amount}`);
|
|
9385
|
-
printResult(` Expected out:${quote.expectedOutput}`);
|
|
9386
|
-
printResult(` Min out: ${quote.minimumOutput}`);
|
|
9387
|
-
printResult(` Contract: ${quote.contractAddress}`);
|
|
9502
|
+
displayDryRunResult(result);
|
|
9388
9503
|
}
|
|
9389
|
-
return;
|
|
9504
|
+
return result;
|
|
9390
9505
|
}
|
|
9506
|
+
const feeBalance = await vault.balance(options.fromChain);
|
|
9507
|
+
const discountTier = await vault.getDiscountTier();
|
|
9391
9508
|
if (!isJsonOutput()) {
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
if (quote.warning) {
|
|
9400
|
-
warn(quote.warning);
|
|
9401
|
-
}
|
|
9402
|
-
}
|
|
9403
|
-
if (!options.yes) {
|
|
9404
|
-
warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
|
|
9405
|
-
throw new ConfirmationRequiredError(
|
|
9406
|
-
"Swap requires confirmation.",
|
|
9407
|
-
"Pass --yes to confirm, or --dry-run to preview."
|
|
9408
|
-
);
|
|
9509
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
9510
|
+
fromDecimals: quote.fromCoin.decimals,
|
|
9511
|
+
toDecimals: quote.toCoin.decimals,
|
|
9512
|
+
feeDecimals: feeBalance.decimals,
|
|
9513
|
+
feeSymbol: feeBalance.symbol,
|
|
9514
|
+
discountTier
|
|
9515
|
+
});
|
|
9409
9516
|
}
|
|
9517
|
+
await confirmSwapIfNeeded(options);
|
|
9410
9518
|
await ensureVaultUnlocked(vault, options.password);
|
|
9411
|
-
const
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
}
|
|
9419
|
-
}
|
|
9420
|
-
async function executeRujiraWithdraw(ctx2, options) {
|
|
9421
|
-
const vault = await ctx2.ensureActiveVault();
|
|
9422
|
-
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9423
|
-
throw new ConfirmationRequiredError(
|
|
9424
|
-
"Withdrawal requires confirmation.",
|
|
9425
|
-
"Pass --yes to confirm, or --dry-run to preview."
|
|
9426
|
-
);
|
|
9427
|
-
}
|
|
9428
|
-
const client = await createRujiraClient(ctx2, options);
|
|
9429
|
-
const prepSpinner = createSpinner("Preparing withdrawal (MsgDeposit)...");
|
|
9430
|
-
const prepared = await client.withdraw.prepare({
|
|
9431
|
-
asset: options.asset,
|
|
9432
|
-
amount: options.amount,
|
|
9433
|
-
l1Address: options.l1Address,
|
|
9434
|
-
maxFeeBps: options.maxFeeBps
|
|
9519
|
+
const intent = buildSwapBroadcastIntent(vault, {
|
|
9520
|
+
fromChain: options.fromChain,
|
|
9521
|
+
toChain: options.toChain,
|
|
9522
|
+
fromToken: options.fromToken,
|
|
9523
|
+
toToken: options.toToken,
|
|
9524
|
+
amount: fromAmountRaw,
|
|
9525
|
+
isMax: options.amount === "max"
|
|
9435
9526
|
});
|
|
9436
|
-
|
|
9437
|
-
|
|
9527
|
+
let signSpinner;
|
|
9528
|
+
try {
|
|
9529
|
+
const broadcast = await guardedBroadcast(intent, options.force ?? false, async () => {
|
|
9530
|
+
signSpinner = createSpinner("Signing swap transaction...");
|
|
9531
|
+
vault.on("signingProgress", ({ step }) => {
|
|
9532
|
+
if (signSpinner) signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
9533
|
+
});
|
|
9534
|
+
const result = await vault.swap(toSwapRequest(options, semanticAmount));
|
|
9535
|
+
if (result.dryRun) throw new Error("unreachable");
|
|
9536
|
+
return result;
|
|
9537
|
+
});
|
|
9538
|
+
signSpinner?.succeed(`Swap broadcast: ${broadcast.txHash}`);
|
|
9438
9539
|
if (isJsonOutput()) {
|
|
9439
|
-
outputJson({
|
|
9540
|
+
outputJson({
|
|
9541
|
+
txHash: broadcast.txHash,
|
|
9542
|
+
fromChain: options.fromChain,
|
|
9543
|
+
toChain: options.toChain,
|
|
9544
|
+
quote
|
|
9545
|
+
});
|
|
9440
9546
|
} else {
|
|
9441
|
-
|
|
9442
|
-
printResult(` Asset: ${prepared.asset}`);
|
|
9443
|
-
printResult(` Amount: ${prepared.amount}`);
|
|
9444
|
-
printResult(` Destination: ${prepared.destination}`);
|
|
9445
|
-
printResult(` Memo: ${prepared.memo}`);
|
|
9446
|
-
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
9547
|
+
displaySwapResult(options.fromChain, options.toChain, broadcast.txHash, quote, quote.toCoin.decimals);
|
|
9447
9548
|
}
|
|
9448
|
-
return;
|
|
9549
|
+
return { txHash: broadcast.txHash, quote };
|
|
9550
|
+
} catch (err) {
|
|
9551
|
+
signSpinner?.stop();
|
|
9552
|
+
throw err;
|
|
9553
|
+
} finally {
|
|
9554
|
+
vault.removeAllListeners("signingProgress");
|
|
9449
9555
|
}
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9556
|
+
}
|
|
9557
|
+
|
|
9558
|
+
// src/commands/settings.ts
|
|
9559
|
+
import { Chain as Chain9, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
9560
|
+
import chalk6 from "chalk";
|
|
9561
|
+
async function executeCurrency(ctx2, newCurrency) {
|
|
9562
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9563
|
+
if (!newCurrency) {
|
|
9564
|
+
const currentCurrency = vault.currency;
|
|
9565
|
+
const currencyName2 = fiatCurrencyNameRecord3[currentCurrency];
|
|
9566
|
+
printResult(chalk6.cyan("\nCurrent Currency Preference:"));
|
|
9567
|
+
printResult(` ${chalk6.green(currentCurrency.toUpperCase())} - ${currencyName2}`);
|
|
9568
|
+
info(chalk6.gray(`
|
|
9569
|
+
Supported currencies: ${fiatCurrencies2.join(", ")}`));
|
|
9570
|
+
info(chalk6.gray('Use "vultisig currency <code>" to change'));
|
|
9571
|
+
return currentCurrency;
|
|
9457
9572
|
}
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
);
|
|
9573
|
+
const currency = newCurrency.toLowerCase();
|
|
9574
|
+
if (!fiatCurrencies2.includes(currency)) {
|
|
9575
|
+
error(`x Invalid currency: ${newCurrency}`);
|
|
9576
|
+
warn(`Supported currencies: ${fiatCurrencies2.join(", ")}`);
|
|
9577
|
+
throw new Error("Invalid currency");
|
|
9464
9578
|
}
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9579
|
+
const spinner = createSpinner("Updating currency preference...");
|
|
9580
|
+
await vault.setCurrency(currency);
|
|
9581
|
+
spinner.succeed("Currency updated");
|
|
9582
|
+
const currencyName = fiatCurrencyNameRecord3[currency];
|
|
9469
9583
|
if (isJsonOutput()) {
|
|
9470
|
-
outputJson({
|
|
9471
|
-
|
|
9472
|
-
printResult(`Tx Hash: ${result.txHash}`);
|
|
9584
|
+
outputJson({ currency, name: currencyName, updated: true });
|
|
9585
|
+
return currency;
|
|
9473
9586
|
}
|
|
9587
|
+
success(`
|
|
9588
|
+
+ Currency preference set to ${currency.toUpperCase()} (${currencyName})`);
|
|
9589
|
+
return currency;
|
|
9474
9590
|
}
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
|
|
9487
|
-
|
|
9488
|
-
{
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
platinum: chalk7.hex("#E5E4E2"),
|
|
9503
|
-
diamond: chalk7.hex("#B9F2FF"),
|
|
9504
|
-
ultimate: chalk7.hex("#FF00FF")
|
|
9505
|
-
};
|
|
9506
|
-
return colors[tier] || chalk7.white;
|
|
9591
|
+
async function executeServer(ctx2) {
|
|
9592
|
+
const spinner = createSpinner("Checking server status...");
|
|
9593
|
+
try {
|
|
9594
|
+
const status = await ctx2.sdk.getServerStatus();
|
|
9595
|
+
spinner.succeed("Server status retrieved");
|
|
9596
|
+
if (isJsonOutput()) {
|
|
9597
|
+
outputJson({ server: status });
|
|
9598
|
+
return status;
|
|
9599
|
+
}
|
|
9600
|
+
printResult(chalk6.cyan("\nServer Status:\n"));
|
|
9601
|
+
printResult(chalk6.bold("Fast Vault Server:"));
|
|
9602
|
+
printResult(` Online: ${status.fastVault.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9603
|
+
if (status.fastVault.latency) {
|
|
9604
|
+
printResult(` Latency: ${status.fastVault.latency}ms`);
|
|
9605
|
+
}
|
|
9606
|
+
printResult(chalk6.bold("\nMessage Relay:"));
|
|
9607
|
+
printResult(` Online: ${status.messageRelay.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9608
|
+
if (status.messageRelay.latency) {
|
|
9609
|
+
printResult(` Latency: ${status.messageRelay.latency}ms`);
|
|
9610
|
+
}
|
|
9611
|
+
return status;
|
|
9612
|
+
} catch (err) {
|
|
9613
|
+
spinner.fail("Failed to check server status");
|
|
9614
|
+
error(`
|
|
9615
|
+
x ${err.message}`);
|
|
9616
|
+
throw err;
|
|
9617
|
+
}
|
|
9507
9618
|
}
|
|
9508
|
-
function
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9619
|
+
async function executeAddressBook(ctx2, options = {}) {
|
|
9620
|
+
if (options.add) {
|
|
9621
|
+
let chain = options.chain;
|
|
9622
|
+
let address = options.address;
|
|
9623
|
+
let name = options.name;
|
|
9624
|
+
const prompts = [];
|
|
9625
|
+
if (!chain) {
|
|
9626
|
+
prompts.push({
|
|
9627
|
+
type: "select",
|
|
9628
|
+
name: "chain",
|
|
9629
|
+
message: "Select chain:",
|
|
9630
|
+
choices: Object.values(Chain9)
|
|
9631
|
+
});
|
|
9632
|
+
}
|
|
9633
|
+
if (!address) {
|
|
9634
|
+
prompts.push({
|
|
9635
|
+
type: "input",
|
|
9636
|
+
name: "address",
|
|
9637
|
+
message: "Enter address:",
|
|
9638
|
+
validate: (input) => input.trim() !== "" || "Address is required"
|
|
9639
|
+
});
|
|
9640
|
+
}
|
|
9641
|
+
if (!name) {
|
|
9642
|
+
prompts.push({
|
|
9643
|
+
type: "input",
|
|
9644
|
+
name: "name",
|
|
9645
|
+
message: "Enter name/label:",
|
|
9646
|
+
validate: (input) => input.trim() !== "" || "Name is required"
|
|
9647
|
+
});
|
|
9648
|
+
}
|
|
9649
|
+
if (prompts.length > 0) {
|
|
9650
|
+
const answers = await prompt(prompts);
|
|
9651
|
+
chain = chain || answers.chain;
|
|
9652
|
+
address = address || answers.address?.trim();
|
|
9653
|
+
name = name || answers.name?.trim();
|
|
9654
|
+
}
|
|
9655
|
+
const spinner2 = createSpinner("Adding address to address book...");
|
|
9656
|
+
const entry = {
|
|
9657
|
+
chain,
|
|
9658
|
+
address,
|
|
9659
|
+
name,
|
|
9660
|
+
source: "saved",
|
|
9661
|
+
dateAdded: Date.now()
|
|
9662
|
+
};
|
|
9663
|
+
await ctx2.sdk.addAddressBookEntry([entry]);
|
|
9664
|
+
spinner2.succeed("Address added");
|
|
9665
|
+
if (isJsonOutput()) {
|
|
9666
|
+
outputJson({ added: entry });
|
|
9667
|
+
return [];
|
|
9668
|
+
}
|
|
9669
|
+
success(`
|
|
9670
|
+
+ Added ${name} (${chain}: ${address})`);
|
|
9671
|
+
return [];
|
|
9513
9672
|
}
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9673
|
+
if (options.remove) {
|
|
9674
|
+
const spinner2 = createSpinner("Removing address from address book...");
|
|
9675
|
+
await ctx2.sdk.removeAddressBookEntry([{ address: options.remove, chain: options.chain }]);
|
|
9676
|
+
spinner2.succeed("Address removed");
|
|
9677
|
+
if (isJsonOutput()) {
|
|
9678
|
+
outputJson({ removed: { address: options.remove, chain: options.chain } });
|
|
9679
|
+
return [];
|
|
9680
|
+
}
|
|
9681
|
+
success(`
|
|
9682
|
+
+ Removed ${options.remove}`);
|
|
9683
|
+
return [];
|
|
9518
9684
|
}
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
const
|
|
9523
|
-
const spinner = createSpinner(options.refresh ? "Refreshing discount tier..." : "Loading discount tier...");
|
|
9524
|
-
const tierResult = options.refresh ? await vault.updateDiscountTier() : await vault.getDiscountTier();
|
|
9525
|
-
const tier = tierResult || "none";
|
|
9526
|
-
const config = TIER_CONFIG[tier];
|
|
9527
|
-
const nextTier = getNextTier(tier);
|
|
9528
|
-
const tierInfo = {
|
|
9529
|
-
tier,
|
|
9530
|
-
feeBps: config.bps,
|
|
9531
|
-
discountBps: config.discount,
|
|
9532
|
-
nextTier
|
|
9533
|
-
};
|
|
9534
|
-
spinner.succeed("Discount tier loaded");
|
|
9685
|
+
const spinner = createSpinner("Loading address book...");
|
|
9686
|
+
const addressBook = await ctx2.sdk.getAddressBook(options.chain);
|
|
9687
|
+
spinner.succeed("Address book loaded");
|
|
9688
|
+
const allEntries = [...addressBook.saved, ...addressBook.vaults];
|
|
9535
9689
|
if (isJsonOutput()) {
|
|
9536
|
-
outputJson({
|
|
9537
|
-
|
|
9538
|
-
feeBps: tierInfo.feeBps,
|
|
9539
|
-
discountBps: tierInfo.discountBps,
|
|
9540
|
-
nextTier: tierInfo.nextTier
|
|
9541
|
-
});
|
|
9542
|
-
return tierInfo;
|
|
9690
|
+
outputJson({ addressBook: allEntries, chain: options.chain });
|
|
9691
|
+
return allEntries;
|
|
9543
9692
|
}
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
}
|
|
9547
|
-
|
|
9548
|
-
const tierColor = getTierColor(tierInfo.tier);
|
|
9549
|
-
printResult(chalk7.cyan("\n+----------------------------------------+"));
|
|
9550
|
-
printResult(chalk7.cyan("| VULT Discount Tier |"));
|
|
9551
|
-
printResult(chalk7.cyan("+----------------------------------------+\n"));
|
|
9552
|
-
const tierDisplay = tierInfo.tier === "none" ? chalk7.gray("No Tier") : tierColor(tierInfo.tier.charAt(0).toUpperCase() + tierInfo.tier.slice(1));
|
|
9553
|
-
printResult(` Current Tier: ${tierDisplay}`);
|
|
9554
|
-
if (tierInfo.tier === "none") {
|
|
9555
|
-
printResult(` Swap Fee: ${chalk7.gray("50 bps (0.50%)")}`);
|
|
9556
|
-
printResult(` Discount: ${chalk7.gray("None")}`);
|
|
9693
|
+
if (allEntries.length === 0) {
|
|
9694
|
+
warn(`
|
|
9695
|
+
No addresses in address book${options.chain ? ` for ${options.chain}` : ""}`);
|
|
9696
|
+
info(chalk6.gray("\nUse --add to add an address to the address book"));
|
|
9557
9697
|
} else {
|
|
9558
|
-
printResult(
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
);
|
|
9567
|
-
|
|
9568
|
-
|
|
9698
|
+
printResult(chalk6.cyan(`
|
|
9699
|
+
Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
9700
|
+
`));
|
|
9701
|
+
const table = allEntries.map((entry) => ({
|
|
9702
|
+
Name: entry.name,
|
|
9703
|
+
Chain: entry.chain,
|
|
9704
|
+
Address: entry.address,
|
|
9705
|
+
Source: entry.source
|
|
9706
|
+
}));
|
|
9707
|
+
printTable(table);
|
|
9708
|
+
info(chalk6.gray("\nUse --add to add or --remove <address> to remove an address"));
|
|
9569
9709
|
}
|
|
9570
|
-
|
|
9571
|
-
printResult("");
|
|
9710
|
+
return allEntries;
|
|
9572
9711
|
}
|
|
9573
9712
|
|
|
9574
|
-
// src/commands/
|
|
9575
|
-
import {
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9582
|
-
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
warnings = [];
|
|
9591
|
-
outcome;
|
|
9592
|
-
error;
|
|
9593
|
-
// Tracks whether the currently-latched `error` is a terminal one (e.g. the
|
|
9594
|
-
// depth cap). A terminal error may overwrite a prior non-terminal one; once a
|
|
9595
|
-
// terminal error is recorded, later frames cannot replace it. See onError.
|
|
9596
|
-
errorIsTerminal = false;
|
|
9597
|
-
constructor(session, verbose = false, autoApprove = false) {
|
|
9598
|
-
this.session = session;
|
|
9599
|
-
this.verbose = verbose;
|
|
9600
|
-
this.autoApprove = autoApprove;
|
|
9601
|
-
}
|
|
9602
|
-
/**
|
|
9603
|
-
* Whether the turn threw with a still-unacknowledged broadcast (the F1
|
|
9604
|
-
* ack-failure case). The command's catch uses this to gate the ACK_FAILED
|
|
9605
|
-
* re-tag so a later, unrelated retryable error after an already-acked
|
|
9606
|
-
* broadcast keeps its own (retryable) classification instead of exit 8.
|
|
9607
|
-
*/
|
|
9608
|
-
hasUnacknowledgedBroadcast() {
|
|
9609
|
-
return this.session.hasUnacknowledgedBroadcast();
|
|
9610
|
-
}
|
|
9611
|
-
/**
|
|
9612
|
-
* Get UI callbacks that silently collect results.
|
|
9613
|
-
* Tool progress is logged to stderr in verbose mode.
|
|
9614
|
-
*/
|
|
9615
|
-
getCallbacks() {
|
|
9616
|
-
return {
|
|
9617
|
-
onTextDelta: (_delta) => {
|
|
9618
|
-
},
|
|
9619
|
-
onToolCall: (_id, action, params) => {
|
|
9620
|
-
if (this.verbose) {
|
|
9621
|
-
const paramStr = params ? ` ${JSON.stringify(params)}` : "";
|
|
9622
|
-
process.stderr.write(`[tool] ${action}${paramStr} ...
|
|
9623
|
-
`);
|
|
9624
|
-
}
|
|
9625
|
-
},
|
|
9626
|
-
onToolResult: (id, action, success2, data, error2, code) => {
|
|
9627
|
-
this.toolCalls.push({ id, action, success: success2, data, error: error2, code });
|
|
9628
|
-
if (this.verbose) {
|
|
9629
|
-
const status = success2 ? "ok" : `error: ${error2}${code ? ` [${code}]` : ""}`;
|
|
9630
|
-
process.stderr.write(`[tool] ${action}: ${status}
|
|
9631
|
-
`);
|
|
9632
|
-
}
|
|
9633
|
-
},
|
|
9634
|
-
onAssistantMessage: (content) => {
|
|
9635
|
-
if (content) {
|
|
9636
|
-
this.responseParts.push(content);
|
|
9637
|
-
}
|
|
9638
|
-
},
|
|
9639
|
-
onBalanceSummary: (card) => {
|
|
9640
|
-
this.cards.push(card);
|
|
9641
|
-
},
|
|
9642
|
-
onTurnOutcome: (outcome) => {
|
|
9643
|
-
this.outcome = outcome;
|
|
9644
|
-
},
|
|
9645
|
-
onSuggestions: (_suggestions) => {
|
|
9646
|
-
},
|
|
9647
|
-
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
9648
|
-
const existing = this.transactions.find((t) => t.hash === txHash);
|
|
9649
|
-
if (existing) {
|
|
9650
|
-
existing.status = status;
|
|
9651
|
-
if (explorerUrl) existing.explorerUrl = explorerUrl;
|
|
9652
|
-
} else {
|
|
9653
|
-
this.transactions.push({ hash: txHash, chain, explorerUrl, status });
|
|
9654
|
-
}
|
|
9655
|
-
if (this.verbose) {
|
|
9656
|
-
process.stderr.write(`[tx] ${chain}: ${txHash} (${status})
|
|
9657
|
-
`);
|
|
9658
|
-
}
|
|
9659
|
-
},
|
|
9660
|
-
onError: (message, code) => {
|
|
9661
|
-
const isTerminal2 = isTerminalAgentErrorCode(code);
|
|
9662
|
-
if (!this.error || isTerminal2 && !this.errorIsTerminal) {
|
|
9663
|
-
this.error = { message, code };
|
|
9664
|
-
this.errorIsTerminal = isTerminal2;
|
|
9665
|
-
}
|
|
9666
|
-
process.stderr.write(`[error] ${message} [${code}]
|
|
9667
|
-
`);
|
|
9668
|
-
},
|
|
9669
|
-
onProtocolWarning: (warning) => {
|
|
9670
|
-
this.warnings.push(warning);
|
|
9671
|
-
process.stderr.write(`[warning] ${warning.message} [${warning.code}]
|
|
9672
|
-
`);
|
|
9673
|
-
},
|
|
9674
|
-
onDone: () => {
|
|
9675
|
-
},
|
|
9676
|
-
requestPassword: async () => {
|
|
9677
|
-
throw new Error("Password required but not provided. Use --password flag.");
|
|
9678
|
-
},
|
|
9679
|
-
requestConfirmation: async (message) => {
|
|
9680
|
-
if (!this.autoApprove) {
|
|
9681
|
-
process.stderr.write(`[confirm] signing requires --yes \u2014 NOT broadcasting: ${message}
|
|
9682
|
-
`);
|
|
9683
|
-
} else {
|
|
9684
|
-
process.stderr.write(`[confirm] auto-approved (--yes): ${message}
|
|
9685
|
-
`);
|
|
9686
|
-
}
|
|
9687
|
-
return this.autoApprove;
|
|
9688
|
-
}
|
|
9689
|
-
};
|
|
9690
|
-
}
|
|
9691
|
-
/**
|
|
9692
|
-
* Send a message and wait for the complete response.
|
|
9693
|
-
* All tool calls and actions are executed automatically.
|
|
9694
|
-
*/
|
|
9695
|
-
async ask(message) {
|
|
9696
|
-
this.responseParts = [];
|
|
9697
|
-
this.toolCalls = [];
|
|
9698
|
-
this.transactions = [];
|
|
9699
|
-
this.cards = [];
|
|
9700
|
-
this.warnings = [];
|
|
9701
|
-
this.outcome = void 0;
|
|
9702
|
-
this.error = void 0;
|
|
9703
|
-
this.errorIsTerminal = false;
|
|
9704
|
-
const callbacks = this.getCallbacks();
|
|
9705
|
-
await this.session.sendMessage(message, callbacks);
|
|
9706
|
-
return this.partialResult();
|
|
9707
|
-
}
|
|
9708
|
-
/**
|
|
9709
|
-
* Snapshot of everything collected so far this turn. Identical to a normal
|
|
9710
|
-
* `ask()` return, but callable from a catch block when `ask()` THREW mid-turn
|
|
9711
|
-
* — e.g. the follow-up request that reports recent_actions back to the backend
|
|
9712
|
-
* fails (timeout/5xx/auth) AFTER a tx has already broadcast and `onTxStatus`
|
|
9713
|
-
* fired. Lets the caller still surface the already-broadcast tx hash in the
|
|
9714
|
-
* error envelope instead of stranding funds the turn just moved.
|
|
9715
|
-
*/
|
|
9716
|
-
partialResult() {
|
|
9717
|
-
return {
|
|
9718
|
-
sessionId: this.session.getConversationId() || "",
|
|
9719
|
-
response: this.responseParts[this.responseParts.length - 1] || "",
|
|
9720
|
-
toolCalls: this.toolCalls,
|
|
9721
|
-
transactions: this.transactions,
|
|
9722
|
-
cards: this.cards,
|
|
9723
|
-
warnings: this.warnings,
|
|
9724
|
-
error: this.error,
|
|
9725
|
-
...this.outcome ? { outcome: this.outcome } : {}
|
|
9726
|
-
};
|
|
9727
|
-
}
|
|
9728
|
-
};
|
|
9729
|
-
|
|
9730
|
-
// src/agent/auth.ts
|
|
9731
|
-
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
9732
|
-
import { Chain as Chain10, computePersonalSignHash, formatEcdsaSignature65 } from "@vultisig/sdk";
|
|
9733
|
-
async function authenticateVault(client, vault, password, maxAttempts = 3) {
|
|
9734
|
-
const publicKey = vault.publicKeys.ecdsa;
|
|
9735
|
-
const chainCode = vault.hexChainCode;
|
|
9736
|
-
const ethAddress = await vault.address(Chain10.Ethereum);
|
|
9737
|
-
const nonce = "0x" + randomBytes2(16).toString("hex");
|
|
9738
|
-
const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
9739
|
-
const authMessage = JSON.stringify({
|
|
9740
|
-
message: "Sign into Vultisig Plugin Marketplace",
|
|
9741
|
-
nonce,
|
|
9742
|
-
expiresAt,
|
|
9743
|
-
address: ethAddress
|
|
9744
|
-
});
|
|
9745
|
-
const messageHash = computePersonalSignHash(authMessage);
|
|
9746
|
-
let lastError = null;
|
|
9747
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
9748
|
-
try {
|
|
9749
|
-
if (attempt > 1) {
|
|
9750
|
-
process.stderr.write(` Retry ${attempt}/${maxAttempts}...
|
|
9751
|
-
`);
|
|
9752
|
-
}
|
|
9753
|
-
const signature = await vault.signBytes({ data: Buffer.from(messageHash), chain: Chain10.Ethereum }, {});
|
|
9754
|
-
if (signature.recovery === void 0) {
|
|
9755
|
-
throw new Error("Agent authentication requires an ECDSA recovery id");
|
|
9756
|
-
}
|
|
9757
|
-
const sigHex = formatEcdsaSignature65(signature.signature, signature.recovery);
|
|
9758
|
-
const authResponse = await client.authenticate({
|
|
9759
|
-
public_key: publicKey,
|
|
9760
|
-
chain_code_hex: chainCode,
|
|
9761
|
-
message: authMessage,
|
|
9762
|
-
signature: sigHex
|
|
9763
|
-
});
|
|
9764
|
-
return {
|
|
9765
|
-
token: authResponse.token,
|
|
9766
|
-
expiresAt: authResponse.expires_at,
|
|
9767
|
-
// Captured + persisted by the session token cache. The backend exposes
|
|
9768
|
-
// POST /auth/refresh to exchange it for a fresh access token without a
|
|
9769
|
-
// new MPC round; wiring that exchange is a future enhancement — today
|
|
9770
|
-
// the CLI re-auths via a full MPC re-sign (authenticateVault), which is
|
|
9771
|
-
// always available and avoids depending on refresh-token rotation.
|
|
9772
|
-
refreshToken: authResponse.refresh_token
|
|
9773
|
-
};
|
|
9774
|
-
} catch (err) {
|
|
9775
|
-
lastError = err;
|
|
9776
|
-
if (attempt < maxAttempts && err.message?.includes("timeout")) {
|
|
9777
|
-
continue;
|
|
9778
|
-
}
|
|
9779
|
-
throw err;
|
|
9713
|
+
// src/commands/rujira.ts
|
|
9714
|
+
import {
|
|
9715
|
+
getRoutesSummary,
|
|
9716
|
+
listEasyRoutes,
|
|
9717
|
+
RujiraClient,
|
|
9718
|
+
VultisigRujiraProvider
|
|
9719
|
+
} from "@vultisig/rujira";
|
|
9720
|
+
async function createRujiraClient(ctx2, options = {}) {
|
|
9721
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9722
|
+
const provider = new VultisigRujiraProvider(vault);
|
|
9723
|
+
const client = new RujiraClient({
|
|
9724
|
+
signer: provider,
|
|
9725
|
+
rpcEndpoint: options.rpcEndpoint,
|
|
9726
|
+
config: {
|
|
9727
|
+
// Allow overriding rest endpoint via config (used for thornode calls)
|
|
9728
|
+
...options.restEndpoint ? { restEndpoint: options.restEndpoint } : {}
|
|
9780
9729
|
}
|
|
9781
|
-
}
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
import chalk8 from "chalk";
|
|
9787
|
-
var CLI_SUPPORTED_SURFACES = ["balance_summary", "turn_outcome"];
|
|
9788
|
-
function parseTurnOutcome(raw) {
|
|
9789
|
-
if (!raw || typeof raw !== "object") return null;
|
|
9790
|
-
const kind = raw.kind;
|
|
9791
|
-
if (kind !== "success" && kind !== "blocked" && kind !== "refusal" && kind !== "error") return null;
|
|
9792
|
-
const code = raw.code;
|
|
9793
|
-
const detail = raw.detail;
|
|
9794
|
-
return {
|
|
9795
|
-
kind,
|
|
9796
|
-
...typeof code === "string" ? { code } : {},
|
|
9797
|
-
...typeof detail === "string" ? { detail } : {}
|
|
9798
|
-
};
|
|
9730
|
+
});
|
|
9731
|
+
const spinner = createSpinner("Connecting to Rujira/THORChain...");
|
|
9732
|
+
await client.connect();
|
|
9733
|
+
spinner.succeed("Connected");
|
|
9734
|
+
return client;
|
|
9799
9735
|
}
|
|
9800
|
-
function
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9736
|
+
async function executeRujiraBalance(ctx2, options = {}) {
|
|
9737
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9738
|
+
const thorAddress = await vault.address("THORChain");
|
|
9739
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9740
|
+
const spinner = createSpinner("Loading THORChain balances...");
|
|
9741
|
+
const balances = await client.deposit.getBalances(thorAddress);
|
|
9742
|
+
spinner.succeed("Balances loaded");
|
|
9743
|
+
const filtered = options.securedOnly ? balances.filter((b) => b.denom.includes("-") || b.denom.includes("/")) : balances;
|
|
9744
|
+
if (isJsonOutput()) {
|
|
9745
|
+
outputJson({ thorAddress, balances: filtered });
|
|
9746
|
+
return;
|
|
9806
9747
|
}
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
}
|
|
9812
|
-
|
|
9813
|
-
|
|
9814
|
-
|
|
9815
|
-
|
|
9816
|
-
|
|
9817
|
-
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
if (amountUsd) token.amountUsd = amountUsd;
|
|
9821
|
-
return token;
|
|
9822
|
-
}
|
|
9823
|
-
function parseAccount(v) {
|
|
9824
|
-
if (!v || typeof v !== "object") return null;
|
|
9825
|
-
const o = v;
|
|
9826
|
-
const chainId = asString(o.chainId);
|
|
9827
|
-
if (!chainId) return null;
|
|
9828
|
-
const tokensRaw = Array.isArray(o.tokens) ? o.tokens : [];
|
|
9829
|
-
const tokens = tokensRaw.map(parseToken).filter((t) => t !== null);
|
|
9830
|
-
return { chainId, address: asString(o.address) || "\u2014", tokens };
|
|
9748
|
+
info(`THORChain address: ${thorAddress}`);
|
|
9749
|
+
if (!filtered.length) {
|
|
9750
|
+
printResult("No balances found");
|
|
9751
|
+
return;
|
|
9752
|
+
}
|
|
9753
|
+
printTable(
|
|
9754
|
+
filtered.map((b) => ({
|
|
9755
|
+
asset: b.asset,
|
|
9756
|
+
denom: b.denom,
|
|
9757
|
+
amount: b.formatted,
|
|
9758
|
+
raw: b.amount
|
|
9759
|
+
}))
|
|
9760
|
+
);
|
|
9831
9761
|
}
|
|
9832
|
-
function
|
|
9833
|
-
|
|
9834
|
-
const
|
|
9835
|
-
if (
|
|
9836
|
-
|
|
9837
|
-
|
|
9838
|
-
if (accounts.length === 0) return null;
|
|
9839
|
-
const card = { surface: "balance_summary", accounts };
|
|
9840
|
-
if (o.stale === true) {
|
|
9841
|
-
card.stale = true;
|
|
9842
|
-
if (typeof o.stale_secs === "number") card.staleSecs = o.stale_secs;
|
|
9762
|
+
async function executeRujiraRoutes() {
|
|
9763
|
+
const routes = listEasyRoutes();
|
|
9764
|
+
const summary = getRoutesSummary();
|
|
9765
|
+
if (isJsonOutput()) {
|
|
9766
|
+
outputJson({ routes, summary });
|
|
9767
|
+
return;
|
|
9843
9768
|
}
|
|
9844
|
-
|
|
9769
|
+
printResult(summary);
|
|
9770
|
+
printResult("");
|
|
9771
|
+
printTable(
|
|
9772
|
+
routes.map((r) => ({
|
|
9773
|
+
name: r.name,
|
|
9774
|
+
from: r.from,
|
|
9775
|
+
to: r.to,
|
|
9776
|
+
liquidity: r.liquidity,
|
|
9777
|
+
description: r.description
|
|
9778
|
+
}))
|
|
9779
|
+
);
|
|
9845
9780
|
}
|
|
9846
|
-
function
|
|
9847
|
-
|
|
9848
|
-
|
|
9849
|
-
|
|
9850
|
-
|
|
9851
|
-
const
|
|
9852
|
-
|
|
9853
|
-
|
|
9854
|
-
|
|
9855
|
-
|
|
9856
|
-
|
|
9857
|
-
}
|
|
9858
|
-
if (ch === '"') inString = true;
|
|
9859
|
-
else if (ch === "{") depth++;
|
|
9860
|
-
else if (ch === "}") {
|
|
9861
|
-
depth--;
|
|
9862
|
-
if (depth === 0) return i;
|
|
9781
|
+
async function executeRujiraDeposit(ctx2, options = {}) {
|
|
9782
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9783
|
+
const thorAddress = await vault.address("THORChain");
|
|
9784
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9785
|
+
if (!options.asset) {
|
|
9786
|
+
const spinner2 = createSpinner("Loading THORChain inbound addresses...");
|
|
9787
|
+
const inbound = await client.deposit.getInboundAddresses();
|
|
9788
|
+
spinner2.succeed("Inbound addresses loaded");
|
|
9789
|
+
if (isJsonOutput()) {
|
|
9790
|
+
outputJson({ thorAddress, inboundAddresses: inbound });
|
|
9791
|
+
return;
|
|
9863
9792
|
}
|
|
9793
|
+
info(`THORChain address: ${thorAddress}`);
|
|
9794
|
+
printResult("Provide an L1 asset to get a chain-specific inbound address + memo.");
|
|
9795
|
+
printResult("Example: vultisig rujira deposit --asset BTC.BTC --amount 100000");
|
|
9796
|
+
printResult("");
|
|
9797
|
+
printTable(
|
|
9798
|
+
inbound.map((a) => ({
|
|
9799
|
+
chain: a.chain,
|
|
9800
|
+
address: a.address,
|
|
9801
|
+
halted: a.halted,
|
|
9802
|
+
globalTradingPaused: a.global_trading_paused,
|
|
9803
|
+
chainTradingPaused: a.chain_trading_paused
|
|
9804
|
+
}))
|
|
9805
|
+
);
|
|
9806
|
+
return;
|
|
9807
|
+
}
|
|
9808
|
+
const amount = options.amount ?? "1";
|
|
9809
|
+
const spinner = createSpinner("Preparing deposit instructions...");
|
|
9810
|
+
const prepared = await client.deposit.prepare({
|
|
9811
|
+
fromAsset: options.asset,
|
|
9812
|
+
amount,
|
|
9813
|
+
thorAddress,
|
|
9814
|
+
affiliate: options.affiliate,
|
|
9815
|
+
affiliateBps: options.affiliateBps
|
|
9816
|
+
});
|
|
9817
|
+
spinner.succeed("Deposit prepared");
|
|
9818
|
+
if (isJsonOutput()) {
|
|
9819
|
+
outputJson({ thorAddress, deposit: prepared });
|
|
9820
|
+
return;
|
|
9821
|
+
}
|
|
9822
|
+
info(`THORChain address: ${thorAddress}`);
|
|
9823
|
+
printResult("Deposit instructions (send from L1):");
|
|
9824
|
+
printResult(` Chain: ${prepared.chain}`);
|
|
9825
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
9826
|
+
printResult(` Inbound address:${prepared.inboundAddress}`);
|
|
9827
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
9828
|
+
printResult(` Min amount: ${prepared.minimumAmount}`);
|
|
9829
|
+
if (prepared.warning) {
|
|
9830
|
+
warn(prepared.warning);
|
|
9864
9831
|
}
|
|
9865
|
-
return -1;
|
|
9866
9832
|
}
|
|
9867
|
-
function
|
|
9868
|
-
|
|
9869
|
-
if (
|
|
9870
|
-
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
9875
|
-
|
|
9876
|
-
|
|
9877
|
-
|
|
9878
|
-
|
|
9879
|
-
|
|
9833
|
+
async function executeRujiraSwap(ctx2, options) {
|
|
9834
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9835
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9836
|
+
throw new ConfirmationRequiredError(
|
|
9837
|
+
"Swap requires confirmation.",
|
|
9838
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9839
|
+
);
|
|
9840
|
+
}
|
|
9841
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9842
|
+
const destination = options.destination ?? await vault.address("THORChain");
|
|
9843
|
+
const quoteSpinner = createSpinner("Getting FIN swap quote...");
|
|
9844
|
+
const quote = await client.swap.getQuote({
|
|
9845
|
+
fromAsset: options.fromAsset,
|
|
9846
|
+
toAsset: options.toAsset,
|
|
9847
|
+
amount: options.amount,
|
|
9848
|
+
destination,
|
|
9849
|
+
slippageBps: options.slippageBps
|
|
9850
|
+
});
|
|
9851
|
+
quoteSpinner.succeed("Quote received");
|
|
9852
|
+
if (options.dryRun) {
|
|
9853
|
+
if (isJsonOutput()) {
|
|
9854
|
+
outputJson({ dryRun: true, quote });
|
|
9855
|
+
} else {
|
|
9856
|
+
printResult("FIN Swap Preview (dry-run)");
|
|
9857
|
+
printResult(` From: ${options.fromAsset}`);
|
|
9858
|
+
printResult(` To: ${options.toAsset}`);
|
|
9859
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
9860
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
9861
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
9862
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
9880
9863
|
}
|
|
9881
|
-
|
|
9882
|
-
if (!card) continue;
|
|
9883
|
-
const before = content.slice(0, i).replace(/```(?:json)?\s*$/i, "");
|
|
9884
|
-
const after = content.slice(end + 1).replace(/^\s*```/, "");
|
|
9885
|
-
const remainingText = (before + after).trim();
|
|
9886
|
-
return { card, remainingText };
|
|
9864
|
+
return;
|
|
9887
9865
|
}
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
9891
|
-
|
|
9892
|
-
|
|
9893
|
-
|
|
9894
|
-
}
|
|
9895
|
-
|
|
9896
|
-
|
|
9897
|
-
|
|
9898
|
-
if (!cleaned) return null;
|
|
9899
|
-
const n = Number(cleaned);
|
|
9900
|
-
return Number.isFinite(n) ? n : null;
|
|
9901
|
-
}
|
|
9902
|
-
function formatUsd(n) {
|
|
9903
|
-
return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
9904
|
-
}
|
|
9905
|
-
function renderBalanceSummaryCard(card) {
|
|
9906
|
-
const lines = [];
|
|
9907
|
-
const staleCue = card.stale ? chalk8.gray(` (stale${card.staleSecs ? ` ~${Math.round(card.staleSecs / 60)}m` : ""}, refreshing\u2026)`) : "";
|
|
9908
|
-
lines.push(chalk8.bold(" Balances") + staleCue);
|
|
9909
|
-
let total = 0;
|
|
9910
|
-
let sawUsd = false;
|
|
9911
|
-
for (const account of card.accounts) {
|
|
9912
|
-
lines.push(` ${chalk8.cyan(account.chainId)} ${chalk8.gray(`(${shortenAddress(account.address)})`)}`);
|
|
9913
|
-
if (account.tokens.length === 0) {
|
|
9914
|
-
lines.push(chalk8.gray(" (no balances)"));
|
|
9915
|
-
continue;
|
|
9866
|
+
if (!isJsonOutput()) {
|
|
9867
|
+
printResult("FIN Swap Preview");
|
|
9868
|
+
printResult(` From: ${options.fromAsset}`);
|
|
9869
|
+
printResult(` To: ${options.toAsset}`);
|
|
9870
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
9871
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
9872
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
9873
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
9874
|
+
if (quote.warning) {
|
|
9875
|
+
warn(quote.warning);
|
|
9916
9876
|
}
|
|
9917
|
-
|
|
9918
|
-
|
|
9919
|
-
|
|
9920
|
-
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9877
|
+
}
|
|
9878
|
+
if (!options.yes) {
|
|
9879
|
+
warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
|
|
9880
|
+
throw new ConfirmationRequiredError(
|
|
9881
|
+
"Swap requires confirmation.",
|
|
9882
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9883
|
+
);
|
|
9884
|
+
}
|
|
9885
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
9886
|
+
const execSpinner = createSpinner("Executing FIN swap...");
|
|
9887
|
+
const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
|
|
9888
|
+
execSpinner.succeed("Swap submitted");
|
|
9889
|
+
if (isJsonOutput()) {
|
|
9890
|
+
outputJson({ quote, result });
|
|
9891
|
+
} else {
|
|
9892
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
9893
|
+
}
|
|
9894
|
+
}
|
|
9895
|
+
async function executeRujiraWithdraw(ctx2, options) {
|
|
9896
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9897
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9898
|
+
throw new ConfirmationRequiredError(
|
|
9899
|
+
"Withdrawal requires confirmation.",
|
|
9900
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9901
|
+
);
|
|
9902
|
+
}
|
|
9903
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9904
|
+
const prepSpinner = createSpinner("Preparing withdrawal (MsgDeposit)...");
|
|
9905
|
+
const prepared = await client.withdraw.prepare({
|
|
9906
|
+
asset: options.asset,
|
|
9907
|
+
amount: options.amount,
|
|
9908
|
+
l1Address: options.l1Address,
|
|
9909
|
+
maxFeeBps: options.maxFeeBps
|
|
9910
|
+
});
|
|
9911
|
+
prepSpinner.succeed("Withdrawal prepared");
|
|
9912
|
+
if (options.dryRun) {
|
|
9913
|
+
if (isJsonOutput()) {
|
|
9914
|
+
outputJson({ dryRun: true, prepared });
|
|
9915
|
+
} else {
|
|
9916
|
+
printResult("Withdraw Preview (dry-run)");
|
|
9917
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
9918
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
9919
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
9920
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
9921
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
9927
9922
|
}
|
|
9923
|
+
return;
|
|
9928
9924
|
}
|
|
9929
|
-
if (
|
|
9930
|
-
|
|
9931
|
-
|
|
9925
|
+
if (!isJsonOutput()) {
|
|
9926
|
+
printResult("Withdraw Preview");
|
|
9927
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
9928
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
9929
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
9930
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
9931
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
9932
|
+
}
|
|
9933
|
+
if (!options.yes) {
|
|
9934
|
+
warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
|
|
9935
|
+
throw new ConfirmationRequiredError(
|
|
9936
|
+
"Withdrawal requires confirmation.",
|
|
9937
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9938
|
+
);
|
|
9939
|
+
}
|
|
9940
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
9941
|
+
const execSpinner = createSpinner("Broadcasting withdrawal...");
|
|
9942
|
+
const result = await client.withdraw.execute(prepared);
|
|
9943
|
+
execSpinner.succeed("Withdrawal submitted");
|
|
9944
|
+
if (isJsonOutput()) {
|
|
9945
|
+
outputJson({ prepared, result });
|
|
9946
|
+
} else {
|
|
9947
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
9932
9948
|
}
|
|
9933
|
-
return lines.join("\n");
|
|
9934
9949
|
}
|
|
9935
9950
|
|
|
9936
|
-
// src/
|
|
9937
|
-
import { randomUUID } from "node:crypto";
|
|
9938
|
-
|
|
9939
|
-
// src/agent/toolOutputSigning.ts
|
|
9940
|
-
import { getChainKind as getChainKind4 } from "@vultisig/sdk";
|
|
9941
|
-
|
|
9942
|
-
// src/agent/executor.ts
|
|
9951
|
+
// src/commands/discount.ts
|
|
9943
9952
|
import {
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
resolveChainReference,
|
|
9948
|
-
VaultError as VaultError3,
|
|
9949
|
-
VaultErrorCode as VaultErrorCode3,
|
|
9950
|
-
Vultisig as VultisigSdk
|
|
9953
|
+
baseAffiliateBps,
|
|
9954
|
+
vultDiscountTierBps,
|
|
9955
|
+
vultDiscountTierMinBalances
|
|
9951
9956
|
} from "@vultisig/sdk";
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
|
|
9957
|
+
import chalk7 from "chalk";
|
|
9958
|
+
var TIER_CONFIG = {
|
|
9959
|
+
none: { bps: baseAffiliateBps, discount: 0 },
|
|
9960
|
+
...Object.fromEntries(
|
|
9961
|
+
Object.entries(vultDiscountTierMinBalances).map(([tier, minVult]) => [
|
|
9962
|
+
tier,
|
|
9963
|
+
{
|
|
9964
|
+
bps: baseAffiliateBps - vultDiscountTierBps[tier],
|
|
9965
|
+
discount: vultDiscountTierBps[tier],
|
|
9966
|
+
minVult
|
|
9967
|
+
}
|
|
9968
|
+
])
|
|
9969
|
+
)
|
|
9970
|
+
};
|
|
9971
|
+
function getTierColor(tier) {
|
|
9972
|
+
const colors = {
|
|
9973
|
+
none: chalk7.gray,
|
|
9974
|
+
bronze: chalk7.hex("#CD7F32"),
|
|
9975
|
+
silver: chalk7.hex("#C0C0C0"),
|
|
9976
|
+
gold: chalk7.hex("#FFD700"),
|
|
9977
|
+
platinum: chalk7.hex("#E5E4E2"),
|
|
9978
|
+
diamond: chalk7.hex("#B9F2FF"),
|
|
9979
|
+
ultimate: chalk7.hex("#FF00FF")
|
|
9980
|
+
};
|
|
9981
|
+
return colors[tier] || chalk7.white;
|
|
9959
9982
|
}
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
|
|
9964
|
-
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
const
|
|
9968
|
-
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
9972
|
-
const yParityOrV2 = Number(yParity ?? v);
|
|
9973
|
-
const recoveryBit2 = toRecoveryBit(yParityOrV2);
|
|
9974
|
-
return new secp256k12.Signature(hexToBigInt(r), hexToBigInt(s)).addRecoveryBit(recoveryBit2);
|
|
9975
|
-
}
|
|
9976
|
-
const signatureHex = isHex(signature) ? signature : toHex(signature);
|
|
9977
|
-
if (size(signatureHex) !== 65)
|
|
9978
|
-
throw new Error("invalid signature length");
|
|
9979
|
-
const yParityOrV = hexToNumber(`0x${signatureHex.slice(130)}`);
|
|
9980
|
-
const recoveryBit = toRecoveryBit(yParityOrV);
|
|
9981
|
-
return secp256k12.Signature.fromCompact(signatureHex.substring(2, 130)).addRecoveryBit(recoveryBit);
|
|
9982
|
-
})();
|
|
9983
|
-
const publicKey = signature_.recoverPublicKey(hashHex.substring(2)).toHex(false);
|
|
9984
|
-
return `0x${publicKey}`;
|
|
9983
|
+
function getNextTier(currentTier) {
|
|
9984
|
+
const tierOrder = ["none", "bronze", "silver", "gold", "platinum", "diamond", "ultimate"];
|
|
9985
|
+
const currentIndex = tierOrder.indexOf(currentTier);
|
|
9986
|
+
if (currentIndex === -1 || currentIndex >= tierOrder.length - 1) {
|
|
9987
|
+
return null;
|
|
9988
|
+
}
|
|
9989
|
+
const nextTierName = tierOrder[currentIndex + 1];
|
|
9990
|
+
const config = TIER_CONFIG[nextTierName];
|
|
9991
|
+
if ("minVult" in config) {
|
|
9992
|
+
return { name: nextTierName, vultRequired: config.minVult };
|
|
9993
|
+
}
|
|
9994
|
+
return null;
|
|
9985
9995
|
}
|
|
9986
|
-
function
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9990
|
-
|
|
9991
|
-
|
|
9992
|
-
|
|
9993
|
-
|
|
9996
|
+
async function executeDiscount(ctx2, options = {}) {
|
|
9997
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9998
|
+
const spinner = createSpinner(options.refresh ? "Refreshing discount tier..." : "Loading discount tier...");
|
|
9999
|
+
const tierResult = options.refresh ? await vault.updateDiscountTier() : await vault.getDiscountTier();
|
|
10000
|
+
const tier = tierResult || "none";
|
|
10001
|
+
const config = TIER_CONFIG[tier];
|
|
10002
|
+
const nextTier = getNextTier(tier);
|
|
10003
|
+
const tierInfo = {
|
|
10004
|
+
tier,
|
|
10005
|
+
feeBps: config.bps,
|
|
10006
|
+
discountBps: config.discount,
|
|
10007
|
+
nextTier
|
|
10008
|
+
};
|
|
10009
|
+
spinner.succeed("Discount tier loaded");
|
|
10010
|
+
if (isJsonOutput()) {
|
|
10011
|
+
outputJson({
|
|
10012
|
+
tier: tierInfo.tier,
|
|
10013
|
+
feeBps: tierInfo.feeBps,
|
|
10014
|
+
discountBps: tierInfo.discountBps,
|
|
10015
|
+
nextTier: tierInfo.nextTier
|
|
10016
|
+
});
|
|
10017
|
+
return tierInfo;
|
|
10018
|
+
}
|
|
10019
|
+
displayDiscountTier(tierInfo);
|
|
10020
|
+
return tierInfo;
|
|
9994
10021
|
}
|
|
9995
|
-
|
|
9996
|
-
|
|
9997
|
-
|
|
9998
|
-
|
|
10022
|
+
function displayDiscountTier(tierInfo) {
|
|
10023
|
+
const tierColor = getTierColor(tierInfo.tier);
|
|
10024
|
+
printResult(chalk7.cyan("\n+----------------------------------------+"));
|
|
10025
|
+
printResult(chalk7.cyan("| VULT Discount Tier |"));
|
|
10026
|
+
printResult(chalk7.cyan("+----------------------------------------+\n"));
|
|
10027
|
+
const tierDisplay = tierInfo.tier === "none" ? chalk7.gray("No Tier") : tierColor(tierInfo.tier.charAt(0).toUpperCase() + tierInfo.tier.slice(1));
|
|
10028
|
+
printResult(` Current Tier: ${tierDisplay}`);
|
|
10029
|
+
if (tierInfo.tier === "none") {
|
|
10030
|
+
printResult(` Swap Fee: ${chalk7.gray("50 bps (0.50%)")}`);
|
|
10031
|
+
printResult(` Discount: ${chalk7.gray("None")}`);
|
|
10032
|
+
} else {
|
|
10033
|
+
printResult(` Swap Fee: ${chalk7.green(`${tierInfo.feeBps} bps (${(tierInfo.feeBps / 100).toFixed(2)}%)`)}`);
|
|
10034
|
+
printResult(` Discount: ${chalk7.green(`${tierInfo.discountBps} bps saved`)}`);
|
|
10035
|
+
}
|
|
10036
|
+
if (tierInfo.nextTier) {
|
|
10037
|
+
const nextTierColor = getTierColor(tierInfo.nextTier.name);
|
|
10038
|
+
printResult(chalk7.bold("\n Next Tier:"));
|
|
10039
|
+
printResult(
|
|
10040
|
+
` ${nextTierColor(tierInfo.nextTier.name.charAt(0).toUpperCase() + tierInfo.nextTier.name.slice(1))} - requires ${tierInfo.nextTier.vultRequired.toLocaleString()} VULT`
|
|
10041
|
+
);
|
|
10042
|
+
} else if (tierInfo.tier === "ultimate") {
|
|
10043
|
+
printResult(chalk7.bold("\n ") + chalk7.magenta("You have the highest tier! 0% swap fees."));
|
|
10044
|
+
}
|
|
10045
|
+
info(chalk7.gray("\n Tip: Thorguard NFT holders get +1 tier upgrade (up to gold)"));
|
|
10046
|
+
printResult("");
|
|
9999
10047
|
}
|
|
10000
10048
|
|
|
10001
|
-
//
|
|
10002
|
-
|
|
10003
|
-
init_concat();
|
|
10004
|
-
init_toHex();
|
|
10005
|
-
init_keccak256();
|
|
10049
|
+
// src/commands/auth.ts
|
|
10050
|
+
import { executeAuthLogout, executeAuthSetup, executeAuthStatus } from "@vultisig/client-shared";
|
|
10006
10051
|
|
|
10007
|
-
//
|
|
10008
|
-
|
|
10009
|
-
|
|
10052
|
+
// src/commands/agent.ts
|
|
10053
|
+
import chalk10 from "chalk";
|
|
10054
|
+
import Table from "cli-table3";
|
|
10010
10055
|
|
|
10011
|
-
//
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10018
|
-
|
|
10056
|
+
// src/agent/ask.ts
|
|
10057
|
+
var AskInterface = class {
|
|
10058
|
+
session;
|
|
10059
|
+
verbose;
|
|
10060
|
+
autoApprove;
|
|
10061
|
+
responseParts = [];
|
|
10062
|
+
toolCalls = [];
|
|
10063
|
+
transactions = [];
|
|
10064
|
+
cards = [];
|
|
10065
|
+
warnings = [];
|
|
10066
|
+
outcome;
|
|
10067
|
+
error;
|
|
10068
|
+
// Tracks whether the currently-latched `error` is a terminal one (e.g. the
|
|
10069
|
+
// depth cap). A terminal error may overwrite a prior non-terminal one; once a
|
|
10070
|
+
// terminal error is recorded, later frames cannot replace it. See onError.
|
|
10071
|
+
errorIsTerminal = false;
|
|
10072
|
+
constructor(session, verbose = false, autoApprove = false) {
|
|
10073
|
+
this.session = session;
|
|
10074
|
+
this.verbose = verbose;
|
|
10075
|
+
this.autoApprove = autoApprove;
|
|
10076
|
+
}
|
|
10077
|
+
/**
|
|
10078
|
+
* Whether the turn threw with a still-unacknowledged broadcast (the F1
|
|
10079
|
+
* ack-failure case). The command's catch uses this to gate the ACK_FAILED
|
|
10080
|
+
* re-tag so a later, unrelated retryable error after an already-acked
|
|
10081
|
+
* broadcast keeps its own (retryable) classification instead of exit 8.
|
|
10082
|
+
*/
|
|
10083
|
+
hasUnacknowledgedBroadcast() {
|
|
10084
|
+
return this.session.hasUnacknowledgedBroadcast();
|
|
10019
10085
|
}
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10086
|
+
/**
|
|
10087
|
+
* Get UI callbacks that silently collect results.
|
|
10088
|
+
* Tool progress is logged to stderr in verbose mode.
|
|
10089
|
+
*/
|
|
10090
|
+
getCallbacks() {
|
|
10091
|
+
return {
|
|
10092
|
+
onTextDelta: (_delta) => {
|
|
10093
|
+
},
|
|
10094
|
+
onToolCall: (_id, action, params) => {
|
|
10095
|
+
if (this.verbose) {
|
|
10096
|
+
const paramStr = params ? ` ${JSON.stringify(params)}` : "";
|
|
10097
|
+
process.stderr.write(`[tool] ${action}${paramStr} ...
|
|
10098
|
+
`);
|
|
10099
|
+
}
|
|
10100
|
+
},
|
|
10101
|
+
onToolResult: (id, action, success2, data, error2, code) => {
|
|
10102
|
+
this.toolCalls.push({ id, action, success: success2, data, error: error2, code });
|
|
10103
|
+
if (this.verbose) {
|
|
10104
|
+
const status = success2 ? "ok" : `error: ${error2}${code ? ` [${code}]` : ""}`;
|
|
10105
|
+
process.stderr.write(`[tool] ${action}: ${status}
|
|
10106
|
+
`);
|
|
10107
|
+
}
|
|
10108
|
+
},
|
|
10109
|
+
onAssistantMessage: (content) => {
|
|
10110
|
+
if (content) {
|
|
10111
|
+
this.responseParts.push(content);
|
|
10112
|
+
}
|
|
10113
|
+
},
|
|
10114
|
+
onBalanceSummary: (card) => {
|
|
10115
|
+
this.cards.push(card);
|
|
10116
|
+
},
|
|
10117
|
+
onTurnOutcome: (outcome) => {
|
|
10118
|
+
this.outcome = outcome;
|
|
10119
|
+
},
|
|
10120
|
+
onSuggestions: (_suggestions) => {
|
|
10121
|
+
},
|
|
10122
|
+
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
10123
|
+
const existing = this.transactions.find((t) => t.hash === txHash);
|
|
10124
|
+
if (existing) {
|
|
10125
|
+
existing.status = status;
|
|
10126
|
+
if (explorerUrl) existing.explorerUrl = explorerUrl;
|
|
10127
|
+
} else {
|
|
10128
|
+
this.transactions.push({ hash: txHash, chain, explorerUrl, status });
|
|
10129
|
+
}
|
|
10130
|
+
if (this.verbose) {
|
|
10131
|
+
process.stderr.write(`[tx] ${chain}: ${txHash} (${status})
|
|
10132
|
+
`);
|
|
10133
|
+
}
|
|
10134
|
+
},
|
|
10135
|
+
onError: (message, code) => {
|
|
10136
|
+
const isTerminal2 = isTerminalAgentErrorCode(code);
|
|
10137
|
+
if (!this.error || isTerminal2 && !this.errorIsTerminal) {
|
|
10138
|
+
this.error = { message, code };
|
|
10139
|
+
this.errorIsTerminal = isTerminal2;
|
|
10140
|
+
}
|
|
10141
|
+
process.stderr.write(`[error] ${message} [${code}]
|
|
10142
|
+
`);
|
|
10143
|
+
},
|
|
10144
|
+
onProtocolWarning: (warning) => {
|
|
10145
|
+
this.warnings.push(warning);
|
|
10146
|
+
process.stderr.write(`[warning] ${warning.message} [${warning.code}]
|
|
10147
|
+
`);
|
|
10148
|
+
},
|
|
10149
|
+
onDone: () => {
|
|
10150
|
+
},
|
|
10151
|
+
requestPassword: async () => {
|
|
10152
|
+
throw new Error("Password required but not provided. Use --password flag.");
|
|
10153
|
+
},
|
|
10154
|
+
requestConfirmation: async (message) => {
|
|
10155
|
+
if (!this.autoApprove) {
|
|
10156
|
+
process.stderr.write(`[confirm] signing requires --yes \u2014 NOT broadcasting: ${message}
|
|
10157
|
+
`);
|
|
10158
|
+
} else {
|
|
10159
|
+
process.stderr.write(`[confirm] auto-approved (--yes): ${message}
|
|
10160
|
+
`);
|
|
10161
|
+
}
|
|
10162
|
+
return this.autoApprove;
|
|
10163
|
+
}
|
|
10164
|
+
};
|
|
10027
10165
|
}
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10166
|
+
/**
|
|
10167
|
+
* Send a message and wait for the complete response.
|
|
10168
|
+
* All tool calls and actions are executed automatically.
|
|
10169
|
+
*/
|
|
10170
|
+
async ask(message) {
|
|
10171
|
+
this.responseParts = [];
|
|
10172
|
+
this.toolCalls = [];
|
|
10173
|
+
this.transactions = [];
|
|
10174
|
+
this.cards = [];
|
|
10175
|
+
this.warnings = [];
|
|
10176
|
+
this.outcome = void 0;
|
|
10177
|
+
this.error = void 0;
|
|
10178
|
+
this.errorIsTerminal = false;
|
|
10179
|
+
const callbacks = this.getCallbacks();
|
|
10180
|
+
await this.session.sendMessage(message, callbacks);
|
|
10181
|
+
return this.partialResult();
|
|
10182
|
+
}
|
|
10183
|
+
/**
|
|
10184
|
+
* Snapshot of everything collected so far this turn. Identical to a normal
|
|
10185
|
+
* `ask()` return, but callable from a catch block when `ask()` THREW mid-turn
|
|
10186
|
+
* — e.g. the follow-up request that reports recent_actions back to the backend
|
|
10187
|
+
* fails (timeout/5xx/auth) AFTER a tx has already broadcast and `onTxStatus`
|
|
10188
|
+
* fired. Lets the caller still surface the already-broadcast tx hash in the
|
|
10189
|
+
* error envelope instead of stranding funds the turn just moved.
|
|
10190
|
+
*/
|
|
10191
|
+
partialResult() {
|
|
10192
|
+
return {
|
|
10193
|
+
sessionId: this.session.getConversationId() || "",
|
|
10194
|
+
response: this.responseParts[this.responseParts.length - 1] || "",
|
|
10195
|
+
toolCalls: this.toolCalls,
|
|
10196
|
+
transactions: this.transactions,
|
|
10197
|
+
cards: this.cards,
|
|
10198
|
+
warnings: this.warnings,
|
|
10199
|
+
error: this.error,
|
|
10200
|
+
...this.outcome ? { outcome: this.outcome } : {}
|
|
10201
|
+
};
|
|
10035
10202
|
}
|
|
10036
10203
|
};
|
|
10037
10204
|
|
|
10038
|
-
//
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
const
|
|
10045
|
-
const
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10205
|
+
// src/agent/auth.ts
|
|
10206
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
10207
|
+
import { Chain as Chain10, computePersonalSignHash, formatEcdsaSignature65 } from "@vultisig/sdk";
|
|
10208
|
+
async function authenticateVault(client, vault, password, maxAttempts = 3) {
|
|
10209
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
10210
|
+
const chainCode = vault.hexChainCode;
|
|
10211
|
+
const ethAddress = await vault.address(Chain10.Ethereum);
|
|
10212
|
+
const nonce = "0x" + randomBytes3(16).toString("hex");
|
|
10213
|
+
const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
10214
|
+
const authMessage = JSON.stringify({
|
|
10215
|
+
message: "Sign into Vultisig Plugin Marketplace",
|
|
10216
|
+
nonce,
|
|
10217
|
+
expiresAt,
|
|
10218
|
+
address: ethAddress
|
|
10219
|
+
});
|
|
10220
|
+
const messageHash = computePersonalSignHash(authMessage);
|
|
10221
|
+
let lastError = null;
|
|
10222
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
10223
|
+
try {
|
|
10224
|
+
if (attempt > 1) {
|
|
10225
|
+
process.stderr.write(` Retry ${attempt}/${maxAttempts}...
|
|
10226
|
+
`);
|
|
10056
10227
|
}
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10060
|
-
if (bytesMatch) {
|
|
10061
|
-
const [_type, size_] = bytesMatch;
|
|
10062
|
-
if (size_ && size(value) !== Number.parseInt(size_, 10))
|
|
10063
|
-
throw new BytesSizeMismatchError({
|
|
10064
|
-
expectedSize: Number.parseInt(size_, 10),
|
|
10065
|
-
givenSize: size(value)
|
|
10066
|
-
});
|
|
10228
|
+
const signature = await vault.signBytes({ data: Buffer.from(messageHash), chain: Chain10.Ethereum }, {});
|
|
10229
|
+
if (signature.recovery === void 0) {
|
|
10230
|
+
throw new Error("Agent authentication requires an ECDSA recovery id");
|
|
10067
10231
|
}
|
|
10068
|
-
const
|
|
10069
|
-
|
|
10070
|
-
|
|
10071
|
-
|
|
10232
|
+
const sigHex = formatEcdsaSignature65(signature.signature, signature.recovery);
|
|
10233
|
+
const authResponse = await client.authenticate({
|
|
10234
|
+
public_key: publicKey,
|
|
10235
|
+
chain_code_hex: chainCode,
|
|
10236
|
+
message: authMessage,
|
|
10237
|
+
signature: sigHex
|
|
10238
|
+
});
|
|
10239
|
+
return {
|
|
10240
|
+
token: authResponse.token,
|
|
10241
|
+
expiresAt: authResponse.expires_at,
|
|
10242
|
+
// Captured + persisted by the session token cache. The backend exposes
|
|
10243
|
+
// POST /auth/refresh to exchange it for a fresh access token without a
|
|
10244
|
+
// new MPC round; wiring that exchange is a future enhancement — today
|
|
10245
|
+
// the CLI re-auths via a full MPC re-sign (authenticateVault), which is
|
|
10246
|
+
// always available and avoids depending on refresh-token rotation.
|
|
10247
|
+
refreshToken: authResponse.refresh_token
|
|
10248
|
+
};
|
|
10249
|
+
} catch (err) {
|
|
10250
|
+
lastError = err;
|
|
10251
|
+
if (attempt < maxAttempts && err.message?.includes("timeout")) {
|
|
10252
|
+
continue;
|
|
10072
10253
|
}
|
|
10254
|
+
throw err;
|
|
10073
10255
|
}
|
|
10074
|
-
};
|
|
10075
|
-
if (types.EIP712Domain && domain) {
|
|
10076
|
-
if (typeof domain !== "object")
|
|
10077
|
-
throw new InvalidDomainError({ domain });
|
|
10078
|
-
validateData(types.EIP712Domain, domain);
|
|
10079
|
-
}
|
|
10080
|
-
if (primaryType !== "EIP712Domain") {
|
|
10081
|
-
if (types[primaryType])
|
|
10082
|
-
validateData(types[primaryType], message);
|
|
10083
|
-
else
|
|
10084
|
-
throw new InvalidPrimaryTypeError({ primaryType, types });
|
|
10085
10256
|
}
|
|
10086
|
-
|
|
10087
|
-
function getTypesForEIP712Domain({ domain }) {
|
|
10088
|
-
return [
|
|
10089
|
-
typeof domain?.name === "string" && { name: "name", type: "string" },
|
|
10090
|
-
domain?.version && { name: "version", type: "string" },
|
|
10091
|
-
(typeof domain?.chainId === "number" || typeof domain?.chainId === "bigint") && {
|
|
10092
|
-
name: "chainId",
|
|
10093
|
-
type: "uint256"
|
|
10094
|
-
},
|
|
10095
|
-
domain?.verifyingContract && {
|
|
10096
|
-
name: "verifyingContract",
|
|
10097
|
-
type: "address"
|
|
10098
|
-
},
|
|
10099
|
-
domain?.salt && { name: "salt", type: "bytes32" }
|
|
10100
|
-
].filter(Boolean);
|
|
10101
|
-
}
|
|
10102
|
-
function validateReference(type) {
|
|
10103
|
-
if (type === "address" || type === "bool" || type === "string" || type.startsWith("bytes") || type.startsWith("uint") || type.startsWith("int"))
|
|
10104
|
-
throw new InvalidStructTypeError({ type });
|
|
10257
|
+
throw lastError || new Error("Authentication failed after all attempts");
|
|
10105
10258
|
}
|
|
10106
10259
|
|
|
10107
|
-
//
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10260
|
+
// src/agent/cards.ts
|
|
10261
|
+
import chalk8 from "chalk";
|
|
10262
|
+
var CLI_SUPPORTED_SURFACES = ["balance_summary", "turn_outcome"];
|
|
10263
|
+
function parseTurnOutcome(raw) {
|
|
10264
|
+
if (!raw || typeof raw !== "object") return null;
|
|
10265
|
+
const kind = raw.kind;
|
|
10266
|
+
if (kind !== "success" && kind !== "blocked" && kind !== "refusal" && kind !== "error") return null;
|
|
10267
|
+
const code = raw.code;
|
|
10268
|
+
const detail = raw.detail;
|
|
10269
|
+
return {
|
|
10270
|
+
kind,
|
|
10271
|
+
...typeof code === "string" ? { code } : {},
|
|
10272
|
+
...typeof detail === "string" ? { detail } : {}
|
|
10113
10273
|
};
|
|
10114
|
-
validateTypedData({
|
|
10115
|
-
domain,
|
|
10116
|
-
message,
|
|
10117
|
-
primaryType,
|
|
10118
|
-
types
|
|
10119
|
-
});
|
|
10120
|
-
const parts = ["0x1901"];
|
|
10121
|
-
if (domain)
|
|
10122
|
-
parts.push(hashDomain({
|
|
10123
|
-
domain,
|
|
10124
|
-
types
|
|
10125
|
-
}));
|
|
10126
|
-
if (primaryType !== "EIP712Domain")
|
|
10127
|
-
parts.push(hashStruct({
|
|
10128
|
-
data: message,
|
|
10129
|
-
primaryType,
|
|
10130
|
-
types
|
|
10131
|
-
}));
|
|
10132
|
-
return keccak256(concat(parts));
|
|
10133
10274
|
}
|
|
10134
|
-
function
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10275
|
+
function stripControlChars(s) {
|
|
10276
|
+
let out = "";
|
|
10277
|
+
for (const ch of s) {
|
|
10278
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
10279
|
+
if (code <= 31 || code >= 127 && code <= 159) continue;
|
|
10280
|
+
out += ch;
|
|
10281
|
+
}
|
|
10282
|
+
return out;
|
|
10140
10283
|
}
|
|
10141
|
-
function
|
|
10142
|
-
|
|
10143
|
-
data,
|
|
10144
|
-
primaryType,
|
|
10145
|
-
types
|
|
10146
|
-
});
|
|
10147
|
-
return keccak256(encoded);
|
|
10284
|
+
function asString(v) {
|
|
10285
|
+
return typeof v === "string" ? stripControlChars(v) : "";
|
|
10148
10286
|
}
|
|
10149
|
-
function
|
|
10150
|
-
|
|
10151
|
-
const
|
|
10152
|
-
|
|
10153
|
-
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
encodedTypes.push(type);
|
|
10160
|
-
encodedValues.push(value);
|
|
10161
|
-
}
|
|
10162
|
-
return encodeAbiParameters(encodedTypes, encodedValues);
|
|
10287
|
+
function parseToken(v) {
|
|
10288
|
+
if (!v || typeof v !== "object") return null;
|
|
10289
|
+
const o = v;
|
|
10290
|
+
const symbol = asString(o.symbol);
|
|
10291
|
+
const amountDecimal = asString(o.amountDecimal);
|
|
10292
|
+
if (!symbol && !amountDecimal) return null;
|
|
10293
|
+
const token = { symbol, amountDecimal };
|
|
10294
|
+
const amountUsd = asString(o.amountUsd);
|
|
10295
|
+
if (amountUsd) token.amountUsd = amountUsd;
|
|
10296
|
+
return token;
|
|
10163
10297
|
}
|
|
10164
|
-
function
|
|
10165
|
-
|
|
10166
|
-
|
|
10298
|
+
function parseAccount(v) {
|
|
10299
|
+
if (!v || typeof v !== "object") return null;
|
|
10300
|
+
const o = v;
|
|
10301
|
+
const chainId = asString(o.chainId);
|
|
10302
|
+
if (!chainId) return null;
|
|
10303
|
+
const tokensRaw = Array.isArray(o.tokens) ? o.tokens : [];
|
|
10304
|
+
const tokens = tokensRaw.map(parseToken).filter((t) => t !== null);
|
|
10305
|
+
return { chainId, address: asString(o.address) || "\u2014", tokens };
|
|
10167
10306
|
}
|
|
10168
|
-
function
|
|
10169
|
-
|
|
10170
|
-
const
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10307
|
+
function parseBalanceSummaryEnvelope(value) {
|
|
10308
|
+
if (!value || typeof value !== "object") return null;
|
|
10309
|
+
const o = value;
|
|
10310
|
+
if (o.surface !== "balance_summary") return null;
|
|
10311
|
+
if (!Array.isArray(o.accounts)) return null;
|
|
10312
|
+
const accounts = o.accounts.map(parseAccount).filter((a) => a !== null);
|
|
10313
|
+
if (accounts.length === 0) return null;
|
|
10314
|
+
const card = { surface: "balance_summary", accounts };
|
|
10315
|
+
if (o.stale === true) {
|
|
10316
|
+
card.stale = true;
|
|
10317
|
+
if (typeof o.stale_secs === "number") card.staleSecs = o.stale_secs;
|
|
10175
10318
|
}
|
|
10176
|
-
return
|
|
10319
|
+
return card;
|
|
10177
10320
|
}
|
|
10178
|
-
function
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10321
|
+
function matchBrace(text, start) {
|
|
10322
|
+
let depth = 0;
|
|
10323
|
+
let inString = false;
|
|
10324
|
+
let escaped = false;
|
|
10325
|
+
for (let i = start; i < text.length; i++) {
|
|
10326
|
+
const ch = text[i];
|
|
10327
|
+
if (inString) {
|
|
10328
|
+
if (escaped) escaped = false;
|
|
10329
|
+
else if (ch === "\\") escaped = true;
|
|
10330
|
+
else if (ch === '"') inString = false;
|
|
10331
|
+
continue;
|
|
10332
|
+
}
|
|
10333
|
+
if (ch === '"') inString = true;
|
|
10334
|
+
else if (ch === "{") depth++;
|
|
10335
|
+
else if (ch === "}") {
|
|
10336
|
+
depth--;
|
|
10337
|
+
if (depth === 0) return i;
|
|
10338
|
+
}
|
|
10183
10339
|
}
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10340
|
+
return -1;
|
|
10341
|
+
}
|
|
10342
|
+
function extractBalanceSummaryFromText(content) {
|
|
10343
|
+
if (!content || !content.includes("balance_summary")) return null;
|
|
10344
|
+
if (content.length > 2e5) return null;
|
|
10345
|
+
for (let i = content.indexOf("{"); i !== -1; i = content.indexOf("{", i + 1)) {
|
|
10346
|
+
const end = matchBrace(content, i);
|
|
10347
|
+
if (end === -1) break;
|
|
10348
|
+
const blob = content.slice(i, end + 1);
|
|
10349
|
+
if (!blob.includes("balance_summary")) continue;
|
|
10350
|
+
let parsed;
|
|
10351
|
+
try {
|
|
10352
|
+
parsed = JSON.parse(blob);
|
|
10353
|
+
} catch {
|
|
10354
|
+
continue;
|
|
10355
|
+
}
|
|
10356
|
+
const card = parseBalanceSummaryEnvelope(parsed);
|
|
10357
|
+
if (!card) continue;
|
|
10358
|
+
const before = content.slice(0, i).replace(/```(?:json)?\s*$/i, "");
|
|
10359
|
+
const after = content.slice(end + 1).replace(/^\s*```/, "");
|
|
10360
|
+
const remainingText = (before + after).trim();
|
|
10361
|
+
return { card, remainingText };
|
|
10187
10362
|
}
|
|
10188
|
-
return
|
|
10363
|
+
return null;
|
|
10189
10364
|
}
|
|
10190
|
-
function
|
|
10191
|
-
if (
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10365
|
+
function shortenAddress(address) {
|
|
10366
|
+
if (!address || address === "\u2014") return address || "\u2014";
|
|
10367
|
+
if (address.length <= 16) return address;
|
|
10368
|
+
return `${address.slice(0, 8)}\u2026${address.slice(-6)}`;
|
|
10369
|
+
}
|
|
10370
|
+
function parseUsd(amountUsd) {
|
|
10371
|
+
if (!amountUsd) return null;
|
|
10372
|
+
const cleaned = amountUsd.replace(/[$,\s]/g, "");
|
|
10373
|
+
if (!cleaned) return null;
|
|
10374
|
+
const n = Number(cleaned);
|
|
10375
|
+
return Number.isFinite(n) ? n : null;
|
|
10376
|
+
}
|
|
10377
|
+
function formatUsd(n) {
|
|
10378
|
+
return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
10379
|
+
}
|
|
10380
|
+
function renderBalanceSummaryCard(card) {
|
|
10381
|
+
const lines = [];
|
|
10382
|
+
const staleCue = card.stale ? chalk8.gray(` (stale${card.staleSecs ? ` ~${Math.round(card.staleSecs / 60)}m` : ""}, refreshing\u2026)`) : "";
|
|
10383
|
+
lines.push(chalk8.bold(" Balances") + staleCue);
|
|
10384
|
+
let total = 0;
|
|
10385
|
+
let sawUsd = false;
|
|
10386
|
+
for (const account of card.accounts) {
|
|
10387
|
+
lines.push(` ${chalk8.cyan(account.chainId)} ${chalk8.gray(`(${shortenAddress(account.address)})`)}`);
|
|
10388
|
+
if (account.tokens.length === 0) {
|
|
10389
|
+
lines.push(chalk8.gray(" (no balances)"));
|
|
10390
|
+
continue;
|
|
10391
|
+
}
|
|
10392
|
+
for (const token of account.tokens) {
|
|
10393
|
+
const usd = parseUsd(token.amountUsd);
|
|
10394
|
+
if (usd !== null) {
|
|
10395
|
+
total += usd;
|
|
10396
|
+
sawUsd = true;
|
|
10397
|
+
}
|
|
10398
|
+
const symbol = token.symbol.padEnd(10);
|
|
10399
|
+
const amount = token.amountDecimal.padStart(16);
|
|
10400
|
+
const usdCol = token.amountUsd ? chalk8.gray(` ${token.amountUsd}`) : "";
|
|
10401
|
+
lines.push(` ${chalk8.bold(symbol)}${amount}${usdCol}`);
|
|
10402
|
+
}
|
|
10196
10403
|
}
|
|
10197
|
-
if (
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
return [{ type: "bytes32" }, keccak256(toHex(value))];
|
|
10201
|
-
if (type.lastIndexOf("]") === type.length - 1) {
|
|
10202
|
-
const parsedType = type.slice(0, type.lastIndexOf("["));
|
|
10203
|
-
const typeValuePairs = value.map((item) => encodeField({
|
|
10204
|
-
name,
|
|
10205
|
-
type: parsedType,
|
|
10206
|
-
types,
|
|
10207
|
-
value: item
|
|
10208
|
-
}));
|
|
10209
|
-
return [
|
|
10210
|
-
{ type: "bytes32" },
|
|
10211
|
-
keccak256(encodeAbiParameters(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v)))
|
|
10212
|
-
];
|
|
10404
|
+
if (sawUsd) {
|
|
10405
|
+
lines.push(chalk8.gray(" " + "\u2500".repeat(36)));
|
|
10406
|
+
lines.push(` ${chalk8.bold("Total")} ${chalk8.green(formatUsd(total))}`);
|
|
10213
10407
|
}
|
|
10214
|
-
return
|
|
10408
|
+
return lines.join("\n");
|
|
10215
10409
|
}
|
|
10216
10410
|
|
|
10217
|
-
//
|
|
10218
|
-
|
|
10411
|
+
// src/agent/client.ts
|
|
10412
|
+
import { randomUUID } from "node:crypto";
|
|
10413
|
+
|
|
10414
|
+
// src/agent/toolOutputSigning.ts
|
|
10415
|
+
import { getChainKind as getChainKind4 } from "@vultisig/sdk";
|
|
10416
|
+
|
|
10417
|
+
// src/agent/executor.ts
|
|
10418
|
+
import {
|
|
10419
|
+
Chain as Chain11,
|
|
10420
|
+
chainFeeCoin,
|
|
10421
|
+
getChainKind as getChainKind3,
|
|
10422
|
+
parseThorSwapMemo,
|
|
10423
|
+
resolveChainReference,
|
|
10424
|
+
VaultError as VaultError3,
|
|
10425
|
+
VaultErrorCode as VaultErrorCode3,
|
|
10426
|
+
Vultisig as VultisigSdk
|
|
10427
|
+
} from "@vultisig/sdk";
|
|
10219
10428
|
|
|
10220
10429
|
// src/core/VaultStateStore.ts
|
|
10221
10430
|
import * as fs3 from "node:fs";
|
|
10222
10431
|
import * as path3 from "node:path";
|
|
10223
|
-
import { getVultisigConfigDir } from "@vultisig/client-shared";
|
|
10432
|
+
import { getVultisigConfigDir as getVultisigConfigDir2 } from "@vultisig/client-shared";
|
|
10224
10433
|
var LOCK_STALE_MS = 6e4;
|
|
10225
10434
|
var LOCK_RETRY_INIT_MS = 100;
|
|
10226
10435
|
var LOCK_MAX_WAIT_MS = 3e4;
|
|
@@ -10232,7 +10441,7 @@ var VaultStateStore = class {
|
|
|
10232
10441
|
if (!safeId) {
|
|
10233
10442
|
throw new Error("Invalid vaultId: must contain alphanumeric characters");
|
|
10234
10443
|
}
|
|
10235
|
-
this.baseDir = path3.join(
|
|
10444
|
+
this.baseDir = path3.join(getVultisigConfigDir2(), "vault-state", safeId);
|
|
10236
10445
|
fs3.mkdirSync(this.baseDir, { recursive: true });
|
|
10237
10446
|
}
|
|
10238
10447
|
// -------------------------------------------------------------------------
|
|
@@ -10373,45 +10582,6 @@ function sleep2(ms) {
|
|
|
10373
10582
|
}
|
|
10374
10583
|
|
|
10375
10584
|
// src/agent/executor.ts
|
|
10376
|
-
var THOR_MEMO_CHAIN_TO_ENUM = {
|
|
10377
|
-
BTC: Chain11.Bitcoin,
|
|
10378
|
-
ETH: Chain11.Ethereum,
|
|
10379
|
-
BSC: Chain11.BSC,
|
|
10380
|
-
AVAX: Chain11.Avalanche,
|
|
10381
|
-
BASE: Chain11.Base,
|
|
10382
|
-
// L2 — THORChain routinely quotes Base destinations (PR #439 review finding 1)
|
|
10383
|
-
ARB: Chain11.Arbitrum,
|
|
10384
|
-
// L1-via-bridge path (PR #439 review finding 1)
|
|
10385
|
-
BCH: Chain11.BitcoinCash,
|
|
10386
|
-
LTC: Chain11.Litecoin,
|
|
10387
|
-
DOGE: Chain11.Dogecoin,
|
|
10388
|
-
GAIA: Chain11.Cosmos,
|
|
10389
|
-
THOR: Chain11.THORChain,
|
|
10390
|
-
RUNE: Chain11.THORChain,
|
|
10391
|
-
XRP: Chain11.Ripple,
|
|
10392
|
-
DASH: Chain11.Dash,
|
|
10393
|
-
ZEC: Chain11.Zcash,
|
|
10394
|
-
MAYA: Chain11.MayaChain,
|
|
10395
|
-
CACAO: Chain11.MayaChain
|
|
10396
|
-
};
|
|
10397
|
-
var THOR_MEMO_ASSET_SHORTCUTS = {
|
|
10398
|
-
b: "BTC.BTC",
|
|
10399
|
-
e: "ETH.ETH",
|
|
10400
|
-
s: "BSC.BNB",
|
|
10401
|
-
a: "AVAX.AVAX",
|
|
10402
|
-
c: "BCH.BCH",
|
|
10403
|
-
l: "LTC.LTC",
|
|
10404
|
-
d: "DOGE.DOGE",
|
|
10405
|
-
g: "GAIA.ATOM",
|
|
10406
|
-
r: "THOR.RUNE",
|
|
10407
|
-
x: "XRP.XRP",
|
|
10408
|
-
cacao: "MAYA.CACAO",
|
|
10409
|
-
dash: "DASH.DASH",
|
|
10410
|
-
zec: "ZEC.ZEC"
|
|
10411
|
-
// BASE / ARB don't have documented single-letter shortcuts; THORChain
|
|
10412
|
-
// emits these as the full CHAIN.ASSET form in memos. Listed in
|
|
10413
|
-
// THOR_MEMO_CHAIN_TO_ENUM only.
|
|
10414
|
-
};
|
|
10415
10585
|
var EVM_CHAINS = /* @__PURE__ */ new Set([
|
|
10416
10586
|
"Ethereum",
|
|
10417
10587
|
"BSC",
|
|
@@ -11072,13 +11242,7 @@ var AgentExecutor = class {
|
|
|
11072
11242
|
const txArgs = serverTxData?.txArgs ?? {};
|
|
11073
11243
|
const memo = typeof txArgs.memo === "string" ? txArgs.memo : "";
|
|
11074
11244
|
const parsed = parseThorSwapMemo(memo);
|
|
11075
|
-
const toChain =
|
|
11076
|
-
if (!toChain) {
|
|
11077
|
-
throw new VaultError3(
|
|
11078
|
-
VaultErrorCode3.UnsupportedChain,
|
|
11079
|
-
`signThorMsgDepositSwap: unsupported destination chain code '${parsed.destChainCode}' in memo '${memo}'.`
|
|
11080
|
-
);
|
|
11081
|
-
}
|
|
11245
|
+
const toChain = parsed.toChain;
|
|
11082
11246
|
const vaultDestAddress = await this.vault.address(toChain);
|
|
11083
11247
|
const normalizeForCompare = (addr) => EVM_CHAINS.has(toChain) ? addr.toLowerCase() : addr;
|
|
11084
11248
|
if (parsed.destAddress && normalizeForCompare(parsed.destAddress) !== normalizeForCompare(vaultDestAddress)) {
|
|
@@ -11887,31 +12051,6 @@ function parseNonEvmEnvelope(serverTxData, chain) {
|
|
|
11887
12051
|
const memo = typeof txArgs.memo === "string" && txArgs.memo.length > 0 ? txArgs.memo : void 0;
|
|
11888
12052
|
return { chain, to, amount: amountDecimal, symbol, memo };
|
|
11889
12053
|
}
|
|
11890
|
-
function parseThorSwapMemo(memo) {
|
|
11891
|
-
if (!memo.startsWith("=:")) {
|
|
11892
|
-
throw new VaultError3(
|
|
11893
|
-
VaultErrorCode3.NotImplemented,
|
|
11894
|
-
`parseThorSwapMemo: only swap memos (=:CHAIN.ASSET:DEST...) supported on this path; got memo='${memo}'. LP memos (+:/-:) route through signThorMsgDepositLp; loan / validator ops out of scope.`
|
|
11895
|
-
);
|
|
11896
|
-
}
|
|
11897
|
-
const memoBody = memo.slice(2);
|
|
11898
|
-
const parts = memoBody.split(":");
|
|
11899
|
-
let chainAsset = parts[0];
|
|
11900
|
-
if (chainAsset && !chainAsset.includes(".")) {
|
|
11901
|
-
const expanded = THOR_MEMO_ASSET_SHORTCUTS[chainAsset.toLowerCase()];
|
|
11902
|
-
if (expanded) chainAsset = expanded;
|
|
11903
|
-
}
|
|
11904
|
-
if (!chainAsset || !chainAsset.includes(".")) {
|
|
11905
|
-
throw new VaultError3(
|
|
11906
|
-
VaultErrorCode3.InvalidConfig,
|
|
11907
|
-
`parseThorSwapMemo: malformed swap memo '${memo}': missing CHAIN.ASSET in first segment.`
|
|
11908
|
-
);
|
|
11909
|
-
}
|
|
11910
|
-
const [destChainCode, destAssetRaw] = chainAsset.split(".");
|
|
11911
|
-
const destAsset = destAssetRaw?.split("-")[0] ?? "";
|
|
11912
|
-
const destAddress = typeof parts[1] === "string" ? parts[1] : "";
|
|
11913
|
-
return { destChainCode, destAsset, destAddress };
|
|
11914
|
-
}
|
|
11915
12054
|
function resolveChainId(chainId) {
|
|
11916
12055
|
return resolveChainReference(chainId) ?? null;
|
|
11917
12056
|
}
|
|
@@ -13348,13 +13487,13 @@ import {
|
|
|
13348
13487
|
statSync as statSync3,
|
|
13349
13488
|
writeFileSync as writeFileSync3
|
|
13350
13489
|
} from "node:fs";
|
|
13351
|
-
import { homedir
|
|
13490
|
+
import { homedir } from "node:os";
|
|
13352
13491
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
13353
13492
|
var LOCK_RETRY_MS = 25;
|
|
13354
13493
|
var LOCK_MAX_WAIT_MS2 = 5e3;
|
|
13355
13494
|
var LOCK_STALE_MS2 = 3e4;
|
|
13356
13495
|
function getTokenCachePath() {
|
|
13357
|
-
const dir = process.env.VULTISIG_CONFIG_DIR ?? join3(
|
|
13496
|
+
const dir = process.env.VULTISIG_CONFIG_DIR ?? join3(homedir(), ".vultisig");
|
|
13358
13497
|
return join3(dir, "agent-tokens.json");
|
|
13359
13498
|
}
|
|
13360
13499
|
function tokenCacheKey(scope) {
|
|
@@ -15124,7 +15263,7 @@ function formatDate(iso) {
|
|
|
15124
15263
|
}
|
|
15125
15264
|
|
|
15126
15265
|
// src/lib/version.ts
|
|
15127
|
-
import { getVultisigConfigDir as
|
|
15266
|
+
import { getVultisigConfigDir as getVultisigConfigDir3 } from "@vultisig/client-shared";
|
|
15128
15267
|
import chalk11 from "chalk";
|
|
15129
15268
|
import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
15130
15269
|
import { join as join4 } from "path";
|
|
@@ -15132,7 +15271,7 @@ var cachedVersion = null;
|
|
|
15132
15271
|
function getVersion() {
|
|
15133
15272
|
if (cachedVersion) return cachedVersion;
|
|
15134
15273
|
if (true) {
|
|
15135
|
-
cachedVersion = "
|
|
15274
|
+
cachedVersion = "3.0.0";
|
|
15136
15275
|
return cachedVersion;
|
|
15137
15276
|
}
|
|
15138
15277
|
try {
|
|
@@ -15145,7 +15284,7 @@ function getVersion() {
|
|
|
15145
15284
|
return cachedVersion;
|
|
15146
15285
|
}
|
|
15147
15286
|
}
|
|
15148
|
-
var CACHE_DIR = join4(
|
|
15287
|
+
var CACHE_DIR = join4(getVultisigConfigDir3(), "cache");
|
|
15149
15288
|
var VERSION_CACHE_FILE = join4(CACHE_DIR, "version-check.json");
|
|
15150
15289
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
15151
15290
|
function readVersionCache() {
|
|
@@ -15229,7 +15368,7 @@ function formatVersionShort() {
|
|
|
15229
15368
|
}
|
|
15230
15369
|
function formatVersionDetailed() {
|
|
15231
15370
|
const lines = [];
|
|
15232
|
-
const configDir =
|
|
15371
|
+
const configDir = getVultisigConfigDir3();
|
|
15233
15372
|
lines.push(chalk11.bold(`Vultisig CLI v${getVersion()}`));
|
|
15234
15373
|
lines.push("");
|
|
15235
15374
|
lines.push(` Node.js: ${process.version}`);
|
|
@@ -16529,7 +16668,7 @@ Error: ${error2.message}`));
|
|
|
16529
16668
|
const [fromChainStr, toChainStr, amountStr, ...rest] = args;
|
|
16530
16669
|
const fromChain = findChainByName(fromChainStr) || fromChainStr;
|
|
16531
16670
|
const toChain = findChainByName(toChainStr) || toChainStr;
|
|
16532
|
-
const amount =
|
|
16671
|
+
const amount = amountStr;
|
|
16533
16672
|
let fromToken;
|
|
16534
16673
|
let toToken;
|
|
16535
16674
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -16555,7 +16694,7 @@ Error: ${error2.message}`));
|
|
|
16555
16694
|
const [fromChainStr, toChainStr, amountStr, ...rest] = args;
|
|
16556
16695
|
const fromChain = findChainByName(fromChainStr) || fromChainStr;
|
|
16557
16696
|
const toChain = findChainByName(toChainStr) || toChainStr;
|
|
16558
|
-
const amount =
|
|
16697
|
+
const amount = amountStr;
|
|
16559
16698
|
let fromToken;
|
|
16560
16699
|
let toToken;
|
|
16561
16700
|
let slippage;
|
|
@@ -16669,10 +16808,10 @@ Error: ${error2.message}`));
|
|
|
16669
16808
|
};
|
|
16670
16809
|
|
|
16671
16810
|
// src/lib/completion.ts
|
|
16811
|
+
import { getVultisigConfigDir as getVultisigConfigDir4 } from "@vultisig/client-shared";
|
|
16672
16812
|
import { SUPPORTED_CHAINS as SUPPORTED_CHAINS2 } from "@vultisig/sdk";
|
|
16673
16813
|
import { program } from "commander";
|
|
16674
16814
|
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5 } from "fs";
|
|
16675
|
-
import { homedir as homedir3 } from "os";
|
|
16676
16815
|
import { join as join5 } from "path";
|
|
16677
16816
|
var tabtab = null;
|
|
16678
16817
|
async function getTabtab() {
|
|
@@ -16696,7 +16835,7 @@ function getChains() {
|
|
|
16696
16835
|
}
|
|
16697
16836
|
function getVaultNames() {
|
|
16698
16837
|
try {
|
|
16699
|
-
const vaultDir =
|
|
16838
|
+
const vaultDir = getVultisigConfigDir4();
|
|
16700
16839
|
if (!existsSync3(vaultDir)) return [];
|
|
16701
16840
|
const files = readdirSync(vaultDir);
|
|
16702
16841
|
const names = [];
|
|
@@ -16704,9 +16843,10 @@ function getVaultNames() {
|
|
|
16704
16843
|
if (file.startsWith("vault:") && file.endsWith(".json")) {
|
|
16705
16844
|
try {
|
|
16706
16845
|
const content = readFileSync5(join5(vaultDir, file), "utf-8");
|
|
16707
|
-
const
|
|
16708
|
-
|
|
16709
|
-
if (vault
|
|
16846
|
+
const parsed = JSON.parse(content);
|
|
16847
|
+
const vault = parsed?.value ?? parsed;
|
|
16848
|
+
if (vault?.name) names.push(vault.name);
|
|
16849
|
+
if (vault?.id) names.push(vault.id);
|
|
16710
16850
|
} catch {
|
|
16711
16851
|
}
|
|
16712
16852
|
}
|
|
@@ -16948,12 +17088,10 @@ complete -c vsig -n "__fish_seen_subcommand_from import export" -a "(__fish_comp
|
|
|
16948
17088
|
}
|
|
16949
17089
|
|
|
16950
17090
|
// src/lib/config.ts
|
|
17091
|
+
import { getVultisigConfigDir as getVultisigConfigDir5 } from "@vultisig/client-shared";
|
|
16951
17092
|
import { FileStorage } from "@vultisig/sdk/node";
|
|
16952
|
-
import { homedir as homedir4 } from "os";
|
|
16953
|
-
import { join as join6 } from "path";
|
|
16954
17093
|
function getConfigDir() {
|
|
16955
|
-
|
|
16956
|
-
return override ? override : join6(homedir4(), ".vultisig");
|
|
17094
|
+
return getVultisigConfigDir5();
|
|
16957
17095
|
}
|
|
16958
17096
|
function createVaultStorage() {
|
|
16959
17097
|
return new FileStorage({ basePath: getConfigDir() });
|
|
@@ -17511,9 +17649,13 @@ program2.command("delete [vault]").description("Delete a vault from local storag
|
|
|
17511
17649
|
});
|
|
17512
17650
|
})
|
|
17513
17651
|
);
|
|
17514
|
-
program2.command("tokens <chain>").description("List and manage tokens for a chain").option("--add <contractAddress>", "Add a token by contract address").option("--remove <tokenId>", "Remove a token by ID").option("--discover", "
|
|
17652
|
+
program2.command("tokens <chain>").description("List and manage tokens for a chain").option("--add <contractAddress>", "Add a token by contract address").option("--remove <tokenId>", "Remove a token by ID").option("--discover", "Find tokens with balances on the chain and save them to this vault").addHelpText(
|
|
17515
17653
|
"after",
|
|
17516
17654
|
`
|
|
17655
|
+
--discover writes to the vault: every token it finds is saved to the tracked
|
|
17656
|
+
list, so it also changes what portfolio and balance --tokens report. Use
|
|
17657
|
+
--remove <tokenId> to stop tracking one.
|
|
17658
|
+
|
|
17517
17659
|
Examples:
|
|
17518
17660
|
vultisig tokens Ethereum
|
|
17519
17661
|
vultisig tokens Ethereum --discover --output json
|
|
@@ -17558,7 +17700,7 @@ Examples:
|
|
|
17558
17700
|
await executeSwapQuote(context, {
|
|
17559
17701
|
fromChain,
|
|
17560
17702
|
toChain,
|
|
17561
|
-
amount: options.max ? "max" :
|
|
17703
|
+
amount: options.max ? "max" : amountStr,
|
|
17562
17704
|
fromToken: options.fromToken,
|
|
17563
17705
|
toToken: options.toToken
|
|
17564
17706
|
});
|
|
@@ -17583,7 +17725,7 @@ See also: swap-quote, swap-chains, balance`
|
|
|
17583
17725
|
await executeSwap(context, {
|
|
17584
17726
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
17585
17727
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
17586
|
-
amount: options.max ? "max" :
|
|
17728
|
+
amount: options.max ? "max" : amountStr,
|
|
17587
17729
|
fromToken: options.fromToken,
|
|
17588
17730
|
toToken: options.toToken,
|
|
17589
17731
|
slippage: options.slippage ? parseFloat(options.slippage) : void 0,
|