@t2000/cli 10.27.2 → 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 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 preflightSend(input) {
28882
+ function classifySendAsset(asset) {
28883
+ const coinType = resolveTokenType(asset.trim());
28884
+ if (!coinType) return null;
28885
+ let normalized;
28904
28886
  try {
28905
- assertAllowedAsset("send", input.asset);
28906
- } catch (e) {
28907
- return preflightFail("INVALID_ASSET", e.message);
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 ((input.asset === "USDC" || input.asset === "USDsui") && input.amount < GASLESS_MIN_STABLE_AMOUNT) {
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} ${input.asset}. Got ${input.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 assetInfo = SUPPORTED_ASSETS[asset];
28932
- if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
28933
- const rawAmount = displayToRaw(amount, assetInfo.decimals);
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: assetInfo.type });
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 ${asset} balance`, {
28940
- available: Number(totalBalance) / 10 ** assetInfo.decimals,
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 (asset === "USDC" || asset === "USDsui") {
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
- const coinType = GASLESS_STABLE_TYPES[asset];
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
  });
@@ -30349,18 +30352,17 @@ var T2000 = class _T2000 extends import_index2.default {
30349
30352
  /**
30350
30353
  * Send `amount` of `asset` to `to` (hex address or SuiNS name).
30351
30354
  *
30352
- * [v4.0 Phase A Day 2SPEC_AGENT_WALLET_GREENFIELD §A]
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`.
30353
30359
  *
30354
- * **Breaking changes from v3.x:**
30355
- * - `asset` is now REQUIRED (no implicit `?? 'USDC'` default). Callers
30356
- * must specify `'USDC' | 'USDsui' | 'SUI'`. Sending `'USDT'` /
30357
- * `'USDe'` / `'WAL'` / `'ETH'` / `'NAVX'` / `'GOLD'` now errors
30358
- * with `INVALID_ASSET` — swap to a stable first.
30359
30360
  * - USDC + USDsui builds go through `SuiGrpcClient` so the gRPC build
30360
30361
  * resolver auto-detects `0x2::balance::send_funds` eligibility and
30361
- * zeros gas at simulate time. Result: **gasless USDC / USDsui sends
30362
- * from a zero-SUI wallet.** SUI sends stay on the standard gas-paid
30363
- * path.
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).
30364
30366
  *
30365
30367
  * Submission stays on the JSON-RPC client (the rest of the SDK
30366
30368
  * expects JSON-RPC for read paths, and Sui's docs explicitly support
@@ -30371,30 +30373,31 @@ var T2000 = class _T2000 extends import_index2.default {
30371
30373
  if (!asset) {
30372
30374
  throw new T2000Error(
30373
30375
  "INVALID_ASSET",
30374
- "send() requires an explicit asset. Use one of: USDC, USDsui, SUI."
30376
+ "send() requires an explicit asset. Use a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE)."
30375
30377
  );
30376
30378
  }
30377
- assertAllowedAsset("send", asset);
30378
- const sendableAsset = asset;
30379
+ const cls = classifySendAsset(asset);
30380
+ if (!cls) {
30381
+ throw new T2000Error("INVALID_ASSET", invalidSendAssetMessage(asset));
30382
+ }
30379
30383
  this.limits.assert({
30380
30384
  operation: "send",
30381
- amountUsd: approxUsdValue(sendableAsset, params.amount) ?? 0,
30385
+ amountUsd: approxUsdValue(cls.symbol, params.amount) ?? 0,
30382
30386
  force: params.force
30383
30387
  });
30384
30388
  const resolved = await this.resolveRecipient(params.to);
30385
30389
  const sendAmount = params.amount;
30386
30390
  const sendTo = resolved.address;
30387
- const useGrpc = sendableAsset === "USDC" || sendableAsset === "USDsui";
30388
- const buildClient = useGrpc ? getSuiGrpcClient() : void 0;
30391
+ const buildClient = cls.kind === "gasless-stable" ? getSuiGrpcClient() : void 0;
30389
30392
  const gasResult = await executeTx(
30390
30393
  this.client,
30391
30394
  this._signer,
30392
- () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset: sendableAsset }),
30395
+ () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset }),
30393
30396
  { buildClient }
30394
30397
  );
30395
- this.limits.record(approxUsdValue(sendableAsset, sendAmount) ?? 0);
30398
+ this.limits.record(approxUsdValue(cls.symbol, sendAmount) ?? 0);
30396
30399
  const balance = await this.balance();
30397
- this.emitBalanceChange(sendableAsset, sendAmount, "send", gasResult.digest);
30400
+ this.emitBalanceChange(cls.symbol, sendAmount, "send", gasResult.digest);
30398
30401
  return {
30399
30402
  success: true,
30400
30403
  tx: gasResult.digest,
@@ -34104,20 +34107,19 @@ function registerStatus(program3) {
34104
34107
 
34105
34108
  // src/commands/send.ts
34106
34109
  var import_picocolors6 = __toESM(require_picocolors(), 1);
34107
- var ACCEPTED_ASSETS = ["USDC", "USDsui", "SUI"];
34108
- 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)";
34109
34111
  function parseSendArgs(args) {
34110
34112
  const filtered = args.filter((a) => a.toLowerCase() !== "to");
34111
34113
  if (filtered.length < 2) {
34112
34114
  throw new Error(
34113
34115
  `Usage: t2 send <amount> <asset> <recipient>
34114
- asset must be one of: ${ACCEPTED_ASSETS_LIST}
34116
+ asset is ${ASSET_HINT}
34115
34117
  recipient can be a 0x address or SuiNS name (alice.sui, alice.audric.sui)`
34116
34118
  );
34117
34119
  }
34118
34120
  if (filtered.length === 2) {
34119
34121
  throw new Error(
34120
- `Missing required <asset> argument. Use one of: ${ACCEPTED_ASSETS_LIST}. Example: t2 send ${filtered[0]} USDC ${filtered[1]}`
34122
+ `Missing required <asset> argument. Use ${ASSET_HINT}. Example: t2 send ${filtered[0]} USDC ${filtered[1]}`
34121
34123
  );
34122
34124
  }
34123
34125
  const amount = parseFloat(filtered[0]);
@@ -34125,36 +34127,34 @@ function parseSendArgs(args) {
34125
34127
  throw new Error(`Amount must be a positive number (got "${filtered[0]}").`);
34126
34128
  }
34127
34129
  const candidate = filtered[1];
34128
- const normalized = normalizeAssetSymbol(candidate);
34129
- if (!normalized) {
34130
+ const cls = classifySendAsset(candidate);
34131
+ if (!cls) {
34130
34132
  throw new Error(
34131
- `Unsupported asset "${candidate}". Use one of: ${ACCEPTED_ASSETS_LIST}. Swap to USDC or USDsui first with \`t2 swap\`, or send SUI.`
34133
+ `Unknown asset "${candidate}". Use ${ASSET_HINT}. Check your holdings with \`t2 balance\`.`
34132
34134
  );
34133
34135
  }
34134
34136
  const recipient = filtered[2];
34135
34137
  if (!recipient) {
34136
34138
  throw new Error(`Missing recipient. Usage: t2 send <amount> <asset> <recipient>.`);
34137
34139
  }
34138
- return { amount, asset: normalized, recipient };
34139
- }
34140
- function normalizeAssetSymbol(input) {
34141
- const lower = input.toLowerCase();
34142
- if (lower === "usdc") return "USDC";
34143
- if (lower === "usdsui") return "USDsui";
34144
- if (lower === "sui") return "SUI";
34145
- return void 0;
34140
+ const asset = cls.kind === "gasless-stable" || cls.kind === "sui" ? cls.symbol : candidate;
34141
+ return { amount, asset, recipient };
34146
34142
  }
34147
34143
  function registerSend(program3) {
34148
34144
  program3.command("send").argument("<amount>", "Amount of <asset> to send (denominated in asset units, NOT USD)").argument(
34149
34145
  "[args...]",
34150
- 'Asset (USDC | USDsui | SUI), optional "to" keyword, and recipient (0x address or SuiNS name like alice.sui)'
34151
- ).description("Send USDC, USDsui, or SUI. USDC + USDsui are gasless (no SUI required).").option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--force", "Override spending limits for this call (see `t2 limit`)").addHelpText(
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(
34152
34150
  "after",
34153
34151
  `
34154
34152
  Examples:
34155
34153
  $ t2 send 5 USDC 0xabc\u2026 Send 5 USDC (gasless) to a hex address
34156
34154
  $ t2 send 5 USDsui alice.sui Send 5 USDsui (gasless) to a SuiNS name
34157
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
34158
34158
  `
34159
34159
  ).action(async (amount, args, opts) => {
34160
34160
  try {
@@ -34163,9 +34163,8 @@ Examples:
34163
34163
  const result = await agent.send({
34164
34164
  to: recipient,
34165
34165
  amount: parsedAmount,
34166
- // The CLI parser already narrowed asset to USDC / USDsui / SUI;
34167
- // the SDK accepts `SupportedAsset` and re-validates via
34168
- // `assertAllowedAsset('send', …)` at runtime.
34166
+ // [S.957] Any resolvable asset string the SDK re-resolves via
34167
+ // `classifySendAsset` (same rule the parser used).
34169
34168
  asset,
34170
34169
  force: opts.force
34171
34170
  });
@@ -34178,7 +34177,7 @@ Examples:
34178
34177
  return;
34179
34178
  }
34180
34179
  const displayTo = result.suinsName ? `${result.suinsName} ${import_picocolors6.default.dim(`(${truncateAddress(result.to)})`)}` : truncateAddress(result.to);
34181
- const amountDisplay = asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${formatUsd(result.amount)} ${asset}`;
34180
+ const amountDisplay = asset === "USDC" || asset === "USDsui" ? `${formatUsd(result.amount)} ${asset}` : asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${result.amount} ${asset}`;
34182
34181
  printBlank();
34183
34182
  printSuccess(`Sent ${amountDisplay} \u2192 ${displayTo}`);
34184
34183
  if (result.gasCost === 0) {