@vultisig/cli 2.21.1 → 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 +58 -0
- package/dist/index.js +1423 -1226
- 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);
|
|
@@ -5121,7 +5121,15 @@ var EVM_PERMANENT_BROADCAST_INPUT_RE = /failed to decode signed transaction|coul
|
|
|
5121
5121
|
var UTXO_PERMANENT_BROADCAST_INPUT_RE = /\bTX decode failed\b/i;
|
|
5122
5122
|
var SOLANA_PERMANENT_BROADCAST_INPUT_RE = /failed to deserialize(?: transaction)?|failed to sanitize|(?:transaction )?signature verification (?:failed|failure)|non-base58 character|invalid base58/i;
|
|
5123
5123
|
var SUI_REQUIRED_FIELDS_GUARD_RE = /Sui broadcast requires JSON with "unsignedTx" and "signature" fields/i;
|
|
5124
|
-
var
|
|
5124
|
+
var SUI_PERMANENT_GRPC_STATUS = "INVALID_ARGUMENT";
|
|
5125
|
+
var SUI_PERMANENT_EXECUTION_MESSAGE_RE = /invalid (?:user )?signature|signature verification (?:failed|failure)|malformed transaction|invalid transaction(?: (?:data|bytes))?\b/i;
|
|
5126
|
+
var decodeRpcDetails = (details) => {
|
|
5127
|
+
try {
|
|
5128
|
+
return decodeURIComponent(details);
|
|
5129
|
+
} catch {
|
|
5130
|
+
return details;
|
|
5131
|
+
}
|
|
5132
|
+
};
|
|
5125
5133
|
var COSMOS_PERMANENT_SDK_CODES = {
|
|
5126
5134
|
2: /tx parse error/i,
|
|
5127
5135
|
4: /unauthorized|signature verification failed/i,
|
|
@@ -5151,7 +5159,8 @@ var permanentBroadcastInputClassifiers = {
|
|
|
5151
5159
|
sui: (err, details) => {
|
|
5152
5160
|
if (SUI_REQUIRED_FIELDS_GUARD_RE.test(details)) return true;
|
|
5153
5161
|
const rpcCode = err.originalError?.code;
|
|
5154
|
-
|
|
5162
|
+
if (rpcCode !== SUI_PERMANENT_GRPC_STATUS) return false;
|
|
5163
|
+
return SUI_PERMANENT_EXECUTION_MESSAGE_RE.test(details) || SUI_PERMANENT_EXECUTION_MESSAGE_RE.test(decodeRpcDetails(details));
|
|
5155
5164
|
},
|
|
5156
5165
|
cosmos: (_err, details) => isCosmosPermanentBroadcastInput(details)
|
|
5157
5166
|
};
|
|
@@ -8881,1332 +8890,1479 @@ Please specify more characters of the vault ID.`
|
|
|
8881
8890
|
throw new Error(`Vault not found: "${idOrName}"`);
|
|
8882
8891
|
}
|
|
8883
8892
|
|
|
8884
|
-
//
|
|
8885
|
-
|
|
8886
|
-
|
|
8887
|
-
|
|
8888
|
-
const
|
|
8889
|
-
|
|
8890
|
-
if (isJsonOutput()) {
|
|
8891
|
-
outputJson({ swapChains: [...chains] });
|
|
8892
|
-
return chains;
|
|
8893
|
-
}
|
|
8894
|
-
displaySwapChains(chains);
|
|
8895
|
-
return chains;
|
|
8896
|
-
}
|
|
8897
|
-
async function executeSwapQuote(ctx2, options) {
|
|
8898
|
-
const vault = await ctx2.ensureActiveVault();
|
|
8899
|
-
const isMax = options.amount === "max";
|
|
8900
|
-
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
8901
|
-
throw new Error("Invalid amount");
|
|
8902
|
-
}
|
|
8903
|
-
const spinner = createSpinner("Getting swap quote...");
|
|
8904
|
-
const result = await vault.swap({
|
|
8905
|
-
fromChain: options.fromChain,
|
|
8906
|
-
fromSymbol: options.fromToken || "",
|
|
8907
|
-
toChain: options.toChain,
|
|
8908
|
-
toSymbol: options.toToken || "",
|
|
8909
|
-
amount: isMax ? "max" : String(options.amount),
|
|
8910
|
-
dryRun: true
|
|
8911
|
-
});
|
|
8912
|
-
if (!result.dryRun) throw new Error("unreachable");
|
|
8913
|
-
spinner.succeed("Quote received");
|
|
8914
|
-
const quote = result.quote;
|
|
8915
|
-
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(options.amount);
|
|
8916
|
-
if (isJsonOutput()) {
|
|
8917
|
-
outputJson({
|
|
8918
|
-
fromChain: options.fromChain,
|
|
8919
|
-
toChain: options.toChain,
|
|
8920
|
-
amount: isMax ? "max" : options.amount,
|
|
8921
|
-
isMax,
|
|
8922
|
-
quote
|
|
8923
|
-
});
|
|
8924
|
-
return quote;
|
|
8925
|
-
}
|
|
8926
|
-
const feeBalance = await vault.balance(options.fromChain);
|
|
8927
|
-
const discountTier = await vault.getDiscountTier();
|
|
8928
|
-
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
8929
|
-
fromDecimals: quote.fromCoin.decimals,
|
|
8930
|
-
toDecimals: quote.toCoin.decimals,
|
|
8931
|
-
feeDecimals: feeBalance.decimals,
|
|
8932
|
-
feeSymbol: feeBalance.symbol,
|
|
8933
|
-
discountTier
|
|
8934
|
-
});
|
|
8935
|
-
info('\nTo execute this swap, use the "swap" command');
|
|
8936
|
-
return quote;
|
|
8937
|
-
}
|
|
8938
|
-
function validateSwapAmount(amount) {
|
|
8939
|
-
if (amount === "max") return;
|
|
8940
|
-
if (isNaN(amount) || amount <= 0) throw new Error("Invalid amount");
|
|
8941
|
-
}
|
|
8942
|
-
function getSwapAmountString(amount) {
|
|
8943
|
-
return amount === "max" ? "max" : String(amount);
|
|
8944
|
-
}
|
|
8945
|
-
function toSwapRequest(options, amount, dryRun) {
|
|
8946
|
-
return {
|
|
8947
|
-
fromChain: options.fromChain,
|
|
8948
|
-
fromSymbol: options.fromToken || "",
|
|
8949
|
-
toChain: options.toChain,
|
|
8950
|
-
toSymbol: options.toToken || "",
|
|
8951
|
-
amount,
|
|
8952
|
-
...options.slippage !== void 0 && { slippageTolerance: options.slippage },
|
|
8953
|
-
...dryRun && { dryRun: true }
|
|
8954
|
-
};
|
|
8955
|
-
}
|
|
8956
|
-
function toDryRunResult(options, quote, fromAmountRaw) {
|
|
8957
|
-
const result = {
|
|
8958
|
-
dryRun: true,
|
|
8959
|
-
fromChain: String(options.fromChain),
|
|
8960
|
-
fromToken: quote.fromCoin.ticker,
|
|
8961
|
-
toChain: String(options.toChain),
|
|
8962
|
-
toToken: quote.toCoin.ticker,
|
|
8963
|
-
inputAmount: fromAmountRaw,
|
|
8964
|
-
...options.amount === "max" && { isMax: true },
|
|
8965
|
-
estimatedOutput: formatBigintAmount(quote.estimatedOutput, quote.toCoin.decimals),
|
|
8966
|
-
provider: quote.provider
|
|
8967
|
-
};
|
|
8968
|
-
if (quote.estimatedOutputFiat != null) result.estimatedOutputFiat = parseFloat(quote.estimatedOutputFiat.toFixed(2));
|
|
8969
|
-
if (quote.requiresApproval) result.requiresApproval = true;
|
|
8970
|
-
if (quote.warnings?.length) result.warnings = [...quote.warnings];
|
|
8971
|
-
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}`);
|
|
8972
8899
|
}
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
8981
|
-
|
|
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}`;
|
|
8982
8925
|
}
|
|
8983
|
-
function
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
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");
|
|
8988
8934
|
}
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
}
|
|
8994
|
-
const confirmed = await confirmSwap();
|
|
8995
|
-
if (!confirmed) {
|
|
8996
|
-
throw new ConfirmationRequiredError("Swap declined at the confirmation prompt");
|
|
8997
|
-
}
|
|
8935
|
+
|
|
8936
|
+
// ../../node_modules/viem/_esm/utils/signature/recoverAddress.js
|
|
8937
|
+
async function recoverAddress({ hash, signature }) {
|
|
8938
|
+
return publicKeyToAddress(await recoverPublicKey({ hash, signature }));
|
|
8998
8939
|
}
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9004
|
-
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
}
|
|
9018
|
-
displayDryRunResult(result);
|
|
9019
|
-
}
|
|
9020
|
-
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
|
+
});
|
|
9021
8959
|
}
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
feeDecimals: feeBalance.decimals,
|
|
9029
|
-
feeSymbol: feeBalance.symbol,
|
|
9030
|
-
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`."]
|
|
9031
8966
|
});
|
|
9032
8967
|
}
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
toToken: options.toToken,
|
|
9040
|
-
amount: fromAmountRaw,
|
|
9041
|
-
isMax: options.amount === "max"
|
|
9042
|
-
});
|
|
9043
|
-
let signSpinner;
|
|
9044
|
-
try {
|
|
9045
|
-
const broadcast = await guardedBroadcast(intent, options.force ?? false, async () => {
|
|
9046
|
-
signSpinner = createSpinner("Signing swap transaction...");
|
|
9047
|
-
vault.on("signingProgress", ({ step }) => {
|
|
9048
|
-
if (signSpinner) signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
9049
|
-
});
|
|
9050
|
-
const result = await vault.swap(toSwapRequest(options, amountStr));
|
|
9051
|
-
if (result.dryRun) throw new Error("unreachable");
|
|
9052
|
-
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"
|
|
9053
8974
|
});
|
|
9054
|
-
signSpinner?.succeed(`Swap broadcast: ${broadcast.txHash}`);
|
|
9055
|
-
if (isJsonOutput()) {
|
|
9056
|
-
outputJson({
|
|
9057
|
-
txHash: broadcast.txHash,
|
|
9058
|
-
fromChain: options.fromChain,
|
|
9059
|
-
toChain: options.toChain,
|
|
9060
|
-
quote
|
|
9061
|
-
});
|
|
9062
|
-
} else {
|
|
9063
|
-
displaySwapResult(options.fromChain, options.toChain, broadcast.txHash, quote, quote.toCoin.decimals);
|
|
9064
|
-
}
|
|
9065
|
-
return { txHash: broadcast.txHash, quote };
|
|
9066
|
-
} catch (err) {
|
|
9067
|
-
signSpinner?.stop();
|
|
9068
|
-
throw err;
|
|
9069
|
-
} finally {
|
|
9070
|
-
vault.removeAllListeners("signingProgress");
|
|
9071
8975
|
}
|
|
9072
|
-
}
|
|
8976
|
+
};
|
|
9073
8977
|
|
|
9074
|
-
//
|
|
9075
|
-
|
|
9076
|
-
|
|
9077
|
-
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
|
|
9084
|
-
|
|
9085
|
-
|
|
9086
|
-
|
|
9087
|
-
|
|
9088
|
-
|
|
9089
|
-
|
|
9090
|
-
|
|
9091
|
-
|
|
9092
|
-
|
|
9093
|
-
|
|
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);
|
|
9094
9019
|
}
|
|
9095
|
-
|
|
9096
|
-
|
|
9097
|
-
|
|
9098
|
-
|
|
9099
|
-
|
|
9100
|
-
outputJson({ currency, name: currencyName, updated: true });
|
|
9101
|
-
return currency;
|
|
9020
|
+
if (primaryType !== "EIP712Domain") {
|
|
9021
|
+
if (types[primaryType])
|
|
9022
|
+
validateData(types[primaryType], message);
|
|
9023
|
+
else
|
|
9024
|
+
throw new InvalidPrimaryTypeError({ primaryType, types });
|
|
9102
9025
|
}
|
|
9103
|
-
success(`
|
|
9104
|
-
+ Currency preference set to ${currency.toUpperCase()} (${currencyName})`);
|
|
9105
|
-
return currency;
|
|
9106
9026
|
}
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
|
|
9111
|
-
|
|
9112
|
-
|
|
9113
|
-
|
|
9114
|
-
|
|
9115
|
-
|
|
9116
|
-
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
}
|
|
9122
|
-
printResult(chalk6.bold("\nMessage Relay:"));
|
|
9123
|
-
printResult(` Online: ${status.messageRelay.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9124
|
-
if (status.messageRelay.latency) {
|
|
9125
|
-
printResult(` Latency: ${status.messageRelay.latency}ms`);
|
|
9126
|
-
}
|
|
9127
|
-
return status;
|
|
9128
|
-
} catch (err) {
|
|
9129
|
-
spinner.fail("Failed to check server status");
|
|
9130
|
-
error(`
|
|
9131
|
-
x ${err.message}`);
|
|
9132
|
-
throw err;
|
|
9133
|
-
}
|
|
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);
|
|
9134
9041
|
}
|
|
9135
|
-
|
|
9136
|
-
if (
|
|
9137
|
-
|
|
9138
|
-
let address = options.address;
|
|
9139
|
-
let name = options.name;
|
|
9140
|
-
const prompts = [];
|
|
9141
|
-
if (!chain) {
|
|
9142
|
-
prompts.push({
|
|
9143
|
-
type: "select",
|
|
9144
|
-
name: "chain",
|
|
9145
|
-
message: "Select chain:",
|
|
9146
|
-
choices: Object.values(Chain9)
|
|
9147
|
-
});
|
|
9148
|
-
}
|
|
9149
|
-
if (!address) {
|
|
9150
|
-
prompts.push({
|
|
9151
|
-
type: "input",
|
|
9152
|
-
name: "address",
|
|
9153
|
-
message: "Enter address:",
|
|
9154
|
-
validate: (input) => input.trim() !== "" || "Address is required"
|
|
9155
|
-
});
|
|
9156
|
-
}
|
|
9157
|
-
if (!name) {
|
|
9158
|
-
prompts.push({
|
|
9159
|
-
type: "input",
|
|
9160
|
-
name: "name",
|
|
9161
|
-
message: "Enter name/label:",
|
|
9162
|
-
validate: (input) => input.trim() !== "" || "Name is required"
|
|
9163
|
-
});
|
|
9164
|
-
}
|
|
9165
|
-
if (prompts.length > 0) {
|
|
9166
|
-
const answers = await prompt(prompts);
|
|
9167
|
-
chain = chain || answers.chain;
|
|
9168
|
-
address = address || answers.address?.trim();
|
|
9169
|
-
name = name || answers.name?.trim();
|
|
9170
|
-
}
|
|
9171
|
-
const spinner2 = createSpinner("Adding address to address book...");
|
|
9172
|
-
const entry = {
|
|
9173
|
-
chain,
|
|
9174
|
-
address,
|
|
9175
|
-
name,
|
|
9176
|
-
source: "saved",
|
|
9177
|
-
dateAdded: Date.now()
|
|
9178
|
-
};
|
|
9179
|
-
await ctx2.sdk.addAddressBookEntry([entry]);
|
|
9180
|
-
spinner2.succeed("Address added");
|
|
9181
|
-
if (isJsonOutput()) {
|
|
9182
|
-
outputJson({ added: entry });
|
|
9183
|
-
return [];
|
|
9184
|
-
}
|
|
9185
|
-
success(`
|
|
9186
|
-
+ Added ${name} (${chain}: ${address})`);
|
|
9187
|
-
return [];
|
|
9188
|
-
}
|
|
9189
|
-
if (options.remove) {
|
|
9190
|
-
const spinner2 = createSpinner("Removing address from address book...");
|
|
9191
|
-
await ctx2.sdk.removeAddressBookEntry([{ address: options.remove, chain: options.chain }]);
|
|
9192
|
-
spinner2.succeed("Address removed");
|
|
9193
|
-
if (isJsonOutput()) {
|
|
9194
|
-
outputJson({ removed: { address: options.remove, chain: options.chain } });
|
|
9195
|
-
return [];
|
|
9196
|
-
}
|
|
9197
|
-
success(`
|
|
9198
|
-
+ Removed ${options.remove}`);
|
|
9199
|
-
return [];
|
|
9200
|
-
}
|
|
9201
|
-
const spinner = createSpinner("Loading address book...");
|
|
9202
|
-
const addressBook = await ctx2.sdk.getAddressBook(options.chain);
|
|
9203
|
-
spinner.succeed("Address book loaded");
|
|
9204
|
-
const allEntries = [...addressBook.saved, ...addressBook.vaults];
|
|
9205
|
-
if (isJsonOutput()) {
|
|
9206
|
-
outputJson({ addressBook: allEntries, chain: options.chain });
|
|
9207
|
-
return allEntries;
|
|
9208
|
-
}
|
|
9209
|
-
if (allEntries.length === 0) {
|
|
9210
|
-
warn(`
|
|
9211
|
-
No addresses in address book${options.chain ? ` for ${options.chain}` : ""}`);
|
|
9212
|
-
info(chalk6.gray("\nUse --add to add an address to the address book"));
|
|
9213
|
-
} else {
|
|
9214
|
-
printResult(chalk6.cyan(`
|
|
9215
|
-
Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
9216
|
-
`));
|
|
9217
|
-
const table = allEntries.map((entry) => ({
|
|
9218
|
-
Name: entry.name,
|
|
9219
|
-
Chain: entry.chain,
|
|
9220
|
-
Address: entry.address,
|
|
9221
|
-
Source: entry.source
|
|
9222
|
-
}));
|
|
9223
|
-
printTable(table);
|
|
9224
|
-
info(chalk6.gray("\nUse --add to add or --remove <address> to remove an address"));
|
|
9225
|
-
}
|
|
9226
|
-
return allEntries;
|
|
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 });
|
|
9227
9045
|
}
|
|
9228
9046
|
|
|
9229
|
-
//
|
|
9230
|
-
|
|
9231
|
-
|
|
9232
|
-
|
|
9233
|
-
|
|
9234
|
-
|
|
9235
|
-
}
|
|
9236
|
-
|
|
9237
|
-
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
rpcEndpoint: options.rpcEndpoint,
|
|
9242
|
-
config: {
|
|
9243
|
-
// Allow overriding rest endpoint via config (used for thornode calls)
|
|
9244
|
-
...options.restEndpoint ? { restEndpoint: options.restEndpoint } : {}
|
|
9245
|
-
}
|
|
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
|
|
9246
9059
|
});
|
|
9247
|
-
const
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
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));
|
|
9251
9073
|
}
|
|
9252
|
-
|
|
9253
|
-
|
|
9254
|
-
|
|
9255
|
-
|
|
9256
|
-
|
|
9257
|
-
|
|
9258
|
-
|
|
9259
|
-
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
|
|
9263
|
-
|
|
9264
|
-
|
|
9265
|
-
|
|
9266
|
-
|
|
9267
|
-
|
|
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);
|
|
9268
9101
|
}
|
|
9269
|
-
|
|
9270
|
-
filtered.map((b) => ({
|
|
9271
|
-
asset: b.asset,
|
|
9272
|
-
denom: b.denom,
|
|
9273
|
-
amount: b.formatted,
|
|
9274
|
-
raw: b.amount
|
|
9275
|
-
}))
|
|
9276
|
-
);
|
|
9102
|
+
return encodeAbiParameters(encodedTypes, encodedValues);
|
|
9277
9103
|
}
|
|
9278
|
-
|
|
9279
|
-
const
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
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(",")})`;
|
|
9284
9115
|
}
|
|
9285
|
-
|
|
9286
|
-
printResult("");
|
|
9287
|
-
printTable(
|
|
9288
|
-
routes.map((r) => ({
|
|
9289
|
-
name: r.name,
|
|
9290
|
-
from: r.from,
|
|
9291
|
-
to: r.to,
|
|
9292
|
-
liquidity: r.liquidity,
|
|
9293
|
-
description: r.description
|
|
9294
|
-
}))
|
|
9295
|
-
);
|
|
9116
|
+
return result;
|
|
9296
9117
|
}
|
|
9297
|
-
|
|
9298
|
-
const
|
|
9299
|
-
const
|
|
9300
|
-
|
|
9301
|
-
|
|
9302
|
-
const spinner2 = createSpinner("Loading THORChain inbound addresses...");
|
|
9303
|
-
const inbound = await client.deposit.getInboundAddresses();
|
|
9304
|
-
spinner2.succeed("Inbound addresses loaded");
|
|
9305
|
-
if (isJsonOutput()) {
|
|
9306
|
-
outputJson({ thorAddress, inboundAddresses: inbound });
|
|
9307
|
-
return;
|
|
9308
|
-
}
|
|
9309
|
-
info(`THORChain address: ${thorAddress}`);
|
|
9310
|
-
printResult("Provide an L1 asset to get a chain-specific inbound address + memo.");
|
|
9311
|
-
printResult("Example: vultisig rujira deposit --asset BTC.BTC --amount 100000");
|
|
9312
|
-
printResult("");
|
|
9313
|
-
printTable(
|
|
9314
|
-
inbound.map((a) => ({
|
|
9315
|
-
chain: a.chain,
|
|
9316
|
-
address: a.address,
|
|
9317
|
-
halted: a.halted,
|
|
9318
|
-
globalTradingPaused: a.global_trading_paused,
|
|
9319
|
-
chainTradingPaused: a.chain_trading_paused
|
|
9320
|
-
}))
|
|
9321
|
-
);
|
|
9322
|
-
return;
|
|
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;
|
|
9323
9123
|
}
|
|
9324
|
-
|
|
9325
|
-
const
|
|
9326
|
-
|
|
9327
|
-
fromAsset: options.asset,
|
|
9328
|
-
amount,
|
|
9329
|
-
thorAddress,
|
|
9330
|
-
affiliate: options.affiliate,
|
|
9331
|
-
affiliateBps: options.affiliateBps
|
|
9332
|
-
});
|
|
9333
|
-
spinner.succeed("Deposit prepared");
|
|
9334
|
-
if (isJsonOutput()) {
|
|
9335
|
-
outputJson({ thorAddress, deposit: prepared });
|
|
9336
|
-
return;
|
|
9124
|
+
results.add(primaryType);
|
|
9125
|
+
for (const field of types[primaryType]) {
|
|
9126
|
+
findTypeDependencies({ primaryType: field.type, types }, results);
|
|
9337
9127
|
}
|
|
9338
|
-
|
|
9339
|
-
|
|
9340
|
-
|
|
9341
|
-
|
|
9342
|
-
|
|
9343
|
-
|
|
9344
|
-
|
|
9345
|
-
|
|
9346
|
-
|
|
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
|
+
];
|
|
9347
9153
|
}
|
|
9154
|
+
return [{ type }, value];
|
|
9348
9155
|
}
|
|
9349
|
-
|
|
9350
|
-
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
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
|
+
});
|
|
9356
9164
|
}
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
|
|
9368
|
-
if (
|
|
9369
|
-
if (
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
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}`;
|
|
9379
9194
|
}
|
|
9380
|
-
|
|
9195
|
+
fraction = fraction.slice(0, decimals);
|
|
9196
|
+
} else {
|
|
9197
|
+
fraction = fraction.padEnd(decimals, "0");
|
|
9381
9198
|
}
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
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);
|
|
9393
9210
|
}
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
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;
|
|
9400
9218
|
}
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
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;
|
|
9407
9239
|
} else {
|
|
9408
|
-
|
|
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}`;
|
|
9409
9268
|
}
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
const vault = await ctx2.ensureActiveVault();
|
|
9413
|
-
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9414
|
-
throw new ConfirmationRequiredError(
|
|
9415
|
-
"Withdrawal requires confirmation.",
|
|
9416
|
-
"Pass --yes to confirm, or --dry-run to preview."
|
|
9417
|
-
);
|
|
9269
|
+
if (signNeg && allDigits !== 0n) {
|
|
9270
|
+
return `-${absResult}`;
|
|
9418
9271
|
}
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
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");
|
|
9438
9294
|
}
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
printResult(` Amount: ${prepared.amount}`);
|
|
9445
|
-
printResult(` Destination: ${prepared.destination}`);
|
|
9446
|
-
printResult(` Memo: ${prepared.memo}`);
|
|
9447
|
-
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
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);
|
|
9448
9300
|
}
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
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;
|
|
9455
9313
|
}
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
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;
|
|
9460
9335
|
if (isJsonOutput()) {
|
|
9461
|
-
outputJson({
|
|
9462
|
-
|
|
9463
|
-
|
|
9336
|
+
outputJson({
|
|
9337
|
+
fromChain: options.fromChain,
|
|
9338
|
+
toChain: options.toChain,
|
|
9339
|
+
amount: semanticAmount,
|
|
9340
|
+
isMax,
|
|
9341
|
+
quote
|
|
9342
|
+
});
|
|
9343
|
+
return quote;
|
|
9464
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;
|
|
9465
9356
|
}
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
9478
|
-
|
|
9479
|
-
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
};
|
|
9487
|
-
function getTierColor(tier) {
|
|
9488
|
-
const colors = {
|
|
9489
|
-
none: chalk7.gray,
|
|
9490
|
-
bronze: chalk7.hex("#CD7F32"),
|
|
9491
|
-
silver: chalk7.hex("#C0C0C0"),
|
|
9492
|
-
gold: chalk7.hex("#FFD700"),
|
|
9493
|
-
platinum: chalk7.hex("#E5E4E2"),
|
|
9494
|
-
diamond: chalk7.hex("#B9F2FF"),
|
|
9495
|
-
ultimate: chalk7.hex("#FF00FF")
|
|
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 }
|
|
9496
9377
|
};
|
|
9497
|
-
return colors[tier] || chalk7.white;
|
|
9498
9378
|
}
|
|
9499
|
-
function
|
|
9500
|
-
const
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
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();
|
|
9504
9416
|
}
|
|
9505
|
-
const
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
return { name: nextTierName, vultRequired: config.minVult };
|
|
9417
|
+
const confirmed = await confirmSwap();
|
|
9418
|
+
if (!confirmed) {
|
|
9419
|
+
throw new ConfirmationRequiredError("Swap declined at the confirmation prompt");
|
|
9509
9420
|
}
|
|
9510
|
-
return null;
|
|
9511
9421
|
}
|
|
9512
|
-
async function
|
|
9422
|
+
async function executeSwap(ctx2, options) {
|
|
9423
|
+
const amountStr = normalizeSwapAmount(options.amount);
|
|
9513
9424
|
const vault = await ctx2.ensureActiveVault();
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
const tier = tierResult || "none";
|
|
9517
|
-
const config = TIER_CONFIG[tier];
|
|
9518
|
-
const nextTier = getNextTier(tier);
|
|
9519
|
-
const tierInfo = {
|
|
9520
|
-
tier,
|
|
9521
|
-
feeBps: config.bps,
|
|
9522
|
-
discountBps: config.discount,
|
|
9523
|
-
nextTier
|
|
9524
|
-
};
|
|
9525
|
-
spinner.succeed("Discount tier loaded");
|
|
9526
|
-
if (isJsonOutput()) {
|
|
9527
|
-
outputJson({
|
|
9528
|
-
tier: tierInfo.tier,
|
|
9529
|
-
feeBps: tierInfo.feeBps,
|
|
9530
|
-
discountBps: tierInfo.discountBps,
|
|
9531
|
-
nextTier: tierInfo.nextTier
|
|
9532
|
-
});
|
|
9533
|
-
return tierInfo;
|
|
9425
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9426
|
+
refuseSwapWhenNonInteractive();
|
|
9534
9427
|
}
|
|
9535
|
-
|
|
9536
|
-
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
const
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
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;
|
|
9551
9444
|
}
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
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");
|
|
9560
9494
|
}
|
|
9561
|
-
info(chalk7.gray("\n Tip: Thorguard NFT holders get +1 tier upgrade (up to gold)"));
|
|
9562
|
-
printResult("");
|
|
9563
9495
|
}
|
|
9564
9496
|
|
|
9565
|
-
// src/commands/
|
|
9566
|
-
import {
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
transactions = [];
|
|
9580
|
-
cards = [];
|
|
9581
|
-
warnings = [];
|
|
9582
|
-
outcome;
|
|
9583
|
-
error;
|
|
9584
|
-
// Tracks whether the currently-latched `error` is a terminal one (e.g. the
|
|
9585
|
-
// depth cap). A terminal error may overwrite a prior non-terminal one; once a
|
|
9586
|
-
// terminal error is recorded, later frames cannot replace it. See onError.
|
|
9587
|
-
errorIsTerminal = false;
|
|
9588
|
-
constructor(session, verbose = false, autoApprove = false) {
|
|
9589
|
-
this.session = session;
|
|
9590
|
-
this.verbose = verbose;
|
|
9591
|
-
this.autoApprove = autoApprove;
|
|
9592
|
-
}
|
|
9593
|
-
/**
|
|
9594
|
-
* Whether the turn threw with a still-unacknowledged broadcast (the F1
|
|
9595
|
-
* ack-failure case). The command's catch uses this to gate the ACK_FAILED
|
|
9596
|
-
* re-tag so a later, unrelated retryable error after an already-acked
|
|
9597
|
-
* broadcast keeps its own (retryable) classification instead of exit 8.
|
|
9598
|
-
*/
|
|
9599
|
-
hasUnacknowledgedBroadcast() {
|
|
9600
|
-
return this.session.hasUnacknowledgedBroadcast();
|
|
9601
|
-
}
|
|
9602
|
-
/**
|
|
9603
|
-
* Get UI callbacks that silently collect results.
|
|
9604
|
-
* Tool progress is logged to stderr in verbose mode.
|
|
9605
|
-
*/
|
|
9606
|
-
getCallbacks() {
|
|
9607
|
-
return {
|
|
9608
|
-
onTextDelta: (_delta) => {
|
|
9609
|
-
},
|
|
9610
|
-
onToolCall: (_id, action, params) => {
|
|
9611
|
-
if (this.verbose) {
|
|
9612
|
-
const paramStr = params ? ` ${JSON.stringify(params)}` : "";
|
|
9613
|
-
process.stderr.write(`[tool] ${action}${paramStr} ...
|
|
9614
|
-
`);
|
|
9615
|
-
}
|
|
9616
|
-
},
|
|
9617
|
-
onToolResult: (id, action, success2, data, error2, code) => {
|
|
9618
|
-
this.toolCalls.push({ id, action, success: success2, data, error: error2, code });
|
|
9619
|
-
if (this.verbose) {
|
|
9620
|
-
const status = success2 ? "ok" : `error: ${error2}${code ? ` [${code}]` : ""}`;
|
|
9621
|
-
process.stderr.write(`[tool] ${action}: ${status}
|
|
9622
|
-
`);
|
|
9623
|
-
}
|
|
9624
|
-
},
|
|
9625
|
-
onAssistantMessage: (content) => {
|
|
9626
|
-
if (content) {
|
|
9627
|
-
this.responseParts.push(content);
|
|
9628
|
-
}
|
|
9629
|
-
},
|
|
9630
|
-
onBalanceSummary: (card) => {
|
|
9631
|
-
this.cards.push(card);
|
|
9632
|
-
},
|
|
9633
|
-
onTurnOutcome: (outcome) => {
|
|
9634
|
-
this.outcome = outcome;
|
|
9635
|
-
},
|
|
9636
|
-
onSuggestions: (_suggestions) => {
|
|
9637
|
-
},
|
|
9638
|
-
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
9639
|
-
const existing = this.transactions.find((t) => t.hash === txHash);
|
|
9640
|
-
if (existing) {
|
|
9641
|
-
existing.status = status;
|
|
9642
|
-
if (explorerUrl) existing.explorerUrl = explorerUrl;
|
|
9643
|
-
} else {
|
|
9644
|
-
this.transactions.push({ hash: txHash, chain, explorerUrl, status });
|
|
9645
|
-
}
|
|
9646
|
-
if (this.verbose) {
|
|
9647
|
-
process.stderr.write(`[tx] ${chain}: ${txHash} (${status})
|
|
9648
|
-
`);
|
|
9649
|
-
}
|
|
9650
|
-
},
|
|
9651
|
-
onError: (message, code) => {
|
|
9652
|
-
const isTerminal2 = isTerminalAgentErrorCode(code);
|
|
9653
|
-
if (!this.error || isTerminal2 && !this.errorIsTerminal) {
|
|
9654
|
-
this.error = { message, code };
|
|
9655
|
-
this.errorIsTerminal = isTerminal2;
|
|
9656
|
-
}
|
|
9657
|
-
process.stderr.write(`[error] ${message} [${code}]
|
|
9658
|
-
`);
|
|
9659
|
-
},
|
|
9660
|
-
onProtocolWarning: (warning) => {
|
|
9661
|
-
this.warnings.push(warning);
|
|
9662
|
-
process.stderr.write(`[warning] ${warning.message} [${warning.code}]
|
|
9663
|
-
`);
|
|
9664
|
-
},
|
|
9665
|
-
onDone: () => {
|
|
9666
|
-
},
|
|
9667
|
-
requestPassword: async () => {
|
|
9668
|
-
throw new Error("Password required but not provided. Use --password flag.");
|
|
9669
|
-
},
|
|
9670
|
-
requestConfirmation: async (message) => {
|
|
9671
|
-
if (!this.autoApprove) {
|
|
9672
|
-
process.stderr.write(`[confirm] signing requires --yes \u2014 NOT broadcasting: ${message}
|
|
9673
|
-
`);
|
|
9674
|
-
} else {
|
|
9675
|
-
process.stderr.write(`[confirm] auto-approved (--yes): ${message}
|
|
9676
|
-
`);
|
|
9677
|
-
}
|
|
9678
|
-
return this.autoApprove;
|
|
9679
|
-
}
|
|
9680
|
-
};
|
|
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;
|
|
9681
9511
|
}
|
|
9682
|
-
|
|
9683
|
-
|
|
9684
|
-
|
|
9685
|
-
|
|
9686
|
-
|
|
9687
|
-
this.responseParts = [];
|
|
9688
|
-
this.toolCalls = [];
|
|
9689
|
-
this.transactions = [];
|
|
9690
|
-
this.cards = [];
|
|
9691
|
-
this.warnings = [];
|
|
9692
|
-
this.outcome = void 0;
|
|
9693
|
-
this.error = void 0;
|
|
9694
|
-
this.errorIsTerminal = false;
|
|
9695
|
-
const callbacks = this.getCallbacks();
|
|
9696
|
-
await this.session.sendMessage(message, callbacks);
|
|
9697
|
-
return this.partialResult();
|
|
9512
|
+
const currency = newCurrency.toLowerCase();
|
|
9513
|
+
if (!fiatCurrencies2.includes(currency)) {
|
|
9514
|
+
error(`x Invalid currency: ${newCurrency}`);
|
|
9515
|
+
warn(`Supported currencies: ${fiatCurrencies2.join(", ")}`);
|
|
9516
|
+
throw new Error("Invalid currency");
|
|
9698
9517
|
}
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
*/
|
|
9707
|
-
partialResult() {
|
|
9708
|
-
return {
|
|
9709
|
-
sessionId: this.session.getConversationId() || "",
|
|
9710
|
-
response: this.responseParts[this.responseParts.length - 1] || "",
|
|
9711
|
-
toolCalls: this.toolCalls,
|
|
9712
|
-
transactions: this.transactions,
|
|
9713
|
-
cards: this.cards,
|
|
9714
|
-
warnings: this.warnings,
|
|
9715
|
-
error: this.error,
|
|
9716
|
-
...this.outcome ? { outcome: this.outcome } : {}
|
|
9717
|
-
};
|
|
9518
|
+
const spinner = createSpinner("Updating currency preference...");
|
|
9519
|
+
await vault.setCurrency(currency);
|
|
9520
|
+
spinner.succeed("Currency updated");
|
|
9521
|
+
const currencyName = fiatCurrencyNameRecord3[currency];
|
|
9522
|
+
if (isJsonOutput()) {
|
|
9523
|
+
outputJson({ currency, name: currencyName, updated: true });
|
|
9524
|
+
return currency;
|
|
9718
9525
|
}
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9526
|
+
success(`
|
|
9527
|
+
+ Currency preference set to ${currency.toUpperCase()} (${currencyName})`);
|
|
9528
|
+
return currency;
|
|
9529
|
+
}
|
|
9530
|
+
async function executeServer(ctx2) {
|
|
9531
|
+
const spinner = createSpinner("Checking server status...");
|
|
9532
|
+
try {
|
|
9533
|
+
const status = await ctx2.sdk.getServerStatus();
|
|
9534
|
+
spinner.succeed("Server status retrieved");
|
|
9535
|
+
if (isJsonOutput()) {
|
|
9536
|
+
outputJson({ server: status });
|
|
9537
|
+
return status;
|
|
9538
|
+
}
|
|
9539
|
+
printResult(chalk6.cyan("\nServer Status:\n"));
|
|
9540
|
+
printResult(chalk6.bold("Fast Vault Server:"));
|
|
9541
|
+
printResult(` Online: ${status.fastVault.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9542
|
+
if (status.fastVault.latency) {
|
|
9543
|
+
printResult(` Latency: ${status.fastVault.latency}ms`);
|
|
9544
|
+
}
|
|
9545
|
+
printResult(chalk6.bold("\nMessage Relay:"));
|
|
9546
|
+
printResult(` Online: ${status.messageRelay.online ? chalk6.green("Yes") : chalk6.red("No")}`);
|
|
9547
|
+
if (status.messageRelay.latency) {
|
|
9548
|
+
printResult(` Latency: ${status.messageRelay.latency}ms`);
|
|
9549
|
+
}
|
|
9550
|
+
return status;
|
|
9551
|
+
} catch (err) {
|
|
9552
|
+
spinner.fail("Failed to check server status");
|
|
9553
|
+
error(`
|
|
9554
|
+
x ${err.message}`);
|
|
9555
|
+
throw err;
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
9558
|
+
async function executeAddressBook(ctx2, options = {}) {
|
|
9559
|
+
if (options.add) {
|
|
9560
|
+
let chain = options.chain;
|
|
9561
|
+
let address = options.address;
|
|
9562
|
+
let name = options.name;
|
|
9563
|
+
const prompts = [];
|
|
9564
|
+
if (!chain) {
|
|
9565
|
+
prompts.push({
|
|
9566
|
+
type: "select",
|
|
9567
|
+
name: "chain",
|
|
9568
|
+
message: "Select chain:",
|
|
9569
|
+
choices: Object.values(Chain9)
|
|
9754
9570
|
});
|
|
9755
|
-
return {
|
|
9756
|
-
token: authResponse.token,
|
|
9757
|
-
expiresAt: authResponse.expires_at,
|
|
9758
|
-
// Captured + persisted by the session token cache. The backend exposes
|
|
9759
|
-
// POST /auth/refresh to exchange it for a fresh access token without a
|
|
9760
|
-
// new MPC round; wiring that exchange is a future enhancement — today
|
|
9761
|
-
// the CLI re-auths via a full MPC re-sign (authenticateVault), which is
|
|
9762
|
-
// always available and avoids depending on refresh-token rotation.
|
|
9763
|
-
refreshToken: authResponse.refresh_token
|
|
9764
|
-
};
|
|
9765
|
-
} catch (err) {
|
|
9766
|
-
lastError = err;
|
|
9767
|
-
if (attempt < maxAttempts && err.message?.includes("timeout")) {
|
|
9768
|
-
continue;
|
|
9769
|
-
}
|
|
9770
|
-
throw err;
|
|
9771
9571
|
}
|
|
9572
|
+
if (!address) {
|
|
9573
|
+
prompts.push({
|
|
9574
|
+
type: "input",
|
|
9575
|
+
name: "address",
|
|
9576
|
+
message: "Enter address:",
|
|
9577
|
+
validate: (input) => input.trim() !== "" || "Address is required"
|
|
9578
|
+
});
|
|
9579
|
+
}
|
|
9580
|
+
if (!name) {
|
|
9581
|
+
prompts.push({
|
|
9582
|
+
type: "input",
|
|
9583
|
+
name: "name",
|
|
9584
|
+
message: "Enter name/label:",
|
|
9585
|
+
validate: (input) => input.trim() !== "" || "Name is required"
|
|
9586
|
+
});
|
|
9587
|
+
}
|
|
9588
|
+
if (prompts.length > 0) {
|
|
9589
|
+
const answers = await prompt(prompts);
|
|
9590
|
+
chain = chain || answers.chain;
|
|
9591
|
+
address = address || answers.address?.trim();
|
|
9592
|
+
name = name || answers.name?.trim();
|
|
9593
|
+
}
|
|
9594
|
+
const spinner2 = createSpinner("Adding address to address book...");
|
|
9595
|
+
const entry = {
|
|
9596
|
+
chain,
|
|
9597
|
+
address,
|
|
9598
|
+
name,
|
|
9599
|
+
source: "saved",
|
|
9600
|
+
dateAdded: Date.now()
|
|
9601
|
+
};
|
|
9602
|
+
await ctx2.sdk.addAddressBookEntry([entry]);
|
|
9603
|
+
spinner2.succeed("Address added");
|
|
9604
|
+
if (isJsonOutput()) {
|
|
9605
|
+
outputJson({ added: entry });
|
|
9606
|
+
return [];
|
|
9607
|
+
}
|
|
9608
|
+
success(`
|
|
9609
|
+
+ Added ${name} (${chain}: ${address})`);
|
|
9610
|
+
return [];
|
|
9772
9611
|
}
|
|
9773
|
-
|
|
9612
|
+
if (options.remove) {
|
|
9613
|
+
const spinner2 = createSpinner("Removing address from address book...");
|
|
9614
|
+
await ctx2.sdk.removeAddressBookEntry([{ address: options.remove, chain: options.chain }]);
|
|
9615
|
+
spinner2.succeed("Address removed");
|
|
9616
|
+
if (isJsonOutput()) {
|
|
9617
|
+
outputJson({ removed: { address: options.remove, chain: options.chain } });
|
|
9618
|
+
return [];
|
|
9619
|
+
}
|
|
9620
|
+
success(`
|
|
9621
|
+
+ Removed ${options.remove}`);
|
|
9622
|
+
return [];
|
|
9623
|
+
}
|
|
9624
|
+
const spinner = createSpinner("Loading address book...");
|
|
9625
|
+
const addressBook = await ctx2.sdk.getAddressBook(options.chain);
|
|
9626
|
+
spinner.succeed("Address book loaded");
|
|
9627
|
+
const allEntries = [...addressBook.saved, ...addressBook.vaults];
|
|
9628
|
+
if (isJsonOutput()) {
|
|
9629
|
+
outputJson({ addressBook: allEntries, chain: options.chain });
|
|
9630
|
+
return allEntries;
|
|
9631
|
+
}
|
|
9632
|
+
if (allEntries.length === 0) {
|
|
9633
|
+
warn(`
|
|
9634
|
+
No addresses in address book${options.chain ? ` for ${options.chain}` : ""}`);
|
|
9635
|
+
info(chalk6.gray("\nUse --add to add an address to the address book"));
|
|
9636
|
+
} else {
|
|
9637
|
+
printResult(chalk6.cyan(`
|
|
9638
|
+
Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
9639
|
+
`));
|
|
9640
|
+
const table = allEntries.map((entry) => ({
|
|
9641
|
+
Name: entry.name,
|
|
9642
|
+
Chain: entry.chain,
|
|
9643
|
+
Address: entry.address,
|
|
9644
|
+
Source: entry.source
|
|
9645
|
+
}));
|
|
9646
|
+
printTable(table);
|
|
9647
|
+
info(chalk6.gray("\nUse --add to add or --remove <address> to remove an address"));
|
|
9648
|
+
}
|
|
9649
|
+
return allEntries;
|
|
9774
9650
|
}
|
|
9775
9651
|
|
|
9776
|
-
// src/
|
|
9777
|
-
import
|
|
9778
|
-
|
|
9779
|
-
|
|
9780
|
-
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
const
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9652
|
+
// src/commands/rujira.ts
|
|
9653
|
+
import {
|
|
9654
|
+
getRoutesSummary,
|
|
9655
|
+
listEasyRoutes,
|
|
9656
|
+
RujiraClient,
|
|
9657
|
+
VultisigRujiraProvider
|
|
9658
|
+
} from "@vultisig/rujira";
|
|
9659
|
+
async function createRujiraClient(ctx2, options = {}) {
|
|
9660
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9661
|
+
const provider = new VultisigRujiraProvider(vault);
|
|
9662
|
+
const client = new RujiraClient({
|
|
9663
|
+
signer: provider,
|
|
9664
|
+
rpcEndpoint: options.rpcEndpoint,
|
|
9665
|
+
config: {
|
|
9666
|
+
// Allow overriding rest endpoint via config (used for thornode calls)
|
|
9667
|
+
...options.restEndpoint ? { restEndpoint: options.restEndpoint } : {}
|
|
9668
|
+
}
|
|
9669
|
+
});
|
|
9670
|
+
const spinner = createSpinner("Connecting to Rujira/THORChain...");
|
|
9671
|
+
await client.connect();
|
|
9672
|
+
spinner.succeed("Connected");
|
|
9673
|
+
return client;
|
|
9790
9674
|
}
|
|
9791
|
-
function
|
|
9792
|
-
|
|
9793
|
-
|
|
9794
|
-
|
|
9795
|
-
|
|
9796
|
-
|
|
9675
|
+
async function executeRujiraBalance(ctx2, options = {}) {
|
|
9676
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9677
|
+
const thorAddress = await vault.address("THORChain");
|
|
9678
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9679
|
+
const spinner = createSpinner("Loading THORChain balances...");
|
|
9680
|
+
const balances = await client.deposit.getBalances(thorAddress);
|
|
9681
|
+
spinner.succeed("Balances loaded");
|
|
9682
|
+
const filtered = options.securedOnly ? balances.filter((b) => b.denom.includes("-") || b.denom.includes("/")) : balances;
|
|
9683
|
+
if (isJsonOutput()) {
|
|
9684
|
+
outputJson({ thorAddress, balances: filtered });
|
|
9685
|
+
return;
|
|
9797
9686
|
}
|
|
9798
|
-
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
}
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
|
|
9811
|
-
if (amountUsd) token.amountUsd = amountUsd;
|
|
9812
|
-
return token;
|
|
9813
|
-
}
|
|
9814
|
-
function parseAccount(v) {
|
|
9815
|
-
if (!v || typeof v !== "object") return null;
|
|
9816
|
-
const o = v;
|
|
9817
|
-
const chainId = asString(o.chainId);
|
|
9818
|
-
if (!chainId) return null;
|
|
9819
|
-
const tokensRaw = Array.isArray(o.tokens) ? o.tokens : [];
|
|
9820
|
-
const tokens = tokensRaw.map(parseToken).filter((t) => t !== null);
|
|
9821
|
-
return { chainId, address: asString(o.address) || "\u2014", tokens };
|
|
9687
|
+
info(`THORChain address: ${thorAddress}`);
|
|
9688
|
+
if (!filtered.length) {
|
|
9689
|
+
printResult("No balances found");
|
|
9690
|
+
return;
|
|
9691
|
+
}
|
|
9692
|
+
printTable(
|
|
9693
|
+
filtered.map((b) => ({
|
|
9694
|
+
asset: b.asset,
|
|
9695
|
+
denom: b.denom,
|
|
9696
|
+
amount: b.formatted,
|
|
9697
|
+
raw: b.amount
|
|
9698
|
+
}))
|
|
9699
|
+
);
|
|
9822
9700
|
}
|
|
9823
|
-
function
|
|
9824
|
-
|
|
9825
|
-
const
|
|
9826
|
-
if (
|
|
9827
|
-
|
|
9828
|
-
|
|
9829
|
-
if (accounts.length === 0) return null;
|
|
9830
|
-
const card = { surface: "balance_summary", accounts };
|
|
9831
|
-
if (o.stale === true) {
|
|
9832
|
-
card.stale = true;
|
|
9833
|
-
if (typeof o.stale_secs === "number") card.staleSecs = o.stale_secs;
|
|
9701
|
+
async function executeRujiraRoutes() {
|
|
9702
|
+
const routes = listEasyRoutes();
|
|
9703
|
+
const summary = getRoutesSummary();
|
|
9704
|
+
if (isJsonOutput()) {
|
|
9705
|
+
outputJson({ routes, summary });
|
|
9706
|
+
return;
|
|
9834
9707
|
}
|
|
9835
|
-
|
|
9708
|
+
printResult(summary);
|
|
9709
|
+
printResult("");
|
|
9710
|
+
printTable(
|
|
9711
|
+
routes.map((r) => ({
|
|
9712
|
+
name: r.name,
|
|
9713
|
+
from: r.from,
|
|
9714
|
+
to: r.to,
|
|
9715
|
+
liquidity: r.liquidity,
|
|
9716
|
+
description: r.description
|
|
9717
|
+
}))
|
|
9718
|
+
);
|
|
9836
9719
|
}
|
|
9837
|
-
function
|
|
9838
|
-
|
|
9839
|
-
|
|
9840
|
-
|
|
9841
|
-
|
|
9842
|
-
const
|
|
9843
|
-
|
|
9844
|
-
|
|
9845
|
-
|
|
9846
|
-
|
|
9847
|
-
|
|
9848
|
-
}
|
|
9849
|
-
if (ch === '"') inString = true;
|
|
9850
|
-
else if (ch === "{") depth++;
|
|
9851
|
-
else if (ch === "}") {
|
|
9852
|
-
depth--;
|
|
9853
|
-
if (depth === 0) return i;
|
|
9720
|
+
async function executeRujiraDeposit(ctx2, options = {}) {
|
|
9721
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9722
|
+
const thorAddress = await vault.address("THORChain");
|
|
9723
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9724
|
+
if (!options.asset) {
|
|
9725
|
+
const spinner2 = createSpinner("Loading THORChain inbound addresses...");
|
|
9726
|
+
const inbound = await client.deposit.getInboundAddresses();
|
|
9727
|
+
spinner2.succeed("Inbound addresses loaded");
|
|
9728
|
+
if (isJsonOutput()) {
|
|
9729
|
+
outputJson({ thorAddress, inboundAddresses: inbound });
|
|
9730
|
+
return;
|
|
9854
9731
|
}
|
|
9732
|
+
info(`THORChain address: ${thorAddress}`);
|
|
9733
|
+
printResult("Provide an L1 asset to get a chain-specific inbound address + memo.");
|
|
9734
|
+
printResult("Example: vultisig rujira deposit --asset BTC.BTC --amount 100000");
|
|
9735
|
+
printResult("");
|
|
9736
|
+
printTable(
|
|
9737
|
+
inbound.map((a) => ({
|
|
9738
|
+
chain: a.chain,
|
|
9739
|
+
address: a.address,
|
|
9740
|
+
halted: a.halted,
|
|
9741
|
+
globalTradingPaused: a.global_trading_paused,
|
|
9742
|
+
chainTradingPaused: a.chain_trading_paused
|
|
9743
|
+
}))
|
|
9744
|
+
);
|
|
9745
|
+
return;
|
|
9746
|
+
}
|
|
9747
|
+
const amount = options.amount ?? "1";
|
|
9748
|
+
const spinner = createSpinner("Preparing deposit instructions...");
|
|
9749
|
+
const prepared = await client.deposit.prepare({
|
|
9750
|
+
fromAsset: options.asset,
|
|
9751
|
+
amount,
|
|
9752
|
+
thorAddress,
|
|
9753
|
+
affiliate: options.affiliate,
|
|
9754
|
+
affiliateBps: options.affiliateBps
|
|
9755
|
+
});
|
|
9756
|
+
spinner.succeed("Deposit prepared");
|
|
9757
|
+
if (isJsonOutput()) {
|
|
9758
|
+
outputJson({ thorAddress, deposit: prepared });
|
|
9759
|
+
return;
|
|
9760
|
+
}
|
|
9761
|
+
info(`THORChain address: ${thorAddress}`);
|
|
9762
|
+
printResult("Deposit instructions (send from L1):");
|
|
9763
|
+
printResult(` Chain: ${prepared.chain}`);
|
|
9764
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
9765
|
+
printResult(` Inbound address:${prepared.inboundAddress}`);
|
|
9766
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
9767
|
+
printResult(` Min amount: ${prepared.minimumAmount}`);
|
|
9768
|
+
if (prepared.warning) {
|
|
9769
|
+
warn(prepared.warning);
|
|
9855
9770
|
}
|
|
9856
|
-
return -1;
|
|
9857
9771
|
}
|
|
9858
|
-
function
|
|
9859
|
-
|
|
9860
|
-
if (
|
|
9861
|
-
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
9866
|
-
|
|
9867
|
-
|
|
9868
|
-
|
|
9869
|
-
|
|
9870
|
-
|
|
9772
|
+
async function executeRujiraSwap(ctx2, options) {
|
|
9773
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9774
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9775
|
+
throw new ConfirmationRequiredError(
|
|
9776
|
+
"Swap requires confirmation.",
|
|
9777
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9778
|
+
);
|
|
9779
|
+
}
|
|
9780
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9781
|
+
const destination = options.destination ?? await vault.address("THORChain");
|
|
9782
|
+
const quoteSpinner = createSpinner("Getting FIN swap quote...");
|
|
9783
|
+
const quote = await client.swap.getQuote({
|
|
9784
|
+
fromAsset: options.fromAsset,
|
|
9785
|
+
toAsset: options.toAsset,
|
|
9786
|
+
amount: options.amount,
|
|
9787
|
+
destination,
|
|
9788
|
+
slippageBps: options.slippageBps
|
|
9789
|
+
});
|
|
9790
|
+
quoteSpinner.succeed("Quote received");
|
|
9791
|
+
if (options.dryRun) {
|
|
9792
|
+
if (isJsonOutput()) {
|
|
9793
|
+
outputJson({ dryRun: true, quote });
|
|
9794
|
+
} else {
|
|
9795
|
+
printResult("FIN Swap Preview (dry-run)");
|
|
9796
|
+
printResult(` From: ${options.fromAsset}`);
|
|
9797
|
+
printResult(` To: ${options.toAsset}`);
|
|
9798
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
9799
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
9800
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
9801
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
9871
9802
|
}
|
|
9872
|
-
|
|
9873
|
-
if (!card) continue;
|
|
9874
|
-
const before = content.slice(0, i).replace(/```(?:json)?\s*$/i, "");
|
|
9875
|
-
const after = content.slice(end + 1).replace(/^\s*```/, "");
|
|
9876
|
-
const remainingText = (before + after).trim();
|
|
9877
|
-
return { card, remainingText };
|
|
9803
|
+
return;
|
|
9878
9804
|
}
|
|
9879
|
-
|
|
9880
|
-
|
|
9881
|
-
|
|
9882
|
-
|
|
9883
|
-
|
|
9884
|
-
|
|
9885
|
-
}
|
|
9886
|
-
|
|
9887
|
-
|
|
9888
|
-
|
|
9889
|
-
if (!cleaned) return null;
|
|
9890
|
-
const n = Number(cleaned);
|
|
9891
|
-
return Number.isFinite(n) ? n : null;
|
|
9892
|
-
}
|
|
9893
|
-
function formatUsd(n) {
|
|
9894
|
-
return `$${n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
9895
|
-
}
|
|
9896
|
-
function renderBalanceSummaryCard(card) {
|
|
9897
|
-
const lines = [];
|
|
9898
|
-
const staleCue = card.stale ? chalk8.gray(` (stale${card.staleSecs ? ` ~${Math.round(card.staleSecs / 60)}m` : ""}, refreshing\u2026)`) : "";
|
|
9899
|
-
lines.push(chalk8.bold(" Balances") + staleCue);
|
|
9900
|
-
let total = 0;
|
|
9901
|
-
let sawUsd = false;
|
|
9902
|
-
for (const account of card.accounts) {
|
|
9903
|
-
lines.push(` ${chalk8.cyan(account.chainId)} ${chalk8.gray(`(${shortenAddress(account.address)})`)}`);
|
|
9904
|
-
if (account.tokens.length === 0) {
|
|
9905
|
-
lines.push(chalk8.gray(" (no balances)"));
|
|
9906
|
-
continue;
|
|
9805
|
+
if (!isJsonOutput()) {
|
|
9806
|
+
printResult("FIN Swap Preview");
|
|
9807
|
+
printResult(` From: ${options.fromAsset}`);
|
|
9808
|
+
printResult(` To: ${options.toAsset}`);
|
|
9809
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
9810
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
9811
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
9812
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
9813
|
+
if (quote.warning) {
|
|
9814
|
+
warn(quote.warning);
|
|
9907
9815
|
}
|
|
9908
|
-
|
|
9909
|
-
|
|
9910
|
-
|
|
9911
|
-
|
|
9912
|
-
|
|
9913
|
-
|
|
9914
|
-
|
|
9915
|
-
|
|
9916
|
-
|
|
9917
|
-
|
|
9816
|
+
}
|
|
9817
|
+
if (!options.yes) {
|
|
9818
|
+
warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
|
|
9819
|
+
throw new ConfirmationRequiredError(
|
|
9820
|
+
"Swap requires confirmation.",
|
|
9821
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9822
|
+
);
|
|
9823
|
+
}
|
|
9824
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
9825
|
+
const execSpinner = createSpinner("Executing FIN swap...");
|
|
9826
|
+
const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
|
|
9827
|
+
execSpinner.succeed("Swap submitted");
|
|
9828
|
+
if (isJsonOutput()) {
|
|
9829
|
+
outputJson({ quote, result });
|
|
9830
|
+
} else {
|
|
9831
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
9832
|
+
}
|
|
9833
|
+
}
|
|
9834
|
+
async function executeRujiraWithdraw(ctx2, options) {
|
|
9835
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9836
|
+
if (!options.dryRun && !options.yes && isNonInteractive()) {
|
|
9837
|
+
throw new ConfirmationRequiredError(
|
|
9838
|
+
"Withdrawal requires confirmation.",
|
|
9839
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9840
|
+
);
|
|
9841
|
+
}
|
|
9842
|
+
const client = await createRujiraClient(ctx2, options);
|
|
9843
|
+
const prepSpinner = createSpinner("Preparing withdrawal (MsgDeposit)...");
|
|
9844
|
+
const prepared = await client.withdraw.prepare({
|
|
9845
|
+
asset: options.asset,
|
|
9846
|
+
amount: options.amount,
|
|
9847
|
+
l1Address: options.l1Address,
|
|
9848
|
+
maxFeeBps: options.maxFeeBps
|
|
9849
|
+
});
|
|
9850
|
+
prepSpinner.succeed("Withdrawal prepared");
|
|
9851
|
+
if (options.dryRun) {
|
|
9852
|
+
if (isJsonOutput()) {
|
|
9853
|
+
outputJson({ dryRun: true, prepared });
|
|
9854
|
+
} else {
|
|
9855
|
+
printResult("Withdraw Preview (dry-run)");
|
|
9856
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
9857
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
9858
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
9859
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
9860
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
9918
9861
|
}
|
|
9862
|
+
return;
|
|
9919
9863
|
}
|
|
9920
|
-
if (
|
|
9921
|
-
|
|
9922
|
-
|
|
9864
|
+
if (!isJsonOutput()) {
|
|
9865
|
+
printResult("Withdraw Preview");
|
|
9866
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
9867
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
9868
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
9869
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
9870
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
9871
|
+
}
|
|
9872
|
+
if (!options.yes) {
|
|
9873
|
+
warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
|
|
9874
|
+
throw new ConfirmationRequiredError(
|
|
9875
|
+
"Withdrawal requires confirmation.",
|
|
9876
|
+
"Pass --yes to confirm, or --dry-run to preview."
|
|
9877
|
+
);
|
|
9878
|
+
}
|
|
9879
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
9880
|
+
const execSpinner = createSpinner("Broadcasting withdrawal...");
|
|
9881
|
+
const result = await client.withdraw.execute(prepared);
|
|
9882
|
+
execSpinner.succeed("Withdrawal submitted");
|
|
9883
|
+
if (isJsonOutput()) {
|
|
9884
|
+
outputJson({ prepared, result });
|
|
9885
|
+
} else {
|
|
9886
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
9923
9887
|
}
|
|
9924
|
-
return lines.join("\n");
|
|
9925
9888
|
}
|
|
9926
9889
|
|
|
9927
|
-
// src/
|
|
9928
|
-
import { randomUUID } from "node:crypto";
|
|
9929
|
-
|
|
9930
|
-
// src/agent/toolOutputSigning.ts
|
|
9931
|
-
import { getChainKind as getChainKind4 } from "@vultisig/sdk";
|
|
9932
|
-
|
|
9933
|
-
// src/agent/executor.ts
|
|
9890
|
+
// src/commands/discount.ts
|
|
9934
9891
|
import {
|
|
9935
|
-
|
|
9936
|
-
|
|
9937
|
-
|
|
9938
|
-
resolveChainReference,
|
|
9939
|
-
VaultError as VaultError3,
|
|
9940
|
-
VaultErrorCode as VaultErrorCode3,
|
|
9941
|
-
Vultisig as VultisigSdk
|
|
9892
|
+
baseAffiliateBps,
|
|
9893
|
+
vultDiscountTierBps,
|
|
9894
|
+
vultDiscountTierMinBalances
|
|
9942
9895
|
} from "@vultisig/sdk";
|
|
9943
|
-
|
|
9944
|
-
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
|
|
9949
|
-
|
|
9896
|
+
import chalk7 from "chalk";
|
|
9897
|
+
var TIER_CONFIG = {
|
|
9898
|
+
none: { bps: baseAffiliateBps, discount: 0 },
|
|
9899
|
+
...Object.fromEntries(
|
|
9900
|
+
Object.entries(vultDiscountTierMinBalances).map(([tier, minVult]) => [
|
|
9901
|
+
tier,
|
|
9902
|
+
{
|
|
9903
|
+
bps: baseAffiliateBps - vultDiscountTierBps[tier],
|
|
9904
|
+
discount: vultDiscountTierBps[tier],
|
|
9905
|
+
minVult
|
|
9906
|
+
}
|
|
9907
|
+
])
|
|
9908
|
+
)
|
|
9909
|
+
};
|
|
9910
|
+
function getTierColor(tier) {
|
|
9911
|
+
const colors = {
|
|
9912
|
+
none: chalk7.gray,
|
|
9913
|
+
bronze: chalk7.hex("#CD7F32"),
|
|
9914
|
+
silver: chalk7.hex("#C0C0C0"),
|
|
9915
|
+
gold: chalk7.hex("#FFD700"),
|
|
9916
|
+
platinum: chalk7.hex("#E5E4E2"),
|
|
9917
|
+
diamond: chalk7.hex("#B9F2FF"),
|
|
9918
|
+
ultimate: chalk7.hex("#FF00FF")
|
|
9919
|
+
};
|
|
9920
|
+
return colors[tier] || chalk7.white;
|
|
9950
9921
|
}
|
|
9951
|
-
|
|
9952
|
-
|
|
9953
|
-
|
|
9954
|
-
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
const
|
|
9959
|
-
|
|
9960
|
-
|
|
9961
|
-
|
|
9962
|
-
|
|
9963
|
-
const yParityOrV2 = Number(yParity ?? v);
|
|
9964
|
-
const recoveryBit2 = toRecoveryBit(yParityOrV2);
|
|
9965
|
-
return new secp256k12.Signature(hexToBigInt(r), hexToBigInt(s)).addRecoveryBit(recoveryBit2);
|
|
9966
|
-
}
|
|
9967
|
-
const signatureHex = isHex(signature) ? signature : toHex(signature);
|
|
9968
|
-
if (size(signatureHex) !== 65)
|
|
9969
|
-
throw new Error("invalid signature length");
|
|
9970
|
-
const yParityOrV = hexToNumber(`0x${signatureHex.slice(130)}`);
|
|
9971
|
-
const recoveryBit = toRecoveryBit(yParityOrV);
|
|
9972
|
-
return secp256k12.Signature.fromCompact(signatureHex.substring(2, 130)).addRecoveryBit(recoveryBit);
|
|
9973
|
-
})();
|
|
9974
|
-
const publicKey = signature_.recoverPublicKey(hashHex.substring(2)).toHex(false);
|
|
9975
|
-
return `0x${publicKey}`;
|
|
9922
|
+
function getNextTier(currentTier) {
|
|
9923
|
+
const tierOrder = ["none", "bronze", "silver", "gold", "platinum", "diamond", "ultimate"];
|
|
9924
|
+
const currentIndex = tierOrder.indexOf(currentTier);
|
|
9925
|
+
if (currentIndex === -1 || currentIndex >= tierOrder.length - 1) {
|
|
9926
|
+
return null;
|
|
9927
|
+
}
|
|
9928
|
+
const nextTierName = tierOrder[currentIndex + 1];
|
|
9929
|
+
const config = TIER_CONFIG[nextTierName];
|
|
9930
|
+
if ("minVult" in config) {
|
|
9931
|
+
return { name: nextTierName, vultRequired: config.minVult };
|
|
9932
|
+
}
|
|
9933
|
+
return null;
|
|
9976
9934
|
}
|
|
9977
|
-
function
|
|
9978
|
-
|
|
9979
|
-
|
|
9980
|
-
|
|
9981
|
-
|
|
9982
|
-
|
|
9983
|
-
|
|
9984
|
-
|
|
9935
|
+
async function executeDiscount(ctx2, options = {}) {
|
|
9936
|
+
const vault = await ctx2.ensureActiveVault();
|
|
9937
|
+
const spinner = createSpinner(options.refresh ? "Refreshing discount tier..." : "Loading discount tier...");
|
|
9938
|
+
const tierResult = options.refresh ? await vault.updateDiscountTier() : await vault.getDiscountTier();
|
|
9939
|
+
const tier = tierResult || "none";
|
|
9940
|
+
const config = TIER_CONFIG[tier];
|
|
9941
|
+
const nextTier = getNextTier(tier);
|
|
9942
|
+
const tierInfo = {
|
|
9943
|
+
tier,
|
|
9944
|
+
feeBps: config.bps,
|
|
9945
|
+
discountBps: config.discount,
|
|
9946
|
+
nextTier
|
|
9947
|
+
};
|
|
9948
|
+
spinner.succeed("Discount tier loaded");
|
|
9949
|
+
if (isJsonOutput()) {
|
|
9950
|
+
outputJson({
|
|
9951
|
+
tier: tierInfo.tier,
|
|
9952
|
+
feeBps: tierInfo.feeBps,
|
|
9953
|
+
discountBps: tierInfo.discountBps,
|
|
9954
|
+
nextTier: tierInfo.nextTier
|
|
9955
|
+
});
|
|
9956
|
+
return tierInfo;
|
|
9957
|
+
}
|
|
9958
|
+
displayDiscountTier(tierInfo);
|
|
9959
|
+
return tierInfo;
|
|
9985
9960
|
}
|
|
9986
|
-
|
|
9987
|
-
|
|
9988
|
-
|
|
9989
|
-
|
|
9961
|
+
function displayDiscountTier(tierInfo) {
|
|
9962
|
+
const tierColor = getTierColor(tierInfo.tier);
|
|
9963
|
+
printResult(chalk7.cyan("\n+----------------------------------------+"));
|
|
9964
|
+
printResult(chalk7.cyan("| VULT Discount Tier |"));
|
|
9965
|
+
printResult(chalk7.cyan("+----------------------------------------+\n"));
|
|
9966
|
+
const tierDisplay = tierInfo.tier === "none" ? chalk7.gray("No Tier") : tierColor(tierInfo.tier.charAt(0).toUpperCase() + tierInfo.tier.slice(1));
|
|
9967
|
+
printResult(` Current Tier: ${tierDisplay}`);
|
|
9968
|
+
if (tierInfo.tier === "none") {
|
|
9969
|
+
printResult(` Swap Fee: ${chalk7.gray("50 bps (0.50%)")}`);
|
|
9970
|
+
printResult(` Discount: ${chalk7.gray("None")}`);
|
|
9971
|
+
} else {
|
|
9972
|
+
printResult(` Swap Fee: ${chalk7.green(`${tierInfo.feeBps} bps (${(tierInfo.feeBps / 100).toFixed(2)}%)`)}`);
|
|
9973
|
+
printResult(` Discount: ${chalk7.green(`${tierInfo.discountBps} bps saved`)}`);
|
|
9974
|
+
}
|
|
9975
|
+
if (tierInfo.nextTier) {
|
|
9976
|
+
const nextTierColor = getTierColor(tierInfo.nextTier.name);
|
|
9977
|
+
printResult(chalk7.bold("\n Next Tier:"));
|
|
9978
|
+
printResult(
|
|
9979
|
+
` ${nextTierColor(tierInfo.nextTier.name.charAt(0).toUpperCase() + tierInfo.nextTier.name.slice(1))} - requires ${tierInfo.nextTier.vultRequired.toLocaleString()} VULT`
|
|
9980
|
+
);
|
|
9981
|
+
} else if (tierInfo.tier === "ultimate") {
|
|
9982
|
+
printResult(chalk7.bold("\n ") + chalk7.magenta("You have the highest tier! 0% swap fees."));
|
|
9983
|
+
}
|
|
9984
|
+
info(chalk7.gray("\n Tip: Thorguard NFT holders get +1 tier upgrade (up to gold)"));
|
|
9985
|
+
printResult("");
|
|
9990
9986
|
}
|
|
9991
9987
|
|
|
9992
|
-
//
|
|
9993
|
-
|
|
9994
|
-
init_concat();
|
|
9995
|
-
init_toHex();
|
|
9996
|
-
init_keccak256();
|
|
9988
|
+
// src/commands/auth.ts
|
|
9989
|
+
import { executeAuthLogout, executeAuthSetup, executeAuthStatus } from "@vultisig/client-shared";
|
|
9997
9990
|
|
|
9998
|
-
//
|
|
9999
|
-
|
|
10000
|
-
|
|
9991
|
+
// src/commands/agent.ts
|
|
9992
|
+
import chalk10 from "chalk";
|
|
9993
|
+
import Table from "cli-table3";
|
|
10001
9994
|
|
|
10002
|
-
//
|
|
10003
|
-
|
|
10004
|
-
|
|
10005
|
-
|
|
10006
|
-
|
|
10007
|
-
|
|
10008
|
-
|
|
10009
|
-
|
|
9995
|
+
// src/agent/ask.ts
|
|
9996
|
+
var AskInterface = class {
|
|
9997
|
+
session;
|
|
9998
|
+
verbose;
|
|
9999
|
+
autoApprove;
|
|
10000
|
+
responseParts = [];
|
|
10001
|
+
toolCalls = [];
|
|
10002
|
+
transactions = [];
|
|
10003
|
+
cards = [];
|
|
10004
|
+
warnings = [];
|
|
10005
|
+
outcome;
|
|
10006
|
+
error;
|
|
10007
|
+
// Tracks whether the currently-latched `error` is a terminal one (e.g. the
|
|
10008
|
+
// depth cap). A terminal error may overwrite a prior non-terminal one; once a
|
|
10009
|
+
// terminal error is recorded, later frames cannot replace it. See onError.
|
|
10010
|
+
errorIsTerminal = false;
|
|
10011
|
+
constructor(session, verbose = false, autoApprove = false) {
|
|
10012
|
+
this.session = session;
|
|
10013
|
+
this.verbose = verbose;
|
|
10014
|
+
this.autoApprove = autoApprove;
|
|
10010
10015
|
}
|
|
10011
|
-
|
|
10012
|
-
|
|
10013
|
-
|
|
10014
|
-
|
|
10015
|
-
|
|
10016
|
-
|
|
10017
|
-
|
|
10016
|
+
/**
|
|
10017
|
+
* Whether the turn threw with a still-unacknowledged broadcast (the F1
|
|
10018
|
+
* ack-failure case). The command's catch uses this to gate the ACK_FAILED
|
|
10019
|
+
* re-tag so a later, unrelated retryable error after an already-acked
|
|
10020
|
+
* broadcast keeps its own (retryable) classification instead of exit 8.
|
|
10021
|
+
*/
|
|
10022
|
+
hasUnacknowledgedBroadcast() {
|
|
10023
|
+
return this.session.hasUnacknowledgedBroadcast();
|
|
10018
10024
|
}
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
10022
|
-
|
|
10023
|
-
|
|
10024
|
-
|
|
10025
|
-
|
|
10025
|
+
/**
|
|
10026
|
+
* Get UI callbacks that silently collect results.
|
|
10027
|
+
* Tool progress is logged to stderr in verbose mode.
|
|
10028
|
+
*/
|
|
10029
|
+
getCallbacks() {
|
|
10030
|
+
return {
|
|
10031
|
+
onTextDelta: (_delta) => {
|
|
10032
|
+
},
|
|
10033
|
+
onToolCall: (_id, action, params) => {
|
|
10034
|
+
if (this.verbose) {
|
|
10035
|
+
const paramStr = params ? ` ${JSON.stringify(params)}` : "";
|
|
10036
|
+
process.stderr.write(`[tool] ${action}${paramStr} ...
|
|
10037
|
+
`);
|
|
10038
|
+
}
|
|
10039
|
+
},
|
|
10040
|
+
onToolResult: (id, action, success2, data, error2, code) => {
|
|
10041
|
+
this.toolCalls.push({ id, action, success: success2, data, error: error2, code });
|
|
10042
|
+
if (this.verbose) {
|
|
10043
|
+
const status = success2 ? "ok" : `error: ${error2}${code ? ` [${code}]` : ""}`;
|
|
10044
|
+
process.stderr.write(`[tool] ${action}: ${status}
|
|
10045
|
+
`);
|
|
10046
|
+
}
|
|
10047
|
+
},
|
|
10048
|
+
onAssistantMessage: (content) => {
|
|
10049
|
+
if (content) {
|
|
10050
|
+
this.responseParts.push(content);
|
|
10051
|
+
}
|
|
10052
|
+
},
|
|
10053
|
+
onBalanceSummary: (card) => {
|
|
10054
|
+
this.cards.push(card);
|
|
10055
|
+
},
|
|
10056
|
+
onTurnOutcome: (outcome) => {
|
|
10057
|
+
this.outcome = outcome;
|
|
10058
|
+
},
|
|
10059
|
+
onSuggestions: (_suggestions) => {
|
|
10060
|
+
},
|
|
10061
|
+
onTxStatus: (txHash, chain, status, explorerUrl) => {
|
|
10062
|
+
const existing = this.transactions.find((t) => t.hash === txHash);
|
|
10063
|
+
if (existing) {
|
|
10064
|
+
existing.status = status;
|
|
10065
|
+
if (explorerUrl) existing.explorerUrl = explorerUrl;
|
|
10066
|
+
} else {
|
|
10067
|
+
this.transactions.push({ hash: txHash, chain, explorerUrl, status });
|
|
10068
|
+
}
|
|
10069
|
+
if (this.verbose) {
|
|
10070
|
+
process.stderr.write(`[tx] ${chain}: ${txHash} (${status})
|
|
10071
|
+
`);
|
|
10072
|
+
}
|
|
10073
|
+
},
|
|
10074
|
+
onError: (message, code) => {
|
|
10075
|
+
const isTerminal2 = isTerminalAgentErrorCode(code);
|
|
10076
|
+
if (!this.error || isTerminal2 && !this.errorIsTerminal) {
|
|
10077
|
+
this.error = { message, code };
|
|
10078
|
+
this.errorIsTerminal = isTerminal2;
|
|
10079
|
+
}
|
|
10080
|
+
process.stderr.write(`[error] ${message} [${code}]
|
|
10081
|
+
`);
|
|
10082
|
+
},
|
|
10083
|
+
onProtocolWarning: (warning) => {
|
|
10084
|
+
this.warnings.push(warning);
|
|
10085
|
+
process.stderr.write(`[warning] ${warning.message} [${warning.code}]
|
|
10086
|
+
`);
|
|
10087
|
+
},
|
|
10088
|
+
onDone: () => {
|
|
10089
|
+
},
|
|
10090
|
+
requestPassword: async () => {
|
|
10091
|
+
throw new Error("Password required but not provided. Use --password flag.");
|
|
10092
|
+
},
|
|
10093
|
+
requestConfirmation: async (message) => {
|
|
10094
|
+
if (!this.autoApprove) {
|
|
10095
|
+
process.stderr.write(`[confirm] signing requires --yes \u2014 NOT broadcasting: ${message}
|
|
10096
|
+
`);
|
|
10097
|
+
} else {
|
|
10098
|
+
process.stderr.write(`[confirm] auto-approved (--yes): ${message}
|
|
10099
|
+
`);
|
|
10100
|
+
}
|
|
10101
|
+
return this.autoApprove;
|
|
10102
|
+
}
|
|
10103
|
+
};
|
|
10104
|
+
}
|
|
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();
|
|
10121
|
+
}
|
|
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
|
+
};
|
|
10026
10141
|
}
|
|
10027
10142
|
};
|
|
10028
10143
|
|
|
10029
|
-
//
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
const
|
|
10036
|
-
const
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
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
|
+
`);
|
|
10047
10166
|
}
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
if (bytesMatch) {
|
|
10052
|
-
const [_type, size_] = bytesMatch;
|
|
10053
|
-
if (size_ && size(value) !== Number.parseInt(size_, 10))
|
|
10054
|
-
throw new BytesSizeMismatchError({
|
|
10055
|
-
expectedSize: Number.parseInt(size_, 10),
|
|
10056
|
-
givenSize: size(value)
|
|
10057
|
-
});
|
|
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");
|
|
10058
10170
|
}
|
|
10059
|
-
const
|
|
10060
|
-
|
|
10061
|
-
|
|
10062
|
-
|
|
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;
|
|
10063
10192
|
}
|
|
10193
|
+
throw err;
|
|
10064
10194
|
}
|
|
10065
|
-
};
|
|
10066
|
-
if (types.EIP712Domain && domain) {
|
|
10067
|
-
if (typeof domain !== "object")
|
|
10068
|
-
throw new InvalidDomainError({ domain });
|
|
10069
|
-
validateData(types.EIP712Domain, domain);
|
|
10070
|
-
}
|
|
10071
|
-
if (primaryType !== "EIP712Domain") {
|
|
10072
|
-
if (types[primaryType])
|
|
10073
|
-
validateData(types[primaryType], message);
|
|
10074
|
-
else
|
|
10075
|
-
throw new InvalidPrimaryTypeError({ primaryType, types });
|
|
10076
10195
|
}
|
|
10077
|
-
|
|
10078
|
-
function getTypesForEIP712Domain({ domain }) {
|
|
10079
|
-
return [
|
|
10080
|
-
typeof domain?.name === "string" && { name: "name", type: "string" },
|
|
10081
|
-
domain?.version && { name: "version", type: "string" },
|
|
10082
|
-
(typeof domain?.chainId === "number" || typeof domain?.chainId === "bigint") && {
|
|
10083
|
-
name: "chainId",
|
|
10084
|
-
type: "uint256"
|
|
10085
|
-
},
|
|
10086
|
-
domain?.verifyingContract && {
|
|
10087
|
-
name: "verifyingContract",
|
|
10088
|
-
type: "address"
|
|
10089
|
-
},
|
|
10090
|
-
domain?.salt && { name: "salt", type: "bytes32" }
|
|
10091
|
-
].filter(Boolean);
|
|
10092
|
-
}
|
|
10093
|
-
function validateReference(type) {
|
|
10094
|
-
if (type === "address" || type === "bool" || type === "string" || type.startsWith("bytes") || type.startsWith("uint") || type.startsWith("int"))
|
|
10095
|
-
throw new InvalidStructTypeError({ type });
|
|
10196
|
+
throw lastError || new Error("Authentication failed after all attempts");
|
|
10096
10197
|
}
|
|
10097
10198
|
|
|
10098
|
-
//
|
|
10099
|
-
|
|
10100
|
-
|
|
10101
|
-
|
|
10102
|
-
|
|
10103
|
-
|
|
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 } : {}
|
|
10104
10212
|
};
|
|
10105
|
-
validateTypedData({
|
|
10106
|
-
domain,
|
|
10107
|
-
message,
|
|
10108
|
-
primaryType,
|
|
10109
|
-
types
|
|
10110
|
-
});
|
|
10111
|
-
const parts = ["0x1901"];
|
|
10112
|
-
if (domain)
|
|
10113
|
-
parts.push(hashDomain({
|
|
10114
|
-
domain,
|
|
10115
|
-
types
|
|
10116
|
-
}));
|
|
10117
|
-
if (primaryType !== "EIP712Domain")
|
|
10118
|
-
parts.push(hashStruct({
|
|
10119
|
-
data: message,
|
|
10120
|
-
primaryType,
|
|
10121
|
-
types
|
|
10122
|
-
}));
|
|
10123
|
-
return keccak256(concat(parts));
|
|
10124
10213
|
}
|
|
10125
|
-
function
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
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;
|
|
10131
10222
|
}
|
|
10132
|
-
function
|
|
10133
|
-
|
|
10134
|
-
data,
|
|
10135
|
-
primaryType,
|
|
10136
|
-
types
|
|
10137
|
-
});
|
|
10138
|
-
return keccak256(encoded);
|
|
10223
|
+
function asString(v) {
|
|
10224
|
+
return typeof v === "string" ? stripControlChars(v) : "";
|
|
10139
10225
|
}
|
|
10140
|
-
function
|
|
10141
|
-
|
|
10142
|
-
const
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
encodedTypes.push(type);
|
|
10151
|
-
encodedValues.push(value);
|
|
10152
|
-
}
|
|
10153
|
-
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;
|
|
10154
10236
|
}
|
|
10155
|
-
function
|
|
10156
|
-
|
|
10157
|
-
|
|
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 };
|
|
10158
10245
|
}
|
|
10159
|
-
function
|
|
10160
|
-
|
|
10161
|
-
const
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
|
|
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;
|
|
10166
10257
|
}
|
|
10167
|
-
return
|
|
10258
|
+
return card;
|
|
10168
10259
|
}
|
|
10169
|
-
function
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
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
|
+
}
|
|
10174
10278
|
}
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
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 };
|
|
10178
10301
|
}
|
|
10179
|
-
return
|
|
10302
|
+
return null;
|
|
10180
10303
|
}
|
|
10181
|
-
function
|
|
10182
|
-
if (
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
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
|
+
}
|
|
10187
10342
|
}
|
|
10188
|
-
if (
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
return [{ type: "bytes32" }, keccak256(toHex(value))];
|
|
10192
|
-
if (type.lastIndexOf("]") === type.length - 1) {
|
|
10193
|
-
const parsedType = type.slice(0, type.lastIndexOf("["));
|
|
10194
|
-
const typeValuePairs = value.map((item) => encodeField({
|
|
10195
|
-
name,
|
|
10196
|
-
type: parsedType,
|
|
10197
|
-
types,
|
|
10198
|
-
value: item
|
|
10199
|
-
}));
|
|
10200
|
-
return [
|
|
10201
|
-
{ type: "bytes32" },
|
|
10202
|
-
keccak256(encodeAbiParameters(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v)))
|
|
10203
|
-
];
|
|
10343
|
+
if (sawUsd) {
|
|
10344
|
+
lines.push(chalk8.gray(" " + "\u2500".repeat(36)));
|
|
10345
|
+
lines.push(` ${chalk8.bold("Total")} ${chalk8.green(formatUsd(total))}`);
|
|
10204
10346
|
}
|
|
10205
|
-
return
|
|
10347
|
+
return lines.join("\n");
|
|
10206
10348
|
}
|
|
10207
10349
|
|
|
10208
|
-
//
|
|
10209
|
-
|
|
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";
|
|
10210
10366
|
|
|
10211
10367
|
// src/core/VaultStateStore.ts
|
|
10212
10368
|
import * as fs3 from "node:fs";
|
|
@@ -11992,6 +12148,7 @@ var CLI_SIGNABLE_PREP_TOOLS = /* @__PURE__ */ new Set([
|
|
|
11992
12148
|
"execute_send",
|
|
11993
12149
|
"execute_contract_call"
|
|
11994
12150
|
]);
|
|
12151
|
+
var CLI_SIGNABLE_YIELD_TOOLS = /* @__PURE__ */ new Set(["yield_enter", "yield_exit"]);
|
|
11995
12152
|
function asRecord(value) {
|
|
11996
12153
|
if (typeof value === "string") {
|
|
11997
12154
|
try {
|
|
@@ -12092,11 +12249,50 @@ function buildTxReadyFromToolOutput(toolName, output) {
|
|
|
12092
12249
|
}
|
|
12093
12250
|
return { __buildTx: true, chain: chainStr, chain_id: chainIdStr, ...action ? { action } : {}, tx: main };
|
|
12094
12251
|
}
|
|
12252
|
+
function buildTxReadyFromYieldOutput(toolName, output) {
|
|
12253
|
+
if (!CLI_SIGNABLE_YIELD_TOOLS.has(toolName)) return null;
|
|
12254
|
+
const env = asRecord(output);
|
|
12255
|
+
if (!env) return null;
|
|
12256
|
+
if (env.status === "error" || "error" in env) return null;
|
|
12257
|
+
const chain = asChainString(env.chain);
|
|
12258
|
+
if (!chain) return null;
|
|
12259
|
+
const resolved = resolveChain(chain);
|
|
12260
|
+
if (!resolved || getChainKind4(resolved) !== "evm") return null;
|
|
12261
|
+
const chainStr = chain;
|
|
12262
|
+
const rawTxs = env.transactions;
|
|
12263
|
+
if (!Array.isArray(rawTxs) || rawTxs.length === 0) return null;
|
|
12264
|
+
const legs = [];
|
|
12265
|
+
for (const raw of rawTxs) {
|
|
12266
|
+
const legObj = asRecord(raw);
|
|
12267
|
+
if (!legObj) return null;
|
|
12268
|
+
if (legObj.status === "error" || "error" in legObj) return null;
|
|
12269
|
+
const leg = extractLeg(legObj);
|
|
12270
|
+
if (!leg) return null;
|
|
12271
|
+
legs.push(leg);
|
|
12272
|
+
}
|
|
12273
|
+
if (legs.length > 2) return null;
|
|
12274
|
+
const action = typeof env.action === "string" && env.action !== "" ? env.action : void 0;
|
|
12275
|
+
if (legs.length === 2) {
|
|
12276
|
+
const [approveLeg, mainLeg] = legs;
|
|
12277
|
+
return {
|
|
12278
|
+
__buildTx: true,
|
|
12279
|
+
chain: chainStr,
|
|
12280
|
+
...action ? { action } : {},
|
|
12281
|
+
approvalTxArgs: { chain: chainStr, tx: approveLeg },
|
|
12282
|
+
txArgs: { chain: chainStr, tx: mainLeg }
|
|
12283
|
+
};
|
|
12284
|
+
}
|
|
12285
|
+
return { __buildTx: true, chain: chainStr, ...action ? { action } : {}, tx: legs[0] };
|
|
12286
|
+
}
|
|
12095
12287
|
function deriveToolOutputCandidate(toolName, output) {
|
|
12096
12288
|
if (CLI_SIGNABLE_FLAT_TOOLS.has(toolName)) {
|
|
12097
12289
|
const payload = buildTxReadyFromToolOutput(toolName, output);
|
|
12098
12290
|
return payload ? { payload, source: "flat", toolName } : null;
|
|
12099
12291
|
}
|
|
12292
|
+
if (CLI_SIGNABLE_YIELD_TOOLS.has(toolName)) {
|
|
12293
|
+
const payload = buildTxReadyFromYieldOutput(toolName, output);
|
|
12294
|
+
return payload ? { payload, source: "yield", toolName } : null;
|
|
12295
|
+
}
|
|
12100
12296
|
if (CLI_SIGNABLE_PREP_TOOLS.has(toolName)) {
|
|
12101
12297
|
const env = asRecord(output);
|
|
12102
12298
|
if (!env || env.status === "error" || "error" in env) return null;
|
|
@@ -12713,7 +12909,8 @@ var AgentClient = class {
|
|
|
12713
12909
|
maybeSignToolOutput(status, toolName, output, callbacks, v1Type) {
|
|
12714
12910
|
if (v1Type === "tool-output-error") return;
|
|
12715
12911
|
if (status !== "done" || !toolName || !callbacks.onToolOutputTx) return;
|
|
12716
|
-
if (!CLI_SIGNABLE_FLAT_TOOLS.has(toolName) && !CLI_SIGNABLE_PREP_TOOLS.has(toolName))
|
|
12912
|
+
if (!CLI_SIGNABLE_FLAT_TOOLS.has(toolName) && !CLI_SIGNABLE_PREP_TOOLS.has(toolName) && !CLI_SIGNABLE_YIELD_TOOLS.has(toolName))
|
|
12913
|
+
return;
|
|
12717
12914
|
const candidate = deriveToolOutputCandidate(toolName, output);
|
|
12718
12915
|
if (!candidate) return;
|
|
12719
12916
|
if (this.verbose)
|
|
@@ -15082,7 +15279,7 @@ var cachedVersion = null;
|
|
|
15082
15279
|
function getVersion() {
|
|
15083
15280
|
if (cachedVersion) return cachedVersion;
|
|
15084
15281
|
if (true) {
|
|
15085
|
-
cachedVersion = "2.
|
|
15282
|
+
cachedVersion = "2.23.0";
|
|
15086
15283
|
return cachedVersion;
|
|
15087
15284
|
}
|
|
15088
15285
|
try {
|
|
@@ -16479,7 +16676,7 @@ Error: ${error2.message}`));
|
|
|
16479
16676
|
const [fromChainStr, toChainStr, amountStr, ...rest] = args;
|
|
16480
16677
|
const fromChain = findChainByName(fromChainStr) || fromChainStr;
|
|
16481
16678
|
const toChain = findChainByName(toChainStr) || toChainStr;
|
|
16482
|
-
const amount =
|
|
16679
|
+
const amount = amountStr;
|
|
16483
16680
|
let fromToken;
|
|
16484
16681
|
let toToken;
|
|
16485
16682
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -16505,7 +16702,7 @@ Error: ${error2.message}`));
|
|
|
16505
16702
|
const [fromChainStr, toChainStr, amountStr, ...rest] = args;
|
|
16506
16703
|
const fromChain = findChainByName(fromChainStr) || fromChainStr;
|
|
16507
16704
|
const toChain = findChainByName(toChainStr) || toChainStr;
|
|
16508
|
-
const amount =
|
|
16705
|
+
const amount = amountStr;
|
|
16509
16706
|
let fromToken;
|
|
16510
16707
|
let toToken;
|
|
16511
16708
|
let slippage;
|
|
@@ -17508,7 +17705,7 @@ Examples:
|
|
|
17508
17705
|
await executeSwapQuote(context, {
|
|
17509
17706
|
fromChain,
|
|
17510
17707
|
toChain,
|
|
17511
|
-
amount: options.max ? "max" :
|
|
17708
|
+
amount: options.max ? "max" : amountStr,
|
|
17512
17709
|
fromToken: options.fromToken,
|
|
17513
17710
|
toToken: options.toToken
|
|
17514
17711
|
});
|
|
@@ -17533,7 +17730,7 @@ See also: swap-quote, swap-chains, balance`
|
|
|
17533
17730
|
await executeSwap(context, {
|
|
17534
17731
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
17535
17732
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
17536
|
-
amount: options.max ? "max" :
|
|
17733
|
+
amount: options.max ? "max" : amountStr,
|
|
17537
17734
|
fromToken: options.fromToken,
|
|
17538
17735
|
toToken: options.toToken,
|
|
17539
17736
|
slippage: options.slippage ? parseFloat(options.slippage) : void 0,
|