@vultisig/cli 2.22.0 → 2.23.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 +11 -0
- package/dist/index.js +858 -711
- package/package.json +4 -4
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);
|
|
@@ -8890,210 +8890,624 @@ Please specify more characters of the vault ID.`
|
|
|
8890
8890
|
throw new Error(`Vault not found: "${idOrName}"`);
|
|
8891
8891
|
}
|
|
8892
8892
|
|
|
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;
|
|
8893
|
+
// ../../node_modules/viem/_esm/accounts/utils/publicKeyToAddress.js
|
|
8894
|
+
init_getAddress();
|
|
8895
|
+
init_keccak256();
|
|
8896
|
+
function publicKeyToAddress(publicKey) {
|
|
8897
|
+
const address = keccak256(`0x${publicKey.substring(4)}`).substring(26);
|
|
8898
|
+
return checksumAddress(`0x${address}`);
|
|
8981
8899
|
}
|
|
8982
|
-
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8900
|
+
|
|
8901
|
+
// ../../node_modules/viem/_esm/utils/signature/recoverPublicKey.js
|
|
8902
|
+
init_isHex();
|
|
8903
|
+
init_size();
|
|
8904
|
+
init_fromHex();
|
|
8905
|
+
init_toHex();
|
|
8906
|
+
async function recoverPublicKey({ hash, signature }) {
|
|
8907
|
+
const hashHex = isHex(hash) ? hash : toHex(hash);
|
|
8908
|
+
const { secp256k1: secp256k12 } = await Promise.resolve().then(() => (init_secp256k1(), secp256k1_exports));
|
|
8909
|
+
const signature_ = (() => {
|
|
8910
|
+
if (typeof signature === "object" && "r" in signature && "s" in signature) {
|
|
8911
|
+
const { r, s, v, yParity } = signature;
|
|
8912
|
+
const yParityOrV2 = Number(yParity ?? v);
|
|
8913
|
+
const recoveryBit2 = toRecoveryBit(yParityOrV2);
|
|
8914
|
+
return new secp256k12.Signature(hexToBigInt(r), hexToBigInt(s)).addRecoveryBit(recoveryBit2);
|
|
8915
|
+
}
|
|
8916
|
+
const signatureHex = isHex(signature) ? signature : toHex(signature);
|
|
8917
|
+
if (size(signatureHex) !== 65)
|
|
8918
|
+
throw new Error("invalid signature length");
|
|
8919
|
+
const yParityOrV = hexToNumber(`0x${signatureHex.slice(130)}`);
|
|
8920
|
+
const recoveryBit = toRecoveryBit(yParityOrV);
|
|
8921
|
+
return secp256k12.Signature.fromCompact(signatureHex.substring(2, 130)).addRecoveryBit(recoveryBit);
|
|
8922
|
+
})();
|
|
8923
|
+
const publicKey = signature_.recoverPublicKey(hashHex.substring(2)).toHex(false);
|
|
8924
|
+
return `0x${publicKey}`;
|
|
8991
8925
|
}
|
|
8992
|
-
function
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8926
|
+
function toRecoveryBit(yParityOrV) {
|
|
8927
|
+
if (yParityOrV === 0 || yParityOrV === 1)
|
|
8928
|
+
return yParityOrV;
|
|
8929
|
+
if (yParityOrV === 27)
|
|
8930
|
+
return 0;
|
|
8931
|
+
if (yParityOrV === 28)
|
|
8932
|
+
return 1;
|
|
8933
|
+
throw new Error("Invalid yParityOrV value");
|
|
8997
8934
|
}
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
}
|
|
9003
|
-
const confirmed = await confirmSwap();
|
|
9004
|
-
if (!confirmed) {
|
|
9005
|
-
throw new ConfirmationRequiredError("Swap declined at the confirmation prompt");
|
|
9006
|
-
}
|
|
8935
|
+
|
|
8936
|
+
// ../../node_modules/viem/_esm/utils/signature/recoverAddress.js
|
|
8937
|
+
async function recoverAddress({ hash, signature }) {
|
|
8938
|
+
return publicKeyToAddress(await recoverPublicKey({ hash, signature }));
|
|
9007
8939
|
}
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
}
|
|
9027
|
-
displayDryRunResult(result);
|
|
9028
|
-
}
|
|
9029
|
-
return result;
|
|
8940
|
+
|
|
8941
|
+
// ../../node_modules/viem/_esm/utils/signature/hashTypedData.js
|
|
8942
|
+
init_encodeAbiParameters();
|
|
8943
|
+
init_concat();
|
|
8944
|
+
init_toHex();
|
|
8945
|
+
init_keccak256();
|
|
8946
|
+
|
|
8947
|
+
// ../../node_modules/viem/_esm/utils/typedData.js
|
|
8948
|
+
init_abi();
|
|
8949
|
+
init_address();
|
|
8950
|
+
|
|
8951
|
+
// ../../node_modules/viem/_esm/errors/typedData.js
|
|
8952
|
+
init_stringify();
|
|
8953
|
+
init_base();
|
|
8954
|
+
var InvalidDomainError = class extends BaseError {
|
|
8955
|
+
constructor({ domain }) {
|
|
8956
|
+
super(`Invalid domain "${stringify(domain)}".`, {
|
|
8957
|
+
metaMessages: ["Must be a valid EIP-712 domain."]
|
|
8958
|
+
});
|
|
9030
8959
|
}
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
feeDecimals: feeBalance.decimals,
|
|
9038
|
-
feeSymbol: feeBalance.symbol,
|
|
9039
|
-
discountTier
|
|
8960
|
+
};
|
|
8961
|
+
var InvalidPrimaryTypeError = class extends BaseError {
|
|
8962
|
+
constructor({ primaryType, types }) {
|
|
8963
|
+
super(`Invalid primary type \`${primaryType}\` must be one of \`${JSON.stringify(Object.keys(types))}\`.`, {
|
|
8964
|
+
docsPath: "/api/glossary/Errors#typeddatainvalidprimarytypeerror",
|
|
8965
|
+
metaMessages: ["Check that the primary type is a key in `types`."]
|
|
9040
8966
|
});
|
|
9041
8967
|
}
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
|
|
9048
|
-
toToken: options.toToken,
|
|
9049
|
-
amount: fromAmountRaw,
|
|
9050
|
-
isMax: options.amount === "max"
|
|
9051
|
-
});
|
|
9052
|
-
let signSpinner;
|
|
9053
|
-
try {
|
|
9054
|
-
const broadcast = await guardedBroadcast(intent, options.force ?? false, async () => {
|
|
9055
|
-
signSpinner = createSpinner("Signing swap transaction...");
|
|
9056
|
-
vault.on("signingProgress", ({ step }) => {
|
|
9057
|
-
if (signSpinner) signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
9058
|
-
});
|
|
9059
|
-
const result = await vault.swap(toSwapRequest(options, amountStr));
|
|
9060
|
-
if (result.dryRun) throw new Error("unreachable");
|
|
9061
|
-
return result;
|
|
8968
|
+
};
|
|
8969
|
+
var InvalidStructTypeError = class extends BaseError {
|
|
8970
|
+
constructor({ type }) {
|
|
8971
|
+
super(`Struct type "${type}" is invalid.`, {
|
|
8972
|
+
metaMessages: ["Struct type must not be a Solidity type."],
|
|
8973
|
+
name: "InvalidStructTypeError"
|
|
9062
8974
|
});
|
|
9063
|
-
signSpinner?.succeed(`Swap broadcast: ${broadcast.txHash}`);
|
|
9064
|
-
if (isJsonOutput()) {
|
|
9065
|
-
outputJson({
|
|
9066
|
-
txHash: broadcast.txHash,
|
|
9067
|
-
fromChain: options.fromChain,
|
|
9068
|
-
toChain: options.toChain,
|
|
9069
|
-
quote
|
|
9070
|
-
});
|
|
9071
|
-
} else {
|
|
9072
|
-
displaySwapResult(options.fromChain, options.toChain, broadcast.txHash, quote, quote.toCoin.decimals);
|
|
9073
|
-
}
|
|
9074
|
-
return { txHash: broadcast.txHash, quote };
|
|
9075
|
-
} catch (err) {
|
|
9076
|
-
signSpinner?.stop();
|
|
9077
|
-
throw err;
|
|
9078
|
-
} finally {
|
|
9079
|
-
vault.removeAllListeners("signingProgress");
|
|
9080
8975
|
}
|
|
9081
|
-
}
|
|
8976
|
+
};
|
|
9082
8977
|
|
|
9083
|
-
//
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
9094
|
-
|
|
9095
|
-
|
|
9096
|
-
|
|
8978
|
+
// ../../node_modules/viem/_esm/utils/typedData.js
|
|
8979
|
+
init_isAddress();
|
|
8980
|
+
init_size();
|
|
8981
|
+
init_toHex();
|
|
8982
|
+
init_regex();
|
|
8983
|
+
function validateTypedData(parameters) {
|
|
8984
|
+
const { domain, message, primaryType, types } = parameters;
|
|
8985
|
+
const validateData = (struct, data) => {
|
|
8986
|
+
for (const param of struct) {
|
|
8987
|
+
const { name, type } = param;
|
|
8988
|
+
const value = data[name];
|
|
8989
|
+
const integerMatch = type.match(integerRegex);
|
|
8990
|
+
if (integerMatch && (typeof value === "number" || typeof value === "bigint")) {
|
|
8991
|
+
const [_type, base, size_] = integerMatch;
|
|
8992
|
+
numberToHex(value, {
|
|
8993
|
+
signed: base === "int",
|
|
8994
|
+
size: Number.parseInt(size_, 10) / 8
|
|
8995
|
+
});
|
|
8996
|
+
}
|
|
8997
|
+
if (type === "address" && typeof value === "string" && !isAddress(value))
|
|
8998
|
+
throw new InvalidAddressError2({ address: value });
|
|
8999
|
+
const bytesMatch = type.match(bytesRegex);
|
|
9000
|
+
if (bytesMatch) {
|
|
9001
|
+
const [_type, size_] = bytesMatch;
|
|
9002
|
+
if (size_ && size(value) !== Number.parseInt(size_, 10))
|
|
9003
|
+
throw new BytesSizeMismatchError({
|
|
9004
|
+
expectedSize: Number.parseInt(size_, 10),
|
|
9005
|
+
givenSize: size(value)
|
|
9006
|
+
});
|
|
9007
|
+
}
|
|
9008
|
+
const struct2 = types[type];
|
|
9009
|
+
if (struct2) {
|
|
9010
|
+
validateReference(type);
|
|
9011
|
+
validateData(struct2, value);
|
|
9012
|
+
}
|
|
9013
|
+
}
|
|
9014
|
+
};
|
|
9015
|
+
if (types.EIP712Domain && domain) {
|
|
9016
|
+
if (typeof domain !== "object")
|
|
9017
|
+
throw new InvalidDomainError({ domain });
|
|
9018
|
+
validateData(types.EIP712Domain, domain);
|
|
9019
|
+
}
|
|
9020
|
+
if (primaryType !== "EIP712Domain") {
|
|
9021
|
+
if (types[primaryType])
|
|
9022
|
+
validateData(types[primaryType], message);
|
|
9023
|
+
else
|
|
9024
|
+
throw new InvalidPrimaryTypeError({ primaryType, types });
|
|
9025
|
+
}
|
|
9026
|
+
}
|
|
9027
|
+
function getTypesForEIP712Domain({ domain }) {
|
|
9028
|
+
return [
|
|
9029
|
+
typeof domain?.name === "string" && { name: "name", type: "string" },
|
|
9030
|
+
domain?.version && { name: "version", type: "string" },
|
|
9031
|
+
(typeof domain?.chainId === "number" || typeof domain?.chainId === "bigint") && {
|
|
9032
|
+
name: "chainId",
|
|
9033
|
+
type: "uint256"
|
|
9034
|
+
},
|
|
9035
|
+
domain?.verifyingContract && {
|
|
9036
|
+
name: "verifyingContract",
|
|
9037
|
+
type: "address"
|
|
9038
|
+
},
|
|
9039
|
+
domain?.salt && { name: "salt", type: "bytes32" }
|
|
9040
|
+
].filter(Boolean);
|
|
9041
|
+
}
|
|
9042
|
+
function validateReference(type) {
|
|
9043
|
+
if (type === "address" || type === "bool" || type === "string" || type.startsWith("bytes") || type.startsWith("uint") || type.startsWith("int"))
|
|
9044
|
+
throw new InvalidStructTypeError({ type });
|
|
9045
|
+
}
|
|
9046
|
+
|
|
9047
|
+
// ../../node_modules/viem/_esm/utils/signature/hashTypedData.js
|
|
9048
|
+
function hashTypedData(parameters) {
|
|
9049
|
+
const { domain = {}, message, primaryType } = parameters;
|
|
9050
|
+
const types = {
|
|
9051
|
+
EIP712Domain: getTypesForEIP712Domain({ domain }),
|
|
9052
|
+
...parameters.types
|
|
9053
|
+
};
|
|
9054
|
+
validateTypedData({
|
|
9055
|
+
domain,
|
|
9056
|
+
message,
|
|
9057
|
+
primaryType,
|
|
9058
|
+
types
|
|
9059
|
+
});
|
|
9060
|
+
const parts = ["0x1901"];
|
|
9061
|
+
if (domain)
|
|
9062
|
+
parts.push(hashDomain({
|
|
9063
|
+
domain,
|
|
9064
|
+
types
|
|
9065
|
+
}));
|
|
9066
|
+
if (primaryType !== "EIP712Domain")
|
|
9067
|
+
parts.push(hashStruct({
|
|
9068
|
+
data: message,
|
|
9069
|
+
primaryType,
|
|
9070
|
+
types
|
|
9071
|
+
}));
|
|
9072
|
+
return keccak256(concat(parts));
|
|
9073
|
+
}
|
|
9074
|
+
function hashDomain({ domain, types }) {
|
|
9075
|
+
return hashStruct({
|
|
9076
|
+
data: domain,
|
|
9077
|
+
primaryType: "EIP712Domain",
|
|
9078
|
+
types
|
|
9079
|
+
});
|
|
9080
|
+
}
|
|
9081
|
+
function hashStruct({ data, primaryType, types }) {
|
|
9082
|
+
const encoded = encodeData({
|
|
9083
|
+
data,
|
|
9084
|
+
primaryType,
|
|
9085
|
+
types
|
|
9086
|
+
});
|
|
9087
|
+
return keccak256(encoded);
|
|
9088
|
+
}
|
|
9089
|
+
function encodeData({ data, primaryType, types }) {
|
|
9090
|
+
const encodedTypes = [{ type: "bytes32" }];
|
|
9091
|
+
const encodedValues = [hashType({ primaryType, types })];
|
|
9092
|
+
for (const field of types[primaryType]) {
|
|
9093
|
+
const [type, value] = encodeField({
|
|
9094
|
+
types,
|
|
9095
|
+
name: field.name,
|
|
9096
|
+
type: field.type,
|
|
9097
|
+
value: data[field.name]
|
|
9098
|
+
});
|
|
9099
|
+
encodedTypes.push(type);
|
|
9100
|
+
encodedValues.push(value);
|
|
9101
|
+
}
|
|
9102
|
+
return encodeAbiParameters(encodedTypes, encodedValues);
|
|
9103
|
+
}
|
|
9104
|
+
function hashType({ primaryType, types }) {
|
|
9105
|
+
const encodedHashType = toHex(encodeType({ primaryType, types }));
|
|
9106
|
+
return keccak256(encodedHashType);
|
|
9107
|
+
}
|
|
9108
|
+
function encodeType({ primaryType, types }) {
|
|
9109
|
+
let result = "";
|
|
9110
|
+
const unsortedDeps = findTypeDependencies({ primaryType, types });
|
|
9111
|
+
unsortedDeps.delete(primaryType);
|
|
9112
|
+
const deps = [primaryType, ...Array.from(unsortedDeps).sort()];
|
|
9113
|
+
for (const type of deps) {
|
|
9114
|
+
result += `${type}(${types[type].map(({ name, type: t }) => `${t} ${name}`).join(",")})`;
|
|
9115
|
+
}
|
|
9116
|
+
return result;
|
|
9117
|
+
}
|
|
9118
|
+
function findTypeDependencies({ primaryType: primaryType_, types }, results = /* @__PURE__ */ new Set()) {
|
|
9119
|
+
const match = primaryType_.match(/^\w*/u);
|
|
9120
|
+
const primaryType = match?.[0];
|
|
9121
|
+
if (results.has(primaryType) || types[primaryType] === void 0) {
|
|
9122
|
+
return results;
|
|
9123
|
+
}
|
|
9124
|
+
results.add(primaryType);
|
|
9125
|
+
for (const field of types[primaryType]) {
|
|
9126
|
+
findTypeDependencies({ primaryType: field.type, types }, results);
|
|
9127
|
+
}
|
|
9128
|
+
return results;
|
|
9129
|
+
}
|
|
9130
|
+
function encodeField({ types, name, type, value }) {
|
|
9131
|
+
if (types[type] !== void 0) {
|
|
9132
|
+
return [
|
|
9133
|
+
{ type: "bytes32" },
|
|
9134
|
+
keccak256(encodeData({ data: value, primaryType: type, types }))
|
|
9135
|
+
];
|
|
9136
|
+
}
|
|
9137
|
+
if (type === "bytes")
|
|
9138
|
+
return [{ type: "bytes32" }, keccak256(value)];
|
|
9139
|
+
if (type === "string")
|
|
9140
|
+
return [{ type: "bytes32" }, keccak256(toHex(value))];
|
|
9141
|
+
if (type.lastIndexOf("]") === type.length - 1) {
|
|
9142
|
+
const parsedType = type.slice(0, type.lastIndexOf("["));
|
|
9143
|
+
const typeValuePairs = value.map((item) => encodeField({
|
|
9144
|
+
name,
|
|
9145
|
+
type: parsedType,
|
|
9146
|
+
types,
|
|
9147
|
+
value: item
|
|
9148
|
+
}));
|
|
9149
|
+
return [
|
|
9150
|
+
{ type: "bytes32" },
|
|
9151
|
+
keccak256(encodeAbiParameters(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v)))
|
|
9152
|
+
];
|
|
9153
|
+
}
|
|
9154
|
+
return [{ type }, value];
|
|
9155
|
+
}
|
|
9156
|
+
|
|
9157
|
+
// ../../node_modules/viem/_esm/errors/unit.js
|
|
9158
|
+
init_base();
|
|
9159
|
+
var InvalidDecimalNumberError = class extends BaseError {
|
|
9160
|
+
constructor({ value }) {
|
|
9161
|
+
super(`Number \`${value}\` is not a valid decimal number.`, {
|
|
9162
|
+
name: "InvalidDecimalNumberError"
|
|
9163
|
+
});
|
|
9164
|
+
}
|
|
9165
|
+
};
|
|
9166
|
+
|
|
9167
|
+
// ../../node_modules/viem/_esm/utils/unit/parseUnits.js
|
|
9168
|
+
function parseUnits(value, decimals) {
|
|
9169
|
+
if (!/^(-?)([0-9]*)\.?([0-9]*)$/.test(value))
|
|
9170
|
+
throw new InvalidDecimalNumberError({ value });
|
|
9171
|
+
let [integer, fraction = "0"] = value.split(".");
|
|
9172
|
+
const negative = integer.startsWith("-");
|
|
9173
|
+
if (negative)
|
|
9174
|
+
integer = integer.slice(1);
|
|
9175
|
+
fraction = fraction.replace(/(0+)$/, "");
|
|
9176
|
+
if (decimals === 0) {
|
|
9177
|
+
if (Math.round(Number(`.${fraction}`)) === 1)
|
|
9178
|
+
integer = `${BigInt(integer) + 1n}`;
|
|
9179
|
+
fraction = "";
|
|
9180
|
+
} else if (fraction.length > decimals) {
|
|
9181
|
+
const [left, unit, right] = [
|
|
9182
|
+
fraction.slice(0, decimals - 1),
|
|
9183
|
+
fraction.slice(decimals - 1, decimals),
|
|
9184
|
+
fraction.slice(decimals)
|
|
9185
|
+
];
|
|
9186
|
+
const rounded = Math.round(Number(`${unit}.${right}`));
|
|
9187
|
+
if (rounded > 9)
|
|
9188
|
+
fraction = `${BigInt(left) + BigInt(1)}0`.padStart(left.length + 1, "0");
|
|
9189
|
+
else
|
|
9190
|
+
fraction = `${left}${rounded}`;
|
|
9191
|
+
if (fraction.length > decimals) {
|
|
9192
|
+
fraction = fraction.slice(1);
|
|
9193
|
+
integer = `${BigInt(integer) + 1n}`;
|
|
9194
|
+
}
|
|
9195
|
+
fraction = fraction.slice(0, decimals);
|
|
9196
|
+
} else {
|
|
9197
|
+
fraction = fraction.padEnd(decimals, "0");
|
|
9198
|
+
}
|
|
9199
|
+
return BigInt(`${negative ? "-" : ""}${integer}${fraction}`);
|
|
9200
|
+
}
|
|
9201
|
+
|
|
9202
|
+
// ../../node_modules/viem/_esm/index.js
|
|
9203
|
+
init_formatUnits();
|
|
9204
|
+
|
|
9205
|
+
// ../../packages/core/chain/dist/amount/toChainAmount.js
|
|
9206
|
+
var ChainAmountParseError = class extends Error {
|
|
9207
|
+
name = "ChainAmountParseError";
|
|
9208
|
+
constructor(message) {
|
|
9209
|
+
super(message);
|
|
9210
|
+
}
|
|
9211
|
+
};
|
|
9212
|
+
var SCIENTIFIC_DECIMAL = /^([+-]?)(?:(\d+)\.?(\d*)|\.(\d+))[eE]([+-]?\d+)$/i;
|
|
9213
|
+
var MAX_SCALE_ABS = 10000n;
|
|
9214
|
+
var padFractionDigits = (frac, totalLen) => {
|
|
9215
|
+
const need = totalLen - BigInt(frac.length);
|
|
9216
|
+
if (need <= 0n) {
|
|
9217
|
+
return frac;
|
|
9218
|
+
}
|
|
9219
|
+
if (need <= BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
9220
|
+
return `${"0".repeat(Number(need))}${frac}`;
|
|
9221
|
+
}
|
|
9222
|
+
let out = frac;
|
|
9223
|
+
while (BigInt(out.length) < totalLen) {
|
|
9224
|
+
out = `0${out}`;
|
|
9225
|
+
}
|
|
9226
|
+
return out;
|
|
9227
|
+
};
|
|
9228
|
+
var expandScientificNotationToDecimalString = (s) => {
|
|
9229
|
+
const m = SCIENTIFIC_DECIMAL.exec(s.trim());
|
|
9230
|
+
if (!m) {
|
|
9231
|
+
throw new ChainAmountParseError(`Invalid amount: "${s}"`);
|
|
9232
|
+
}
|
|
9233
|
+
const signNeg = m[1] === "-";
|
|
9234
|
+
let digitStr;
|
|
9235
|
+
let fracLen;
|
|
9236
|
+
if (m[4] !== void 0) {
|
|
9237
|
+
digitStr = m[4];
|
|
9238
|
+
fracLen = digitStr.length;
|
|
9239
|
+
} else {
|
|
9240
|
+
digitStr = `${m[2] ?? ""}${m[3] ?? ""}`;
|
|
9241
|
+
fracLen = (m[3] ?? "").length;
|
|
9242
|
+
}
|
|
9243
|
+
if (!/^\d+$/.test(digitStr)) {
|
|
9244
|
+
throw new ChainAmountParseError(`Invalid amount: "${s}"`);
|
|
9245
|
+
}
|
|
9246
|
+
const expStr = m[5] ?? "";
|
|
9247
|
+
if (expStr === "" || expStr === "+" || expStr === "-") {
|
|
9248
|
+
throw new ChainAmountParseError(`Invalid amount: "${s}"`);
|
|
9249
|
+
}
|
|
9250
|
+
const allDigits = BigInt(digitStr);
|
|
9251
|
+
const exp = BigInt(expStr);
|
|
9252
|
+
const scale = exp - BigInt(fracLen);
|
|
9253
|
+
const scaleAbs = scale < 0n ? -scale : scale;
|
|
9254
|
+
if (scaleAbs > MAX_SCALE_ABS) {
|
|
9255
|
+
throw new ChainAmountParseError(`Amount exponent out of supported range: "${s}"`);
|
|
9256
|
+
}
|
|
9257
|
+
let absResult;
|
|
9258
|
+
if (scale >= 0n) {
|
|
9259
|
+
const mult = 10n ** scale;
|
|
9260
|
+
absResult = (allDigits * mult).toString();
|
|
9261
|
+
} else {
|
|
9262
|
+
const k = -scale;
|
|
9263
|
+
const divisor = 10n ** k;
|
|
9264
|
+
const intPart = allDigits / divisor;
|
|
9265
|
+
const rem = allDigits % divisor;
|
|
9266
|
+
const frac = padFractionDigits(rem.toString(), k);
|
|
9267
|
+
absResult = intPart === 0n ? `0.${frac}` : `${intPart.toString()}.${frac}`;
|
|
9268
|
+
}
|
|
9269
|
+
if (signNeg && allDigits !== 0n) {
|
|
9270
|
+
return `-${absResult}`;
|
|
9271
|
+
}
|
|
9272
|
+
return absResult;
|
|
9273
|
+
};
|
|
9274
|
+
var formatNumberAmount = (amount) => {
|
|
9275
|
+
const str2 = amount.toString();
|
|
9276
|
+
return /[eE]/.test(str2) ? expandScientificNotationToDecimalString(str2) : str2;
|
|
9277
|
+
};
|
|
9278
|
+
var truncateToDecimals = (s, decimals) => {
|
|
9279
|
+
const dotIdx = s.indexOf(".");
|
|
9280
|
+
if (dotIdx === -1)
|
|
9281
|
+
return s;
|
|
9282
|
+
if (decimals === 0)
|
|
9283
|
+
return s.slice(0, dotIdx);
|
|
9284
|
+
const fracPart = s.slice(dotIdx + 1);
|
|
9285
|
+
if (fracPart.length <= decimals)
|
|
9286
|
+
return s;
|
|
9287
|
+
return `${s.slice(0, dotIdx + 1)}${fracPart.slice(0, decimals)}`;
|
|
9288
|
+
};
|
|
9289
|
+
var toChainAmount = (amount, decimals) => {
|
|
9290
|
+
if (typeof amount === "string") {
|
|
9291
|
+
const trimmed = amount.trim();
|
|
9292
|
+
if (!trimmed) {
|
|
9293
|
+
throw new ChainAmountParseError("Amount cannot be empty");
|
|
9294
|
+
}
|
|
9295
|
+
if (/[eE]/.test(trimmed)) {
|
|
9296
|
+
const expanded = expandScientificNotationToDecimalString(trimmed);
|
|
9297
|
+
return parseUnits(truncateToDecimals(expanded, decimals), decimals);
|
|
9298
|
+
}
|
|
9299
|
+
return parseUnits(truncateToDecimals(trimmed, decimals), decimals);
|
|
9300
|
+
}
|
|
9301
|
+
return parseUnits(truncateToDecimals(formatNumberAmount(amount), decimals), decimals);
|
|
9302
|
+
};
|
|
9303
|
+
|
|
9304
|
+
// src/commands/swap.ts
|
|
9305
|
+
async function executeSwapChains(ctx2) {
|
|
9306
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9307
|
+
const spinner = createSpinner("Loading supported swap chains...");
|
|
9308
|
+
const chains = await vault.getSupportedSwapChains();
|
|
9309
|
+
spinner.succeed("Swap chains loaded");
|
|
9310
|
+
if (isJsonOutput()) {
|
|
9311
|
+
outputJson({ swapChains: [...chains] });
|
|
9312
|
+
return chains;
|
|
9313
|
+
}
|
|
9314
|
+
displaySwapChains(chains);
|
|
9315
|
+
return chains;
|
|
9316
|
+
}
|
|
9317
|
+
async function executeSwapQuote(ctx2, options) {
|
|
9318
|
+
const isMax = options.amount === "max";
|
|
9319
|
+
const amount = normalizeSwapAmount(options.amount);
|
|
9320
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9321
|
+
const spinner = createSpinner("Getting swap quote...");
|
|
9322
|
+
const result = await vault.swap({
|
|
9323
|
+
fromChain: options.fromChain,
|
|
9324
|
+
fromSymbol: options.fromToken || "",
|
|
9325
|
+
toChain: options.toChain,
|
|
9326
|
+
toSymbol: options.toToken || "",
|
|
9327
|
+
amount,
|
|
9328
|
+
dryRun: true
|
|
9329
|
+
});
|
|
9330
|
+
if (!result.dryRun) throw new Error("unreachable");
|
|
9331
|
+
spinner.succeed("Quote received");
|
|
9332
|
+
const quote = result.quote;
|
|
9333
|
+
const semanticAmount = isMax ? amount : normalizeSwapAmount(amount, quote.fromCoin.decimals);
|
|
9334
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : semanticAmount;
|
|
9335
|
+
if (isJsonOutput()) {
|
|
9336
|
+
outputJson({
|
|
9337
|
+
fromChain: options.fromChain,
|
|
9338
|
+
toChain: options.toChain,
|
|
9339
|
+
amount: semanticAmount,
|
|
9340
|
+
isMax,
|
|
9341
|
+
quote
|
|
9342
|
+
});
|
|
9343
|
+
return quote;
|
|
9344
|
+
}
|
|
9345
|
+
const feeBalance = await vault.balance(options.fromChain);
|
|
9346
|
+
const discountTier = await vault.getDiscountTier();
|
|
9347
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
9348
|
+
fromDecimals: quote.fromCoin.decimals,
|
|
9349
|
+
toDecimals: quote.toCoin.decimals,
|
|
9350
|
+
feeDecimals: feeBalance.decimals,
|
|
9351
|
+
feeSymbol: feeBalance.symbol,
|
|
9352
|
+
discountTier
|
|
9353
|
+
});
|
|
9354
|
+
info('\nTo execute this swap, use the "swap" command');
|
|
9355
|
+
return quote;
|
|
9356
|
+
}
|
|
9357
|
+
var SWAP_AMOUNT_CANONICAL_DECIMALS = 1e4;
|
|
9358
|
+
function normalizeSwapAmount(amount, decimals = SWAP_AMOUNT_CANONICAL_DECIMALS) {
|
|
9359
|
+
if (amount === "max") return amount;
|
|
9360
|
+
try {
|
|
9361
|
+
const chainAmount = toChainAmount(amount, decimals);
|
|
9362
|
+
if (chainAmount <= 0n) throw new Error("Invalid amount");
|
|
9363
|
+
return formatUnits(chainAmount, decimals);
|
|
9364
|
+
} catch {
|
|
9365
|
+
throw new Error("Invalid amount");
|
|
9366
|
+
}
|
|
9367
|
+
}
|
|
9368
|
+
function toSwapRequest(options, amount, dryRun) {
|
|
9369
|
+
return {
|
|
9370
|
+
fromChain: options.fromChain,
|
|
9371
|
+
fromSymbol: options.fromToken || "",
|
|
9372
|
+
toChain: options.toChain,
|
|
9373
|
+
toSymbol: options.toToken || "",
|
|
9374
|
+
amount,
|
|
9375
|
+
...options.slippage !== void 0 && { slippageTolerance: options.slippage },
|
|
9376
|
+
...dryRun && { dryRun: true }
|
|
9377
|
+
};
|
|
9378
|
+
}
|
|
9379
|
+
function toDryRunResult(options, quote, fromAmountRaw) {
|
|
9380
|
+
const result = {
|
|
9381
|
+
dryRun: true,
|
|
9382
|
+
fromChain: String(options.fromChain),
|
|
9383
|
+
fromToken: quote.fromCoin.ticker,
|
|
9384
|
+
toChain: String(options.toChain),
|
|
9385
|
+
toToken: quote.toCoin.ticker,
|
|
9386
|
+
inputAmount: fromAmountRaw,
|
|
9387
|
+
...options.amount === "max" && { isMax: true },
|
|
9388
|
+
estimatedOutput: formatBigintAmount(quote.estimatedOutput, quote.toCoin.decimals),
|
|
9389
|
+
provider: quote.provider
|
|
9390
|
+
};
|
|
9391
|
+
if (quote.estimatedOutputFiat != null) result.estimatedOutputFiat = parseFloat(quote.estimatedOutputFiat.toFixed(2));
|
|
9392
|
+
if (quote.requiresApproval) result.requiresApproval = true;
|
|
9393
|
+
if (quote.warnings?.length) result.warnings = [...quote.warnings];
|
|
9394
|
+
return result;
|
|
9395
|
+
}
|
|
9396
|
+
function displayDryRunResult(result) {
|
|
9397
|
+
info(`
|
|
9398
|
+
Dry-run preview:`);
|
|
9399
|
+
info(` From: ${result.inputAmount} ${result.fromToken} (${result.fromChain})`);
|
|
9400
|
+
info(` To: ${result.estimatedOutput} ${result.toToken} (${result.toChain})`);
|
|
9401
|
+
info(` Provider: ${result.provider}`);
|
|
9402
|
+
if (result.estimatedOutputFiat != null) info(` Est. value (USD): $${result.estimatedOutputFiat}`);
|
|
9403
|
+
if (result.requiresApproval) info(` Requires approval: yes`);
|
|
9404
|
+
if (result.warnings?.length) result.warnings.forEach((w) => warn(` Warning: ${w}`));
|
|
9405
|
+
}
|
|
9406
|
+
function refuseSwapWhenNonInteractive() {
|
|
9407
|
+
throw new ConfirmationRequiredError(
|
|
9408
|
+
"Swap requires confirmation.",
|
|
9409
|
+
"Pass --yes to confirm, or --dry-run to preview without signing."
|
|
9410
|
+
);
|
|
9411
|
+
}
|
|
9412
|
+
async function confirmSwapIfNeeded(options) {
|
|
9413
|
+
if (options.yes) return;
|
|
9414
|
+
if (isNonInteractive()) {
|
|
9415
|
+
refuseSwapWhenNonInteractive();
|
|
9416
|
+
}
|
|
9417
|
+
const confirmed = await confirmSwap();
|
|
9418
|
+
if (!confirmed) {
|
|
9419
|
+
throw new ConfirmationRequiredError("Swap declined at the confirmation prompt");
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
async function executeSwap(ctx2, options) {
|
|
9423
|
+
const amountStr = normalizeSwapAmount(options.amount);
|
|
9424
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9425
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9426
|
+
refuseSwapWhenNonInteractive();
|
|
9427
|
+
}
|
|
9428
|
+
const quoteSpinner = createSpinner("Getting swap quote...");
|
|
9429
|
+
const dryResult = await vault.swap(toSwapRequest(options, amountStr, true));
|
|
9430
|
+
if (!dryResult.dryRun) throw new Error("unreachable");
|
|
9431
|
+
quoteSpinner.succeed("Quote received");
|
|
9432
|
+
const quote = dryResult.quote;
|
|
9433
|
+
const semanticAmount = options.amount === "max" ? amountStr : normalizeSwapAmount(amountStr, quote.fromCoin.decimals);
|
|
9434
|
+
const fromAmountRaw = options.amount === "max" ? formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals) : semanticAmount;
|
|
9435
|
+
const fromAmountDisplay = options.amount === "max" ? `${fromAmountRaw} (max)` : fromAmountRaw;
|
|
9436
|
+
if (options.dryRun) {
|
|
9437
|
+
const result = toDryRunResult(options, quote, fromAmountRaw);
|
|
9438
|
+
if (isJsonOutput()) {
|
|
9439
|
+
outputJson(result);
|
|
9440
|
+
} else {
|
|
9441
|
+
displayDryRunResult(result);
|
|
9442
|
+
}
|
|
9443
|
+
return result;
|
|
9444
|
+
}
|
|
9445
|
+
const feeBalance = await vault.balance(options.fromChain);
|
|
9446
|
+
const discountTier = await vault.getDiscountTier();
|
|
9447
|
+
if (!isJsonOutput()) {
|
|
9448
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
9449
|
+
fromDecimals: quote.fromCoin.decimals,
|
|
9450
|
+
toDecimals: quote.toCoin.decimals,
|
|
9451
|
+
feeDecimals: feeBalance.decimals,
|
|
9452
|
+
feeSymbol: feeBalance.symbol,
|
|
9453
|
+
discountTier
|
|
9454
|
+
});
|
|
9455
|
+
}
|
|
9456
|
+
await confirmSwapIfNeeded(options);
|
|
9457
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
9458
|
+
const intent = buildSwapBroadcastIntent(vault, {
|
|
9459
|
+
fromChain: options.fromChain,
|
|
9460
|
+
toChain: options.toChain,
|
|
9461
|
+
fromToken: options.fromToken,
|
|
9462
|
+
toToken: options.toToken,
|
|
9463
|
+
amount: fromAmountRaw,
|
|
9464
|
+
isMax: options.amount === "max"
|
|
9465
|
+
});
|
|
9466
|
+
let signSpinner;
|
|
9467
|
+
try {
|
|
9468
|
+
const broadcast = await guardedBroadcast(intent, options.force ?? false, async () => {
|
|
9469
|
+
signSpinner = createSpinner("Signing swap transaction...");
|
|
9470
|
+
vault.on("signingProgress", ({ step }) => {
|
|
9471
|
+
if (signSpinner) signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
9472
|
+
});
|
|
9473
|
+
const result = await vault.swap(toSwapRequest(options, semanticAmount));
|
|
9474
|
+
if (result.dryRun) throw new Error("unreachable");
|
|
9475
|
+
return result;
|
|
9476
|
+
});
|
|
9477
|
+
signSpinner?.succeed(`Swap broadcast: ${broadcast.txHash}`);
|
|
9478
|
+
if (isJsonOutput()) {
|
|
9479
|
+
outputJson({
|
|
9480
|
+
txHash: broadcast.txHash,
|
|
9481
|
+
fromChain: options.fromChain,
|
|
9482
|
+
toChain: options.toChain,
|
|
9483
|
+
quote
|
|
9484
|
+
});
|
|
9485
|
+
} else {
|
|
9486
|
+
displaySwapResult(options.fromChain, options.toChain, broadcast.txHash, quote, quote.toCoin.decimals);
|
|
9487
|
+
}
|
|
9488
|
+
return { txHash: broadcast.txHash, quote };
|
|
9489
|
+
} catch (err) {
|
|
9490
|
+
signSpinner?.stop();
|
|
9491
|
+
throw err;
|
|
9492
|
+
} finally {
|
|
9493
|
+
vault.removeAllListeners("signingProgress");
|
|
9494
|
+
}
|
|
9495
|
+
}
|
|
9496
|
+
|
|
9497
|
+
// src/commands/settings.ts
|
|
9498
|
+
import { Chain as Chain9, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
9499
|
+
import chalk6 from "chalk";
|
|
9500
|
+
async function executeCurrency(ctx2, newCurrency) {
|
|
9501
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9502
|
+
if (!newCurrency) {
|
|
9503
|
+
const currentCurrency = vault.currency;
|
|
9504
|
+
const currencyName2 = fiatCurrencyNameRecord3[currentCurrency];
|
|
9505
|
+
printResult(chalk6.cyan("\nCurrent Currency Preference:"));
|
|
9506
|
+
printResult(` ${chalk6.green(currentCurrency.toUpperCase())} - ${currencyName2}`);
|
|
9507
|
+
info(chalk6.gray(`
|
|
9508
|
+
Supported currencies: ${fiatCurrencies2.join(", ")}`));
|
|
9509
|
+
info(chalk6.gray('Use "vultisig currency <code>" to change'));
|
|
9510
|
+
return currentCurrency;
|
|
9097
9511
|
}
|
|
9098
9512
|
const currency = newCurrency.toLowerCase();
|
|
9099
9513
|
if (!fiatCurrencies2.includes(currency)) {
|
|
@@ -9682,540 +10096,273 @@ var AskInterface = class {
|
|
|
9682
10096
|
`);
|
|
9683
10097
|
} else {
|
|
9684
10098
|
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;
|
|
9780
|
-
}
|
|
9781
|
-
}
|
|
9782
|
-
throw lastError || new Error("Authentication failed after all attempts");
|
|
9783
|
-
}
|
|
9784
|
-
|
|
9785
|
-
// src/agent/cards.ts
|
|
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
|
-
};
|
|
9799
|
-
}
|
|
9800
|
-
function stripControlChars(s) {
|
|
9801
|
-
let out = "";
|
|
9802
|
-
for (const ch of s) {
|
|
9803
|
-
const code = ch.codePointAt(0) ?? 0;
|
|
9804
|
-
if (code <= 31 || code >= 127 && code <= 159) continue;
|
|
9805
|
-
out += ch;
|
|
9806
|
-
}
|
|
9807
|
-
return out;
|
|
9808
|
-
}
|
|
9809
|
-
function asString(v) {
|
|
9810
|
-
return typeof v === "string" ? stripControlChars(v) : "";
|
|
9811
|
-
}
|
|
9812
|
-
function parseToken(v) {
|
|
9813
|
-
if (!v || typeof v !== "object") return null;
|
|
9814
|
-
const o = v;
|
|
9815
|
-
const symbol = asString(o.symbol);
|
|
9816
|
-
const amountDecimal = asString(o.amountDecimal);
|
|
9817
|
-
if (!symbol && !amountDecimal) return null;
|
|
9818
|
-
const token = { symbol, amountDecimal };
|
|
9819
|
-
const amountUsd = asString(o.amountUsd);
|
|
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 };
|
|
9831
|
-
}
|
|
9832
|
-
function parseBalanceSummaryEnvelope(value) {
|
|
9833
|
-
if (!value || typeof value !== "object") return null;
|
|
9834
|
-
const o = value;
|
|
9835
|
-
if (o.surface !== "balance_summary") return null;
|
|
9836
|
-
if (!Array.isArray(o.accounts)) return null;
|
|
9837
|
-
const accounts = o.accounts.map(parseAccount).filter((a) => a !== null);
|
|
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;
|
|
9843
|
-
}
|
|
9844
|
-
return card;
|
|
9845
|
-
}
|
|
9846
|
-
function matchBrace(text, start) {
|
|
9847
|
-
let depth = 0;
|
|
9848
|
-
let inString = false;
|
|
9849
|
-
let escaped = false;
|
|
9850
|
-
for (let i = start; i < text.length; i++) {
|
|
9851
|
-
const ch = text[i];
|
|
9852
|
-
if (inString) {
|
|
9853
|
-
if (escaped) escaped = false;
|
|
9854
|
-
else if (ch === "\\") escaped = true;
|
|
9855
|
-
else if (ch === '"') inString = false;
|
|
9856
|
-
continue;
|
|
9857
|
-
}
|
|
9858
|
-
if (ch === '"') inString = true;
|
|
9859
|
-
else if (ch === "{") depth++;
|
|
9860
|
-
else if (ch === "}") {
|
|
9861
|
-
depth--;
|
|
9862
|
-
if (depth === 0) return i;
|
|
9863
|
-
}
|
|
9864
|
-
}
|
|
9865
|
-
return -1;
|
|
9866
|
-
}
|
|
9867
|
-
function extractBalanceSummaryFromText(content) {
|
|
9868
|
-
if (!content || !content.includes("balance_summary")) return null;
|
|
9869
|
-
if (content.length > 2e5) return null;
|
|
9870
|
-
for (let i = content.indexOf("{"); i !== -1; i = content.indexOf("{", i + 1)) {
|
|
9871
|
-
const end = matchBrace(content, i);
|
|
9872
|
-
if (end === -1) break;
|
|
9873
|
-
const blob = content.slice(i, end + 1);
|
|
9874
|
-
if (!blob.includes("balance_summary")) continue;
|
|
9875
|
-
let parsed;
|
|
9876
|
-
try {
|
|
9877
|
-
parsed = JSON.parse(blob);
|
|
9878
|
-
} catch {
|
|
9879
|
-
continue;
|
|
9880
|
-
}
|
|
9881
|
-
const card = parseBalanceSummaryEnvelope(parsed);
|
|
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 };
|
|
9887
|
-
}
|
|
9888
|
-
return null;
|
|
9889
|
-
}
|
|
9890
|
-
function shortenAddress(address) {
|
|
9891
|
-
if (!address || address === "\u2014") return address || "\u2014";
|
|
9892
|
-
if (address.length <= 16) return address;
|
|
9893
|
-
return `${address.slice(0, 8)}\u2026${address.slice(-6)}`;
|
|
9894
|
-
}
|
|
9895
|
-
function parseUsd(amountUsd) {
|
|
9896
|
-
if (!amountUsd) return null;
|
|
9897
|
-
const cleaned = amountUsd.replace(/[$,\s]/g, "");
|
|
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;
|
|
9916
|
-
}
|
|
9917
|
-
for (const token of account.tokens) {
|
|
9918
|
-
const usd = parseUsd(token.amountUsd);
|
|
9919
|
-
if (usd !== null) {
|
|
9920
|
-
total += usd;
|
|
9921
|
-
sawUsd = true;
|
|
10099
|
+
`);
|
|
10100
|
+
}
|
|
10101
|
+
return this.autoApprove;
|
|
9922
10102
|
}
|
|
9923
|
-
|
|
9924
|
-
const amount = token.amountDecimal.padStart(16);
|
|
9925
|
-
const usdCol = token.amountUsd ? chalk8.gray(` ${token.amountUsd}`) : "";
|
|
9926
|
-
lines.push(` ${chalk8.bold(symbol)}${amount}${usdCol}`);
|
|
9927
|
-
}
|
|
9928
|
-
}
|
|
9929
|
-
if (sawUsd) {
|
|
9930
|
-
lines.push(chalk8.gray(" " + "\u2500".repeat(36)));
|
|
9931
|
-
lines.push(` ${chalk8.bold("Total")} ${chalk8.green(formatUsd(total))}`);
|
|
9932
|
-
}
|
|
9933
|
-
return lines.join("\n");
|
|
9934
|
-
}
|
|
9935
|
-
|
|
9936
|
-
// src/agent/client.ts
|
|
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
|
|
9943
|
-
import {
|
|
9944
|
-
Chain as Chain11,
|
|
9945
|
-
chainFeeCoin,
|
|
9946
|
-
getChainKind as getChainKind3,
|
|
9947
|
-
resolveChainReference,
|
|
9948
|
-
VaultError as VaultError3,
|
|
9949
|
-
VaultErrorCode as VaultErrorCode3,
|
|
9950
|
-
Vultisig as VultisigSdk
|
|
9951
|
-
} from "@vultisig/sdk";
|
|
9952
|
-
|
|
9953
|
-
// ../../node_modules/viem/_esm/accounts/utils/publicKeyToAddress.js
|
|
9954
|
-
init_getAddress();
|
|
9955
|
-
init_keccak256();
|
|
9956
|
-
function publicKeyToAddress(publicKey) {
|
|
9957
|
-
const address = keccak256(`0x${publicKey.substring(4)}`).substring(26);
|
|
9958
|
-
return checksumAddress(`0x${address}`);
|
|
9959
|
-
}
|
|
9960
|
-
|
|
9961
|
-
// ../../node_modules/viem/_esm/utils/signature/recoverPublicKey.js
|
|
9962
|
-
init_isHex();
|
|
9963
|
-
init_size();
|
|
9964
|
-
init_fromHex();
|
|
9965
|
-
init_toHex();
|
|
9966
|
-
async function recoverPublicKey({ hash, signature }) {
|
|
9967
|
-
const hashHex = isHex(hash) ? hash : toHex(hash);
|
|
9968
|
-
const { secp256k1: secp256k12 } = await Promise.resolve().then(() => (init_secp256k1(), secp256k1_exports));
|
|
9969
|
-
const signature_ = (() => {
|
|
9970
|
-
if (typeof signature === "object" && "r" in signature && "s" in signature) {
|
|
9971
|
-
const { r, s, v, yParity } = signature;
|
|
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}`;
|
|
9985
|
-
}
|
|
9986
|
-
function toRecoveryBit(yParityOrV) {
|
|
9987
|
-
if (yParityOrV === 0 || yParityOrV === 1)
|
|
9988
|
-
return yParityOrV;
|
|
9989
|
-
if (yParityOrV === 27)
|
|
9990
|
-
return 0;
|
|
9991
|
-
if (yParityOrV === 28)
|
|
9992
|
-
return 1;
|
|
9993
|
-
throw new Error("Invalid yParityOrV value");
|
|
9994
|
-
}
|
|
9995
|
-
|
|
9996
|
-
// ../../node_modules/viem/_esm/utils/signature/recoverAddress.js
|
|
9997
|
-
async function recoverAddress({ hash, signature }) {
|
|
9998
|
-
return publicKeyToAddress(await recoverPublicKey({ hash, signature }));
|
|
9999
|
-
}
|
|
10000
|
-
|
|
10001
|
-
// ../../node_modules/viem/_esm/utils/signature/hashTypedData.js
|
|
10002
|
-
init_encodeAbiParameters();
|
|
10003
|
-
init_concat();
|
|
10004
|
-
init_toHex();
|
|
10005
|
-
init_keccak256();
|
|
10006
|
-
|
|
10007
|
-
// ../../node_modules/viem/_esm/utils/typedData.js
|
|
10008
|
-
init_abi();
|
|
10009
|
-
init_address();
|
|
10010
|
-
|
|
10011
|
-
// ../../node_modules/viem/_esm/errors/typedData.js
|
|
10012
|
-
init_stringify();
|
|
10013
|
-
init_base();
|
|
10014
|
-
var InvalidDomainError = class extends BaseError {
|
|
10015
|
-
constructor({ domain }) {
|
|
10016
|
-
super(`Invalid domain "${stringify(domain)}".`, {
|
|
10017
|
-
metaMessages: ["Must be a valid EIP-712 domain."]
|
|
10018
|
-
});
|
|
10103
|
+
};
|
|
10019
10104
|
}
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10105
|
+
/**
|
|
10106
|
+
* Send a message and wait for the complete response.
|
|
10107
|
+
* All tool calls and actions are executed automatically.
|
|
10108
|
+
*/
|
|
10109
|
+
async ask(message) {
|
|
10110
|
+
this.responseParts = [];
|
|
10111
|
+
this.toolCalls = [];
|
|
10112
|
+
this.transactions = [];
|
|
10113
|
+
this.cards = [];
|
|
10114
|
+
this.warnings = [];
|
|
10115
|
+
this.outcome = void 0;
|
|
10116
|
+
this.error = void 0;
|
|
10117
|
+
this.errorIsTerminal = false;
|
|
10118
|
+
const callbacks = this.getCallbacks();
|
|
10119
|
+
await this.session.sendMessage(message, callbacks);
|
|
10120
|
+
return this.partialResult();
|
|
10027
10121
|
}
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10122
|
+
/**
|
|
10123
|
+
* Snapshot of everything collected so far this turn. Identical to a normal
|
|
10124
|
+
* `ask()` return, but callable from a catch block when `ask()` THREW mid-turn
|
|
10125
|
+
* — e.g. the follow-up request that reports recent_actions back to the backend
|
|
10126
|
+
* fails (timeout/5xx/auth) AFTER a tx has already broadcast and `onTxStatus`
|
|
10127
|
+
* fired. Lets the caller still surface the already-broadcast tx hash in the
|
|
10128
|
+
* error envelope instead of stranding funds the turn just moved.
|
|
10129
|
+
*/
|
|
10130
|
+
partialResult() {
|
|
10131
|
+
return {
|
|
10132
|
+
sessionId: this.session.getConversationId() || "",
|
|
10133
|
+
response: this.responseParts[this.responseParts.length - 1] || "",
|
|
10134
|
+
toolCalls: this.toolCalls,
|
|
10135
|
+
transactions: this.transactions,
|
|
10136
|
+
cards: this.cards,
|
|
10137
|
+
warnings: this.warnings,
|
|
10138
|
+
error: this.error,
|
|
10139
|
+
...this.outcome ? { outcome: this.outcome } : {}
|
|
10140
|
+
};
|
|
10035
10141
|
}
|
|
10036
10142
|
};
|
|
10037
10143
|
|
|
10038
|
-
//
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
const
|
|
10045
|
-
const
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
if (
|
|
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
|
-
});
|
|
10144
|
+
// src/agent/auth.ts
|
|
10145
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
10146
|
+
import { Chain as Chain10, computePersonalSignHash, formatEcdsaSignature65 } from "@vultisig/sdk";
|
|
10147
|
+
async function authenticateVault(client, vault, password, maxAttempts = 3) {
|
|
10148
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
10149
|
+
const chainCode = vault.hexChainCode;
|
|
10150
|
+
const ethAddress = await vault.address(Chain10.Ethereum);
|
|
10151
|
+
const nonce = "0x" + randomBytes3(16).toString("hex");
|
|
10152
|
+
const expiresAt = new Date(Date.now() + 15 * 60 * 1e3).toISOString();
|
|
10153
|
+
const authMessage = JSON.stringify({
|
|
10154
|
+
message: "Sign into Vultisig Plugin Marketplace",
|
|
10155
|
+
nonce,
|
|
10156
|
+
expiresAt,
|
|
10157
|
+
address: ethAddress
|
|
10158
|
+
});
|
|
10159
|
+
const messageHash = computePersonalSignHash(authMessage);
|
|
10160
|
+
let lastError = null;
|
|
10161
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
10162
|
+
try {
|
|
10163
|
+
if (attempt > 1) {
|
|
10164
|
+
process.stderr.write(` Retry ${attempt}/${maxAttempts}...
|
|
10165
|
+
`);
|
|
10067
10166
|
}
|
|
10068
|
-
const
|
|
10069
|
-
if (
|
|
10070
|
-
|
|
10071
|
-
validateData(struct2, value);
|
|
10167
|
+
const signature = await vault.signBytes({ data: Buffer.from(messageHash), chain: Chain10.Ethereum }, {});
|
|
10168
|
+
if (signature.recovery === void 0) {
|
|
10169
|
+
throw new Error("Agent authentication requires an ECDSA recovery id");
|
|
10072
10170
|
}
|
|
10073
|
-
|
|
10074
|
-
|
|
10075
|
-
|
|
10076
|
-
|
|
10077
|
-
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10087
|
-
|
|
10088
|
-
|
|
10089
|
-
|
|
10090
|
-
|
|
10091
|
-
|
|
10092
|
-
|
|
10093
|
-
|
|
10094
|
-
|
|
10095
|
-
|
|
10096
|
-
|
|
10097
|
-
|
|
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 });
|
|
10171
|
+
const sigHex = formatEcdsaSignature65(signature.signature, signature.recovery);
|
|
10172
|
+
const authResponse = await client.authenticate({
|
|
10173
|
+
public_key: publicKey,
|
|
10174
|
+
chain_code_hex: chainCode,
|
|
10175
|
+
message: authMessage,
|
|
10176
|
+
signature: sigHex
|
|
10177
|
+
});
|
|
10178
|
+
return {
|
|
10179
|
+
token: authResponse.token,
|
|
10180
|
+
expiresAt: authResponse.expires_at,
|
|
10181
|
+
// Captured + persisted by the session token cache. The backend exposes
|
|
10182
|
+
// POST /auth/refresh to exchange it for a fresh access token without a
|
|
10183
|
+
// new MPC round; wiring that exchange is a future enhancement — today
|
|
10184
|
+
// the CLI re-auths via a full MPC re-sign (authenticateVault), which is
|
|
10185
|
+
// always available and avoids depending on refresh-token rotation.
|
|
10186
|
+
refreshToken: authResponse.refresh_token
|
|
10187
|
+
};
|
|
10188
|
+
} catch (err) {
|
|
10189
|
+
lastError = err;
|
|
10190
|
+
if (attempt < maxAttempts && err.message?.includes("timeout")) {
|
|
10191
|
+
continue;
|
|
10192
|
+
}
|
|
10193
|
+
throw err;
|
|
10194
|
+
}
|
|
10195
|
+
}
|
|
10196
|
+
throw lastError || new Error("Authentication failed after all attempts");
|
|
10105
10197
|
}
|
|
10106
10198
|
|
|
10107
|
-
//
|
|
10108
|
-
|
|
10109
|
-
|
|
10110
|
-
|
|
10111
|
-
|
|
10112
|
-
|
|
10199
|
+
// src/agent/cards.ts
|
|
10200
|
+
import chalk8 from "chalk";
|
|
10201
|
+
var CLI_SUPPORTED_SURFACES = ["balance_summary", "turn_outcome"];
|
|
10202
|
+
function parseTurnOutcome(raw) {
|
|
10203
|
+
if (!raw || typeof raw !== "object") return null;
|
|
10204
|
+
const kind = raw.kind;
|
|
10205
|
+
if (kind !== "success" && kind !== "blocked" && kind !== "refusal" && kind !== "error") return null;
|
|
10206
|
+
const code = raw.code;
|
|
10207
|
+
const detail = raw.detail;
|
|
10208
|
+
return {
|
|
10209
|
+
kind,
|
|
10210
|
+
...typeof code === "string" ? { code } : {},
|
|
10211
|
+
...typeof detail === "string" ? { detail } : {}
|
|
10113
10212
|
};
|
|
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
10213
|
}
|
|
10134
|
-
function
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
|
|
10139
|
-
|
|
10214
|
+
function stripControlChars(s) {
|
|
10215
|
+
let out = "";
|
|
10216
|
+
for (const ch of s) {
|
|
10217
|
+
const code = ch.codePointAt(0) ?? 0;
|
|
10218
|
+
if (code <= 31 || code >= 127 && code <= 159) continue;
|
|
10219
|
+
out += ch;
|
|
10220
|
+
}
|
|
10221
|
+
return out;
|
|
10140
10222
|
}
|
|
10141
|
-
function
|
|
10142
|
-
|
|
10143
|
-
data,
|
|
10144
|
-
primaryType,
|
|
10145
|
-
types
|
|
10146
|
-
});
|
|
10147
|
-
return keccak256(encoded);
|
|
10223
|
+
function asString(v) {
|
|
10224
|
+
return typeof v === "string" ? stripControlChars(v) : "";
|
|
10148
10225
|
}
|
|
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);
|
|
10226
|
+
function parseToken(v) {
|
|
10227
|
+
if (!v || typeof v !== "object") return null;
|
|
10228
|
+
const o = v;
|
|
10229
|
+
const symbol = asString(o.symbol);
|
|
10230
|
+
const amountDecimal = asString(o.amountDecimal);
|
|
10231
|
+
if (!symbol && !amountDecimal) return null;
|
|
10232
|
+
const token = { symbol, amountDecimal };
|
|
10233
|
+
const amountUsd = asString(o.amountUsd);
|
|
10234
|
+
if (amountUsd) token.amountUsd = amountUsd;
|
|
10235
|
+
return token;
|
|
10163
10236
|
}
|
|
10164
|
-
function
|
|
10165
|
-
|
|
10166
|
-
|
|
10237
|
+
function parseAccount(v) {
|
|
10238
|
+
if (!v || typeof v !== "object") return null;
|
|
10239
|
+
const o = v;
|
|
10240
|
+
const chainId = asString(o.chainId);
|
|
10241
|
+
if (!chainId) return null;
|
|
10242
|
+
const tokensRaw = Array.isArray(o.tokens) ? o.tokens : [];
|
|
10243
|
+
const tokens = tokensRaw.map(parseToken).filter((t) => t !== null);
|
|
10244
|
+
return { chainId, address: asString(o.address) || "\u2014", tokens };
|
|
10167
10245
|
}
|
|
10168
|
-
function
|
|
10169
|
-
|
|
10170
|
-
const
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10246
|
+
function parseBalanceSummaryEnvelope(value) {
|
|
10247
|
+
if (!value || typeof value !== "object") return null;
|
|
10248
|
+
const o = value;
|
|
10249
|
+
if (o.surface !== "balance_summary") return null;
|
|
10250
|
+
if (!Array.isArray(o.accounts)) return null;
|
|
10251
|
+
const accounts = o.accounts.map(parseAccount).filter((a) => a !== null);
|
|
10252
|
+
if (accounts.length === 0) return null;
|
|
10253
|
+
const card = { surface: "balance_summary", accounts };
|
|
10254
|
+
if (o.stale === true) {
|
|
10255
|
+
card.stale = true;
|
|
10256
|
+
if (typeof o.stale_secs === "number") card.staleSecs = o.stale_secs;
|
|
10175
10257
|
}
|
|
10176
|
-
return
|
|
10258
|
+
return card;
|
|
10177
10259
|
}
|
|
10178
|
-
function
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10260
|
+
function matchBrace(text, start) {
|
|
10261
|
+
let depth = 0;
|
|
10262
|
+
let inString = false;
|
|
10263
|
+
let escaped = false;
|
|
10264
|
+
for (let i = start; i < text.length; i++) {
|
|
10265
|
+
const ch = text[i];
|
|
10266
|
+
if (inString) {
|
|
10267
|
+
if (escaped) escaped = false;
|
|
10268
|
+
else if (ch === "\\") escaped = true;
|
|
10269
|
+
else if (ch === '"') inString = false;
|
|
10270
|
+
continue;
|
|
10271
|
+
}
|
|
10272
|
+
if (ch === '"') inString = true;
|
|
10273
|
+
else if (ch === "{") depth++;
|
|
10274
|
+
else if (ch === "}") {
|
|
10275
|
+
depth--;
|
|
10276
|
+
if (depth === 0) return i;
|
|
10277
|
+
}
|
|
10183
10278
|
}
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10279
|
+
return -1;
|
|
10280
|
+
}
|
|
10281
|
+
function extractBalanceSummaryFromText(content) {
|
|
10282
|
+
if (!content || !content.includes("balance_summary")) return null;
|
|
10283
|
+
if (content.length > 2e5) return null;
|
|
10284
|
+
for (let i = content.indexOf("{"); i !== -1; i = content.indexOf("{", i + 1)) {
|
|
10285
|
+
const end = matchBrace(content, i);
|
|
10286
|
+
if (end === -1) break;
|
|
10287
|
+
const blob = content.slice(i, end + 1);
|
|
10288
|
+
if (!blob.includes("balance_summary")) continue;
|
|
10289
|
+
let parsed;
|
|
10290
|
+
try {
|
|
10291
|
+
parsed = JSON.parse(blob);
|
|
10292
|
+
} catch {
|
|
10293
|
+
continue;
|
|
10294
|
+
}
|
|
10295
|
+
const card = parseBalanceSummaryEnvelope(parsed);
|
|
10296
|
+
if (!card) continue;
|
|
10297
|
+
const before = content.slice(0, i).replace(/```(?:json)?\s*$/i, "");
|
|
10298
|
+
const after = content.slice(end + 1).replace(/^\s*```/, "");
|
|
10299
|
+
const remainingText = (before + after).trim();
|
|
10300
|
+
return { card, remainingText };
|
|
10187
10301
|
}
|
|
10188
|
-
return
|
|
10302
|
+
return null;
|
|
10189
10303
|
}
|
|
10190
|
-
function
|
|
10191
|
-
if (
|
|
10192
|
-
|
|
10193
|
-
|
|
10194
|
-
|
|
10195
|
-
|
|
10304
|
+
function shortenAddress(address) {
|
|
10305
|
+
if (!address || address === "\u2014") return address || "\u2014";
|
|
10306
|
+
if (address.length <= 16) return address;
|
|
10307
|
+
return `${address.slice(0, 8)}\u2026${address.slice(-6)}`;
|
|
10308
|
+
}
|
|
10309
|
+
function parseUsd(amountUsd) {
|
|
10310
|
+
if (!amountUsd) return null;
|
|
10311
|
+
const cleaned = amountUsd.replace(/[$,\s]/g, "");
|
|
10312
|
+
if (!cleaned) return null;
|
|
10313
|
+
const n = Number(cleaned);
|
|
10314
|
+
return Number.isFinite(n) ? n : null;
|
|
10315
|
+
}
|
|
10316
|
+
function formatUsd(n) {
|
|
10317
|
+
return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
10318
|
+
}
|
|
10319
|
+
function renderBalanceSummaryCard(card) {
|
|
10320
|
+
const lines = [];
|
|
10321
|
+
const staleCue = card.stale ? chalk8.gray(` (stale${card.staleSecs ? ` ~${Math.round(card.staleSecs / 60)}m` : ""}, refreshing\u2026)`) : "";
|
|
10322
|
+
lines.push(chalk8.bold(" Balances") + staleCue);
|
|
10323
|
+
let total = 0;
|
|
10324
|
+
let sawUsd = false;
|
|
10325
|
+
for (const account of card.accounts) {
|
|
10326
|
+
lines.push(` ${chalk8.cyan(account.chainId)} ${chalk8.gray(`(${shortenAddress(account.address)})`)}`);
|
|
10327
|
+
if (account.tokens.length === 0) {
|
|
10328
|
+
lines.push(chalk8.gray(" (no balances)"));
|
|
10329
|
+
continue;
|
|
10330
|
+
}
|
|
10331
|
+
for (const token of account.tokens) {
|
|
10332
|
+
const usd = parseUsd(token.amountUsd);
|
|
10333
|
+
if (usd !== null) {
|
|
10334
|
+
total += usd;
|
|
10335
|
+
sawUsd = true;
|
|
10336
|
+
}
|
|
10337
|
+
const symbol = token.symbol.padEnd(10);
|
|
10338
|
+
const amount = token.amountDecimal.padStart(16);
|
|
10339
|
+
const usdCol = token.amountUsd ? chalk8.gray(` ${token.amountUsd}`) : "";
|
|
10340
|
+
lines.push(` ${chalk8.bold(symbol)}${amount}${usdCol}`);
|
|
10341
|
+
}
|
|
10196
10342
|
}
|
|
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
|
-
];
|
|
10343
|
+
if (sawUsd) {
|
|
10344
|
+
lines.push(chalk8.gray(" " + "\u2500".repeat(36)));
|
|
10345
|
+
lines.push(` ${chalk8.bold("Total")} ${chalk8.green(formatUsd(total))}`);
|
|
10213
10346
|
}
|
|
10214
|
-
return
|
|
10347
|
+
return lines.join("\n");
|
|
10215
10348
|
}
|
|
10216
10349
|
|
|
10217
|
-
//
|
|
10218
|
-
|
|
10350
|
+
// src/agent/client.ts
|
|
10351
|
+
import { randomUUID } from "node:crypto";
|
|
10352
|
+
|
|
10353
|
+
// src/agent/toolOutputSigning.ts
|
|
10354
|
+
import { getChainKind as getChainKind4 } from "@vultisig/sdk";
|
|
10355
|
+
|
|
10356
|
+
// src/agent/executor.ts
|
|
10357
|
+
import {
|
|
10358
|
+
Chain as Chain11,
|
|
10359
|
+
chainFeeCoin,
|
|
10360
|
+
getChainKind as getChainKind3,
|
|
10361
|
+
resolveChainReference,
|
|
10362
|
+
VaultError as VaultError3,
|
|
10363
|
+
VaultErrorCode as VaultErrorCode3,
|
|
10364
|
+
Vultisig as VultisigSdk
|
|
10365
|
+
} from "@vultisig/sdk";
|
|
10219
10366
|
|
|
10220
10367
|
// src/core/VaultStateStore.ts
|
|
10221
10368
|
import * as fs3 from "node:fs";
|
|
@@ -15132,7 +15279,7 @@ var cachedVersion = null;
|
|
|
15132
15279
|
function getVersion() {
|
|
15133
15280
|
if (cachedVersion) return cachedVersion;
|
|
15134
15281
|
if (true) {
|
|
15135
|
-
cachedVersion = "2.
|
|
15282
|
+
cachedVersion = "2.23.0";
|
|
15136
15283
|
return cachedVersion;
|
|
15137
15284
|
}
|
|
15138
15285
|
try {
|
|
@@ -16529,7 +16676,7 @@ Error: ${error2.message}`));
|
|
|
16529
16676
|
const [fromChainStr, toChainStr, amountStr, ...rest] = args;
|
|
16530
16677
|
const fromChain = findChainByName(fromChainStr) || fromChainStr;
|
|
16531
16678
|
const toChain = findChainByName(toChainStr) || toChainStr;
|
|
16532
|
-
const amount =
|
|
16679
|
+
const amount = amountStr;
|
|
16533
16680
|
let fromToken;
|
|
16534
16681
|
let toToken;
|
|
16535
16682
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -16555,7 +16702,7 @@ Error: ${error2.message}`));
|
|
|
16555
16702
|
const [fromChainStr, toChainStr, amountStr, ...rest] = args;
|
|
16556
16703
|
const fromChain = findChainByName(fromChainStr) || fromChainStr;
|
|
16557
16704
|
const toChain = findChainByName(toChainStr) || toChainStr;
|
|
16558
|
-
const amount =
|
|
16705
|
+
const amount = amountStr;
|
|
16559
16706
|
let fromToken;
|
|
16560
16707
|
let toToken;
|
|
16561
16708
|
let slippage;
|
|
@@ -17558,7 +17705,7 @@ Examples:
|
|
|
17558
17705
|
await executeSwapQuote(context, {
|
|
17559
17706
|
fromChain,
|
|
17560
17707
|
toChain,
|
|
17561
|
-
amount: options.max ? "max" :
|
|
17708
|
+
amount: options.max ? "max" : amountStr,
|
|
17562
17709
|
fromToken: options.fromToken,
|
|
17563
17710
|
toToken: options.toToken
|
|
17564
17711
|
});
|
|
@@ -17583,7 +17730,7 @@ See also: swap-quote, swap-chains, balance`
|
|
|
17583
17730
|
await executeSwap(context, {
|
|
17584
17731
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
17585
17732
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
17586
|
-
amount: options.max ? "max" :
|
|
17733
|
+
amount: options.max ? "max" : amountStr,
|
|
17587
17734
|
fromToken: options.fromToken,
|
|
17588
17735
|
toToken: options.toToken,
|
|
17589
17736
|
slippage: options.slippage ? parseFloat(options.slippage) : void 0,
|