@trustwallet/cli 0.10.0 → 0.11.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/dist/commands/automate.d.ts.map +1 -1
- package/dist/commands/onramp.d.ts.map +1 -1
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/transfer.d.ts +8 -0
- package/dist/commands/transfer.d.ts.map +1 -1
- package/dist/commands/wallet.d.ts.map +1 -1
- package/dist/commands/watch.d.ts.map +1 -1
- package/dist/index.js +1444 -735
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -3111,9 +3111,12 @@ var require_commander = __commonJS({
|
|
|
3111
3111
|
var dist_exports = {};
|
|
3112
3112
|
__export(dist_exports, {
|
|
3113
3113
|
ALERT_ERROR_CODES: () => ALERT_ERROR_CODES,
|
|
3114
|
+
ALLOWED_OFFRAMP_PROVIDERS: () => ALLOWED_OFFRAMP_PROVIDERS,
|
|
3114
3115
|
AUTOMATION_ERROR_CODES: () => AUTOMATION_ERROR_CODES,
|
|
3115
3116
|
AlertError: () => AlertError,
|
|
3116
3117
|
AutomationError: () => AutomationError,
|
|
3118
|
+
BALANCE_ERROR_CODES: () => BALANCE_ERROR_CODES,
|
|
3119
|
+
BalanceError: () => BalanceError,
|
|
3117
3120
|
CELO_NATIVE_ADDRESS: () => CELO_NATIVE_ADDRESS,
|
|
3118
3121
|
CHAINS: () => CHAINS,
|
|
3119
3122
|
COMMON_ERROR_CODES: () => COMMON_ERROR_CODES,
|
|
@@ -3140,12 +3143,15 @@ __export(dist_exports, {
|
|
|
3140
3143
|
WatchError: () => WatchError,
|
|
3141
3144
|
X402Error: () => X402Error,
|
|
3142
3145
|
X402_ERROR_CODES: () => X402_ERROR_CODES,
|
|
3146
|
+
assertStrictPositiveAmount: () => assertStrictPositiveAmount,
|
|
3143
3147
|
chainKeyToCoinType: () => chainKeyToCoinType,
|
|
3144
3148
|
classifyApiError: () => classifyApiError,
|
|
3145
3149
|
classifyStorageError: () => classifyStorageError,
|
|
3146
3150
|
classifyWalletLoadError: () => classifyWalletLoadError,
|
|
3147
3151
|
coinTypeToChainKey: () => coinTypeToChainKey,
|
|
3148
3152
|
createTwakClient: () => createTwakClient,
|
|
3153
|
+
fetchBalance: () => fetchBalance,
|
|
3154
|
+
fetchBalances: () => fetchBalances,
|
|
3149
3155
|
fiatToTokenAmount: () => fiatToTokenAmount,
|
|
3150
3156
|
formatAmount: () => formatAmount,
|
|
3151
3157
|
formatTokenId: () => formatTokenId,
|
|
@@ -3167,12 +3173,14 @@ __export(dist_exports, {
|
|
|
3167
3173
|
parseAmount: () => parseAmount,
|
|
3168
3174
|
parseHumanAmount: () => parseHumanAmount,
|
|
3169
3175
|
parseTokenId: () => parseTokenId,
|
|
3176
|
+
rankQuotesByBest: () => rankQuotesByBest,
|
|
3170
3177
|
resolveAddressOrName: () => resolveAddressOrName,
|
|
3171
3178
|
resolveName: () => resolveName,
|
|
3172
3179
|
resolveToken: () => resolveToken,
|
|
3173
3180
|
signRequest: () => signRequest,
|
|
3174
3181
|
sortQueryParams: () => sortQueryParams,
|
|
3175
3182
|
sortQueryParamsGoCompat: () => sortQueryParamsGoCompat,
|
|
3183
|
+
toCamelCase: () => toCamelCase,
|
|
3176
3184
|
tokenIdToChain: () => tokenIdToChain,
|
|
3177
3185
|
withRetry: () => withRetry
|
|
3178
3186
|
});
|
|
@@ -3397,10 +3405,10 @@ function sortQueryParamsGoCompat(query) {
|
|
|
3397
3405
|
}
|
|
3398
3406
|
function formatAmount(amount, decimals) {
|
|
3399
3407
|
const value = BigInt(amount);
|
|
3400
|
-
const divisor =
|
|
3408
|
+
const divisor = 10n ** BigInt(decimals);
|
|
3401
3409
|
const integerPart = value / divisor;
|
|
3402
3410
|
const fractionalPart = value % divisor;
|
|
3403
|
-
if (fractionalPart ===
|
|
3411
|
+
if (fractionalPart === 0n) return integerPart.toString();
|
|
3404
3412
|
const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
|
|
3405
3413
|
const trimmedFractional = fractionalStr.replace(/0+$/, "");
|
|
3406
3414
|
return `${integerPart}.${trimmedFractional}`;
|
|
@@ -3411,6 +3419,24 @@ function parseAmount(amount, decimals) {
|
|
|
3411
3419
|
const combined = integerPart + paddedFractional;
|
|
3412
3420
|
return BigInt(combined).toString();
|
|
3413
3421
|
}
|
|
3422
|
+
function assertStrictPositiveAmount(amount, decimals) {
|
|
3423
|
+
if (!DECIMAL_AMOUNT_RE.test(amount)) {
|
|
3424
|
+
throw new Error(
|
|
3425
|
+
`Amount must be a positive base-10 decimal string (no sign, no exponent). Got: ${amount}`
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
if (/^0+(?:\.0+)?$/.test(amount)) {
|
|
3429
|
+
throw new Error("Amount must be greater than 0");
|
|
3430
|
+
}
|
|
3431
|
+
if (decimals !== void 0) {
|
|
3432
|
+
const frac = amount.split(".")[1] ?? "";
|
|
3433
|
+
if (frac.length > decimals) {
|
|
3434
|
+
throw new Error(
|
|
3435
|
+
`Amount has ${frac.length} fractional digits but the token has only ${decimals} decimals. Truncating excess precision would under-send and likely invalidate the order.`
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3414
3440
|
function parseHumanAmount(amount, decimals) {
|
|
3415
3441
|
const trimmed = amount.trim();
|
|
3416
3442
|
if (trimmed.toLowerCase() === "all") return "all";
|
|
@@ -3421,10 +3447,24 @@ function parseHumanAmount(amount, decimals) {
|
|
|
3421
3447
|
function fiatToTokenAmount(fiatAmount, priceUsd, decimals) {
|
|
3422
3448
|
const stripped = fiatAmount.trim().replace(/^[$€£¥]/, "").trim();
|
|
3423
3449
|
const usdValue = parseFloat(stripped);
|
|
3424
|
-
if (isNaN(usdValue) || priceUsd <= 0)
|
|
3450
|
+
if (isNaN(usdValue) || priceUsd <= 0)
|
|
3451
|
+
throw new Error(`Invalid fiat amount or price: ${fiatAmount}`);
|
|
3425
3452
|
const tokenAmount = usdValue / priceUsd;
|
|
3426
3453
|
return parseAmount(tokenAmount.toFixed(decimals), decimals);
|
|
3427
3454
|
}
|
|
3455
|
+
function toCamelCase(obj) {
|
|
3456
|
+
if (Array.isArray(obj)) return obj.map(toCamelCase);
|
|
3457
|
+
if (obj !== null && typeof obj === "object") {
|
|
3458
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
3459
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
3460
|
+
const camelKey = key.replace(/_([a-z])/g, (_2, char) => char.toUpperCase());
|
|
3461
|
+
if (UNSAFE_KEYS.has(key) || UNSAFE_KEYS.has(camelKey)) continue;
|
|
3462
|
+
result[camelKey] = toCamelCase(value);
|
|
3463
|
+
}
|
|
3464
|
+
return result;
|
|
3465
|
+
}
|
|
3466
|
+
return obj;
|
|
3467
|
+
}
|
|
3428
3468
|
async function resolveName(name2) {
|
|
3429
3469
|
if (!name2.includes(".")) return null;
|
|
3430
3470
|
try {
|
|
@@ -3513,7 +3553,60 @@ async function withRetry(fn2, opts = {}) {
|
|
|
3513
3553
|
}
|
|
3514
3554
|
}
|
|
3515
3555
|
}
|
|
3516
|
-
|
|
3556
|
+
function rankQuotesByBest(quotes, getAmount) {
|
|
3557
|
+
const valid = [];
|
|
3558
|
+
for (const q3 of quotes) {
|
|
3559
|
+
const raw = getAmount(q3);
|
|
3560
|
+
if (raw === void 0) continue;
|
|
3561
|
+
const n4 = parseFloat(raw);
|
|
3562
|
+
if (!Number.isFinite(n4) || n4 <= 0) continue;
|
|
3563
|
+
valid.push({ quote: q3, amount: n4 });
|
|
3564
|
+
}
|
|
3565
|
+
if (valid.length === 0) return [];
|
|
3566
|
+
const best = Math.max(...valid.map((v5) => v5.amount));
|
|
3567
|
+
return valid.map(({ quote, amount }) => {
|
|
3568
|
+
const raw = (best - amount) / best * 100;
|
|
3569
|
+
return { ...quote, vsBestPct: Math.round(raw * 100) / 100 };
|
|
3570
|
+
}).sort((a3, b3) => a3.vsBestPct - b3.vsBestPct);
|
|
3571
|
+
}
|
|
3572
|
+
async function fetchBalances(httpClient, assets, currency = "USD") {
|
|
3573
|
+
try {
|
|
3574
|
+
const response = await withRetry(
|
|
3575
|
+
() => httpClient.gateway("POST", "/v1/balances", {
|
|
3576
|
+
currency,
|
|
3577
|
+
addresses: assets.map(({ assetId, address }) => ({
|
|
3578
|
+
asset_id: assetId,
|
|
3579
|
+
address
|
|
3580
|
+
}))
|
|
3581
|
+
})
|
|
3582
|
+
);
|
|
3583
|
+
const parsed = toCamelCase(response);
|
|
3584
|
+
if (!Array.isArray(parsed.balances)) {
|
|
3585
|
+
throw new BalanceError("NETWORK_ERROR", "Could not fetch balances. Please try again later.");
|
|
3586
|
+
}
|
|
3587
|
+
return parsed.balances;
|
|
3588
|
+
} catch (err) {
|
|
3589
|
+
if (err instanceof BalanceError) throw err;
|
|
3590
|
+
throw new BalanceError("NETWORK_ERROR", "Could not fetch balances. Please try again later.");
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
async function fetchBalance(httpClient, assetId, address, currency = "USD") {
|
|
3594
|
+
try {
|
|
3595
|
+
const params = new URLSearchParams({ asset_id: assetId, address, currency });
|
|
3596
|
+
const response = await withRetry(
|
|
3597
|
+
() => httpClient.gateway("GET", "/v1/balances", void 0, params.toString())
|
|
3598
|
+
);
|
|
3599
|
+
const parsed = toCamelCase(response);
|
|
3600
|
+
if (!parsed.balance) {
|
|
3601
|
+
throw new BalanceError("NETWORK_ERROR", "Could not fetch balance. Please try again later.");
|
|
3602
|
+
}
|
|
3603
|
+
return parsed.balance;
|
|
3604
|
+
} catch (err) {
|
|
3605
|
+
if (err instanceof BalanceError) throw err;
|
|
3606
|
+
throw new BalanceError("NETWORK_ERROR", "Could not fetch balance. Please try again later.");
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
var import_crypto, NATIVE_TOKEN_ADDRESS, SOLANA_NATIVE_ADDRESS, CHAINS, COMMON_TOKENS, BSC_18_DECIMAL_TOKENS, NATIVE_SYMBOLS, TOKEN_DECIMALS, CELO_NATIVE_ADDRESS, TwakError, TwakApiError, TwakNetworkError, TwakValidationError, CommandError, COMMON_ERROR_CODES, FS_ERRNO_CODES, SWAP_ERROR_CODES, SwapError, TRANSFER_ERROR_CODES, TransferError, WALLET_ERROR_CODES, WalletError, QUERY_ERROR_CODES, QueryError, BALANCE_ERROR_CODES, BalanceError, ALERT_ERROR_CODES, AlertError, ERC20_ERROR_CODES, Erc20Error, WATCH_ERROR_CODES, WatchError, AUTOMATION_ERROR_CODES, AutomationError, X402_ERROR_CODES, X402Error, DECIMAL_AMOUNT_RE, UNSAFE_KEYS, NAMING_API, DEFAULT_GATEWAY_URL, DEFAULT_MARKET_URL, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, DEFAULT_X_ORIGIN, TwakHttpClient, ALLOWED_OFFRAMP_PROVIDERS;
|
|
3517
3610
|
var init_dist = __esm({
|
|
3518
3611
|
"../core/dist/index.mjs"() {
|
|
3519
3612
|
"use strict";
|
|
@@ -4098,6 +4191,15 @@ var init_dist = __esm({
|
|
|
4098
4191
|
this.name = "QueryError";
|
|
4099
4192
|
}
|
|
4100
4193
|
};
|
|
4194
|
+
BALANCE_ERROR_CODES = {
|
|
4195
|
+
...COMMON_ERROR_CODES
|
|
4196
|
+
};
|
|
4197
|
+
BalanceError = class extends CommandError {
|
|
4198
|
+
constructor(code2, message) {
|
|
4199
|
+
super(code2, message);
|
|
4200
|
+
this.name = "BalanceError";
|
|
4201
|
+
}
|
|
4202
|
+
};
|
|
4101
4203
|
ALERT_ERROR_CODES = {
|
|
4102
4204
|
...COMMON_ERROR_CODES,
|
|
4103
4205
|
ALERT_NOT_FOUND: "ALERT_NOT_FOUND"
|
|
@@ -4148,10 +4250,14 @@ var init_dist = __esm({
|
|
|
4148
4250
|
this.name = "X402Error";
|
|
4149
4251
|
}
|
|
4150
4252
|
};
|
|
4253
|
+
DECIMAL_AMOUNT_RE = /^(?:0|[1-9]\d*)(?:\.\d+)?$/;
|
|
4254
|
+
UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
4151
4255
|
NAMING_API = "https://naming.trustwallet.com/v1/lookup";
|
|
4152
4256
|
DEFAULT_GATEWAY_URL = "https://tws.trustwallet.com";
|
|
4153
4257
|
DEFAULT_MARKET_URL = "https://market.trustwallet.com";
|
|
4154
4258
|
DEFAULT_TIMEOUT = 3e4;
|
|
4259
|
+
DEFAULT_USER_AGENT = "Mozilla/5.0 AgentKit";
|
|
4260
|
+
DEFAULT_X_ORIGIN = "Trust/2.15.0 AgentKit";
|
|
4155
4261
|
TwakHttpClient = class {
|
|
4156
4262
|
config;
|
|
4157
4263
|
constructor(config2) {
|
|
@@ -4159,7 +4265,8 @@ var init_dist = __esm({
|
|
|
4159
4265
|
gatewayBaseUrl: DEFAULT_GATEWAY_URL,
|
|
4160
4266
|
marketBaseUrl: DEFAULT_MARKET_URL,
|
|
4161
4267
|
timeout: DEFAULT_TIMEOUT,
|
|
4162
|
-
userAgent:
|
|
4268
|
+
userAgent: DEFAULT_USER_AGENT,
|
|
4269
|
+
xOrigin: DEFAULT_X_ORIGIN,
|
|
4163
4270
|
...config2
|
|
4164
4271
|
};
|
|
4165
4272
|
}
|
|
@@ -4218,8 +4325,13 @@ var init_dist = __esm({
|
|
|
4218
4325
|
async fetch(url, init3) {
|
|
4219
4326
|
const controller = new AbortController();
|
|
4220
4327
|
const timeout = setTimeout(() => controller.abort(), this.config.timeout);
|
|
4328
|
+
const headers = {
|
|
4329
|
+
...this.config.userAgent ? { "User-Agent": this.config.userAgent } : {},
|
|
4330
|
+
...this.config.xOrigin ? { "X-Origin": this.config.xOrigin } : {},
|
|
4331
|
+
...init3.headers
|
|
4332
|
+
};
|
|
4221
4333
|
try {
|
|
4222
|
-
const response = await fetch(url, { ...init3, signal: controller.signal });
|
|
4334
|
+
const response = await fetch(url, { ...init3, headers, signal: controller.signal });
|
|
4223
4335
|
if (!response.ok) {
|
|
4224
4336
|
const text = await response.text().catch(() => "");
|
|
4225
4337
|
let parsed;
|
|
@@ -4237,10 +4349,7 @@ var init_dist = __esm({
|
|
|
4237
4349
|
return await response.json();
|
|
4238
4350
|
} catch (error3) {
|
|
4239
4351
|
if (error3 instanceof TwakApiError) throw error3;
|
|
4240
|
-
throw new TwakNetworkError(
|
|
4241
|
-
error3 instanceof Error ? error3.message : "Network error",
|
|
4242
|
-
error3
|
|
4243
|
-
);
|
|
4352
|
+
throw new TwakNetworkError(error3 instanceof Error ? error3.message : "Network error", error3);
|
|
4244
4353
|
} finally {
|
|
4245
4354
|
clearTimeout(timeout);
|
|
4246
4355
|
}
|
|
@@ -4252,6 +4361,7 @@ var init_dist = __esm({
|
|
|
4252
4361
|
return this.config.hmacSecret;
|
|
4253
4362
|
}
|
|
4254
4363
|
};
|
|
4364
|
+
ALLOWED_OFFRAMP_PROVIDERS = /* @__PURE__ */ new Set(["MoonPay"]);
|
|
4255
4365
|
}
|
|
4256
4366
|
});
|
|
4257
4367
|
|
|
@@ -5954,14 +6064,14 @@ var require_templates = __commonJS({
|
|
|
5954
6064
|
}
|
|
5955
6065
|
return results;
|
|
5956
6066
|
}
|
|
5957
|
-
function buildStyle(
|
|
6067
|
+
function buildStyle(chalk8, styles) {
|
|
5958
6068
|
const enabled = {};
|
|
5959
6069
|
for (const layer of styles) {
|
|
5960
6070
|
for (const style of layer.styles) {
|
|
5961
6071
|
enabled[style[0]] = layer.inverse ? null : style.slice(1);
|
|
5962
6072
|
}
|
|
5963
6073
|
}
|
|
5964
|
-
let current =
|
|
6074
|
+
let current = chalk8;
|
|
5965
6075
|
for (const [styleName, styles2] of Object.entries(enabled)) {
|
|
5966
6076
|
if (!Array.isArray(styles2)) {
|
|
5967
6077
|
continue;
|
|
@@ -5973,7 +6083,7 @@ var require_templates = __commonJS({
|
|
|
5973
6083
|
}
|
|
5974
6084
|
return current;
|
|
5975
6085
|
}
|
|
5976
|
-
module2.exports = (
|
|
6086
|
+
module2.exports = (chalk8, temporary) => {
|
|
5977
6087
|
const styles = [];
|
|
5978
6088
|
const chunks = [];
|
|
5979
6089
|
let chunk2 = [];
|
|
@@ -5983,13 +6093,13 @@ var require_templates = __commonJS({
|
|
|
5983
6093
|
} else if (style) {
|
|
5984
6094
|
const string4 = chunk2.join("");
|
|
5985
6095
|
chunk2 = [];
|
|
5986
|
-
chunks.push(styles.length === 0 ? string4 : buildStyle(
|
|
6096
|
+
chunks.push(styles.length === 0 ? string4 : buildStyle(chalk8, styles)(string4));
|
|
5987
6097
|
styles.push({ inverse, styles: parseStyle(style) });
|
|
5988
6098
|
} else if (close) {
|
|
5989
6099
|
if (styles.length === 0) {
|
|
5990
6100
|
throw new Error("Found extraneous } in Chalk template literal");
|
|
5991
6101
|
}
|
|
5992
|
-
chunks.push(buildStyle(
|
|
6102
|
+
chunks.push(buildStyle(chalk8, styles)(chunk2.join("")));
|
|
5993
6103
|
chunk2 = [];
|
|
5994
6104
|
styles.pop();
|
|
5995
6105
|
} else {
|
|
@@ -6037,16 +6147,16 @@ var require_source = __commonJS({
|
|
|
6037
6147
|
}
|
|
6038
6148
|
};
|
|
6039
6149
|
var chalkFactory = (options) => {
|
|
6040
|
-
const
|
|
6041
|
-
applyOptions(
|
|
6042
|
-
|
|
6043
|
-
Object.setPrototypeOf(
|
|
6044
|
-
Object.setPrototypeOf(
|
|
6045
|
-
|
|
6150
|
+
const chalk9 = {};
|
|
6151
|
+
applyOptions(chalk9, options);
|
|
6152
|
+
chalk9.template = (...arguments_) => chalkTag(chalk9.template, ...arguments_);
|
|
6153
|
+
Object.setPrototypeOf(chalk9, Chalk.prototype);
|
|
6154
|
+
Object.setPrototypeOf(chalk9.template, chalk9);
|
|
6155
|
+
chalk9.template.constructor = () => {
|
|
6046
6156
|
throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.");
|
|
6047
6157
|
};
|
|
6048
|
-
|
|
6049
|
-
return
|
|
6158
|
+
chalk9.template.Instance = ChalkClass;
|
|
6159
|
+
return chalk9.template;
|
|
6050
6160
|
};
|
|
6051
6161
|
function Chalk(options) {
|
|
6052
6162
|
return chalkFactory(options);
|
|
@@ -6157,7 +6267,7 @@ var require_source = __commonJS({
|
|
|
6157
6267
|
return openAll + string4 + closeAll;
|
|
6158
6268
|
};
|
|
6159
6269
|
var template;
|
|
6160
|
-
var chalkTag = (
|
|
6270
|
+
var chalkTag = (chalk9, ...strings) => {
|
|
6161
6271
|
const [firstString] = strings;
|
|
6162
6272
|
if (!isArray(firstString) || !isArray(firstString.raw)) {
|
|
6163
6273
|
return strings.join(" ");
|
|
@@ -6173,14 +6283,14 @@ var require_source = __commonJS({
|
|
|
6173
6283
|
if (template === void 0) {
|
|
6174
6284
|
template = require_templates();
|
|
6175
6285
|
}
|
|
6176
|
-
return template(
|
|
6286
|
+
return template(chalk9, parts.join(""));
|
|
6177
6287
|
};
|
|
6178
6288
|
Object.defineProperties(Chalk.prototype, styles);
|
|
6179
|
-
var
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
module2.exports =
|
|
6289
|
+
var chalk8 = Chalk();
|
|
6290
|
+
chalk8.supportsColor = stdoutColor;
|
|
6291
|
+
chalk8.stderr = Chalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
6292
|
+
chalk8.stderr.supportsColor = stderrColor;
|
|
6293
|
+
module2.exports = chalk8;
|
|
6184
6294
|
}
|
|
6185
6295
|
});
|
|
6186
6296
|
|
|
@@ -12876,7 +12986,7 @@ var require_boxen = __commonJS({
|
|
|
12876
12986
|
"../../node_modules/.pnpm/boxen@5.1.2/node_modules/boxen/index.js"(exports2, module2) {
|
|
12877
12987
|
"use strict";
|
|
12878
12988
|
var stringWidth = require_string_width();
|
|
12879
|
-
var
|
|
12989
|
+
var chalk8 = require_source();
|
|
12880
12990
|
var widestLine = require_widest_line();
|
|
12881
12991
|
var cliBoxes = require_cli_boxes();
|
|
12882
12992
|
var camelCase = require_camelcase();
|
|
@@ -13019,9 +13129,9 @@ var require_boxen = __commonJS({
|
|
|
13019
13129
|
return lines.join(NL);
|
|
13020
13130
|
};
|
|
13021
13131
|
var isHex = (color) => color.match(/^#(?:[0-f]{3}){1,2}$/i);
|
|
13022
|
-
var isColorValid = (color) => typeof color === "string" && (
|
|
13023
|
-
var getColorFn = (color) => isHex(color) ?
|
|
13024
|
-
var getBGColorFn = (color) => isHex(color) ?
|
|
13132
|
+
var isColorValid = (color) => typeof color === "string" && (chalk8[color] || isHex(color));
|
|
13133
|
+
var getColorFn = (color) => isHex(color) ? chalk8.hex(color) : chalk8[color];
|
|
13134
|
+
var getBGColorFn = (color) => isHex(color) ? chalk8.bgHex(color) : chalk8[camelCase(["bg", color])];
|
|
13025
13135
|
module2.exports = (text, options) => {
|
|
13026
13136
|
options = {
|
|
13027
13137
|
padding: 0,
|
|
@@ -13047,7 +13157,7 @@ var require_boxen = __commonJS({
|
|
|
13047
13157
|
const margin = getObject(options.margin);
|
|
13048
13158
|
const colorizeBorder = (border) => {
|
|
13049
13159
|
const newBorder = options.borderColor ? getColorFn(options.borderColor)(border) : border;
|
|
13050
|
-
return options.dimBorder ?
|
|
13160
|
+
return options.dimBorder ? chalk8.dim(newBorder) : newBorder;
|
|
13051
13161
|
};
|
|
13052
13162
|
const colorizeContent = (content) => options.backgroundColor ? getBGColorFn(options.backgroundColor)(content) : content;
|
|
13053
13163
|
const columns = terminalColumns();
|
|
@@ -13090,242 +13200,6 @@ var require_boxen = __commonJS({
|
|
|
13090
13200
|
}
|
|
13091
13201
|
});
|
|
13092
13202
|
|
|
13093
|
-
// ../security/dist/index.mjs
|
|
13094
|
-
var dist_exports3 = {};
|
|
13095
|
-
__export(dist_exports3, {
|
|
13096
|
-
SecurityService: () => SecurityService
|
|
13097
|
-
});
|
|
13098
|
-
var SecurityService;
|
|
13099
|
-
var init_dist5 = __esm({
|
|
13100
|
-
"../security/dist/index.mjs"() {
|
|
13101
|
-
"use strict";
|
|
13102
|
-
init_dist();
|
|
13103
|
-
SecurityService = class {
|
|
13104
|
-
constructor(client) {
|
|
13105
|
-
this.client = client;
|
|
13106
|
-
}
|
|
13107
|
-
async validate(params) {
|
|
13108
|
-
const query = new URLSearchParams();
|
|
13109
|
-
if (params.assetId) query.set("asset_id", params.assetId);
|
|
13110
|
-
query.set("address", params.address);
|
|
13111
|
-
if (params.type) query.set("type", params.type);
|
|
13112
|
-
if (params.version) query.set("version", params.version.toString());
|
|
13113
|
-
try {
|
|
13114
|
-
return await this.client.gateway(
|
|
13115
|
-
"GET",
|
|
13116
|
-
"/v1/validate",
|
|
13117
|
-
void 0,
|
|
13118
|
-
query.toString()
|
|
13119
|
-
);
|
|
13120
|
-
} catch (err) {
|
|
13121
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
13122
|
-
const details = err?.details;
|
|
13123
|
-
let detailsStr = "";
|
|
13124
|
-
if (details != null) {
|
|
13125
|
-
try {
|
|
13126
|
-
detailsStr = JSON.stringify(details);
|
|
13127
|
-
} catch {
|
|
13128
|
-
try {
|
|
13129
|
-
detailsStr = String(details);
|
|
13130
|
-
} catch {
|
|
13131
|
-
}
|
|
13132
|
-
}
|
|
13133
|
-
}
|
|
13134
|
-
if (msg.includes("GraphQL") || msg.includes("Failed to decode") || detailsStr.includes("GraphQL") || detailsStr.includes("Failed to decode")) {
|
|
13135
|
-
return { valid: false, message: "Invalid address format" };
|
|
13136
|
-
}
|
|
13137
|
-
throw err;
|
|
13138
|
-
}
|
|
13139
|
-
}
|
|
13140
|
-
async getAssetInfo(assetId) {
|
|
13141
|
-
const info2 = await this.client.gateway(
|
|
13142
|
-
"GET",
|
|
13143
|
-
`/v1/assets/${encodeURIComponent(assetId)}`
|
|
13144
|
-
);
|
|
13145
|
-
try {
|
|
13146
|
-
const priceResp = await this.client.market("POST", "/v2/market/tickers", { currency: "USD", assets: [assetId] });
|
|
13147
|
-
const ticker = priceResp.tickers?.[0];
|
|
13148
|
-
if (ticker) {
|
|
13149
|
-
if (ticker.price != null) info2.priceUsd = ticker.price;
|
|
13150
|
-
if (ticker.change_24h != null) info2.priceChange24h = ticker.change_24h;
|
|
13151
|
-
if (ticker.market_cap != null) info2.marketCapUsd = ticker.market_cap;
|
|
13152
|
-
if (ticker.volume_24h != null) info2.volume24hUsd = ticker.volume_24h;
|
|
13153
|
-
}
|
|
13154
|
-
} catch {
|
|
13155
|
-
}
|
|
13156
|
-
return info2;
|
|
13157
|
-
}
|
|
13158
|
-
async getTokenHoldings(assets, fromTime = 0) {
|
|
13159
|
-
const response = await this.client.gateway("POST", "/v1/assets", {
|
|
13160
|
-
from_time: fromTime,
|
|
13161
|
-
version: 2,
|
|
13162
|
-
assets
|
|
13163
|
-
});
|
|
13164
|
-
return Array.isArray(response) ? response : response.docs ?? response.data ?? [];
|
|
13165
|
-
}
|
|
13166
|
-
async checkCoinStatus(assetId) {
|
|
13167
|
-
const query = new URLSearchParams({
|
|
13168
|
-
version: "8.4",
|
|
13169
|
-
platform: "android",
|
|
13170
|
-
include_security_info: "true",
|
|
13171
|
-
include_solana_security_info: "true"
|
|
13172
|
-
});
|
|
13173
|
-
const raw = await this.client.gateway(
|
|
13174
|
-
"GET",
|
|
13175
|
-
`/v2/coinstatus/${encodeURIComponent(assetId)}`,
|
|
13176
|
-
void 0,
|
|
13177
|
-
query.toString()
|
|
13178
|
-
);
|
|
13179
|
-
return this.transformCoinStatus(assetId, raw);
|
|
13180
|
-
}
|
|
13181
|
-
async getAssetListings(params) {
|
|
13182
|
-
const limit = params.limit || 20;
|
|
13183
|
-
const query = new URLSearchParams({
|
|
13184
|
-
version: "27",
|
|
13185
|
-
currency: params.currency || "USD",
|
|
13186
|
-
limit: String(params.sort ? Math.max(limit, 50) : limit)
|
|
13187
|
-
});
|
|
13188
|
-
if (params.categoryId) query.set("category_id", params.categoryId);
|
|
13189
|
-
if (params.networks) query.set("networks", params.networks);
|
|
13190
|
-
const response = await this.client.gateway(
|
|
13191
|
-
"GET",
|
|
13192
|
-
"/v1/assets/listings",
|
|
13193
|
-
void 0,
|
|
13194
|
-
query.toString()
|
|
13195
|
-
);
|
|
13196
|
-
const docs = response.docs || response.results || [];
|
|
13197
|
-
const tokens = docs.map((doc) => {
|
|
13198
|
-
const asset = doc.asset || doc;
|
|
13199
|
-
const price = doc.price || {};
|
|
13200
|
-
const market = doc.market || {};
|
|
13201
|
-
const assetIdRaw = asset.asset_id || doc.asset_id || "";
|
|
13202
|
-
const coinId = assetIdRaw ? parseInt(assetIdRaw.replace(/^c/, "").split("_")[0], 10) : NaN;
|
|
13203
|
-
const chain2 = !isNaN(coinId) ? coinTypeToChainKey(coinId) || "" : "";
|
|
13204
|
-
return {
|
|
13205
|
-
assetId: assetIdRaw,
|
|
13206
|
-
name: asset.name || doc.name || "",
|
|
13207
|
-
symbol: asset.symbol || doc.symbol || "",
|
|
13208
|
-
chain: chain2,
|
|
13209
|
-
logoUrl: asset.icon_url || doc.icon_url,
|
|
13210
|
-
priceUsd: price.price ?? doc.price_usd,
|
|
13211
|
-
priceChange24h: price.percent_change_24h ?? doc.price_change_24h,
|
|
13212
|
-
marketCapUsd: market.market_cap ?? price.market_cap ?? doc.market_cap_usd,
|
|
13213
|
-
volume24hUsd: market.volume_24h ?? price.volume_24h ?? doc.volume_24h_usd
|
|
13214
|
-
};
|
|
13215
|
-
});
|
|
13216
|
-
if (params.sort) {
|
|
13217
|
-
const sortKeyMap = {
|
|
13218
|
-
percent_change_24h: "priceChange24h",
|
|
13219
|
-
market_cap: "marketCapUsd",
|
|
13220
|
-
volume_24h: "volume24hUsd"
|
|
13221
|
-
};
|
|
13222
|
-
const key = sortKeyMap[params.sort] || "priceChange24h";
|
|
13223
|
-
tokens.sort((a3, b3) => (b3[key] ?? 0) - (a3[key] ?? 0));
|
|
13224
|
-
}
|
|
13225
|
-
return tokens.slice(0, limit);
|
|
13226
|
-
}
|
|
13227
|
-
async getTrendingTokens(limit = 20, sort = "percent_change_24h") {
|
|
13228
|
-
const knownAssets = [
|
|
13229
|
-
"c60",
|
|
13230
|
-
"c501",
|
|
13231
|
-
"c0",
|
|
13232
|
-
"c20000714",
|
|
13233
|
-
"c966",
|
|
13234
|
-
"c10009000",
|
|
13235
|
-
"c8453",
|
|
13236
|
-
"c10042221",
|
|
13237
|
-
"c10000070",
|
|
13238
|
-
"c195",
|
|
13239
|
-
"c637",
|
|
13240
|
-
"c607",
|
|
13241
|
-
"c784",
|
|
13242
|
-
"c397",
|
|
13243
|
-
"c118",
|
|
13244
|
-
"c2",
|
|
13245
|
-
"c3",
|
|
13246
|
-
"c10000250",
|
|
13247
|
-
"c10000324",
|
|
13248
|
-
"c81457"
|
|
13249
|
-
];
|
|
13250
|
-
const response = await this.client.market("POST", "/v2/market/tickers", {
|
|
13251
|
-
currency: "USD",
|
|
13252
|
-
assets: knownAssets
|
|
13253
|
-
});
|
|
13254
|
-
const tickers = response.tickers || [];
|
|
13255
|
-
const sortKey = sort === "market_cap" ? "market_cap" : sort === "volume_24h" ? "volume_24h" : "change_24h";
|
|
13256
|
-
return tickers.sort((a3, b3) => (b3[sortKey] ?? 0) - (a3[sortKey] ?? 0)).slice(0, limit).map((t) => {
|
|
13257
|
-
const coinId = t.id ? parseInt(t.id.replace(/^c/, ""), 10) : NaN;
|
|
13258
|
-
const chain2 = !isNaN(coinId) ? coinTypeToChainKey(coinId) || "" : "";
|
|
13259
|
-
return {
|
|
13260
|
-
assetId: t.id || "",
|
|
13261
|
-
name: t.slug || t.id || "",
|
|
13262
|
-
symbol: t.slug?.toUpperCase() || "",
|
|
13263
|
-
chain: chain2,
|
|
13264
|
-
logoUrl: void 0,
|
|
13265
|
-
priceUsd: t.price,
|
|
13266
|
-
priceChange24h: t.change_24h,
|
|
13267
|
-
marketCapUsd: t.market_cap,
|
|
13268
|
-
volume24hUsd: t.volume_24h
|
|
13269
|
-
};
|
|
13270
|
-
});
|
|
13271
|
-
}
|
|
13272
|
-
transformCoinStatus(assetId, raw) {
|
|
13273
|
-
if (!raw) return { assetId, isActive: false };
|
|
13274
|
-
const asset = raw.asset || raw;
|
|
13275
|
-
const security = raw.security_info || raw.securityInfo;
|
|
13276
|
-
const solanaSecurity = raw.solana_security_info || raw.solanaSecurityInfo;
|
|
13277
|
-
const warnings = [];
|
|
13278
|
-
let isHoneypot;
|
|
13279
|
-
if (security) {
|
|
13280
|
-
const allItems = [
|
|
13281
|
-
...security.contract_security?.items || [],
|
|
13282
|
-
...security.honeypot_risk?.items || []
|
|
13283
|
-
];
|
|
13284
|
-
for (const item of allItems) {
|
|
13285
|
-
if (item.type === "warning" || item.type === "risk") {
|
|
13286
|
-
const message = item.summary ?? item.description ?? item.code;
|
|
13287
|
-
if (message) warnings.push(message);
|
|
13288
|
-
}
|
|
13289
|
-
if (item.code === "is_honeypot" && item.type === "risk") isHoneypot = true;
|
|
13290
|
-
}
|
|
13291
|
-
}
|
|
13292
|
-
const totalRisks = (security?.contract_security?.num_risks ?? 0) + (security?.honeypot_risk?.num_risks ?? 0);
|
|
13293
|
-
const totalWarnings = (security?.contract_security?.num_warnings ?? 0) + (security?.honeypot_risk?.num_warnings ?? 0);
|
|
13294
|
-
const computedRiskLevel = totalRisks > 0 ? "high" : totalWarnings > 0 ? "medium" : security ? "low" : void 0;
|
|
13295
|
-
let finalRiskLevel = security?.risk_level ?? security?.riskLevel ?? computedRiskLevel;
|
|
13296
|
-
const knownStablecoins = ["USDC", "USDT", "DAI", "BUSD", "TUSD", "USDP", "FRAX", "PYUSD"];
|
|
13297
|
-
const symbol = (asset.symbol ?? raw.symbol ?? "").toUpperCase();
|
|
13298
|
-
let riskContext;
|
|
13299
|
-
if (finalRiskLevel === "medium" && knownStablecoins.includes(symbol) && totalRisks === 0) {
|
|
13300
|
-
riskContext = "Warnings are due to proxy contract pattern, common in audited stablecoins";
|
|
13301
|
-
finalRiskLevel = "low";
|
|
13302
|
-
}
|
|
13303
|
-
return {
|
|
13304
|
-
assetId,
|
|
13305
|
-
name: asset.name ?? raw.name,
|
|
13306
|
-
symbol: asset.symbol ?? raw.symbol,
|
|
13307
|
-
isActive: raw.is_active ?? raw.isActive ?? true,
|
|
13308
|
-
supportsSwap: raw.isSwapEnabled ?? raw.supports_swap ?? raw.supportsSwap,
|
|
13309
|
-
supportsStaking: raw.isStakeEnabled ?? raw.supports_staking ?? raw.supportsStaking,
|
|
13310
|
-
securityInfo: security ? {
|
|
13311
|
-
isHoneypot: isHoneypot ?? security.is_honeypot ?? security.isHoneypot,
|
|
13312
|
-
hasAudit: security.has_audit ?? security.hasAudit ?? security.is_scanned,
|
|
13313
|
-
auditProvider: security.audit_provider ?? security.auditProvider ?? security.provider,
|
|
13314
|
-
riskLevel: finalRiskLevel,
|
|
13315
|
-
...riskContext ? { riskContext } : {},
|
|
13316
|
-
warnings: warnings.length > 0 ? warnings : security.warnings
|
|
13317
|
-
} : void 0,
|
|
13318
|
-
solanaSecurityInfo: solanaSecurity ? {
|
|
13319
|
-
isMintable: solanaSecurity.is_mintable ?? solanaSecurity.isMintable,
|
|
13320
|
-
hasFreezeAuthority: solanaSecurity.has_freeze_authority ?? solanaSecurity.hasFreezeAuthority,
|
|
13321
|
-
riskLevel: solanaSecurity.risk_level ?? solanaSecurity.riskLevel
|
|
13322
|
-
} : void 0
|
|
13323
|
-
};
|
|
13324
|
-
}
|
|
13325
|
-
};
|
|
13326
|
-
}
|
|
13327
|
-
});
|
|
13328
|
-
|
|
13329
13203
|
// ../security/dist/services/dapps-service.mjs
|
|
13330
13204
|
var dapps_service_exports = {};
|
|
13331
13205
|
__export(dapps_service_exports, {
|
|
@@ -39328,201 +39202,136 @@ ${qr3}`;
|
|
|
39328
39202
|
if (!opts.all && !opts.chain) {
|
|
39329
39203
|
throw new WalletError("VALIDATION_ERROR", "Provide --chain <key> or --all");
|
|
39330
39204
|
}
|
|
39331
|
-
|
|
39205
|
+
await ensureTrustSDK();
|
|
39332
39206
|
const password = await resolvePassword(opts);
|
|
39333
39207
|
const provider = await AgentWalletProvider.fromStorage(password);
|
|
39334
|
-
const
|
|
39335
|
-
|
|
39336
|
-
|
|
39337
|
-
|
|
39338
|
-
|
|
39339
|
-
return `${big / div}.${(big % div).toString().padStart(decimals, "0").slice(0, 6)}`;
|
|
39340
|
-
};
|
|
39341
|
-
const holdingsMap = /* @__PURE__ */ new Map();
|
|
39342
|
-
try {
|
|
39343
|
-
const { SecurityService: SecurityService2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports3));
|
|
39344
|
-
const httpClient = await getClient();
|
|
39345
|
-
const securityService = new SecurityService2(httpClient);
|
|
39346
|
-
const allAddrs = await provider.getAddresses();
|
|
39347
|
-
const assets = allAddrs.map((a3) => {
|
|
39348
|
-
const ch = getChainById(a3.chainId);
|
|
39349
|
-
return ch ? { coin: ch.coinId, address: a3.address } : null;
|
|
39350
|
-
}).filter((a3) => a3 !== null);
|
|
39351
|
-
const holdings = await securityService.getTokenHoldings(assets);
|
|
39352
|
-
for (const h4 of holdings) {
|
|
39353
|
-
if (h4.tokens?.length) {
|
|
39354
|
-
holdingsMap.set(
|
|
39355
|
-
h4.coin,
|
|
39356
|
-
h4.tokens.map((t) => ({
|
|
39357
|
-
symbol: t.symbol ?? t.contract,
|
|
39358
|
-
contract: t.contract,
|
|
39359
|
-
decimals: t.decimals ?? 18
|
|
39360
|
-
}))
|
|
39361
|
-
);
|
|
39362
|
-
}
|
|
39208
|
+
const httpClient = await getClient();
|
|
39209
|
+
if (!opts.all) {
|
|
39210
|
+
const chain2 = getChainById(opts.chain);
|
|
39211
|
+
if (!chain2) {
|
|
39212
|
+
throw new WalletError("CHAIN_UNSUPPORTED", `Unknown chain: ${opts.chain}`);
|
|
39363
39213
|
}
|
|
39364
|
-
} catch {
|
|
39365
39214
|
}
|
|
39366
|
-
|
|
39367
|
-
|
|
39368
|
-
|
|
39369
|
-
|
|
39370
|
-
|
|
39371
|
-
|
|
39372
|
-
|
|
39373
|
-
|
|
39374
|
-
|
|
39375
|
-
|
|
39376
|
-
|
|
39377
|
-
|
|
39378
|
-
|
|
39379
|
-
|
|
39380
|
-
|
|
39381
|
-
|
|
39382
|
-
h4.tokens.map((t) => ({
|
|
39383
|
-
symbol: t.symbol ?? t.contract,
|
|
39384
|
-
contract: t.contract,
|
|
39385
|
-
decimals: t.decimals ?? 18
|
|
39386
|
-
}))
|
|
39387
|
-
);
|
|
39388
|
-
}
|
|
39389
|
-
}
|
|
39390
|
-
} catch {
|
|
39391
|
-
}
|
|
39215
|
+
const allAddresses = await provider.getAddresses();
|
|
39216
|
+
const addressMap = /* @__PURE__ */ new Map();
|
|
39217
|
+
for (const a3 of allAddresses) addressMap.set(a3.chainId, a3.address);
|
|
39218
|
+
const requestedChains = opts.all ? Object.keys(CHAINS) : [opts.chain];
|
|
39219
|
+
const skipTokens = opts.tokens === false;
|
|
39220
|
+
const chainEntries = requestedChains.flatMap((chainKey) => {
|
|
39221
|
+
const chain2 = getChainById(chainKey);
|
|
39222
|
+
const address = addressMap.get(chainKey);
|
|
39223
|
+
if (!chain2 || !address) return [];
|
|
39224
|
+
return [{ chainKey, chain: chain2, address }];
|
|
39225
|
+
});
|
|
39226
|
+
if (!opts.all && chainEntries.length === 0) {
|
|
39227
|
+
throw new WalletError(
|
|
39228
|
+
"CHAIN_UNSUPPORTED",
|
|
39229
|
+
`Wallet does not have an address for chain: ${opts.chain}`
|
|
39230
|
+
);
|
|
39392
39231
|
}
|
|
39393
|
-
const
|
|
39394
|
-
|
|
39395
|
-
|
|
39396
|
-
|
|
39397
|
-
|
|
39398
|
-
|
|
39399
|
-
|
|
39400
|
-
|
|
39401
|
-
|
|
39402
|
-
|
|
39403
|
-
|
|
39404
|
-
|
|
39405
|
-
|
|
39406
|
-
|
|
39407
|
-
|
|
39408
|
-
|
|
39409
|
-
|
|
39410
|
-
}
|
|
39232
|
+
const nativeAssets = chainEntries.map(({ chainKey, chain: chain2, address }) => ({
|
|
39233
|
+
chainKey,
|
|
39234
|
+
chain: chain2,
|
|
39235
|
+
token: void 0,
|
|
39236
|
+
assetId: `c${chain2.coinId}`,
|
|
39237
|
+
userAddress: address
|
|
39238
|
+
}));
|
|
39239
|
+
const tokenAssets = skipTokens ? [] : chainEntries.flatMap(({ chainKey, chain: chain2, address }) => {
|
|
39240
|
+
const knownTokens = COMMON_TOKENS[chainKey];
|
|
39241
|
+
if (!knownTokens) return [];
|
|
39242
|
+
return Object.keys(knownTokens).flatMap((sym) => {
|
|
39243
|
+
const resolved = resolveToken(sym, chainKey);
|
|
39244
|
+
if (!resolved) return [];
|
|
39245
|
+
return [
|
|
39246
|
+
{
|
|
39247
|
+
chainKey,
|
|
39248
|
+
chain: chain2,
|
|
39249
|
+
token: { ...resolved, symbol: sym },
|
|
39250
|
+
assetId: `c${chain2.coinId}_t${resolved.address}`,
|
|
39251
|
+
userAddress: address
|
|
39411
39252
|
}
|
|
39412
|
-
|
|
39413
|
-
|
|
39414
|
-
|
|
39415
|
-
|
|
39416
|
-
|
|
39417
|
-
|
|
39418
|
-
|
|
39419
|
-
|
|
39420
|
-
|
|
39421
|
-
|
|
39422
|
-
|
|
39423
|
-
|
|
39424
|
-
return { symbol: t.symbol, contract: t.contract, balance: human };
|
|
39425
|
-
} catch {
|
|
39426
|
-
}
|
|
39427
|
-
return null;
|
|
39428
|
-
})
|
|
39429
|
-
);
|
|
39430
|
-
for (const t of tokenResults) {
|
|
39431
|
-
if (t) tokens.push(t);
|
|
39432
|
-
}
|
|
39433
|
-
}
|
|
39434
|
-
return {
|
|
39253
|
+
];
|
|
39254
|
+
});
|
|
39255
|
+
});
|
|
39256
|
+
const allAssets = [...nativeAssets, ...tokenAssets];
|
|
39257
|
+
const balances = await fetchBalances(
|
|
39258
|
+
httpClient,
|
|
39259
|
+
allAssets.map(({ assetId, userAddress }) => ({ assetId, address: userAddress }))
|
|
39260
|
+
);
|
|
39261
|
+
const assetsById = new Map(allAssets.map((a3) => [a3.assetId, a3]));
|
|
39262
|
+
const resultsByChain = /* @__PURE__ */ new Map();
|
|
39263
|
+
for (const { chainKey, chain: chain2, address } of chainEntries) {
|
|
39264
|
+
resultsByChain.set(chainKey, {
|
|
39435
39265
|
chain: chainKey,
|
|
39436
39266
|
address,
|
|
39437
39267
|
symbol: chain2.symbol,
|
|
39438
|
-
available:
|
|
39439
|
-
|
|
39440
|
-
|
|
39441
|
-
|
|
39442
|
-
|
|
39443
|
-
|
|
39444
|
-
|
|
39445
|
-
|
|
39446
|
-
|
|
39447
|
-
|
|
39448
|
-
|
|
39449
|
-
|
|
39450
|
-
|
|
39268
|
+
available: "0",
|
|
39269
|
+
total: "0",
|
|
39270
|
+
totalUsd: null,
|
|
39271
|
+
tokens: []
|
|
39272
|
+
});
|
|
39273
|
+
}
|
|
39274
|
+
for (const balance of balances) {
|
|
39275
|
+
const assetInfo = assetsById.get(balance.assetId);
|
|
39276
|
+
if (!assetInfo) {
|
|
39277
|
+
throw new WalletError(
|
|
39278
|
+
"NETWORK_ERROR",
|
|
39279
|
+
"Could not fetch balances. Please try again later."
|
|
39280
|
+
);
|
|
39451
39281
|
}
|
|
39452
|
-
const
|
|
39453
|
-
|
|
39454
|
-
|
|
39455
|
-
|
|
39456
|
-
|
|
39457
|
-
|
|
39458
|
-
|
|
39459
|
-
|
|
39460
|
-
|
|
39461
|
-
|
|
39462
|
-
|
|
39463
|
-
if (result.status === "rejected") {
|
|
39464
|
-
const chainKey = chainEntries[i2].chainKey;
|
|
39465
|
-
results.push({
|
|
39466
|
-
chain: chainKey,
|
|
39467
|
-
error: result.reason instanceof Error ? result.reason.message : String(result.reason)
|
|
39282
|
+
const result = resultsByChain.get(assetInfo.chainKey);
|
|
39283
|
+
if (!result) continue;
|
|
39284
|
+
const { token, chain: chain2 } = assetInfo;
|
|
39285
|
+
const amts = balance.amounts;
|
|
39286
|
+
if (token) {
|
|
39287
|
+
const human = formatAmount(amts.total ?? "0", token.decimals);
|
|
39288
|
+
if (human !== "0") {
|
|
39289
|
+
result.tokens.push({
|
|
39290
|
+
symbol: token.symbol,
|
|
39291
|
+
contract: token.address,
|
|
39292
|
+
balance: human
|
|
39468
39293
|
});
|
|
39469
|
-
continue;
|
|
39470
39294
|
}
|
|
39471
|
-
|
|
39472
|
-
|
|
39473
|
-
|
|
39474
|
-
const
|
|
39475
|
-
if (
|
|
39476
|
-
|
|
39295
|
+
} else {
|
|
39296
|
+
result.available = formatAmount(amts.available ?? "0", chain2.decimals);
|
|
39297
|
+
result.total = formatAmount(amts.total ?? "0", chain2.decimals);
|
|
39298
|
+
const lockedHuman = formatAmount(amts.locked ?? "0", chain2.decimals);
|
|
39299
|
+
if (lockedHuman !== "0") result.staked = lockedHuman;
|
|
39300
|
+
const fiat = amts.totalInFiat;
|
|
39301
|
+
result.totalUsd = fiat && fiat !== "" ? parseFloat(fiat) : null;
|
|
39302
|
+
}
|
|
39303
|
+
}
|
|
39304
|
+
const orderedResults = chainEntries.map((e2) => resultsByChain.get(e2.chainKey)).filter((r3) => r3 !== void 0);
|
|
39305
|
+
const visible = opts.all ? orderedResults.filter((r3) => r3.total !== "0" || r3.tokens.length > 0) : orderedResults;
|
|
39306
|
+
const renderBlock = (r3, withSeparator) => {
|
|
39307
|
+
if (withSeparator) console.log("");
|
|
39308
|
+
console.log(import_chalk5.default.bold.underline(`${r3.chain} (${r3.symbol})`));
|
|
39309
|
+
console.log(` ${import_chalk5.default.dim("Address")} ${r3.address}`);
|
|
39310
|
+
console.log(` ${import_chalk5.default.dim("Available")} ${r3.available}`);
|
|
39311
|
+
if (r3.staked) console.log(` ${import_chalk5.default.dim("Staked")} ${r3.staked}`);
|
|
39312
|
+
const usdSuffix = r3.totalUsd !== null ? ` (\u2248 $${r3.totalUsd.toFixed(2)})` : "";
|
|
39313
|
+
console.log(` ${import_chalk5.default.dim("Total")} ${r3.total}${usdSuffix}`);
|
|
39314
|
+
if (r3.tokens.length > 0) {
|
|
39315
|
+
console.log(` ${import_chalk5.default.dim("Tokens:")}`);
|
|
39316
|
+
for (const t of r3.tokens) {
|
|
39317
|
+
console.log(` ${import_chalk5.default.bold(t.symbol.padEnd(10))} ${t.balance}`);
|
|
39477
39318
|
}
|
|
39478
39319
|
}
|
|
39320
|
+
};
|
|
39321
|
+
if (opts.all) {
|
|
39479
39322
|
if (opts.json) {
|
|
39480
|
-
output(
|
|
39323
|
+
output(visible, true);
|
|
39324
|
+
} else if (visible.length === 0) {
|
|
39325
|
+
info("No chains with non-zero native balance found.");
|
|
39481
39326
|
} else {
|
|
39482
|
-
|
|
39483
|
-
info("No chains with non-zero native balance found.");
|
|
39484
|
-
} else {
|
|
39485
|
-
for (let i2 = 0; i2 < results.length; i2++) {
|
|
39486
|
-
const r3 = results[i2];
|
|
39487
|
-
if (i2 > 0) console.log("");
|
|
39488
|
-
console.log(import_chalk5.default.bold.underline(`${String(r3.chain)} (${String(r3.symbol)})`));
|
|
39489
|
-
console.log(` ${import_chalk5.default.dim("Address")} ${r3.address}`);
|
|
39490
|
-
console.log(` ${import_chalk5.default.dim("Available")} ${r3.available}`);
|
|
39491
|
-
if (r3.staked) console.log(` ${import_chalk5.default.dim("Staked")} ${r3.staked}`);
|
|
39492
|
-
if (r3.pending) console.log(` ${import_chalk5.default.dim("Pending")} ${r3.pending}`);
|
|
39493
|
-
console.log(` ${import_chalk5.default.dim("Total")} ${r3.total}`);
|
|
39494
|
-
const tokens = r3.tokens;
|
|
39495
|
-
if (tokens.length > 0) {
|
|
39496
|
-
console.log(` ${import_chalk5.default.dim("Tokens:")}`);
|
|
39497
|
-
for (const t of tokens) {
|
|
39498
|
-
console.log(` ${import_chalk5.default.bold(t.symbol.padEnd(10))} ${t.balance}`);
|
|
39499
|
-
}
|
|
39500
|
-
}
|
|
39501
|
-
}
|
|
39502
|
-
}
|
|
39327
|
+
visible.forEach((r3, i2) => renderBlock(r3, i2 > 0));
|
|
39503
39328
|
}
|
|
39504
39329
|
} else {
|
|
39505
|
-
const
|
|
39506
|
-
if (!chain2) {
|
|
39507
|
-
throw new WalletError("CHAIN_UNSUPPORTED", `Unknown chain: ${opts.chain}`);
|
|
39508
|
-
}
|
|
39509
|
-
const address = await provider.getAddress(opts.chain);
|
|
39510
|
-
const result = await fetchChainBalance(opts.chain, chain2, address, opts.tokens === false);
|
|
39330
|
+
const single = visible[0];
|
|
39511
39331
|
if (opts.json) {
|
|
39512
|
-
output(
|
|
39332
|
+
output(single, true);
|
|
39513
39333
|
} else {
|
|
39514
|
-
|
|
39515
|
-
console.log(` ${import_chalk5.default.dim("Address")} ${result.address}`);
|
|
39516
|
-
console.log(` ${import_chalk5.default.dim("Available")} ${result.available}`);
|
|
39517
|
-
if (result.staked) console.log(` ${import_chalk5.default.dim("Staked")} ${result.staked}`);
|
|
39518
|
-
if (result.pending) console.log(` ${import_chalk5.default.dim("Pending")} ${result.pending}`);
|
|
39519
|
-
console.log(` ${import_chalk5.default.dim("Total")} ${result.total}`);
|
|
39520
|
-
if (result.tokens.length > 0) {
|
|
39521
|
-
console.log(` ${import_chalk5.default.dim("Tokens:")}`);
|
|
39522
|
-
for (const t of result.tokens) {
|
|
39523
|
-
console.log(` ${import_chalk5.default.bold(t.symbol.padEnd(10))} ${t.balance}`);
|
|
39524
|
-
}
|
|
39525
|
-
}
|
|
39334
|
+
renderBlock(single, false);
|
|
39526
39335
|
}
|
|
39527
39336
|
}
|
|
39528
39337
|
} catch (err) {
|
|
@@ -39670,23 +39479,18 @@ ${qr3}`;
|
|
|
39670
39479
|
}
|
|
39671
39480
|
safeExit();
|
|
39672
39481
|
});
|
|
39673
|
-
wallet.command("portfolio").description("Show portfolio balances and USD values across chains, including token holdings").option(
|
|
39674
|
-
"--chains <list>",
|
|
39675
|
-
"Comma-separated chain keys (default: all supported chains)",
|
|
39676
|
-
Object.keys(CHAINS).join(",")
|
|
39677
|
-
).option(
|
|
39482
|
+
wallet.command("portfolio").description("Show portfolio balances and USD values across chains, including token holdings").option("--chains <list>", "Comma-separated chain keys (default: all supported chains)").option(
|
|
39678
39483
|
"--password <password>",
|
|
39679
39484
|
"Wallet password (falls back to keychain / TWAK_WALLET_PASSWORD)"
|
|
39680
39485
|
).option("--json", "JSON output").action(async (opts) => {
|
|
39681
39486
|
try {
|
|
39682
|
-
|
|
39487
|
+
await ensureTrustSDK();
|
|
39683
39488
|
const password = await resolvePassword(opts);
|
|
39684
39489
|
const provider = await AgentWalletProvider.fromStorage(password);
|
|
39685
|
-
const { SecurityService: SecurityService2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports3));
|
|
39686
39490
|
const httpClient = await getClient();
|
|
39687
|
-
const blockchainClient = provider.getBlockchainClient();
|
|
39688
39491
|
const allAddresses = await provider.getAddresses();
|
|
39689
|
-
const
|
|
39492
|
+
const chainsArg = opts.chains ?? Object.keys(CHAINS).join(",");
|
|
39493
|
+
const requestedChains = chainsArg.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
39690
39494
|
if (requestedChains.length === 0) {
|
|
39691
39495
|
throw new WalletError("VALIDATION_ERROR", "--chains must include at least one chain key");
|
|
39692
39496
|
}
|
|
@@ -39702,241 +39506,69 @@ ${qr3}`;
|
|
|
39702
39506
|
`No supported chains found in --chains "${opts.chains}"`
|
|
39703
39507
|
);
|
|
39704
39508
|
}
|
|
39705
|
-
const
|
|
39706
|
-
|
|
39707
|
-
|
|
39708
|
-
|
|
39709
|
-
|
|
39710
|
-
|
|
39711
|
-
|
|
39712
|
-
|
|
39713
|
-
});
|
|
39714
|
-
};
|
|
39715
|
-
const pMap = async (items, fn2, concurrency) => {
|
|
39716
|
-
const results = new Array(items.length);
|
|
39717
|
-
let idx = 0;
|
|
39718
|
-
const run = async () => {
|
|
39719
|
-
while (idx < items.length) {
|
|
39720
|
-
const i2 = idx++;
|
|
39721
|
-
results[i2] = await fn2(items[i2]);
|
|
39722
|
-
}
|
|
39723
|
-
};
|
|
39724
|
-
await Promise.all(
|
|
39725
|
-
Array.from({ length: Math.min(concurrency, items.length) }, () => run())
|
|
39726
|
-
);
|
|
39727
|
-
return results;
|
|
39728
|
-
};
|
|
39729
|
-
const toHuman = (raw, decimals) => {
|
|
39730
|
-
const big = BigInt(raw);
|
|
39731
|
-
const div = 10n ** BigInt(decimals);
|
|
39732
|
-
const intPart = big / div;
|
|
39733
|
-
const fracPart = big % div;
|
|
39734
|
-
const fracStr = fracPart.toString().padStart(decimals, "0").replace(/0+$/, "");
|
|
39735
|
-
return fracStr ? `${intPart}.${fracStr}` : intPart.toString();
|
|
39736
|
-
};
|
|
39737
|
-
const holdingsAssets = chainEntries.map(
|
|
39738
|
-
({ chain: chain2, address }) => ({
|
|
39739
|
-
coin: chain2.coinId,
|
|
39740
|
-
address
|
|
39741
|
-
})
|
|
39742
|
-
);
|
|
39743
|
-
const [nativeResults, holdings] = await Promise.all([
|
|
39744
|
-
pMap(
|
|
39745
|
-
chainEntries,
|
|
39746
|
-
async ({
|
|
39747
|
-
chainKey,
|
|
39748
|
-
chain: chain2,
|
|
39749
|
-
address
|
|
39750
|
-
}) => {
|
|
39751
|
-
try {
|
|
39752
|
-
const balance = await withTimeout(
|
|
39753
|
-
suppress(() => blockchainClient.getBalance(`c${chain2.coinId}`, address)),
|
|
39754
|
-
15e3
|
|
39755
|
-
);
|
|
39756
|
-
if (!balance) {
|
|
39757
|
-
return {
|
|
39758
|
-
chainKey,
|
|
39759
|
-
chain: chain2,
|
|
39760
|
-
address,
|
|
39761
|
-
balance: null,
|
|
39762
|
-
error: "empty response from SDK",
|
|
39763
|
-
errorCode: "NETWORK_ERROR"
|
|
39764
|
-
};
|
|
39765
|
-
}
|
|
39766
|
-
return {
|
|
39767
|
-
chainKey,
|
|
39768
|
-
chain: chain2,
|
|
39769
|
-
address,
|
|
39770
|
-
balance,
|
|
39771
|
-
error: void 0,
|
|
39772
|
-
errorCode: void 0
|
|
39773
|
-
};
|
|
39774
|
-
} catch (e2) {
|
|
39775
|
-
const code2 = e2 != null ? e2.code : void 0;
|
|
39776
|
-
const status = e2 != null ? e2.status : void 0;
|
|
39777
|
-
let msg;
|
|
39778
|
-
let errorCode = "NETWORK_ERROR";
|
|
39779
|
-
if (status === 429 || code2 === 429) {
|
|
39780
|
-
msg = "rate limited (429)";
|
|
39781
|
-
errorCode = "RATE_LIMITED";
|
|
39782
|
-
} else if (e2 instanceof Error && e2.message && e2.message !== "null") {
|
|
39783
|
-
msg = e2.message;
|
|
39784
|
-
} else if (code2) {
|
|
39785
|
-
msg = `HTTP ${code2}`;
|
|
39786
|
-
} else {
|
|
39787
|
-
msg = "unsupported or unavailable";
|
|
39788
|
-
}
|
|
39789
|
-
return { chainKey, chain: chain2, address, balance: null, error: msg, errorCode };
|
|
39790
|
-
}
|
|
39791
|
-
},
|
|
39792
|
-
5
|
|
39793
|
-
),
|
|
39794
|
-
(async () => {
|
|
39795
|
-
try {
|
|
39796
|
-
return await new SecurityService2(httpClient).getTokenHoldings(holdingsAssets);
|
|
39797
|
-
} catch {
|
|
39798
|
-
return [];
|
|
39799
|
-
}
|
|
39800
|
-
})()
|
|
39801
|
-
]);
|
|
39802
|
-
const holdingsMap = /* @__PURE__ */ new Map();
|
|
39803
|
-
for (const h4 of holdings) {
|
|
39804
|
-
if (h4.tokens?.length) holdingsMap.set(h4.coin, h4.tokens);
|
|
39805
|
-
}
|
|
39806
|
-
if (holdingsMap.size === 0) {
|
|
39807
|
-
try {
|
|
39808
|
-
await registerWalletWithBackend(creds, provider);
|
|
39809
|
-
const retryHoldings = await new SecurityService2(httpClient).getTokenHoldings(
|
|
39810
|
-
holdingsAssets
|
|
39811
|
-
);
|
|
39812
|
-
for (const h4 of retryHoldings) {
|
|
39813
|
-
if (h4.tokens?.length) holdingsMap.set(h4.coin, h4.tokens);
|
|
39814
|
-
}
|
|
39815
|
-
} catch {
|
|
39816
|
-
}
|
|
39817
|
-
}
|
|
39818
|
-
for (const { chainKey, chain: chain2 } of chainEntries) {
|
|
39819
|
-
const coinId = chain2.coinId;
|
|
39820
|
-
if (holdingsMap.has(coinId)) continue;
|
|
39509
|
+
const chainAssets = chainEntries.map(({ chainKey, chain: chain2, address }) => ({
|
|
39510
|
+
chainKey,
|
|
39511
|
+
chain: chain2,
|
|
39512
|
+
token: void 0,
|
|
39513
|
+
assetId: `c${chain2.coinId}`,
|
|
39514
|
+
userAddress: address
|
|
39515
|
+
}));
|
|
39516
|
+
const tokenAssets = chainEntries.flatMap(({ chainKey, chain: chain2, address }) => {
|
|
39821
39517
|
const knownTokens = COMMON_TOKENS[chainKey];
|
|
39822
|
-
if (!knownTokens)
|
|
39823
|
-
|
|
39518
|
+
if (!knownTokens) return [];
|
|
39519
|
+
return Object.keys(knownTokens).flatMap((sym) => {
|
|
39824
39520
|
const resolved = resolveToken(sym, chainKey);
|
|
39825
39521
|
if (!resolved) return [];
|
|
39826
|
-
return [
|
|
39827
|
-
|
|
39828
|
-
holdingsMap.set(coinId, fallbackTokens);
|
|
39829
|
-
}
|
|
39830
|
-
const tokenJobs = [];
|
|
39831
|
-
for (const { chainKey, chain: chain2, address } of chainEntries) {
|
|
39832
|
-
const tokens = holdingsMap.get(chain2.coinId) ?? [];
|
|
39833
|
-
for (const t of tokens) {
|
|
39834
|
-
if (t.contract)
|
|
39835
|
-
tokenJobs.push({
|
|
39522
|
+
return [
|
|
39523
|
+
{
|
|
39836
39524
|
chainKey,
|
|
39837
|
-
|
|
39838
|
-
|
|
39839
|
-
|
|
39840
|
-
|
|
39841
|
-
|
|
39842
|
-
|
|
39843
|
-
|
|
39844
|
-
tokenJobs,
|
|
39845
|
-
async (job) => {
|
|
39846
|
-
const fallback = job.balance ?? "0";
|
|
39847
|
-
try {
|
|
39848
|
-
const assetId = `c${job.coinId}_t${job.contract}`;
|
|
39849
|
-
const bal = await withTimeout(
|
|
39850
|
-
suppress(() => blockchainClient.getBalance(assetId, job.address)),
|
|
39851
|
-
15e3
|
|
39852
|
-
);
|
|
39853
|
-
return { ...job, rawBalance: bal.total ?? bal.available ?? fallback };
|
|
39854
|
-
} catch {
|
|
39855
|
-
return { ...job, rawBalance: fallback };
|
|
39856
|
-
}
|
|
39857
|
-
},
|
|
39858
|
-
5
|
|
39859
|
-
);
|
|
39860
|
-
const nativeAssetIds = nativeResults.map(
|
|
39861
|
-
({ chain: chain2 }) => `c${chain2.coinId}`
|
|
39862
|
-
);
|
|
39863
|
-
const nonZeroTokens = tokenBalances.filter((t) => {
|
|
39864
|
-
const dec = t.decimals ?? 18;
|
|
39865
|
-
return toHuman(t.rawBalance, dec) !== "0";
|
|
39525
|
+
chain: chain2,
|
|
39526
|
+
token: { ...resolved, symbol: sym },
|
|
39527
|
+
assetId: `c${chain2.coinId}_t${resolved.address}`,
|
|
39528
|
+
userAddress: address
|
|
39529
|
+
}
|
|
39530
|
+
];
|
|
39531
|
+
});
|
|
39866
39532
|
});
|
|
39867
|
-
const
|
|
39868
|
-
|
|
39533
|
+
const allAssets = [...chainAssets, ...tokenAssets];
|
|
39534
|
+
const balances = await fetchBalances(
|
|
39535
|
+
httpClient,
|
|
39536
|
+
allAssets.map(({ assetId, userAddress }) => ({ assetId, address: userAddress }))
|
|
39869
39537
|
);
|
|
39870
|
-
const
|
|
39871
|
-
|
|
39872
|
-
|
|
39873
|
-
|
|
39874
|
-
|
|
39875
|
-
|
|
39876
|
-
|
|
39877
|
-
|
|
39878
|
-
|
|
39879
|
-
} catch {
|
|
39880
|
-
}
|
|
39881
|
-
const rows = [];
|
|
39882
|
-
const failedChains = [];
|
|
39883
|
-
for (const {
|
|
39884
|
-
chainKey,
|
|
39885
|
-
chain: chain2,
|
|
39886
|
-
address,
|
|
39887
|
-
balance,
|
|
39888
|
-
error: fetchError,
|
|
39889
|
-
errorCode: fetchErrorCode
|
|
39890
|
-
} of nativeResults) {
|
|
39891
|
-
const c3 = chain2;
|
|
39892
|
-
if (fetchError) {
|
|
39893
|
-
failedChains.push(`${chainKey} (${fetchError})`);
|
|
39894
|
-
rows.push({
|
|
39895
|
-
chain: chainKey,
|
|
39896
|
-
type: "native",
|
|
39897
|
-
symbol: c3.symbol,
|
|
39898
|
-
address,
|
|
39899
|
-
balance: "--",
|
|
39900
|
-
usdValue: null,
|
|
39901
|
-
error: fetchError,
|
|
39902
|
-
errorCode: fetchErrorCode
|
|
39903
|
-
});
|
|
39904
|
-
continue;
|
|
39538
|
+
const assetsById = new Map(allAssets.map((a3) => [a3.assetId, a3]));
|
|
39539
|
+
const allRows = [];
|
|
39540
|
+
for (const balance of balances) {
|
|
39541
|
+
const assetInfo = assetsById.get(balance.assetId);
|
|
39542
|
+
if (!assetInfo) {
|
|
39543
|
+
throw new WalletError(
|
|
39544
|
+
"NETWORK_ERROR",
|
|
39545
|
+
"Could not fetch balances. Please try again later."
|
|
39546
|
+
);
|
|
39905
39547
|
}
|
|
39906
|
-
const
|
|
39907
|
-
const
|
|
39908
|
-
const
|
|
39909
|
-
const
|
|
39910
|
-
|
|
39548
|
+
const { token, chain: chain2, chainKey, userAddress } = assetInfo;
|
|
39549
|
+
const decimals = token ? token.decimals : chain2.decimals;
|
|
39550
|
+
const symbol = token ? token.symbol ?? token.address : chain2.symbol;
|
|
39551
|
+
const isToken = token !== void 0;
|
|
39552
|
+
const humanAmount = formatAmount(balance.amounts.total, decimals);
|
|
39553
|
+
const fiatRaw = balance.amounts.totalInFiat;
|
|
39554
|
+
const usdValue = fiatRaw && fiatRaw !== "" ? parseFloat(fiatRaw) : null;
|
|
39555
|
+
allRows.push({
|
|
39911
39556
|
chain: chainKey,
|
|
39912
|
-
type: "native",
|
|
39913
|
-
symbol
|
|
39914
|
-
address,
|
|
39915
|
-
|
|
39916
|
-
usdValue
|
|
39917
|
-
});
|
|
39918
|
-
}
|
|
39919
|
-
for (const t of tokenBalances) {
|
|
39920
|
-
const dec = t.decimals ?? 18;
|
|
39921
|
-
const humanAmount = toHuman(t.rawBalance, dec);
|
|
39922
|
-
if (humanAmount === "0") continue;
|
|
39923
|
-
const tokenAssetId = `c${t.coinId}_t${t.contract}`;
|
|
39924
|
-
const tokenPrice = priceMap.get(tokenAssetId) ?? null;
|
|
39925
|
-
const usdValue = tokenPrice !== null ? parseFloat((parseFloat(humanAmount) * tokenPrice).toFixed(2)) : null;
|
|
39926
|
-
rows.push({
|
|
39927
|
-
chain: t.chainKey,
|
|
39928
|
-
type: "token",
|
|
39929
|
-
symbol: t.symbol ?? t.contract,
|
|
39930
|
-
address: t.address,
|
|
39931
|
-
contract: t.contract,
|
|
39557
|
+
type: isToken ? "token" : "native",
|
|
39558
|
+
symbol,
|
|
39559
|
+
address: userAddress,
|
|
39560
|
+
...token ? { contract: token.address } : {},
|
|
39932
39561
|
balance: humanAmount,
|
|
39933
39562
|
usdValue
|
|
39934
39563
|
});
|
|
39935
39564
|
}
|
|
39565
|
+
const rows = allRows.filter((r3) => r3.type === "native" || r3.balance !== "0");
|
|
39566
|
+
rows.sort((a3, b3) => {
|
|
39567
|
+
const chainCmp = String(a3.chain).localeCompare(String(b3.chain));
|
|
39568
|
+
if (chainCmp !== 0) return chainCmp;
|
|
39569
|
+
return String(a3.type).localeCompare(String(b3.type));
|
|
39570
|
+
});
|
|
39936
39571
|
if (opts.json) {
|
|
39937
|
-
if (failedChains.length > 0) {
|
|
39938
|
-
warn(`Balance unavailable for: ${failedChains.join(", ")}`);
|
|
39939
|
-
}
|
|
39940
39572
|
output(rows, true);
|
|
39941
39573
|
} else {
|
|
39942
39574
|
const totalUsd = rows.reduce(
|
|
@@ -39946,16 +39578,13 @@ ${qr3}`;
|
|
|
39946
39578
|
console.log("Chain Type Symbol Balance USD");
|
|
39947
39579
|
console.log("\u2500".repeat(72));
|
|
39948
39580
|
for (const r3 of rows) {
|
|
39949
|
-
const usd = r3.
|
|
39581
|
+
const usd = r3.usdValue !== null ? `$${r3.usdValue.toFixed(2)}` : "";
|
|
39950
39582
|
console.log(
|
|
39951
39583
|
`${String(r3.chain).padEnd(12)} ${String(r3.type).padEnd(7)} ${String(r3.symbol).padEnd(17)} ${String(r3.balance).padEnd(20)} ${usd}`
|
|
39952
39584
|
);
|
|
39953
39585
|
}
|
|
39954
39586
|
console.log("\u2500".repeat(72));
|
|
39955
39587
|
console.log(`Total USD: $${totalUsd.toFixed(2)}`);
|
|
39956
|
-
if (failedChains.length > 0) {
|
|
39957
|
-
warn(`Balance unavailable for: ${failedChains.join(", ")}`);
|
|
39958
|
-
}
|
|
39959
39588
|
}
|
|
39960
39589
|
} catch (err) {
|
|
39961
39590
|
handleCommandError(err, opts.json, "Portfolio failed");
|
|
@@ -40000,7 +39629,232 @@ ${qr3}`;
|
|
|
40000
39629
|
// src/commands/transfer.ts
|
|
40001
39630
|
init_dist();
|
|
40002
39631
|
init_dist2();
|
|
40003
|
-
|
|
39632
|
+
|
|
39633
|
+
// ../security/dist/index.mjs
|
|
39634
|
+
init_dist();
|
|
39635
|
+
var SecurityService = class {
|
|
39636
|
+
constructor(client) {
|
|
39637
|
+
this.client = client;
|
|
39638
|
+
}
|
|
39639
|
+
async validate(params) {
|
|
39640
|
+
const query = new URLSearchParams();
|
|
39641
|
+
if (params.assetId) query.set("asset_id", params.assetId);
|
|
39642
|
+
query.set("address", params.address);
|
|
39643
|
+
if (params.type) query.set("type", params.type);
|
|
39644
|
+
if (params.version) query.set("version", params.version.toString());
|
|
39645
|
+
try {
|
|
39646
|
+
return await this.client.gateway(
|
|
39647
|
+
"GET",
|
|
39648
|
+
"/v1/validate",
|
|
39649
|
+
void 0,
|
|
39650
|
+
query.toString()
|
|
39651
|
+
);
|
|
39652
|
+
} catch (err) {
|
|
39653
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
39654
|
+
const details = err?.details;
|
|
39655
|
+
let detailsStr = "";
|
|
39656
|
+
if (details != null) {
|
|
39657
|
+
try {
|
|
39658
|
+
detailsStr = JSON.stringify(details);
|
|
39659
|
+
} catch {
|
|
39660
|
+
try {
|
|
39661
|
+
detailsStr = String(details);
|
|
39662
|
+
} catch {
|
|
39663
|
+
}
|
|
39664
|
+
}
|
|
39665
|
+
}
|
|
39666
|
+
if (msg.includes("GraphQL") || msg.includes("Failed to decode") || detailsStr.includes("GraphQL") || detailsStr.includes("Failed to decode")) {
|
|
39667
|
+
return { valid: false, message: "Invalid address format" };
|
|
39668
|
+
}
|
|
39669
|
+
throw err;
|
|
39670
|
+
}
|
|
39671
|
+
}
|
|
39672
|
+
async getAssetInfo(assetId) {
|
|
39673
|
+
const info2 = await this.client.gateway(
|
|
39674
|
+
"GET",
|
|
39675
|
+
`/v1/assets/${encodeURIComponent(assetId)}`
|
|
39676
|
+
);
|
|
39677
|
+
try {
|
|
39678
|
+
const priceResp = await this.client.market("POST", "/v2/market/tickers", { currency: "USD", assets: [assetId] });
|
|
39679
|
+
const ticker = priceResp.tickers?.[0];
|
|
39680
|
+
if (ticker) {
|
|
39681
|
+
if (ticker.price != null) info2.priceUsd = ticker.price;
|
|
39682
|
+
if (ticker.change_24h != null) info2.priceChange24h = ticker.change_24h;
|
|
39683
|
+
if (ticker.market_cap != null) info2.marketCapUsd = ticker.market_cap;
|
|
39684
|
+
if (ticker.volume_24h != null) info2.volume24hUsd = ticker.volume_24h;
|
|
39685
|
+
}
|
|
39686
|
+
} catch {
|
|
39687
|
+
}
|
|
39688
|
+
return info2;
|
|
39689
|
+
}
|
|
39690
|
+
async getTokenHoldings(assets, fromTime = 0) {
|
|
39691
|
+
const response = await this.client.gateway("POST", "/v1/assets", {
|
|
39692
|
+
from_time: fromTime,
|
|
39693
|
+
version: 2,
|
|
39694
|
+
assets
|
|
39695
|
+
});
|
|
39696
|
+
return Array.isArray(response) ? response : response.docs ?? response.data ?? [];
|
|
39697
|
+
}
|
|
39698
|
+
async checkCoinStatus(assetId) {
|
|
39699
|
+
const query = new URLSearchParams({
|
|
39700
|
+
version: "8.4",
|
|
39701
|
+
platform: "android",
|
|
39702
|
+
include_security_info: "true",
|
|
39703
|
+
include_solana_security_info: "true"
|
|
39704
|
+
});
|
|
39705
|
+
const raw = await this.client.gateway(
|
|
39706
|
+
"GET",
|
|
39707
|
+
`/v2/coinstatus/${encodeURIComponent(assetId)}`,
|
|
39708
|
+
void 0,
|
|
39709
|
+
query.toString()
|
|
39710
|
+
);
|
|
39711
|
+
return this.transformCoinStatus(assetId, raw);
|
|
39712
|
+
}
|
|
39713
|
+
async getAssetListings(params) {
|
|
39714
|
+
const limit = params.limit || 20;
|
|
39715
|
+
const query = new URLSearchParams({
|
|
39716
|
+
version: "27",
|
|
39717
|
+
currency: params.currency || "USD",
|
|
39718
|
+
limit: String(params.sort ? Math.max(limit, 50) : limit)
|
|
39719
|
+
});
|
|
39720
|
+
if (params.categoryId) query.set("category_id", params.categoryId);
|
|
39721
|
+
if (params.networks) query.set("networks", params.networks);
|
|
39722
|
+
const response = await this.client.gateway(
|
|
39723
|
+
"GET",
|
|
39724
|
+
"/v1/assets/listings",
|
|
39725
|
+
void 0,
|
|
39726
|
+
query.toString()
|
|
39727
|
+
);
|
|
39728
|
+
const docs = response.docs || response.results || [];
|
|
39729
|
+
const tokens = docs.map((doc) => {
|
|
39730
|
+
const asset = doc.asset || doc;
|
|
39731
|
+
const price = doc.price || {};
|
|
39732
|
+
const market = doc.market || {};
|
|
39733
|
+
const assetIdRaw = asset.asset_id || doc.asset_id || "";
|
|
39734
|
+
const coinId = assetIdRaw ? parseInt(assetIdRaw.replace(/^c/, "").split("_")[0], 10) : NaN;
|
|
39735
|
+
const chain2 = !isNaN(coinId) ? coinTypeToChainKey(coinId) || "" : "";
|
|
39736
|
+
return {
|
|
39737
|
+
assetId: assetIdRaw,
|
|
39738
|
+
name: asset.name || doc.name || "",
|
|
39739
|
+
symbol: asset.symbol || doc.symbol || "",
|
|
39740
|
+
chain: chain2,
|
|
39741
|
+
logoUrl: asset.icon_url || doc.icon_url,
|
|
39742
|
+
priceUsd: price.price ?? doc.price_usd,
|
|
39743
|
+
priceChange24h: price.percent_change_24h ?? doc.price_change_24h,
|
|
39744
|
+
marketCapUsd: market.market_cap ?? price.market_cap ?? doc.market_cap_usd,
|
|
39745
|
+
volume24hUsd: market.volume_24h ?? price.volume_24h ?? doc.volume_24h_usd
|
|
39746
|
+
};
|
|
39747
|
+
});
|
|
39748
|
+
if (params.sort) {
|
|
39749
|
+
const sortKeyMap = {
|
|
39750
|
+
percent_change_24h: "priceChange24h",
|
|
39751
|
+
market_cap: "marketCapUsd",
|
|
39752
|
+
volume_24h: "volume24hUsd"
|
|
39753
|
+
};
|
|
39754
|
+
const key = sortKeyMap[params.sort] || "priceChange24h";
|
|
39755
|
+
tokens.sort((a3, b3) => (b3[key] ?? 0) - (a3[key] ?? 0));
|
|
39756
|
+
}
|
|
39757
|
+
return tokens.slice(0, limit);
|
|
39758
|
+
}
|
|
39759
|
+
async getTrendingTokens(limit = 20, sort = "percent_change_24h") {
|
|
39760
|
+
const knownAssets = [
|
|
39761
|
+
"c60",
|
|
39762
|
+
"c501",
|
|
39763
|
+
"c0",
|
|
39764
|
+
"c20000714",
|
|
39765
|
+
"c966",
|
|
39766
|
+
"c10009000",
|
|
39767
|
+
"c8453",
|
|
39768
|
+
"c10042221",
|
|
39769
|
+
"c10000070",
|
|
39770
|
+
"c195",
|
|
39771
|
+
"c637",
|
|
39772
|
+
"c607",
|
|
39773
|
+
"c784",
|
|
39774
|
+
"c397",
|
|
39775
|
+
"c118",
|
|
39776
|
+
"c2",
|
|
39777
|
+
"c3",
|
|
39778
|
+
"c10000250",
|
|
39779
|
+
"c10000324",
|
|
39780
|
+
"c81457"
|
|
39781
|
+
];
|
|
39782
|
+
const response = await this.client.market("POST", "/v2/market/tickers", {
|
|
39783
|
+
currency: "USD",
|
|
39784
|
+
assets: knownAssets
|
|
39785
|
+
});
|
|
39786
|
+
const tickers = response.tickers || [];
|
|
39787
|
+
const sortKey = sort === "market_cap" ? "market_cap" : sort === "volume_24h" ? "volume_24h" : "change_24h";
|
|
39788
|
+
return tickers.sort((a3, b3) => (b3[sortKey] ?? 0) - (a3[sortKey] ?? 0)).slice(0, limit).map((t) => {
|
|
39789
|
+
const coinId = t.id ? parseInt(t.id.replace(/^c/, ""), 10) : NaN;
|
|
39790
|
+
const chain2 = !isNaN(coinId) ? coinTypeToChainKey(coinId) || "" : "";
|
|
39791
|
+
return {
|
|
39792
|
+
assetId: t.id || "",
|
|
39793
|
+
name: t.slug || t.id || "",
|
|
39794
|
+
symbol: t.slug?.toUpperCase() || "",
|
|
39795
|
+
chain: chain2,
|
|
39796
|
+
logoUrl: void 0,
|
|
39797
|
+
priceUsd: t.price,
|
|
39798
|
+
priceChange24h: t.change_24h,
|
|
39799
|
+
marketCapUsd: t.market_cap,
|
|
39800
|
+
volume24hUsd: t.volume_24h
|
|
39801
|
+
};
|
|
39802
|
+
});
|
|
39803
|
+
}
|
|
39804
|
+
transformCoinStatus(assetId, raw) {
|
|
39805
|
+
if (!raw) return { assetId, isActive: false };
|
|
39806
|
+
const asset = raw.asset || raw;
|
|
39807
|
+
const security = raw.security_info || raw.securityInfo;
|
|
39808
|
+
const solanaSecurity = raw.solana_security_info || raw.solanaSecurityInfo;
|
|
39809
|
+
const warnings = [];
|
|
39810
|
+
let isHoneypot;
|
|
39811
|
+
if (security) {
|
|
39812
|
+
const allItems = [
|
|
39813
|
+
...security.contract_security?.items || [],
|
|
39814
|
+
...security.honeypot_risk?.items || []
|
|
39815
|
+
];
|
|
39816
|
+
for (const item of allItems) {
|
|
39817
|
+
if (item.type === "warning" || item.type === "risk") {
|
|
39818
|
+
const message = item.summary ?? item.description ?? item.code;
|
|
39819
|
+
if (message) warnings.push(message);
|
|
39820
|
+
}
|
|
39821
|
+
if (item.code === "is_honeypot" && item.type === "risk") isHoneypot = true;
|
|
39822
|
+
}
|
|
39823
|
+
}
|
|
39824
|
+
const totalRisks = (security?.contract_security?.num_risks ?? 0) + (security?.honeypot_risk?.num_risks ?? 0);
|
|
39825
|
+
const totalWarnings = (security?.contract_security?.num_warnings ?? 0) + (security?.honeypot_risk?.num_warnings ?? 0);
|
|
39826
|
+
const computedRiskLevel = totalRisks > 0 ? "high" : totalWarnings > 0 ? "medium" : security ? "low" : void 0;
|
|
39827
|
+
let finalRiskLevel = security?.risk_level ?? security?.riskLevel ?? computedRiskLevel;
|
|
39828
|
+
const knownStablecoins = ["USDC", "USDT", "DAI", "BUSD", "TUSD", "USDP", "FRAX", "PYUSD"];
|
|
39829
|
+
const symbol = (asset.symbol ?? raw.symbol ?? "").toUpperCase();
|
|
39830
|
+
let riskContext;
|
|
39831
|
+
if (finalRiskLevel === "medium" && knownStablecoins.includes(symbol) && totalRisks === 0) {
|
|
39832
|
+
riskContext = "Warnings are due to proxy contract pattern, common in audited stablecoins";
|
|
39833
|
+
finalRiskLevel = "low";
|
|
39834
|
+
}
|
|
39835
|
+
return {
|
|
39836
|
+
assetId,
|
|
39837
|
+
name: asset.name ?? raw.name,
|
|
39838
|
+
symbol: asset.symbol ?? raw.symbol,
|
|
39839
|
+
isActive: raw.is_active ?? raw.isActive ?? true,
|
|
39840
|
+
supportsSwap: raw.isSwapEnabled ?? raw.supports_swap ?? raw.supportsSwap,
|
|
39841
|
+
supportsStaking: raw.isStakeEnabled ?? raw.supports_staking ?? raw.supportsStaking,
|
|
39842
|
+
securityInfo: security ? {
|
|
39843
|
+
isHoneypot: isHoneypot ?? security.is_honeypot ?? security.isHoneypot,
|
|
39844
|
+
hasAudit: security.has_audit ?? security.hasAudit ?? security.is_scanned,
|
|
39845
|
+
auditProvider: security.audit_provider ?? security.auditProvider ?? security.provider,
|
|
39846
|
+
riskLevel: finalRiskLevel,
|
|
39847
|
+
...riskContext ? { riskContext } : {},
|
|
39848
|
+
warnings: warnings.length > 0 ? warnings : security.warnings
|
|
39849
|
+
} : void 0,
|
|
39850
|
+
solanaSecurityInfo: solanaSecurity ? {
|
|
39851
|
+
isMintable: solanaSecurity.is_mintable ?? solanaSecurity.isMintable,
|
|
39852
|
+
hasFreezeAuthority: solanaSecurity.has_freeze_authority ?? solanaSecurity.hasFreezeAuthority,
|
|
39853
|
+
riskLevel: solanaSecurity.risk_level ?? solanaSecurity.riskLevel
|
|
39854
|
+
} : void 0
|
|
39855
|
+
};
|
|
39856
|
+
}
|
|
39857
|
+
};
|
|
40004
39858
|
|
|
40005
39859
|
// ../swap/dist/index.mjs
|
|
40006
39860
|
init_dist();
|
|
@@ -40308,6 +40162,37 @@ var SwapExecutor = class {
|
|
|
40308
40162
|
|
|
40309
40163
|
// src/commands/transfer.ts
|
|
40310
40164
|
var DEFAULT_MAX_TRANSFER_USD = 1e4;
|
|
40165
|
+
function mapBroadcastError(err) {
|
|
40166
|
+
if (err instanceof CommandError) throw err;
|
|
40167
|
+
if (err instanceof TwakError) {
|
|
40168
|
+
if (err.code === "VALIDATION_ERROR") {
|
|
40169
|
+
throw new TransferError("VALIDATION_ERROR", err.message);
|
|
40170
|
+
}
|
|
40171
|
+
if (err.code === "NETWORK_ERROR") {
|
|
40172
|
+
throw new TransferError("NETWORK_ERROR", err.message);
|
|
40173
|
+
}
|
|
40174
|
+
if (err instanceof TwakApiError && err.statusCode === 429) {
|
|
40175
|
+
throw new TransferError("RATE_LIMITED", err.message);
|
|
40176
|
+
}
|
|
40177
|
+
if (err.code === "API_ERROR") {
|
|
40178
|
+
throw new TransferError("BROADCAST_FAILED", err.message);
|
|
40179
|
+
}
|
|
40180
|
+
}
|
|
40181
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
40182
|
+
if (/\binsufficient\b/i.test(msg)) {
|
|
40183
|
+
throw new TransferError("INSUFFICIENT_BALANCE", msg);
|
|
40184
|
+
}
|
|
40185
|
+
if (/\bnetwork\b|\becon(nrefused|timedout)\b|\bfetch\b/i.test(msg)) {
|
|
40186
|
+
throw new TransferError("NETWORK_ERROR", msg);
|
|
40187
|
+
}
|
|
40188
|
+
if (/\bbroadcast\b|\bsendtransaction\b|\brpc\b/i.test(msg)) {
|
|
40189
|
+
throw new TransferError("BROADCAST_FAILED", msg);
|
|
40190
|
+
}
|
|
40191
|
+
if (/\bsigning\b|\bsignature\b|\bsign\s+(error|failed)\b/i.test(msg)) {
|
|
40192
|
+
throw new TransferError("SIGN_FAILED", msg);
|
|
40193
|
+
}
|
|
40194
|
+
throw err;
|
|
40195
|
+
}
|
|
40311
40196
|
async function resolvePassword2(opts) {
|
|
40312
40197
|
if (opts.password) return opts.password;
|
|
40313
40198
|
if (process.env.TWAK_WALLET_PASSWORD) return process.env.TWAK_WALLET_PASSWORD;
|
|
@@ -40424,35 +40309,7 @@ function registerTransferCommand(program3) {
|
|
|
40424
40309
|
hdWallet
|
|
40425
40310
|
});
|
|
40426
40311
|
} catch (err) {
|
|
40427
|
-
|
|
40428
|
-
if (err instanceof TwakError) {
|
|
40429
|
-
if (err.code === "VALIDATION_ERROR") {
|
|
40430
|
-
throw new TransferError("VALIDATION_ERROR", err.message);
|
|
40431
|
-
}
|
|
40432
|
-
if (err.code === "NETWORK_ERROR") {
|
|
40433
|
-
throw new TransferError("NETWORK_ERROR", err.message);
|
|
40434
|
-
}
|
|
40435
|
-
if (err instanceof TwakApiError && err.statusCode === 429) {
|
|
40436
|
-
throw new TransferError("RATE_LIMITED", err.message);
|
|
40437
|
-
}
|
|
40438
|
-
if (err.code === "API_ERROR") {
|
|
40439
|
-
throw new TransferError("BROADCAST_FAILED", err.message);
|
|
40440
|
-
}
|
|
40441
|
-
}
|
|
40442
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
40443
|
-
if (/\binsufficient\b/i.test(msg)) {
|
|
40444
|
-
throw new TransferError("INSUFFICIENT_BALANCE", msg);
|
|
40445
|
-
}
|
|
40446
|
-
if (/\bnetwork\b|\becon(nrefused|timedout)\b|\bfetch\b/i.test(msg)) {
|
|
40447
|
-
throw new TransferError("NETWORK_ERROR", msg);
|
|
40448
|
-
}
|
|
40449
|
-
if (/\bbroadcast\b|\bsendtransaction\b|\brpc\b/i.test(msg)) {
|
|
40450
|
-
throw new TransferError("BROADCAST_FAILED", msg);
|
|
40451
|
-
}
|
|
40452
|
-
if (/\bsigning\b|\bsignature\b|\bsign\s+(error|failed)\b/i.test(msg)) {
|
|
40453
|
-
throw new TransferError("SIGN_FAILED", msg);
|
|
40454
|
-
}
|
|
40455
|
-
throw err;
|
|
40312
|
+
mapBroadcastError(err);
|
|
40456
40313
|
}
|
|
40457
40314
|
const explorerUrl = getExplorerTxUrl(result.chainKey, result.hash);
|
|
40458
40315
|
success("Transfer sent!");
|
|
@@ -40971,7 +40828,6 @@ function registerErc20Command(program3) {
|
|
|
40971
40828
|
// src/commands/alert.ts
|
|
40972
40829
|
init_dist2();
|
|
40973
40830
|
init_dist();
|
|
40974
|
-
init_dist5();
|
|
40975
40831
|
var assetCache = new AssetCache();
|
|
40976
40832
|
async function resolveTokenLabel(tokenId, service) {
|
|
40977
40833
|
const cached2 = await assetCache.get(tokenId);
|
|
@@ -41266,7 +41122,6 @@ var TransactionService = class {
|
|
|
41266
41122
|
};
|
|
41267
41123
|
|
|
41268
41124
|
// src/commands/query.ts
|
|
41269
|
-
init_dist5();
|
|
41270
41125
|
init_dist4();
|
|
41271
41126
|
var import_chalk6 = __toESM(require_source());
|
|
41272
41127
|
function handleError(err, json) {
|
|
@@ -41622,13 +41477,11 @@ function registerQueryCommand(program3) {
|
|
|
41622
41477
|
if (err instanceof CommandError) throw err;
|
|
41623
41478
|
}
|
|
41624
41479
|
const assetId = opts.token ? `c${coinId}_t${opts.token}` : `c${coinId}`;
|
|
41625
|
-
const
|
|
41626
|
-
const balance = await client.getBalance(assetId, opts.address);
|
|
41480
|
+
const httpClient = await getClient();
|
|
41627
41481
|
let decimals = chain2.decimals;
|
|
41628
41482
|
let symbol = chain2.symbol;
|
|
41629
41483
|
if (opts.token) {
|
|
41630
|
-
const
|
|
41631
|
-
const swapClient = new SwapClient(apiClient);
|
|
41484
|
+
const swapClient = new SwapClient(httpClient);
|
|
41632
41485
|
const chainKey = opts.chain || Object.entries(CHAINS).find(([, c3]) => c3.coinId === coinId)?.[0] || "";
|
|
41633
41486
|
const tokens = await swapClient.searchTokens({
|
|
41634
41487
|
query: opts.token,
|
|
@@ -41643,22 +41496,21 @@ function registerQueryCommand(program3) {
|
|
|
41643
41496
|
symbol = opts.token;
|
|
41644
41497
|
}
|
|
41645
41498
|
}
|
|
41646
|
-
const
|
|
41647
|
-
|
|
41648
|
-
|
|
41649
|
-
|
|
41650
|
-
|
|
41651
|
-
};
|
|
41499
|
+
const balance = await fetchBalance(httpClient, assetId, opts.address);
|
|
41500
|
+
const amts = balance.amounts;
|
|
41501
|
+
const lockedHuman = formatAmount(amts.locked ?? "0", decimals);
|
|
41502
|
+
const fiatRaw = amts.totalInFiat;
|
|
41503
|
+
const totalUsd = fiatRaw && fiatRaw !== "" ? parseFloat(fiatRaw) : null;
|
|
41652
41504
|
output(
|
|
41653
41505
|
{
|
|
41654
41506
|
address: opts.address,
|
|
41655
41507
|
chain: chain2.id,
|
|
41656
41508
|
symbol,
|
|
41657
41509
|
...opts.token ? { token: opts.token } : {},
|
|
41658
|
-
available:
|
|
41659
|
-
...
|
|
41660
|
-
|
|
41661
|
-
|
|
41510
|
+
available: formatAmount(amts.available ?? "0", decimals),
|
|
41511
|
+
...lockedHuman !== "0" ? { staked: lockedHuman } : {},
|
|
41512
|
+
total: formatAmount(amts.total ?? "0", decimals),
|
|
41513
|
+
totalUsd,
|
|
41662
41514
|
raw: balance
|
|
41663
41515
|
},
|
|
41664
41516
|
opts.json
|
|
@@ -45895,12 +45747,11 @@ var NEVER = INVALID;
|
|
|
45895
45747
|
// ../actions/dist/index.mjs
|
|
45896
45748
|
init_dist();
|
|
45897
45749
|
init_dist();
|
|
45898
|
-
init_dist5();
|
|
45899
45750
|
init_dist();
|
|
45900
45751
|
init_dist();
|
|
45901
45752
|
init_dist();
|
|
45902
45753
|
init_dist3();
|
|
45903
|
-
|
|
45754
|
+
init_dist();
|
|
45904
45755
|
init_dist();
|
|
45905
45756
|
var ActionProvider = class {
|
|
45906
45757
|
supportsChain(_chainId) {
|
|
@@ -46827,6 +46678,433 @@ var MarketActionProvider = class extends ActionProvider {
|
|
|
46827
46678
|
};
|
|
46828
46679
|
}
|
|
46829
46680
|
};
|
|
46681
|
+
var ERC20_TRANSFER_SIG2 = "0xa9059cbb";
|
|
46682
|
+
var EVM_ADDRESS_RE = /^0x[a-fA-F0-9]{40}$/;
|
|
46683
|
+
var DEFAULT_MAX_OFFRAMP_USD = 1e4;
|
|
46684
|
+
function encodeErc20Transfer2(to3, amount) {
|
|
46685
|
+
const paddedTo = to3.slice(2).padStart(64, "0");
|
|
46686
|
+
const paddedAmount = amount.toString(16).padStart(64, "0");
|
|
46687
|
+
return `${ERC20_TRANSFER_SIG2}${paddedTo}${paddedAmount}`;
|
|
46688
|
+
}
|
|
46689
|
+
function buildFollowUp(cryptoAsset, address, direction, paymentMethod) {
|
|
46690
|
+
if (!cryptoAsset) return void 0;
|
|
46691
|
+
let parsed;
|
|
46692
|
+
try {
|
|
46693
|
+
parsed = parseTokenId(cryptoAsset);
|
|
46694
|
+
} catch {
|
|
46695
|
+
return void 0;
|
|
46696
|
+
}
|
|
46697
|
+
const isErc20 = Boolean(parsed.contractAddress);
|
|
46698
|
+
const action = isErc20 ? "get_token_holdings" : "get_balance";
|
|
46699
|
+
if (direction === "onramp") {
|
|
46700
|
+
return {
|
|
46701
|
+
action,
|
|
46702
|
+
arguments: { address, coin: parsed.coinId },
|
|
46703
|
+
why: "Verify the purchased crypto has arrived at the destination address. TWAK has no background polling or completion webhook \u2014 the only way to confirm delivery is to poll the balance yourself.",
|
|
46704
|
+
expectedChange: "increase",
|
|
46705
|
+
pollEveryMs: 6e4,
|
|
46706
|
+
typicalCompletion: "Card purchases usually settle within 5\u201330 minutes; bank transfers can take 1\u20133 business days. Stop polling once the balance has increased or after a reasonable ceiling (e.g., 1 hour for card, 4 days for bank)."
|
|
46707
|
+
};
|
|
46708
|
+
}
|
|
46709
|
+
return {
|
|
46710
|
+
action,
|
|
46711
|
+
arguments: { address, coin: parsed.coinId },
|
|
46712
|
+
why: "Poll this only AFTER calling confirm_offramp_transfer. The browser-hosted provider page cannot sign transactions (it has no access to the wallet keys). Required flow: (1) user opens the checkout URL and completes KYC + order creation in the browser, (2) the provider displays a deposit address (and memo/tag on some chains) + exact amount, (3) the agent calls confirm_offramp_transfer with those values to broadcast the payout natively, (4) then this balance-poll confirms the crypto has left the source address.",
|
|
46713
|
+
expectedChange: "decrease",
|
|
46714
|
+
pollEveryMs: 3e4,
|
|
46715
|
+
typicalCompletion: "Balance decrease is visible within 1\u20132 minutes of confirm_offramp_transfer returning a tx hash. The subsequent fiat payout is processed separately by the provider (typically 1\u20133 business days for bank_transfer, faster for card).",
|
|
46716
|
+
notes: paymentMethod ? `Fiat payout method: ${paymentMethod}` : void 0
|
|
46717
|
+
};
|
|
46718
|
+
}
|
|
46719
|
+
function mapQuote(q3) {
|
|
46720
|
+
return {
|
|
46721
|
+
quoteId: q3.id,
|
|
46722
|
+
cryptoAmount: q3.digital_money?.amount,
|
|
46723
|
+
cryptoCurrency: q3.digital_money?.currency,
|
|
46724
|
+
fiatAmount: q3.fiat_money?.amount,
|
|
46725
|
+
fiatCurrency: q3.fiat_money?.currency,
|
|
46726
|
+
provider: q3.provider?.name,
|
|
46727
|
+
paymentMethod: q3.payment_method?.payment_method,
|
|
46728
|
+
isRecommended: q3.is_recommended
|
|
46729
|
+
};
|
|
46730
|
+
}
|
|
46731
|
+
function summarizeOnrampQuotes(quotes) {
|
|
46732
|
+
if (quotes.length === 0) return "No quotes available for the requested fiat\u2192crypto purchase.";
|
|
46733
|
+
const best = quotes[0];
|
|
46734
|
+
const worstVsBest = quotes[quotes.length - 1].vsBestPct ?? 0;
|
|
46735
|
+
const savingsHint = worstVsBest > 0 ? ` (saves ${worstVsBest.toFixed(2)}% vs cheapest)` : "";
|
|
46736
|
+
return `${quotes.length} quote${quotes.length === 1 ? "" : "s"} available \u2014 best: ${best.cryptoAmount} ${best.cryptoCurrency} for ${best.fiatAmount} ${best.fiatCurrency} via ${best.provider}${savingsHint}.`;
|
|
46737
|
+
}
|
|
46738
|
+
function summarizeOfframpQuotes(quotes) {
|
|
46739
|
+
if (quotes.length === 0) return "No quotes available for the requested crypto\u2192fiat sale.";
|
|
46740
|
+
const best = quotes[0];
|
|
46741
|
+
const worstVsBest = quotes[quotes.length - 1].vsBestPct ?? 0;
|
|
46742
|
+
const savingsHint = worstVsBest > 0 ? ` (saves ${worstVsBest.toFixed(2)}% vs cheapest)` : "";
|
|
46743
|
+
return `${quotes.length} quote${quotes.length === 1 ? "" : "s"} available \u2014 best: ${best.fiatAmount} ${best.fiatCurrency} via ${best.provider}${savingsHint}.`;
|
|
46744
|
+
}
|
|
46745
|
+
var OnrampActionProvider = class extends ActionProvider {
|
|
46746
|
+
// An optional wallet provider lets the action auto-derive the caller's own
|
|
46747
|
+
// address from the asset's chain, matching the safety contract that
|
|
46748
|
+
// transfer/swap/erc20 actions already follow. Explicit `walletAddress`
|
|
46749
|
+
// overrides the derivation for agents operating on behalf of other users.
|
|
46750
|
+
//
|
|
46751
|
+
// `priceLookup` + `maxTransferUsd` enable the same USD safety cap used by
|
|
46752
|
+
// TransferActionProvider for the off-ramp payout path (confirm_offramp_transfer).
|
|
46753
|
+
// Off-ramp sends crypto to a third-party deposit address, so the cap is
|
|
46754
|
+
// strictly more important here than for peer-to-peer transfers.
|
|
46755
|
+
constructor(walletProvider, priceLookup, maxTransferUsd = DEFAULT_MAX_OFFRAMP_USD) {
|
|
46756
|
+
super();
|
|
46757
|
+
this.walletProvider = walletProvider;
|
|
46758
|
+
this.priceLookup = priceLookup;
|
|
46759
|
+
this.maxTransferUsd = maxTransferUsd;
|
|
46760
|
+
}
|
|
46761
|
+
name = "onramp";
|
|
46762
|
+
description = "Fiat on-ramp and off-ramp \u2014 buy crypto with fiat (USD, EUR, etc.) or sell crypto for fiat payouts. On-ramp: two-step flow (get quote \u2192 get checkout URL), provider sends crypto to the destination address. Off-ramp: three-step flow (get quote \u2192 get checkout URL \u2192 confirm_offramp_transfer). The off-ramp checkout URL runs in a plain browser which has no access to wallet keys, so the payout transaction must be broadcast natively via confirm_offramp_transfer once the user has read the provider's deposit address and exact amount. Destination/source address auto-derives from the configured wallet using the asset's chain. IMPORTANT: TWAK has no background polling or completion webhook for ramps. Tool responses include a `followUp` hint telling the agent exactly which balance tool to poll (with args and timing) to observe completion.";
|
|
46763
|
+
getActions() {
|
|
46764
|
+
return [
|
|
46765
|
+
this.getOnrampQuote(),
|
|
46766
|
+
this.getOnrampUrl(),
|
|
46767
|
+
this.getOfframpQuote(),
|
|
46768
|
+
this.getOfframpUrl(),
|
|
46769
|
+
this.confirmOfframpTransfer()
|
|
46770
|
+
];
|
|
46771
|
+
}
|
|
46772
|
+
async resolveAddress(providedAddress, cryptoAsset) {
|
|
46773
|
+
if (providedAddress) return providedAddress;
|
|
46774
|
+
if (!this.walletProvider) {
|
|
46775
|
+
throw new Error(
|
|
46776
|
+
"walletAddress not provided and no wallet provider is configured. Pass walletAddress explicitly or configure an agent wallet."
|
|
46777
|
+
);
|
|
46778
|
+
}
|
|
46779
|
+
if (!cryptoAsset) {
|
|
46780
|
+
throw new Error(
|
|
46781
|
+
"walletAddress not provided and cryptoAsset is missing \u2014 cannot derive chain. Pass walletAddress or cryptoAsset."
|
|
46782
|
+
);
|
|
46783
|
+
}
|
|
46784
|
+
const chainKey = tokenIdToChain(cryptoAsset);
|
|
46785
|
+
if (!chainKey) {
|
|
46786
|
+
throw new Error(
|
|
46787
|
+
`Cannot determine chain for asset ${cryptoAsset}. Pass walletAddress explicitly.`
|
|
46788
|
+
);
|
|
46789
|
+
}
|
|
46790
|
+
return this.walletProvider.getAddress(chainKey);
|
|
46791
|
+
}
|
|
46792
|
+
getOnrampQuote() {
|
|
46793
|
+
return {
|
|
46794
|
+
name: "get_onramp_quote",
|
|
46795
|
+
description: "Step 1 of buying crypto with fiat: get quotes from available payment providers (MoonPay, Transak, etc.) for a given fiat amount. Returns an array of quotes each with a quoteId \u2014 pass one to get_onramp_url to generate the checkout redirect.",
|
|
46796
|
+
schema: external_exports.object({
|
|
46797
|
+
fiatAmount: external_exports.coerce.number().positive().describe("Amount of fiat currency to spend (e.g., 100 for $100)."),
|
|
46798
|
+
fiatCurrency: external_exports.string().length(3).default("USD").describe("Fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
|
|
46799
|
+
cryptoAsset: external_exports.string().describe(
|
|
46800
|
+
"Target crypto asset ID. Format: c<coinId> for native coins (e.g., c60 for ETH, c0 for BTC, c501 for SOL) or c<coinId>_t<contract> for tokens (e.g., c60_t0xA0b8... for USDC on Ethereum)."
|
|
46801
|
+
),
|
|
46802
|
+
walletAddress: external_exports.string().optional().describe(
|
|
46803
|
+
"Destination wallet address where the purchased crypto will be sent. OMIT to auto-derive from the configured wallet using cryptoAsset's chain \u2014 this is the safe default and prevents sending funds to a stranger by mistake. Only supply this when operating on behalf of a different user."
|
|
46804
|
+
)
|
|
46805
|
+
}),
|
|
46806
|
+
invoke: async (ctx, input) => {
|
|
46807
|
+
const address = await this.resolveAddress(input.walletAddress, input.cryptoAsset);
|
|
46808
|
+
const query = new URLSearchParams({
|
|
46809
|
+
amount: input.fiatAmount.toString(),
|
|
46810
|
+
currency: input.fiatCurrency,
|
|
46811
|
+
address
|
|
46812
|
+
});
|
|
46813
|
+
const result = await ctx.httpClient.gateway(
|
|
46814
|
+
"GET",
|
|
46815
|
+
`/v1/buycrypto/quote/${encodeURIComponent(input.cryptoAsset)}`,
|
|
46816
|
+
void 0,
|
|
46817
|
+
query.toString()
|
|
46818
|
+
);
|
|
46819
|
+
const mapped = (result?.quotes ?? []).map(mapQuote);
|
|
46820
|
+
const quotes = rankQuotesByBest(mapped, (q3) => q3.cryptoAmount);
|
|
46821
|
+
return {
|
|
46822
|
+
success: true,
|
|
46823
|
+
summary: summarizeOnrampQuotes(quotes),
|
|
46824
|
+
address,
|
|
46825
|
+
cryptoAsset: input.cryptoAsset,
|
|
46826
|
+
quotes,
|
|
46827
|
+
nextAction: quotes.length > 0 ? "Call get_onramp_url with the chosen quoteId to receive a browser redirect URL for checkout. quotes[0] is the best deal (lowest vsBestPct)." : "No quotes available \u2014 try a different fiatAmount or cryptoAsset."
|
|
46828
|
+
};
|
|
46829
|
+
}
|
|
46830
|
+
};
|
|
46831
|
+
}
|
|
46832
|
+
getOnrampUrl() {
|
|
46833
|
+
return {
|
|
46834
|
+
name: "get_onramp_url",
|
|
46835
|
+
description: "Step 2 of buying crypto with fiat: exchange a quoteId from get_onramp_quote for a browser redirect URL. The user opens that URL to complete KYC and payment with the provider; on success, the provider sends the purchased crypto to the destination address. After this tool returns a URL, poll the balance tool named in the `followUp` field to observe the incoming funds \u2014 there is no background polling, webhook, or push notification.",
|
|
46836
|
+
schema: external_exports.object({
|
|
46837
|
+
quoteId: external_exports.string().describe("Quote ID returned by get_onramp_quote."),
|
|
46838
|
+
walletAddress: external_exports.string().optional().describe(
|
|
46839
|
+
"Destination wallet address. OMIT to auto-derive from the configured wallet \u2014 requires cryptoAsset so the chain can be determined. Only supply this when operating on behalf of a different user."
|
|
46840
|
+
),
|
|
46841
|
+
cryptoAsset: external_exports.string().optional().describe(
|
|
46842
|
+
"Crypto asset ID that was used for the quote (e.g., c60 for ETH). Required when walletAddress is omitted so the chain can be derived for address lookup."
|
|
46843
|
+
)
|
|
46844
|
+
}),
|
|
46845
|
+
invoke: async (ctx, input) => {
|
|
46846
|
+
const address = await this.resolveAddress(input.walletAddress, input.cryptoAsset);
|
|
46847
|
+
const query = new URLSearchParams({ address });
|
|
46848
|
+
let result;
|
|
46849
|
+
try {
|
|
46850
|
+
result = await ctx.httpClient.gateway(
|
|
46851
|
+
"GET",
|
|
46852
|
+
`/v1/buycrypto/request/${encodeURIComponent(input.quoteId)}`,
|
|
46853
|
+
void 0,
|
|
46854
|
+
query.toString()
|
|
46855
|
+
);
|
|
46856
|
+
} catch (err) {
|
|
46857
|
+
if (err instanceof TwakApiError && err.statusCode === 404) {
|
|
46858
|
+
return {
|
|
46859
|
+
success: false,
|
|
46860
|
+
address,
|
|
46861
|
+
summary: `No checkout URL returned for quoteId ${input.quoteId} \u2014 the quote has likely expired (buy quotes are short-lived). Request a fresh quote with get_onramp_quote and retry.`,
|
|
46862
|
+
nextAction: "Call get_onramp_quote again to obtain a fresh quoteId, then retry get_onramp_url."
|
|
46863
|
+
};
|
|
46864
|
+
}
|
|
46865
|
+
throw err;
|
|
46866
|
+
}
|
|
46867
|
+
const url = result.url ?? result.redirect_url;
|
|
46868
|
+
const followUp = url ? buildFollowUp(input.cryptoAsset, address, "onramp") : void 0;
|
|
46869
|
+
return {
|
|
46870
|
+
success: Boolean(url),
|
|
46871
|
+
address,
|
|
46872
|
+
url,
|
|
46873
|
+
summary: url ? `Open the URL in a browser to complete your purchase. Crypto will be delivered to ${address}.` : "No checkout URL returned \u2014 the quoteId may have expired. Request a new quote with get_onramp_quote.",
|
|
46874
|
+
followUp
|
|
46875
|
+
};
|
|
46876
|
+
}
|
|
46877
|
+
};
|
|
46878
|
+
}
|
|
46879
|
+
getOfframpQuote() {
|
|
46880
|
+
return {
|
|
46881
|
+
name: "get_offramp_quote",
|
|
46882
|
+
description: "Step 1 of selling crypto for fiat: get quotes from available providers for converting a crypto amount into fiat. Returns an array of quotes each with a quoteId \u2014 pass one to get_offramp_url to generate the checkout redirect for completing KYC and order creation. The provider page cannot sign the payout (no key access in the browser); the agent must broadcast it natively via confirm_offramp_transfer once the deposit address is known.",
|
|
46883
|
+
schema: external_exports.object({
|
|
46884
|
+
cryptoAmount: external_exports.coerce.number().positive().describe("Amount of crypto to sell (e.g., 0.1 for 0.1 ETH)."),
|
|
46885
|
+
cryptoAsset: external_exports.string().describe(
|
|
46886
|
+
"Crypto asset ID to sell. Format: c<coinId> for native coins (e.g., c60 for ETH, c0 for BTC) or c<coinId>_t<contract> for tokens."
|
|
46887
|
+
),
|
|
46888
|
+
fiatCurrency: external_exports.string().length(3).default("USD").describe("Target fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
|
|
46889
|
+
paymentMethod: external_exports.enum(["ANY", "bank_transfer", "card"]).optional().default("ANY").describe(
|
|
46890
|
+
'Payout method. Default "ANY" is a sentinel that matches any provider \u2014 recommended unless you specifically need "card" or "bank_transfer". Sending an unrecognised value (or `bank_transfer` in regions without bank-payout coverage) silently returns an empty quotes array.'
|
|
46891
|
+
),
|
|
46892
|
+
walletAddress: external_exports.string().optional().describe(
|
|
46893
|
+
"Source wallet address holding the crypto to be sold. OMIT to auto-derive from the configured wallet using cryptoAsset's chain \u2014 this is the safe default. Only supply this when operating on behalf of a different user."
|
|
46894
|
+
)
|
|
46895
|
+
}),
|
|
46896
|
+
invoke: async (ctx, input) => {
|
|
46897
|
+
const address = await this.resolveAddress(input.walletAddress, input.cryptoAsset);
|
|
46898
|
+
const query = new URLSearchParams({
|
|
46899
|
+
amount: input.cryptoAmount.toString(),
|
|
46900
|
+
currency: input.fiatCurrency,
|
|
46901
|
+
// iOS sends `asset_id` as both path segment AND query param. Mirroring that.
|
|
46902
|
+
asset_id: input.cryptoAsset,
|
|
46903
|
+
address,
|
|
46904
|
+
// The HTTP gateway uses `payment_method` for both buy and sell, mirroring the
|
|
46905
|
+
// iOS production client. The sell proto declares `payout_method` but the
|
|
46906
|
+
// gateway doesn't honour it; trust runtime over proto.
|
|
46907
|
+
payment_method: input.paymentMethod ?? "ANY",
|
|
46908
|
+
// iOS always sends this even when the user holds no TWT; omitting it appears
|
|
46909
|
+
// to filter out fee-waiver-aware providers in some regions.
|
|
46910
|
+
twt_amount: "0"
|
|
46911
|
+
});
|
|
46912
|
+
const result = await ctx.httpClient.gateway(
|
|
46913
|
+
"GET",
|
|
46914
|
+
`/v1/sellcrypto/quote/${encodeURIComponent(input.cryptoAsset)}`,
|
|
46915
|
+
void 0,
|
|
46916
|
+
query.toString()
|
|
46917
|
+
);
|
|
46918
|
+
const mapped = (result?.quotes ?? []).filter((q3) => ALLOWED_OFFRAMP_PROVIDERS.has(q3.provider?.name ?? "")).map(mapQuote);
|
|
46919
|
+
const quotes = rankQuotesByBest(mapped, (q3) => q3.fiatAmount);
|
|
46920
|
+
return {
|
|
46921
|
+
success: true,
|
|
46922
|
+
summary: summarizeOfframpQuotes(quotes),
|
|
46923
|
+
address,
|
|
46924
|
+
cryptoAsset: input.cryptoAsset,
|
|
46925
|
+
quotes,
|
|
46926
|
+
nextAction: quotes.length > 0 ? "Call get_offramp_url with the chosen quoteId to receive a browser redirect URL for the sale. quotes[0] is the best deal (lowest vsBestPct)." : "No quotes available \u2014 try a different cryptoAmount, cryptoAsset, or paymentMethod."
|
|
46927
|
+
};
|
|
46928
|
+
}
|
|
46929
|
+
};
|
|
46930
|
+
}
|
|
46931
|
+
getOfframpUrl() {
|
|
46932
|
+
return {
|
|
46933
|
+
name: "get_offramp_url",
|
|
46934
|
+
description: "Step 2 of selling crypto for fiat: exchange a quoteId from get_offramp_quote for a browser checkout URL. The user opens that URL to complete KYC and order creation with the provider; the provider then displays a deposit address (plus a memo/tag on chains that require one) and the exact amount to send. The agent must then call confirm_offramp_transfer with those values to sign and broadcast the payout natively \u2014 the browser has no access to wallet keys and cannot sign the transfer itself. After the payout broadcasts, poll the balance tool named in the `followUp` field to observe the outgoing tx. Fiat payout arrives separately via the provider.",
|
|
46935
|
+
schema: external_exports.object({
|
|
46936
|
+
quoteId: external_exports.string().describe("Quote ID returned by get_offramp_quote."),
|
|
46937
|
+
walletAddress: external_exports.string().optional().describe(
|
|
46938
|
+
"Source wallet address. OMIT to auto-derive from the configured wallet \u2014 requires cryptoAsset so the chain can be determined. Only supply this when operating on behalf of a different user."
|
|
46939
|
+
),
|
|
46940
|
+
cryptoAsset: external_exports.string().optional().describe(
|
|
46941
|
+
"Crypto asset ID that was used for the quote (e.g., c60 for ETH). Required when walletAddress is omitted so the chain can be derived for address lookup."
|
|
46942
|
+
)
|
|
46943
|
+
}),
|
|
46944
|
+
invoke: async (ctx, input) => {
|
|
46945
|
+
const address = await this.resolveAddress(input.walletAddress, input.cryptoAsset);
|
|
46946
|
+
const query = new URLSearchParams({ address });
|
|
46947
|
+
let result;
|
|
46948
|
+
try {
|
|
46949
|
+
result = await ctx.httpClient.gateway(
|
|
46950
|
+
"GET",
|
|
46951
|
+
`/v1/sellcrypto/request/${encodeURIComponent(input.quoteId)}`,
|
|
46952
|
+
void 0,
|
|
46953
|
+
query.toString()
|
|
46954
|
+
);
|
|
46955
|
+
} catch (err) {
|
|
46956
|
+
if (err instanceof TwakApiError && err.statusCode === 404) {
|
|
46957
|
+
return {
|
|
46958
|
+
success: false,
|
|
46959
|
+
address,
|
|
46960
|
+
summary: `No checkout URL returned for quoteId ${input.quoteId} \u2014 the quote has likely expired (sell quotes are short-lived). Request a fresh quote with get_offramp_quote and retry.`,
|
|
46961
|
+
nextAction: "Call get_offramp_quote again to obtain a fresh quoteId, then retry get_offramp_url."
|
|
46962
|
+
};
|
|
46963
|
+
}
|
|
46964
|
+
throw err;
|
|
46965
|
+
}
|
|
46966
|
+
const url = result.url ?? result.redirect_url;
|
|
46967
|
+
const followUp = url ? buildFollowUp(input.cryptoAsset, address, "offramp") : void 0;
|
|
46968
|
+
return {
|
|
46969
|
+
success: Boolean(url),
|
|
46970
|
+
address,
|
|
46971
|
+
url,
|
|
46972
|
+
summary: url ? `Open the URL in a browser to complete KYC and create the sell order from ${address}. The provider will display a deposit address (and memo/tag on chains that need one) plus the exact amount to send. Then call confirm_offramp_transfer with those values to broadcast the payout \u2014 the browser cannot sign it.` : "No checkout URL returned \u2014 the quoteId may have expired. Request a new quote with get_offramp_quote.",
|
|
46973
|
+
nextAction: url ? "After the user reads the provider's deposit address + amount, call confirm_offramp_transfer with those values to broadcast the payout transaction." : void 0,
|
|
46974
|
+
followUp
|
|
46975
|
+
};
|
|
46976
|
+
}
|
|
46977
|
+
};
|
|
46978
|
+
}
|
|
46979
|
+
confirmOfframpTransfer() {
|
|
46980
|
+
return {
|
|
46981
|
+
name: "confirm_offramp_transfer",
|
|
46982
|
+
description: "Step 3 of selling crypto for fiat: sign and broadcast the payout transaction to the provider's deposit address. Call this AFTER the user has completed KYC in the browser and can read the deposit address + exact amount the provider displayed. Works with any wallet provider that implements sendTransaction (the local agent wallet, or a WalletConnect-paired external wallet). EVM-only \u2014 for BTC / Solana / Cosmos off-ramps use the `twak onramp sell-confirm` CLI command instead. Returns a tx hash on success; fiat payout from the provider then arrives separately (no on-chain signal for that step).",
|
|
46983
|
+
schema: external_exports.object({
|
|
46984
|
+
depositAddress: external_exports.string().describe(
|
|
46985
|
+
"The deposit address displayed by the provider after KYC. This is a third-party address \u2014 verify it matches what the provider shows before calling."
|
|
46986
|
+
),
|
|
46987
|
+
cryptoAmount: external_exports.string().describe(
|
|
46988
|
+
'Exact amount to send in human-readable format (e.g., "0.1" for 0.1 ETH). Must match the amount the provider displays; under-sending will invalidate the sell order.'
|
|
46989
|
+
),
|
|
46990
|
+
cryptoAsset: external_exports.string().describe(
|
|
46991
|
+
"Crypto asset being sold. Format: c<coinId> for native coins (e.g., c60 for ETH) or c<coinId>_t<contract> for ERC-20 tokens."
|
|
46992
|
+
),
|
|
46993
|
+
decimals: external_exports.coerce.number().optional().describe(
|
|
46994
|
+
"Token decimals. REQUIRED for ERC-20 assets (pass 6 for USDC/USDT, 18 for DAI, etc.) \u2014 omitting it will be rejected rather than silently defaulting to 18, because a wrong-decimals encode can over-pay the provider by orders of magnitude. Ignored for native coins."
|
|
46995
|
+
),
|
|
46996
|
+
memo: external_exports.string().optional().describe(
|
|
46997
|
+
"Memo / destination tag. Not used on EVM chains, so supplying one on EVM is a no-op warning. Exists in the schema for forward-compat with non-EVM chains."
|
|
46998
|
+
),
|
|
46999
|
+
quoteId: external_exports.string().optional().describe(
|
|
47000
|
+
"Quote ID from get_offramp_quote. Optional \u2014 included only for traceability in the response; tx details come from depositAddress / cryptoAmount."
|
|
47001
|
+
)
|
|
47002
|
+
}),
|
|
47003
|
+
invoke: async (_ctx, input) => {
|
|
47004
|
+
const signer = this.walletProvider;
|
|
47005
|
+
if (!signer) {
|
|
47006
|
+
return { success: false, error: "No wallet provider configured" };
|
|
47007
|
+
}
|
|
47008
|
+
let parsed;
|
|
47009
|
+
try {
|
|
47010
|
+
parsed = parseTokenId(input.cryptoAsset);
|
|
47011
|
+
} catch {
|
|
47012
|
+
throw new TransferError("VALIDATION_ERROR", `Invalid cryptoAsset: ${input.cryptoAsset}`);
|
|
47013
|
+
}
|
|
47014
|
+
const chainKey = coinTypeToChainKey(parsed.coinId);
|
|
47015
|
+
if (!chainKey) {
|
|
47016
|
+
throw new TransferError(
|
|
47017
|
+
"CHAIN_UNSUPPORTED",
|
|
47018
|
+
`Unsupported coin type in cryptoAsset: ${input.cryptoAsset}`
|
|
47019
|
+
);
|
|
47020
|
+
}
|
|
47021
|
+
const chainDef = getChainById(chainKey);
|
|
47022
|
+
if (!chainDef || chainDef.namespace !== "eip155") {
|
|
47023
|
+
throw new TransferError(
|
|
47024
|
+
"CHAIN_UNSUPPORTED",
|
|
47025
|
+
`confirm_offramp_transfer is EVM-only. For ${chainKey} off-ramps use the CLI: twak onramp sell-confirm --asset ${input.cryptoAsset} --to <deposit> --amount <n>${input.memo ? " --memo <tag>" : ""}.`
|
|
47026
|
+
);
|
|
47027
|
+
}
|
|
47028
|
+
const caip2 = `eip155:${chainDef.chainId}`;
|
|
47029
|
+
if (!EVM_ADDRESS_RE.test(input.depositAddress)) {
|
|
47030
|
+
throw new TransferError(
|
|
47031
|
+
"VALIDATION_ERROR",
|
|
47032
|
+
`depositAddress is not a valid EVM address: ${input.depositAddress}. Double-check what the provider displayed.`
|
|
47033
|
+
);
|
|
47034
|
+
}
|
|
47035
|
+
if (parsed.contractAddress && !EVM_ADDRESS_RE.test(parsed.contractAddress)) {
|
|
47036
|
+
throw new TransferError(
|
|
47037
|
+
"VALIDATION_ERROR",
|
|
47038
|
+
`Malformed token contract in cryptoAsset: ${parsed.contractAddress}`
|
|
47039
|
+
);
|
|
47040
|
+
}
|
|
47041
|
+
if (parsed.contractAddress && input.decimals === void 0) {
|
|
47042
|
+
throw new TransferError(
|
|
47043
|
+
"VALIDATION_ERROR",
|
|
47044
|
+
`decimals is required for ERC-20 asset ${input.cryptoAsset}. Pass the token's decimals explicitly (6 for USDC/USDT, 18 for DAI, etc.) \u2014 silently defaulting to 18 could over-pay the provider by orders of magnitude.`
|
|
47045
|
+
);
|
|
47046
|
+
}
|
|
47047
|
+
const decimals = parsed.contractAddress ? input.decimals : 18;
|
|
47048
|
+
try {
|
|
47049
|
+
assertStrictPositiveAmount(input.cryptoAmount, decimals);
|
|
47050
|
+
} catch (err) {
|
|
47051
|
+
throw new TransferError(
|
|
47052
|
+
"VALIDATION_ERROR",
|
|
47053
|
+
err instanceof Error ? err.message : String(err)
|
|
47054
|
+
);
|
|
47055
|
+
}
|
|
47056
|
+
if (this.priceLookup) {
|
|
47057
|
+
try {
|
|
47058
|
+
const tokenInfo = resolveToken(input.cryptoAsset, chainKey);
|
|
47059
|
+
const tokenAddress = tokenInfo?.address ?? parsed.contractAddress ?? "";
|
|
47060
|
+
const price = await this.priceLookup.getTokenPrice(chainKey, tokenAddress);
|
|
47061
|
+
if (price && price > 0) {
|
|
47062
|
+
const usdValue = parseFloat(input.cryptoAmount) * price;
|
|
47063
|
+
if (usdValue > this.maxTransferUsd) {
|
|
47064
|
+
return {
|
|
47065
|
+
success: false,
|
|
47066
|
+
error: `Off-ramp payout value ~$${usdValue.toFixed(2)} USD exceeds safety limit of $${this.maxTransferUsd}. Use the CLI with --max-usd ${Math.ceil(usdValue)} or --skip-safety-check to proceed.`
|
|
47067
|
+
};
|
|
47068
|
+
}
|
|
47069
|
+
}
|
|
47070
|
+
} catch {
|
|
47071
|
+
}
|
|
47072
|
+
}
|
|
47073
|
+
const atomic = parseAmount(input.cryptoAmount, decimals);
|
|
47074
|
+
let txHash;
|
|
47075
|
+
if (parsed.contractAddress) {
|
|
47076
|
+
const calldata = encodeErc20Transfer2(input.depositAddress, BigInt(atomic));
|
|
47077
|
+
txHash = await signer.sendTransaction({
|
|
47078
|
+
chainId: caip2,
|
|
47079
|
+
to: parsed.contractAddress,
|
|
47080
|
+
data: calldata,
|
|
47081
|
+
value: "0x0"
|
|
47082
|
+
});
|
|
47083
|
+
} else {
|
|
47084
|
+
txHash = await signer.sendTransaction({
|
|
47085
|
+
chainId: caip2,
|
|
47086
|
+
to: input.depositAddress,
|
|
47087
|
+
data: "0x",
|
|
47088
|
+
value: "0x" + BigInt(atomic).toString(16)
|
|
47089
|
+
});
|
|
47090
|
+
}
|
|
47091
|
+
const explorerUrl = getExplorerTxUrl(chainKey, txHash) || void 0;
|
|
47092
|
+
return {
|
|
47093
|
+
success: true,
|
|
47094
|
+
txHash,
|
|
47095
|
+
chain: chainKey,
|
|
47096
|
+
to: input.depositAddress,
|
|
47097
|
+
amount: input.cryptoAmount,
|
|
47098
|
+
asset: input.cryptoAsset,
|
|
47099
|
+
memoIgnored: input.memo ? `A memo was supplied (${input.memo}) but EVM transactions have no memo field \u2014 it was ignored. If the provider requires a memo, they likely expect a non-EVM chain.` : void 0,
|
|
47100
|
+
quoteId: input.quoteId,
|
|
47101
|
+
explorer: explorerUrl,
|
|
47102
|
+
summary: `Off-ramp payout of ${input.cryptoAmount} ${input.cryptoAsset} broadcast to ${input.depositAddress} on ${chainKey}. Fiat payout from the provider arrives separately.`
|
|
47103
|
+
};
|
|
47104
|
+
}
|
|
47105
|
+
};
|
|
47106
|
+
}
|
|
47107
|
+
};
|
|
46830
47108
|
var AutomateActionProvider = class extends ActionProvider {
|
|
46831
47109
|
constructor(automationStore, swapClient, walletProvider, blockchainClient) {
|
|
46832
47110
|
super();
|
|
@@ -55882,6 +56160,7 @@ function registerServeCommand(program3) {
|
|
|
55882
56160
|
registry2.register(new AgentWalletActionProvider(blockchainClient));
|
|
55883
56161
|
registry2.register(new TokenActionProvider(blockchainClient, agentWallet));
|
|
55884
56162
|
registry2.register(new MarketActionProvider());
|
|
56163
|
+
registry2.register(new OnrampActionProvider(walletProvider, swapClient));
|
|
55885
56164
|
registry2.register(
|
|
55886
56165
|
new AutomateActionProvider(automationStore, swapClient, walletProvider, blockchainClient)
|
|
55887
56166
|
);
|
|
@@ -55916,11 +56195,435 @@ function registerServeCommand(program3) {
|
|
|
55916
56195
|
});
|
|
55917
56196
|
}
|
|
55918
56197
|
|
|
56198
|
+
// src/commands/onramp.ts
|
|
56199
|
+
var import_chalk7 = __toESM(require_source());
|
|
56200
|
+
init_dist();
|
|
56201
|
+
var DEFAULT_MAX_OFFRAMP_USD2 = 1e4;
|
|
56202
|
+
function fmtVsBest(pct) {
|
|
56203
|
+
return pct === 0 ? "0.00%" : `-${pct.toFixed(2)}%`;
|
|
56204
|
+
}
|
|
56205
|
+
function printBuyQuoteTable(rows, fiatAmount, fiatCurrency, asset) {
|
|
56206
|
+
const cols = rows.map((r3) => ({
|
|
56207
|
+
provider: r3.provider ?? "",
|
|
56208
|
+
pay: `${r3.fiatAmount ?? ""} ${r3.fiatCurrency ?? ""}`.trim(),
|
|
56209
|
+
receive: `${r3.cryptoAmount ?? ""} ${r3.cryptoCurrency ?? ""}`.trim(),
|
|
56210
|
+
vsBest: fmtVsBest(r3.vsBestPct),
|
|
56211
|
+
quoteId: r3.quoteId ?? ""
|
|
56212
|
+
}));
|
|
56213
|
+
const w4 = {
|
|
56214
|
+
provider: Math.max(8, ...cols.map((c3) => c3.provider.length)),
|
|
56215
|
+
pay: Math.max(3, ...cols.map((c3) => c3.pay.length)),
|
|
56216
|
+
receive: Math.max(7, ...cols.map((c3) => c3.receive.length)),
|
|
56217
|
+
vsBest: Math.max(7, ...cols.map((c3) => c3.vsBest.length))
|
|
56218
|
+
};
|
|
56219
|
+
const cryptoCurrency = rows[0]?.cryptoCurrency ?? "";
|
|
56220
|
+
console.log(`${rows.length} quotes for ${fiatAmount} ${fiatCurrency} \u2192 ${cryptoCurrency}
|
|
56221
|
+
`);
|
|
56222
|
+
console.log(
|
|
56223
|
+
import_chalk7.default.bold(
|
|
56224
|
+
`${"PROVIDER".padEnd(w4.provider)} ${"PAY".padEnd(w4.pay)} ${"RECEIVE".padEnd(w4.receive)} ${"VS BEST".padEnd(w4.vsBest)} QUOTE ID`
|
|
56225
|
+
)
|
|
56226
|
+
);
|
|
56227
|
+
for (const c3 of cols) {
|
|
56228
|
+
console.log(
|
|
56229
|
+
`${c3.provider.padEnd(w4.provider)} ${c3.pay.padEnd(w4.pay)} ${c3.receive.padEnd(w4.receive)} ${c3.vsBest.padEnd(w4.vsBest)} ${c3.quoteId}`
|
|
56230
|
+
);
|
|
56231
|
+
}
|
|
56232
|
+
const best = rows[0];
|
|
56233
|
+
console.log(
|
|
56234
|
+
`
|
|
56235
|
+
\u2192 best: ${import_chalk7.default.bold(best.provider ?? "")} \u2014 run: ${import_chalk7.default.bold(`twak onramp buy --quote-id ${best.quoteId} --asset ${asset}`)}`
|
|
56236
|
+
);
|
|
56237
|
+
}
|
|
56238
|
+
function printSellQuoteTable(rows, cryptoAmount, cryptoAsset) {
|
|
56239
|
+
const cols = rows.map((r3) => ({
|
|
56240
|
+
provider: r3.provider ?? "",
|
|
56241
|
+
pay: `${r3.cryptoAmount ?? ""} ${r3.cryptoCurrency ?? ""}`.trim(),
|
|
56242
|
+
receive: `${r3.fiatAmount ?? ""} ${r3.fiatCurrency ?? ""}`.trim(),
|
|
56243
|
+
vsBest: fmtVsBest(r3.vsBestPct),
|
|
56244
|
+
quoteId: r3.quoteId ?? ""
|
|
56245
|
+
}));
|
|
56246
|
+
const w4 = {
|
|
56247
|
+
provider: Math.max(8, ...cols.map((c3) => c3.provider.length)),
|
|
56248
|
+
pay: Math.max(3, ...cols.map((c3) => c3.pay.length)),
|
|
56249
|
+
receive: Math.max(7, ...cols.map((c3) => c3.receive.length)),
|
|
56250
|
+
vsBest: Math.max(7, ...cols.map((c3) => c3.vsBest.length))
|
|
56251
|
+
};
|
|
56252
|
+
const fiatCurrency = rows[0]?.fiatCurrency ?? "";
|
|
56253
|
+
console.log(`${rows.length} quotes for ${cryptoAmount} ${cryptoAsset} \u2192 ${fiatCurrency}
|
|
56254
|
+
`);
|
|
56255
|
+
console.log(
|
|
56256
|
+
import_chalk7.default.bold(
|
|
56257
|
+
`${"PROVIDER".padEnd(w4.provider)} ${"PAY".padEnd(w4.pay)} ${"RECEIVE".padEnd(w4.receive)} ${"VS BEST".padEnd(w4.vsBest)} QUOTE ID`
|
|
56258
|
+
)
|
|
56259
|
+
);
|
|
56260
|
+
for (const c3 of cols) {
|
|
56261
|
+
console.log(
|
|
56262
|
+
`${c3.provider.padEnd(w4.provider)} ${c3.pay.padEnd(w4.pay)} ${c3.receive.padEnd(w4.receive)} ${c3.vsBest.padEnd(w4.vsBest)} ${c3.quoteId}`
|
|
56263
|
+
);
|
|
56264
|
+
}
|
|
56265
|
+
const best = rows[0];
|
|
56266
|
+
console.log(
|
|
56267
|
+
`
|
|
56268
|
+
\u2192 best: ${import_chalk7.default.bold(best.provider ?? "")} \u2014 run: ${import_chalk7.default.bold(`twak onramp sell --quote-id ${best.quoteId} --asset ${cryptoAsset}`)}`
|
|
56269
|
+
);
|
|
56270
|
+
}
|
|
56271
|
+
async function resolveWalletAddress(opts, context) {
|
|
56272
|
+
if (opts.wallet) {
|
|
56273
|
+
warn(
|
|
56274
|
+
`Using manually specified ${context} address ${opts.wallet}. This is not derived from your stored wallet \u2014 double-check it before proceeding.`
|
|
56275
|
+
);
|
|
56276
|
+
return opts.wallet;
|
|
56277
|
+
}
|
|
56278
|
+
if (!opts.asset) {
|
|
56279
|
+
throw new Error(
|
|
56280
|
+
"Provide --wallet, or pass --asset so the address can be derived from your stored wallet."
|
|
56281
|
+
);
|
|
56282
|
+
}
|
|
56283
|
+
const chainKey = tokenIdToChain(opts.asset);
|
|
56284
|
+
if (!chainKey) {
|
|
56285
|
+
throw new Error(
|
|
56286
|
+
`Cannot determine chain for asset ${opts.asset}. Pass --wallet explicitly or use a supported asset ID.`
|
|
56287
|
+
);
|
|
56288
|
+
}
|
|
56289
|
+
await ensureTrustSDK();
|
|
56290
|
+
const password = await resolvePassword({ password: opts.password });
|
|
56291
|
+
const provider = await AgentWalletProvider.fromStorage(password);
|
|
56292
|
+
const address = await provider.getAddress(chainKey);
|
|
56293
|
+
info(`Using ${context} address ${address} on ${chainKey} (derived from your stored wallet).`);
|
|
56294
|
+
return address;
|
|
56295
|
+
}
|
|
56296
|
+
function registerOnrampCommand(program3) {
|
|
56297
|
+
const onramp = program3.command("onramp").description("Buy or sell crypto with fiat currency");
|
|
56298
|
+
onramp.command("quote").description("Get quotes for buying crypto with fiat currency").requiredOption("--amount <n>", "Fiat amount (e.g., 100)").requiredOption("--asset <id>", "Crypto asset ID (e.g., c60 for ETH)").option(
|
|
56299
|
+
"--wallet <address>",
|
|
56300
|
+
"Destination wallet address (defaults to your stored wallet address for the asset's chain)"
|
|
56301
|
+
).option("--currency <code>", "Fiat currency code", "USD").option(
|
|
56302
|
+
"--password <password>",
|
|
56303
|
+
"Wallet password (only needed when --wallet is omitted; falls back to keychain / TWAK_WALLET_PASSWORD)"
|
|
56304
|
+
).option("--json", "JSON output").action(async (opts) => {
|
|
56305
|
+
try {
|
|
56306
|
+
const address = await resolveWalletAddress(opts, "destination");
|
|
56307
|
+
const client = await getClient();
|
|
56308
|
+
const query = new URLSearchParams({
|
|
56309
|
+
amount: opts.amount,
|
|
56310
|
+
currency: opts.currency,
|
|
56311
|
+
address
|
|
56312
|
+
});
|
|
56313
|
+
const result = await client.gateway(
|
|
56314
|
+
"GET",
|
|
56315
|
+
`/v1/buycrypto/quote/${encodeURIComponent(opts.asset)}`,
|
|
56316
|
+
void 0,
|
|
56317
|
+
query.toString()
|
|
56318
|
+
);
|
|
56319
|
+
const mapped = (result?.quotes || []).map((q3) => ({
|
|
56320
|
+
quoteId: q3.id,
|
|
56321
|
+
cryptoAmount: q3.digital_money?.amount,
|
|
56322
|
+
cryptoCurrency: q3.digital_money?.currency,
|
|
56323
|
+
fiatAmount: q3.fiat_money?.amount,
|
|
56324
|
+
fiatCurrency: q3.fiat_money?.currency,
|
|
56325
|
+
provider: q3.provider?.name,
|
|
56326
|
+
paymentMethod: q3.payment_method?.payment_method
|
|
56327
|
+
}));
|
|
56328
|
+
const quotes = rankQuotesByBest(mapped, (q3) => q3.cryptoAmount);
|
|
56329
|
+
if (quotes.length === 0) {
|
|
56330
|
+
if (!opts.json)
|
|
56331
|
+
info(
|
|
56332
|
+
`No quotes available for ${opts.amount} ${opts.currency} \u2192 ${opts.asset}. Try a different amount, currency, or asset.`
|
|
56333
|
+
);
|
|
56334
|
+
output([], opts.json);
|
|
56335
|
+
} else if (opts.json) {
|
|
56336
|
+
output(quotes, true);
|
|
56337
|
+
} else {
|
|
56338
|
+
printBuyQuoteTable(quotes, opts.amount, opts.currency, opts.asset);
|
|
56339
|
+
}
|
|
56340
|
+
} catch (err) {
|
|
56341
|
+
handleCommandError(err, opts.json, "Quote failed");
|
|
56342
|
+
}
|
|
56343
|
+
safeExit();
|
|
56344
|
+
});
|
|
56345
|
+
onramp.command("buy").description("Get a URL to complete a fiat-to-crypto purchase (pass quoteId from onramp quote)").requiredOption("--quote-id <id>", "Quote ID from onramp quote").option(
|
|
56346
|
+
"--wallet <address>",
|
|
56347
|
+
"Destination wallet address (defaults to your stored wallet address for the asset's chain)"
|
|
56348
|
+
).option(
|
|
56349
|
+
"--asset <id>",
|
|
56350
|
+
"Crypto asset ID (e.g., c60) \u2014 used to derive your address when --wallet is omitted"
|
|
56351
|
+
).option(
|
|
56352
|
+
"--password <password>",
|
|
56353
|
+
"Wallet password (only needed when --wallet is omitted; falls back to keychain / TWAK_WALLET_PASSWORD)"
|
|
56354
|
+
).option("--json", "JSON output").action(async (opts) => {
|
|
56355
|
+
try {
|
|
56356
|
+
const address = await resolveWalletAddress(opts, "destination");
|
|
56357
|
+
const client = await getClient();
|
|
56358
|
+
const query = new URLSearchParams({ address });
|
|
56359
|
+
const result = await client.gateway(
|
|
56360
|
+
"GET",
|
|
56361
|
+
`/v1/buycrypto/request/${encodeURIComponent(opts.quoteId)}`,
|
|
56362
|
+
void 0,
|
|
56363
|
+
query.toString()
|
|
56364
|
+
);
|
|
56365
|
+
const url = result.url ?? result.redirect_url;
|
|
56366
|
+
if (!opts.json && url) {
|
|
56367
|
+
info(`Open this URL to complete your purchase:
|
|
56368
|
+
${url}`);
|
|
56369
|
+
const chainKey = opts.asset ? tokenIdToChain(opts.asset) : void 0;
|
|
56370
|
+
if (chainKey) {
|
|
56371
|
+
info(
|
|
56372
|
+
`After payment completes (typically 5\u201330 min for card, 1\u20133 business days for bank transfer), check your balance with:
|
|
56373
|
+
twak wallet balance --chain ${chainKey}`
|
|
56374
|
+
);
|
|
56375
|
+
} else {
|
|
56376
|
+
info(
|
|
56377
|
+
"After payment completes, check your balance with: twak wallet balance --chain <chain>"
|
|
56378
|
+
);
|
|
56379
|
+
}
|
|
56380
|
+
}
|
|
56381
|
+
output({ url }, opts.json);
|
|
56382
|
+
} catch (err) {
|
|
56383
|
+
handleCommandError(err, opts.json, "Buy failed");
|
|
56384
|
+
}
|
|
56385
|
+
safeExit();
|
|
56386
|
+
});
|
|
56387
|
+
onramp.command("sell-quote").description("Get quotes for selling crypto to receive fiat").requiredOption("--amount <n>", "Crypto amount to sell (e.g., 0.1)").requiredOption("--asset <id>", "Crypto asset ID to sell (e.g., c60 for ETH)").option(
|
|
56388
|
+
"--wallet <address>",
|
|
56389
|
+
"Source wallet address (defaults to your stored wallet address for the asset's chain)"
|
|
56390
|
+
).option("--currency <code>", "Fiat currency code", "USD").option(
|
|
56391
|
+
"--method <method>",
|
|
56392
|
+
"Payout method: ANY (default sentinel \u2014 matches any provider), card, bank_transfer",
|
|
56393
|
+
"ANY"
|
|
56394
|
+
).option(
|
|
56395
|
+
"--password <password>",
|
|
56396
|
+
"Wallet password (only needed when --wallet is omitted; falls back to keychain / TWAK_WALLET_PASSWORD)"
|
|
56397
|
+
).option("--json", "JSON output").action(async (opts) => {
|
|
56398
|
+
try {
|
|
56399
|
+
const address = await resolveWalletAddress(opts, "source");
|
|
56400
|
+
const client = await getClient();
|
|
56401
|
+
const query = new URLSearchParams({
|
|
56402
|
+
amount: opts.amount,
|
|
56403
|
+
currency: opts.currency,
|
|
56404
|
+
// iOS sends `asset_id` as both path segment AND query param. Mirroring that —
|
|
56405
|
+
// the gateway appears to require the query form even when the path has it.
|
|
56406
|
+
asset_id: opts.asset,
|
|
56407
|
+
address,
|
|
56408
|
+
// The HTTP gateway uses `payment_method` for both buy and sell, even though
|
|
56409
|
+
// the sell proto declares `payout_method` — runtime evidence (iOS production
|
|
56410
|
+
// client) is the source of truth here.
|
|
56411
|
+
payment_method: opts.method,
|
|
56412
|
+
// iOS always sends this even when the user holds no TWT; omitting it appears
|
|
56413
|
+
// to filter out fee-waiver-aware providers in some regions.
|
|
56414
|
+
twt_amount: "0"
|
|
56415
|
+
});
|
|
56416
|
+
const result = await client.gateway(
|
|
56417
|
+
"GET",
|
|
56418
|
+
`/v1/sellcrypto/quote/${encodeURIComponent(opts.asset)}`,
|
|
56419
|
+
void 0,
|
|
56420
|
+
query.toString()
|
|
56421
|
+
);
|
|
56422
|
+
const mapped = (result?.quotes || []).filter((q3) => ALLOWED_OFFRAMP_PROVIDERS.has(q3.provider?.name ?? "")).map((q3) => ({
|
|
56423
|
+
quoteId: q3.id,
|
|
56424
|
+
cryptoAmount: q3.digital_money?.amount,
|
|
56425
|
+
cryptoCurrency: q3.digital_money?.currency,
|
|
56426
|
+
fiatAmount: q3.fiat_money?.amount,
|
|
56427
|
+
fiatCurrency: q3.fiat_money?.currency,
|
|
56428
|
+
provider: q3.provider?.name,
|
|
56429
|
+
isRecommended: q3.is_recommended
|
|
56430
|
+
}));
|
|
56431
|
+
const quotes = rankQuotesByBest(mapped, (q3) => q3.fiatAmount);
|
|
56432
|
+
if (quotes.length === 0) {
|
|
56433
|
+
if (!opts.json)
|
|
56434
|
+
info(
|
|
56435
|
+
`No quotes available for ${opts.amount} ${opts.asset} \u2192 ${opts.currency}. Try a different amount, asset, or currency.`
|
|
56436
|
+
);
|
|
56437
|
+
output([], opts.json);
|
|
56438
|
+
} else if (opts.json) {
|
|
56439
|
+
output(quotes, true);
|
|
56440
|
+
} else {
|
|
56441
|
+
printSellQuoteTable(quotes, opts.amount, opts.asset);
|
|
56442
|
+
}
|
|
56443
|
+
} catch (err) {
|
|
56444
|
+
handleCommandError(err, opts.json, "Sell quote failed");
|
|
56445
|
+
}
|
|
56446
|
+
safeExit();
|
|
56447
|
+
});
|
|
56448
|
+
onramp.command("sell").description(
|
|
56449
|
+
"Get a URL to complete a crypto-to-fiat sale (pass quoteId from onramp sell-quote)"
|
|
56450
|
+
).requiredOption("--quote-id <id>", "Quote ID from onramp sell-quote").option(
|
|
56451
|
+
"--wallet <address>",
|
|
56452
|
+
"Source wallet address (defaults to your stored wallet address for the asset's chain)"
|
|
56453
|
+
).option(
|
|
56454
|
+
"--asset <id>",
|
|
56455
|
+
"Crypto asset ID (e.g., c60) \u2014 used to derive your address when --wallet is omitted"
|
|
56456
|
+
).option(
|
|
56457
|
+
"--password <password>",
|
|
56458
|
+
"Wallet password (only needed when --wallet is omitted; falls back to keychain / TWAK_WALLET_PASSWORD)"
|
|
56459
|
+
).option("--json", "JSON output").action(async (opts) => {
|
|
56460
|
+
try {
|
|
56461
|
+
const address = await resolveWalletAddress(opts, "source");
|
|
56462
|
+
const client = await getClient();
|
|
56463
|
+
const query = new URLSearchParams({ address });
|
|
56464
|
+
const result = await client.gateway(
|
|
56465
|
+
"GET",
|
|
56466
|
+
`/v1/sellcrypto/request/${encodeURIComponent(opts.quoteId)}`,
|
|
56467
|
+
void 0,
|
|
56468
|
+
query.toString()
|
|
56469
|
+
);
|
|
56470
|
+
const url = result.url ?? result.redirect_url;
|
|
56471
|
+
if (!opts.json && url) {
|
|
56472
|
+
info(`Open this URL and complete KYC + the sell order in your browser:
|
|
56473
|
+
${url}`);
|
|
56474
|
+
info(
|
|
56475
|
+
"The provider will show a deposit address (and a memo/tag for chains that need one) plus the exact amount to send. The browser has no access to your keys \u2014 it cannot sign the transfer itself. Copy those values back here and finalise the payout with:"
|
|
56476
|
+
);
|
|
56477
|
+
const assetArg = opts.asset ? ` --asset ${opts.asset}` : " --asset <assetId>";
|
|
56478
|
+
info(
|
|
56479
|
+
` twak onramp sell-confirm${assetArg} --to <deposit-address> --amount <exact-amount> [--memo <tag>]`
|
|
56480
|
+
);
|
|
56481
|
+
info(
|
|
56482
|
+
"Once sell-confirm broadcasts, fiat arrives via the provider separately (typically 1\u20133 business days for bank transfer, faster for card)."
|
|
56483
|
+
);
|
|
56484
|
+
}
|
|
56485
|
+
output({ url }, opts.json);
|
|
56486
|
+
} catch (err) {
|
|
56487
|
+
handleCommandError(err, opts.json, "Sell failed");
|
|
56488
|
+
}
|
|
56489
|
+
safeExit();
|
|
56490
|
+
});
|
|
56491
|
+
onramp.command("sell-confirm").description(
|
|
56492
|
+
"Finalise an off-ramp by broadcasting the payout transaction to the provider's deposit address. Run this after completing KYC in the browser and copying the deposit address + exact amount shown by the provider."
|
|
56493
|
+
).requiredOption("--asset <id>", "Crypto asset ID being sold (e.g., c60 for ETH, c0 for BTC)").requiredOption("--to <address>", "Deposit address shown by the provider after KYC").requiredOption(
|
|
56494
|
+
"--amount <n>",
|
|
56495
|
+
"Exact amount to send, in human-readable format (must match the amount the provider displays)"
|
|
56496
|
+
).option(
|
|
56497
|
+
"--memo <tag>",
|
|
56498
|
+
"Memo / destination tag required by some chains (Cosmos, XRP, Stellar, BNB Beacon). Include this when the provider displays one \u2014 funds can be unrecoverable without it."
|
|
56499
|
+
).option(
|
|
56500
|
+
"--quote-id <id>",
|
|
56501
|
+
"Quote ID from onramp sell-quote. Optional \u2014 included only for traceability in the output; the authoritative tx details come from --to / --amount."
|
|
56502
|
+
).option(
|
|
56503
|
+
"--max-usd <n>",
|
|
56504
|
+
`Maximum allowed payout value in USD (default: ${DEFAULT_MAX_OFFRAMP_USD2})`
|
|
56505
|
+
).option("--skip-safety-check", "Skip the USD-value safety cap").option(
|
|
56506
|
+
"--password <password>",
|
|
56507
|
+
"Wallet password (falls back to keychain / TWAK_WALLET_PASSWORD)"
|
|
56508
|
+
).option("--json", "JSON output").action(async (opts) => {
|
|
56509
|
+
try {
|
|
56510
|
+
await ensureTrustSDK();
|
|
56511
|
+
const password = await resolvePassword({ password: opts.password });
|
|
56512
|
+
const provider = await AgentWalletProvider.fromStorage(password);
|
|
56513
|
+
let coinId;
|
|
56514
|
+
try {
|
|
56515
|
+
({ coinId } = parseTokenId(opts.asset));
|
|
56516
|
+
} catch {
|
|
56517
|
+
throw new TransferError("VALIDATION_ERROR", `Invalid asset ID: ${opts.asset}`);
|
|
56518
|
+
}
|
|
56519
|
+
const chainKey = coinTypeToChainKey(coinId);
|
|
56520
|
+
if (!chainKey) {
|
|
56521
|
+
throw new TransferError(
|
|
56522
|
+
"CHAIN_UNSUPPORTED",
|
|
56523
|
+
`Unsupported coin type in asset ID: ${opts.asset}`
|
|
56524
|
+
);
|
|
56525
|
+
}
|
|
56526
|
+
const parsedAmount = parseFloat(opts.amount);
|
|
56527
|
+
if (!Number.isFinite(parsedAmount) || parsedAmount <= 0) {
|
|
56528
|
+
throw new TransferError("VALIDATION_ERROR", "--amount must be a positive number");
|
|
56529
|
+
}
|
|
56530
|
+
const httpClient = await getClient();
|
|
56531
|
+
let decimals;
|
|
56532
|
+
try {
|
|
56533
|
+
const assetInfo = await new SecurityService(httpClient).getAssetInfo(opts.asset);
|
|
56534
|
+
decimals = assetInfo.decimals;
|
|
56535
|
+
} catch {
|
|
56536
|
+
}
|
|
56537
|
+
try {
|
|
56538
|
+
assertStrictPositiveAmount(opts.amount, decimals);
|
|
56539
|
+
} catch (err) {
|
|
56540
|
+
throw new TransferError(
|
|
56541
|
+
"VALIDATION_ERROR",
|
|
56542
|
+
err instanceof Error ? err.message : String(err)
|
|
56543
|
+
);
|
|
56544
|
+
}
|
|
56545
|
+
if (!opts.skipSafetyCheck) {
|
|
56546
|
+
const maxUsd = parseFloat(opts.maxUsd ?? String(DEFAULT_MAX_OFFRAMP_USD2));
|
|
56547
|
+
if (!Number.isFinite(maxUsd) || maxUsd <= 0) {
|
|
56548
|
+
throw new TransferError("VALIDATION_ERROR", "--max-usd must be a positive number");
|
|
56549
|
+
}
|
|
56550
|
+
try {
|
|
56551
|
+
const tokenInfo = resolveToken(opts.asset, chainKey);
|
|
56552
|
+
if (tokenInfo) {
|
|
56553
|
+
const swapClient = new SwapClient(httpClient);
|
|
56554
|
+
const price = await swapClient.getTokenPrice(chainKey, tokenInfo.address);
|
|
56555
|
+
if (price && price > 0) {
|
|
56556
|
+
const usdValue = parsedAmount * price;
|
|
56557
|
+
if (usdValue > maxUsd) {
|
|
56558
|
+
throw new TransferError(
|
|
56559
|
+
"USD_LIMIT_EXCEEDED",
|
|
56560
|
+
`Payout value ~$${usdValue.toFixed(2)} USD exceeds safety limit of $${maxUsd}. Re-run with --max-usd ${Math.ceil(usdValue)} or --skip-safety-check to proceed.`
|
|
56561
|
+
);
|
|
56562
|
+
}
|
|
56563
|
+
if (usdValue > 1e3) {
|
|
56564
|
+
warn(`High-value off-ramp payout: ~$${usdValue.toFixed(2)} USD`);
|
|
56565
|
+
}
|
|
56566
|
+
}
|
|
56567
|
+
}
|
|
56568
|
+
} catch (err) {
|
|
56569
|
+
if (err instanceof CommandError) throw err;
|
|
56570
|
+
warn(
|
|
56571
|
+
"Could not verify payout USD value (price lookup failed). Use --skip-safety-check to suppress."
|
|
56572
|
+
);
|
|
56573
|
+
}
|
|
56574
|
+
}
|
|
56575
|
+
const address = await provider.getAddress(chainKey);
|
|
56576
|
+
warn(
|
|
56577
|
+
`About to send ${opts.amount} ${opts.asset} from ${address} to third-party off-ramp deposit address ${opts.to}${opts.memo ? ` (memo: ${opts.memo})` : ""}. This address is not under your control \u2014 verify it matches what the provider displayed before continuing.`
|
|
56578
|
+
);
|
|
56579
|
+
const client = provider.getBlockchainClient();
|
|
56580
|
+
const hdWallet = provider.getSigner().getHDWallet();
|
|
56581
|
+
let result;
|
|
56582
|
+
try {
|
|
56583
|
+
result = await client.transfer({
|
|
56584
|
+
assetId: opts.asset,
|
|
56585
|
+
to: opts.to,
|
|
56586
|
+
amount: opts.amount,
|
|
56587
|
+
decimals,
|
|
56588
|
+
memo: opts.memo,
|
|
56589
|
+
hdWallet
|
|
56590
|
+
});
|
|
56591
|
+
} catch (err) {
|
|
56592
|
+
mapBroadcastError(err);
|
|
56593
|
+
}
|
|
56594
|
+
const explorerUrl = getExplorerTxUrl(result.chainKey, result.hash);
|
|
56595
|
+
success("Off-ramp payout broadcast!");
|
|
56596
|
+
if (!opts.json) {
|
|
56597
|
+
info(
|
|
56598
|
+
"Fiat will be credited by the provider separately (typically 1\u20133 business days for bank transfer, faster for card). There is no on-chain signal for that step \u2014 watch for the provider's email/SMS confirmation."
|
|
56599
|
+
);
|
|
56600
|
+
}
|
|
56601
|
+
output(
|
|
56602
|
+
{
|
|
56603
|
+
hash: result.hash,
|
|
56604
|
+
chain: result.chainKey,
|
|
56605
|
+
from: address,
|
|
56606
|
+
to: opts.to,
|
|
56607
|
+
amount: opts.amount,
|
|
56608
|
+
asset: opts.asset,
|
|
56609
|
+
memo: opts.memo,
|
|
56610
|
+
quoteId: opts.quoteId,
|
|
56611
|
+
explorer: explorerUrl
|
|
56612
|
+
},
|
|
56613
|
+
opts.json
|
|
56614
|
+
);
|
|
56615
|
+
} catch (err) {
|
|
56616
|
+
handleCommandError(err, opts.json, "Sell confirm failed");
|
|
56617
|
+
}
|
|
56618
|
+
safeExit();
|
|
56619
|
+
});
|
|
56620
|
+
}
|
|
56621
|
+
|
|
55919
56622
|
// src/commands/automate.ts
|
|
55920
56623
|
init_dist2();
|
|
55921
56624
|
init_dist();
|
|
55922
56625
|
function parseInterval(value) {
|
|
55923
|
-
const match = value.match(/^(\d+(?:\.\d+)?)\s*(s|m|h)?$/i);
|
|
56626
|
+
const match = value.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
|
|
55924
56627
|
if (!match) return null;
|
|
55925
56628
|
const num = parseFloat(match[1]);
|
|
55926
56629
|
if (isNaN(num)) return null;
|
|
@@ -55928,9 +56631,11 @@ function parseInterval(value) {
|
|
|
55928
56631
|
if (unit === "s") return num * 1e3;
|
|
55929
56632
|
if (unit === "m") return num * 6e4;
|
|
55930
56633
|
if (unit === "h") return num * 36e5;
|
|
56634
|
+
if (unit === "d") return num * 864e5;
|
|
55931
56635
|
return num * 1e3;
|
|
55932
56636
|
}
|
|
55933
56637
|
function formatInterval(ms3) {
|
|
56638
|
+
if (ms3 >= 864e5 && ms3 % 864e5 === 0) return `${ms3 / 864e5}d`;
|
|
55934
56639
|
if (ms3 >= 36e5) return `${ms3 / 36e5}h`;
|
|
55935
56640
|
if (ms3 >= 6e4) return `${ms3 / 6e4}m`;
|
|
55936
56641
|
return `${ms3 / 1e3}s`;
|
|
@@ -55942,8 +56647,10 @@ function formatRelativeTime(diffMs) {
|
|
|
55942
56647
|
value = `${Math.round(absDiff / 1e3)}s`;
|
|
55943
56648
|
} else if (absDiff < 36e5) {
|
|
55944
56649
|
value = `${Math.round(absDiff / 6e4)}m`;
|
|
55945
|
-
} else {
|
|
56650
|
+
} else if (absDiff < 864e5) {
|
|
55946
56651
|
value = `${Math.round(absDiff / 36e5)}h`;
|
|
56652
|
+
} else {
|
|
56653
|
+
value = `${Math.round(absDiff / 864e5)}d`;
|
|
55947
56654
|
}
|
|
55948
56655
|
if (diffMs <= 0) return "overdue";
|
|
55949
56656
|
return `in ${value}`;
|
|
@@ -55957,7 +56664,7 @@ function formatTimeAgo(isoDate) {
|
|
|
55957
56664
|
}
|
|
55958
56665
|
function registerAutomateCommand(program3) {
|
|
55959
56666
|
const automate = program3.command("automate").description("Manage DCA and limit order automations");
|
|
55960
|
-
automate.command("add").description("Create a DCA or limit order automation").requiredOption("--from <token>", "Source token (e.g., USDC)").requiredOption("--to <token>", "Destination token (e.g., ETH)").option("--chain <chain>", "Chain", "ethereum").requiredOption("--amount <n>", "Amount of source token per run").option("--interval <duration>", "DCA interval \u2014 e.g. 30s, 5m, 1h (creates DCA automation)").option("--price <usd>", "Target price in USD (creates limit order)").option("--condition <cond>", "Price condition: above or below", "below").option("--max-runs <n>", "Stop after N executions").option("--expires <date>", "Expiry date (ISO 8601, e.g. 2026-04-01)").option("--json", "JSON output").action(async (opts) => {
|
|
56667
|
+
automate.command("add").description("Create a DCA or limit order automation").requiredOption("--from <token>", "Source token (e.g., USDC)").requiredOption("--to <token>", "Destination token (e.g., ETH)").option("--chain <chain>", "Chain", "ethereum").requiredOption("--amount <n>", "Amount of source token per run").option("--interval <duration>", "DCA interval \u2014 e.g. 30s, 5m, 1h, 7d (creates DCA automation)").option("--price <usd>", "Target price in USD (creates limit order)").option("--condition <cond>", "Price condition: above or below", "below").option("--max-runs <n>", "Stop after N executions").option("--expires <date>", "Expiry date (ISO 8601, e.g. 2026-04-01)").option("--json", "JSON output").action(async (opts) => {
|
|
55961
56668
|
try {
|
|
55962
56669
|
if (opts.interval && opts.price) {
|
|
55963
56670
|
throw new AutomationError(
|
|
@@ -56326,7 +57033,7 @@ var crypto6 = __toESM(require("crypto"));
|
|
|
56326
57033
|
// package.json
|
|
56327
57034
|
var package_default = {
|
|
56328
57035
|
name: "@trustwallet/cli",
|
|
56329
|
-
version: "0.
|
|
57036
|
+
version: "0.11.0",
|
|
56330
57037
|
description: "TWAK CLI \u2014 command-line interface for Trust Wallet Agent Kit",
|
|
56331
57038
|
main: "dist/index.js",
|
|
56332
57039
|
types: "dist/index.d.ts",
|
|
@@ -56495,13 +57202,14 @@ function ts3() {
|
|
|
56495
57202
|
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
56496
57203
|
}
|
|
56497
57204
|
function parseInterval2(value) {
|
|
56498
|
-
const match = value.match(/^(\d+(?:\.\d+)?)\s*(s|m|h)?$/i);
|
|
57205
|
+
const match = value.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/i);
|
|
56499
57206
|
if (!match) return null;
|
|
56500
57207
|
const num = parseFloat(match[1]);
|
|
56501
57208
|
if (isNaN(num)) return null;
|
|
56502
57209
|
const unit = (match[2] || "s").toLowerCase();
|
|
56503
57210
|
if (unit === "m") return num * 60;
|
|
56504
57211
|
if (unit === "h") return num * 3600;
|
|
57212
|
+
if (unit === "d") return num * 86400;
|
|
56505
57213
|
return num;
|
|
56506
57214
|
}
|
|
56507
57215
|
function registerWatchCommand(program3) {
|
|
@@ -56509,7 +57217,7 @@ function registerWatchCommand(program3) {
|
|
|
56509
57217
|
"Background watcher \u2014 polls prices to trigger alerts and execute DCA/limit automations"
|
|
56510
57218
|
).option(
|
|
56511
57219
|
"--interval <duration>",
|
|
56512
|
-
"Poll interval \u2014 e.g. 30s, 5m, 1h, or plain number (seconds)",
|
|
57220
|
+
"Poll interval \u2014 e.g. 30s, 5m, 1h, 1d, or plain number (seconds)",
|
|
56513
57221
|
"60"
|
|
56514
57222
|
).option("--password <password>", "Wallet password for executing swaps").option("--auto-lock <minutes>", "Auto-lock wallet after N minutes of inactivity").option("--dry-run", "Check conditions without executing swaps").option("--json", "JSON output per tick").action(async (opts) => {
|
|
56515
57223
|
let client;
|
|
@@ -56836,6 +57544,7 @@ registerErc20Command(program2);
|
|
|
56836
57544
|
registerAlertCommand(program2);
|
|
56837
57545
|
registerQueryCommand(program2);
|
|
56838
57546
|
registerServeCommand(program2);
|
|
57547
|
+
registerOnrampCommand(program2);
|
|
56839
57548
|
registerAutomateCommand(program2);
|
|
56840
57549
|
registerX402Command(program2);
|
|
56841
57550
|
registerTelemetryCommand(program2);
|