@t2000/cli 10.27.1 → 10.28.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/index.js +100 -91
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -28274,28 +28274,6 @@ var SUPPORTED_ASSETS = {
|
|
|
28274
28274
|
}
|
|
28275
28275
|
};
|
|
28276
28276
|
var STABLE_ASSETS = ["USDC", "USDsui"];
|
|
28277
|
-
var OPERATION_ASSETS = {
|
|
28278
|
-
send: ["USDC", "USDsui", "SUI"],
|
|
28279
|
-
swap: "*"
|
|
28280
|
-
};
|
|
28281
|
-
function isAllowedAsset(op, asset) {
|
|
28282
|
-
const allowed = OPERATION_ASSETS[op];
|
|
28283
|
-
if (allowed === "*") return true;
|
|
28284
|
-
const target = asset.toLowerCase();
|
|
28285
|
-
return allowed.some((a) => a.toLowerCase() === target);
|
|
28286
|
-
}
|
|
28287
|
-
function assertAllowedAsset(op, asset) {
|
|
28288
|
-
if (!asset) return;
|
|
28289
|
-
if (!isAllowedAsset(op, asset)) {
|
|
28290
|
-
const allowed = OPERATION_ASSETS[op];
|
|
28291
|
-
const list = Array.isArray(allowed) ? allowed.join(", ") : "any";
|
|
28292
|
-
const swapHint = op === "send" ? " Swap to USDC or USDsui first, or send SUI." : "";
|
|
28293
|
-
throw new T2000Error(
|
|
28294
|
-
"INVALID_ASSET",
|
|
28295
|
-
`${op} only supports ${list}. Cannot use ${asset}.${swapHint}`
|
|
28296
|
-
);
|
|
28297
|
-
}
|
|
28298
|
-
}
|
|
28299
28277
|
var GASLESS_STABLE_TYPES = {
|
|
28300
28278
|
USDC: SUPPORTED_ASSETS.USDC.type,
|
|
28301
28279
|
USDsui: SUPPORTED_ASSETS.USDsui.type
|
|
@@ -28899,19 +28877,42 @@ for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
|
28899
28877
|
ASSET_LOOKUP.set(info.displayName.toUpperCase(), key);
|
|
28900
28878
|
}
|
|
28901
28879
|
}
|
|
28880
|
+
init_token_registry();
|
|
28902
28881
|
init_preflight();
|
|
28903
|
-
function
|
|
28882
|
+
function classifySendAsset(asset) {
|
|
28883
|
+
const coinType = resolveTokenType(asset.trim());
|
|
28884
|
+
if (!coinType) return null;
|
|
28885
|
+
let normalized;
|
|
28904
28886
|
try {
|
|
28905
|
-
|
|
28906
|
-
} catch
|
|
28907
|
-
return
|
|
28887
|
+
normalized = normalizeStructTag(coinType);
|
|
28888
|
+
} catch {
|
|
28889
|
+
return null;
|
|
28890
|
+
}
|
|
28891
|
+
if (normalized === normalizeStructTag(GASLESS_STABLE_TYPES.USDC)) {
|
|
28892
|
+
return { kind: "gasless-stable", symbol: "USDC", coinType: GASLESS_STABLE_TYPES.USDC };
|
|
28893
|
+
}
|
|
28894
|
+
if (normalized === normalizeStructTag(GASLESS_STABLE_TYPES.USDsui)) {
|
|
28895
|
+
return { kind: "gasless-stable", symbol: "USDsui", coinType: GASLESS_STABLE_TYPES.USDsui };
|
|
28896
|
+
}
|
|
28897
|
+
if (normalized === normalizeStructTag(SUI_TYPE)) {
|
|
28898
|
+
return { kind: "sui", symbol: "SUI", coinType: SUI_TYPE };
|
|
28899
|
+
}
|
|
28900
|
+
return { kind: "coin", symbol: resolveSymbol(coinType), coinType };
|
|
28901
|
+
}
|
|
28902
|
+
function invalidSendAssetMessage(asset) {
|
|
28903
|
+
return `Unknown asset "${asset}". Use a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE).`;
|
|
28904
|
+
}
|
|
28905
|
+
function preflightSend(input) {
|
|
28906
|
+
const cls = classifySendAsset(input.asset);
|
|
28907
|
+
if (!cls) {
|
|
28908
|
+
return preflightFail("INVALID_ASSET", invalidSendAssetMessage(input.asset));
|
|
28908
28909
|
}
|
|
28909
28910
|
const amountCheck = checkPositiveAmount(input.amount);
|
|
28910
28911
|
if (!amountCheck.valid) return amountCheck;
|
|
28911
|
-
if (
|
|
28912
|
+
if (cls.kind === "gasless-stable" && input.amount < GASLESS_MIN_STABLE_AMOUNT) {
|
|
28912
28913
|
return preflightFail(
|
|
28913
28914
|
"INVALID_AMOUNT",
|
|
28914
|
-
`Minimum gasless transfer is ${GASLESS_MIN_STABLE_AMOUNT} ${
|
|
28915
|
+
`Minimum gasless transfer is ${GASLESS_MIN_STABLE_AMOUNT} ${cls.symbol}. Got ${input.amount}.`
|
|
28915
28916
|
);
|
|
28916
28917
|
}
|
|
28917
28918
|
const addressCheck = checkSuiAddress(input.to);
|
|
@@ -28928,42 +28929,44 @@ async function buildSendTx({
|
|
|
28928
28929
|
const pf = preflightSend({ to, amount, asset });
|
|
28929
28930
|
if (!pf.valid) throw new T2000Error(pf.code, pf.error);
|
|
28930
28931
|
const recipient = validateAddress(to);
|
|
28931
|
-
const
|
|
28932
|
-
|
|
28933
|
-
const rawAmount = displayToRaw(amount,
|
|
28932
|
+
const cls = classifySendAsset(asset);
|
|
28933
|
+
const decimals = cls.kind === "gasless-stable" || cls.kind === "sui" ? SUPPORTED_ASSETS[cls.symbol].decimals : await resolveCoinDecimals(client, cls.coinType);
|
|
28934
|
+
const rawAmount = displayToRaw(amount, decimals);
|
|
28934
28935
|
const tx = new Transaction();
|
|
28935
28936
|
tx.setSender(address);
|
|
28936
|
-
const balanceResp = await client.core.getBalance({ owner: address, coinType:
|
|
28937
|
+
const balanceResp = await client.core.getBalance({ owner: address, coinType: cls.coinType });
|
|
28937
28938
|
const totalBalance = BigInt(balanceResp.balance.balance);
|
|
28938
28939
|
if (totalBalance < rawAmount) {
|
|
28939
|
-
throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${
|
|
28940
|
-
available: Number(totalBalance) / 10 **
|
|
28940
|
+
throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${cls.symbol} balance`, {
|
|
28941
|
+
available: Number(totalBalance) / 10 ** decimals,
|
|
28941
28942
|
required: amount
|
|
28942
28943
|
});
|
|
28943
28944
|
}
|
|
28944
|
-
if (
|
|
28945
|
-
const rawFloor = displayToRaw(GASLESS_MIN_STABLE_AMOUNT, assetInfo.decimals);
|
|
28946
|
-
const remainder = totalBalance - rawAmount;
|
|
28947
|
-
if (remainder > 0n && remainder < rawFloor) {
|
|
28948
|
-
const total = Number(totalBalance) / 10 ** assetInfo.decimals;
|
|
28949
|
-
throw new T2000Error(
|
|
28950
|
-
"INVALID_AMOUNT",
|
|
28951
|
-
`Gasless ${asset} transfers must send the entire balance or leave at least ${GASLESS_MIN_STABLE_AMOUNT} ${asset}. Sending ${amount} of ${total} leaves ${(total - amount).toFixed(assetInfo.decimals)}. Send ${total} (everything) or at most ${(total - GASLESS_MIN_STABLE_AMOUNT).toFixed(assetInfo.decimals)}.`,
|
|
28952
|
-
{ available: total, required: amount }
|
|
28953
|
-
);
|
|
28954
|
-
}
|
|
28955
|
-
}
|
|
28956
|
-
if (asset === "SUI") {
|
|
28945
|
+
if (cls.kind === "sui") {
|
|
28957
28946
|
const [sendCoin] = tx.splitCoins(tx.gas, [rawAmount]);
|
|
28958
28947
|
tx.transferObjects([sendCoin], recipient);
|
|
28959
28948
|
return tx;
|
|
28960
28949
|
}
|
|
28961
|
-
|
|
28950
|
+
if (cls.kind === "coin") {
|
|
28951
|
+
const sendCoin = coinWithBalance({ type: cls.coinType, balance: rawAmount })(tx);
|
|
28952
|
+
tx.transferObjects([sendCoin], recipient);
|
|
28953
|
+
return tx;
|
|
28954
|
+
}
|
|
28955
|
+
const rawFloor = displayToRaw(GASLESS_MIN_STABLE_AMOUNT, decimals);
|
|
28956
|
+
const remainder = totalBalance - rawAmount;
|
|
28957
|
+
if (remainder > 0n && remainder < rawFloor) {
|
|
28958
|
+
const total = Number(totalBalance) / 10 ** decimals;
|
|
28959
|
+
throw new T2000Error(
|
|
28960
|
+
"INVALID_AMOUNT",
|
|
28961
|
+
`Gasless ${cls.symbol} transfers must send the entire balance or leave at least ${GASLESS_MIN_STABLE_AMOUNT} ${cls.symbol}. Sending ${amount} of ${total} leaves ${(total - amount).toFixed(decimals)}. Send ${total} (everything) or at most ${(total - GASLESS_MIN_STABLE_AMOUNT).toFixed(decimals)}.`,
|
|
28962
|
+
{ available: total, required: amount }
|
|
28963
|
+
);
|
|
28964
|
+
}
|
|
28962
28965
|
tx.moveCall({
|
|
28963
28966
|
target: "0x2::balance::send_funds",
|
|
28964
|
-
typeArguments: [coinType],
|
|
28967
|
+
typeArguments: [cls.coinType],
|
|
28965
28968
|
arguments: [
|
|
28966
|
-
tx.balance({ type: coinType, balance: rawAmount }),
|
|
28969
|
+
tx.balance({ type: cls.coinType, balance: rawAmount }),
|
|
28967
28970
|
tx.pure.address(recipient)
|
|
28968
28971
|
]
|
|
28969
28972
|
});
|
|
@@ -30191,11 +30194,21 @@ var T2000 = class _T2000 extends import_index2.default {
|
|
|
30191
30194
|
const rawAmount = BigInt(Math.floor(params.amount * 10 ** fromDecimals));
|
|
30192
30195
|
let route;
|
|
30193
30196
|
if (params.serializedRoute) {
|
|
30194
|
-
|
|
30197
|
+
let s = params.serializedRoute;
|
|
30198
|
+
const nested = s.serializedRoute;
|
|
30199
|
+
if (nested && typeof nested === "object" && typeof nested.fromCoinType === "string" && nested.routerData) {
|
|
30200
|
+
s = nested;
|
|
30201
|
+
}
|
|
30202
|
+
if (typeof s.fromCoinType !== "string" || typeof s.toCoinType !== "string") {
|
|
30203
|
+
throw new T2000Error(
|
|
30204
|
+
"SWAP_ROUTE_MISMATCH",
|
|
30205
|
+
"serializedRoute is not the object t2000 quoted \u2014 it is missing fromCoinType/toCoinType. Pass quote.serializedRoute exactly as the quote returned it (not the whole quote response), or omit serializedRoute to discover a fresh route."
|
|
30206
|
+
);
|
|
30207
|
+
}
|
|
30195
30208
|
if (!verifyCetusRouteCoinMatch2(s, { fromCoinType: fromType, toCoinType: toType })) {
|
|
30196
30209
|
throw new T2000Error(
|
|
30197
30210
|
"SWAP_ROUTE_MISMATCH",
|
|
30198
|
-
`The quoted route is for ${s.fromCoinType} -> ${s.toCoinType}, not ${
|
|
30211
|
+
`The quoted route is for ${s.fromCoinType} -> ${s.toCoinType}, not ${fromType} -> ${toType}. Re-quote and pass the new serializedRoute.`
|
|
30199
30212
|
);
|
|
30200
30213
|
}
|
|
30201
30214
|
if (s.byAmountIn !== byAmountIn || BigInt(s.amountIn) !== rawAmount) {
|
|
@@ -30339,18 +30352,17 @@ var T2000 = class _T2000 extends import_index2.default {
|
|
|
30339
30352
|
/**
|
|
30340
30353
|
* Send `amount` of `asset` to `to` (hex address or SuiNS name).
|
|
30341
30354
|
*
|
|
30342
|
-
* [
|
|
30355
|
+
* [S.957 — 2026-08-08] `asset` accepts **any held coin type** — a
|
|
30356
|
+
* registry symbol (`USDC`, `MANIFEST`) or a full `0x…::module::TYPE`.
|
|
30357
|
+
* Unresolvable strings throw `INVALID_ASSET`; empty balances throw
|
|
30358
|
+
* `INSUFFICIENT_BALANCE`.
|
|
30343
30359
|
*
|
|
30344
|
-
* **Breaking changes from v3.x:**
|
|
30345
|
-
* - `asset` is now REQUIRED (no implicit `?? 'USDC'` default). Callers
|
|
30346
|
-
* must specify `'USDC' | 'USDsui' | 'SUI'`. Sending `'USDT'` /
|
|
30347
|
-
* `'USDe'` / `'WAL'` / `'ETH'` / `'NAVX'` / `'GOLD'` now errors
|
|
30348
|
-
* with `INVALID_ASSET` — swap to a stable first.
|
|
30349
30360
|
* - USDC + USDsui builds go through `SuiGrpcClient` so the gRPC build
|
|
30350
30361
|
* resolver auto-detects `0x2::balance::send_funds` eligibility and
|
|
30351
|
-
* zeros gas at simulate time
|
|
30352
|
-
*
|
|
30353
|
-
*
|
|
30362
|
+
* zeros gas at simulate time — **gasless sends from a zero-SUI
|
|
30363
|
+
* wallet.** They remain the ONLY gasless assets.
|
|
30364
|
+
* - SUI and every other coin type are gas-paid (the sender needs SUI).
|
|
30365
|
+
* - `asset` stays REQUIRED (no implicit `?? 'USDC'` default — v4 rule).
|
|
30354
30366
|
*
|
|
30355
30367
|
* Submission stays on the JSON-RPC client (the rest of the SDK
|
|
30356
30368
|
* expects JSON-RPC for read paths, and Sui's docs explicitly support
|
|
@@ -30361,30 +30373,31 @@ var T2000 = class _T2000 extends import_index2.default {
|
|
|
30361
30373
|
if (!asset) {
|
|
30362
30374
|
throw new T2000Error(
|
|
30363
30375
|
"INVALID_ASSET",
|
|
30364
|
-
"send() requires an explicit asset. Use
|
|
30376
|
+
"send() requires an explicit asset. Use a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE)."
|
|
30365
30377
|
);
|
|
30366
30378
|
}
|
|
30367
|
-
|
|
30368
|
-
|
|
30379
|
+
const cls = classifySendAsset(asset);
|
|
30380
|
+
if (!cls) {
|
|
30381
|
+
throw new T2000Error("INVALID_ASSET", invalidSendAssetMessage(asset));
|
|
30382
|
+
}
|
|
30369
30383
|
this.limits.assert({
|
|
30370
30384
|
operation: "send",
|
|
30371
|
-
amountUsd: approxUsdValue(
|
|
30385
|
+
amountUsd: approxUsdValue(cls.symbol, params.amount) ?? 0,
|
|
30372
30386
|
force: params.force
|
|
30373
30387
|
});
|
|
30374
30388
|
const resolved = await this.resolveRecipient(params.to);
|
|
30375
30389
|
const sendAmount = params.amount;
|
|
30376
30390
|
const sendTo = resolved.address;
|
|
30377
|
-
const
|
|
30378
|
-
const buildClient = useGrpc ? getSuiGrpcClient() : void 0;
|
|
30391
|
+
const buildClient = cls.kind === "gasless-stable" ? getSuiGrpcClient() : void 0;
|
|
30379
30392
|
const gasResult = await executeTx(
|
|
30380
30393
|
this.client,
|
|
30381
30394
|
this._signer,
|
|
30382
|
-
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset
|
|
30395
|
+
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset }),
|
|
30383
30396
|
{ buildClient }
|
|
30384
30397
|
);
|
|
30385
|
-
this.limits.record(approxUsdValue(
|
|
30398
|
+
this.limits.record(approxUsdValue(cls.symbol, sendAmount) ?? 0);
|
|
30386
30399
|
const balance = await this.balance();
|
|
30387
|
-
this.emitBalanceChange(
|
|
30400
|
+
this.emitBalanceChange(cls.symbol, sendAmount, "send", gasResult.digest);
|
|
30388
30401
|
return {
|
|
30389
30402
|
success: true,
|
|
30390
30403
|
tx: gasResult.digest,
|
|
@@ -34094,20 +34107,19 @@ function registerStatus(program3) {
|
|
|
34094
34107
|
|
|
34095
34108
|
// src/commands/send.ts
|
|
34096
34109
|
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
34097
|
-
var
|
|
34098
|
-
var ACCEPTED_ASSETS_LIST = ACCEPTED_ASSETS.join(", ");
|
|
34110
|
+
var ASSET_HINT = "a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE)";
|
|
34099
34111
|
function parseSendArgs(args) {
|
|
34100
34112
|
const filtered = args.filter((a) => a.toLowerCase() !== "to");
|
|
34101
34113
|
if (filtered.length < 2) {
|
|
34102
34114
|
throw new Error(
|
|
34103
34115
|
`Usage: t2 send <amount> <asset> <recipient>
|
|
34104
|
-
asset
|
|
34116
|
+
asset is ${ASSET_HINT}
|
|
34105
34117
|
recipient can be a 0x address or SuiNS name (alice.sui, alice.audric.sui)`
|
|
34106
34118
|
);
|
|
34107
34119
|
}
|
|
34108
34120
|
if (filtered.length === 2) {
|
|
34109
34121
|
throw new Error(
|
|
34110
|
-
`Missing required <asset> argument. Use
|
|
34122
|
+
`Missing required <asset> argument. Use ${ASSET_HINT}. Example: t2 send ${filtered[0]} USDC ${filtered[1]}`
|
|
34111
34123
|
);
|
|
34112
34124
|
}
|
|
34113
34125
|
const amount = parseFloat(filtered[0]);
|
|
@@ -34115,36 +34127,34 @@ function parseSendArgs(args) {
|
|
|
34115
34127
|
throw new Error(`Amount must be a positive number (got "${filtered[0]}").`);
|
|
34116
34128
|
}
|
|
34117
34129
|
const candidate = filtered[1];
|
|
34118
|
-
const
|
|
34119
|
-
if (!
|
|
34130
|
+
const cls = classifySendAsset(candidate);
|
|
34131
|
+
if (!cls) {
|
|
34120
34132
|
throw new Error(
|
|
34121
|
-
`
|
|
34133
|
+
`Unknown asset "${candidate}". Use ${ASSET_HINT}. Check your holdings with \`t2 balance\`.`
|
|
34122
34134
|
);
|
|
34123
34135
|
}
|
|
34124
34136
|
const recipient = filtered[2];
|
|
34125
34137
|
if (!recipient) {
|
|
34126
34138
|
throw new Error(`Missing recipient. Usage: t2 send <amount> <asset> <recipient>.`);
|
|
34127
34139
|
}
|
|
34128
|
-
|
|
34129
|
-
}
|
|
34130
|
-
function normalizeAssetSymbol(input) {
|
|
34131
|
-
const lower = input.toLowerCase();
|
|
34132
|
-
if (lower === "usdc") return "USDC";
|
|
34133
|
-
if (lower === "usdsui") return "USDsui";
|
|
34134
|
-
if (lower === "sui") return "SUI";
|
|
34135
|
-
return void 0;
|
|
34140
|
+
const asset = cls.kind === "gasless-stable" || cls.kind === "sui" ? cls.symbol : candidate;
|
|
34141
|
+
return { amount, asset, recipient };
|
|
34136
34142
|
}
|
|
34137
34143
|
function registerSend(program3) {
|
|
34138
34144
|
program3.command("send").argument("<amount>", "Amount of <asset> to send (denominated in asset units, NOT USD)").argument(
|
|
34139
34145
|
"[args...]",
|
|
34140
|
-
'Asset (USDC
|
|
34141
|
-
).description(
|
|
34146
|
+
'Asset (registry symbol like USDC / MANIFEST, or a full 0x\u2026::module::TYPE coin type), optional "to" keyword, and recipient (0x address or SuiNS name like alice.sui)'
|
|
34147
|
+
).description(
|
|
34148
|
+
"Send any held token. USDC + USDsui are gasless; everything else needs SUI for gas."
|
|
34149
|
+
).option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--force", "Override spending limits for this call (see `t2 limit`)").addHelpText(
|
|
34142
34150
|
"after",
|
|
34143
34151
|
`
|
|
34144
34152
|
Examples:
|
|
34145
34153
|
$ t2 send 5 USDC 0xabc\u2026 Send 5 USDC (gasless) to a hex address
|
|
34146
34154
|
$ t2 send 5 USDsui alice.sui Send 5 USDsui (gasless) to a SuiNS name
|
|
34147
34155
|
$ t2 send 0.1 SUI alice.audric.sui Send 0.1 SUI (gas required) to a SuiNS subname
|
|
34156
|
+
$ t2 send 10 MANIFEST 0xabc\u2026 Send any held token (gas required)
|
|
34157
|
+
$ t2 send 10 0xc466\u2026::manifest::MANIFEST 0xabc\u2026 Full coin type works too
|
|
34148
34158
|
`
|
|
34149
34159
|
).action(async (amount, args, opts) => {
|
|
34150
34160
|
try {
|
|
@@ -34153,9 +34163,8 @@ Examples:
|
|
|
34153
34163
|
const result = await agent.send({
|
|
34154
34164
|
to: recipient,
|
|
34155
34165
|
amount: parsedAmount,
|
|
34156
|
-
//
|
|
34157
|
-
//
|
|
34158
|
-
// `assertAllowedAsset('send', …)` at runtime.
|
|
34166
|
+
// [S.957] Any resolvable asset string — the SDK re-resolves via
|
|
34167
|
+
// `classifySendAsset` (same rule the parser used).
|
|
34159
34168
|
asset,
|
|
34160
34169
|
force: opts.force
|
|
34161
34170
|
});
|
|
@@ -34168,7 +34177,7 @@ Examples:
|
|
|
34168
34177
|
return;
|
|
34169
34178
|
}
|
|
34170
34179
|
const displayTo = result.suinsName ? `${result.suinsName} ${import_picocolors6.default.dim(`(${truncateAddress(result.to)})`)}` : truncateAddress(result.to);
|
|
34171
|
-
const amountDisplay = asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${
|
|
34180
|
+
const amountDisplay = asset === "USDC" || asset === "USDsui" ? `${formatUsd(result.amount)} ${asset}` : asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${result.amount} ${asset}`;
|
|
34172
34181
|
printBlank();
|
|
34173
34182
|
printSuccess(`Sent ${amountDisplay} \u2192 ${displayTo}`);
|
|
34174
34183
|
if (result.gasCost === 0) {
|