@t2000/cli 10.27.2 → 10.29.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 +147 -96
- 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
|
});
|
|
@@ -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
|
-
* [
|
|
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
|
|
30362
|
-
*
|
|
30363
|
-
*
|
|
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
|
|
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
|
-
|
|
30378
|
-
|
|
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(
|
|
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
|
|
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
|
|
30395
|
+
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset }),
|
|
30393
30396
|
{ buildClient }
|
|
30394
30397
|
);
|
|
30395
|
-
this.limits.record(approxUsdValue(
|
|
30398
|
+
this.limits.record(approxUsdValue(cls.symbol, sendAmount) ?? 0);
|
|
30396
30399
|
const balance = await this.balance();
|
|
30397
|
-
this.emitBalanceChange(
|
|
30400
|
+
this.emitBalanceChange(cls.symbol, sendAmount, "send", gasResult.digest);
|
|
30398
30401
|
return {
|
|
30399
30402
|
success: true,
|
|
30400
30403
|
tx: gasResult.digest,
|
|
@@ -30820,6 +30823,34 @@ async function getJobSpec(base, hash) {
|
|
|
30820
30823
|
}
|
|
30821
30824
|
return content;
|
|
30822
30825
|
}
|
|
30826
|
+
var TITLE_PREFIX_RE = /^title\s*:\s*(.+)$/i;
|
|
30827
|
+
var ENVELOPE_TITLE_MAX = 80;
|
|
30828
|
+
function customHireEnvelope(brief, title, now) {
|
|
30829
|
+
const body2 = brief.trim();
|
|
30830
|
+
let t = title?.trim() ?? "";
|
|
30831
|
+
if (!t) {
|
|
30832
|
+
const first = body2.split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
30833
|
+
const prefixed = TITLE_PREFIX_RE.exec(first);
|
|
30834
|
+
t = (prefixed ? prefixed[1] : first).trim();
|
|
30835
|
+
}
|
|
30836
|
+
if (t.length > ENVELOPE_TITLE_MAX) {
|
|
30837
|
+
t = `${t.slice(0, ENVELOPE_TITLE_MAX - 1).trimEnd()}\u2026`;
|
|
30838
|
+
}
|
|
30839
|
+
return JSON.stringify({
|
|
30840
|
+
type: "t2-acp-custom@1",
|
|
30841
|
+
title: t || "Custom job",
|
|
30842
|
+
brief: body2,
|
|
30843
|
+
createdAtMs: now
|
|
30844
|
+
});
|
|
30845
|
+
}
|
|
30846
|
+
function isCustomHireEnvelope(text) {
|
|
30847
|
+
try {
|
|
30848
|
+
const parsed = JSON.parse(text);
|
|
30849
|
+
return (parsed.type === "t2-acp-custom@1" || parsed.type === "t2-acp-invite@1") && typeof parsed.brief === "string" && parsed.brief.trim().length > 0;
|
|
30850
|
+
} catch {
|
|
30851
|
+
return false;
|
|
30852
|
+
}
|
|
30853
|
+
}
|
|
30823
30854
|
async function fetchJson(url, init) {
|
|
30824
30855
|
const res = await fetch(url, {
|
|
30825
30856
|
method: init?.method ?? "GET",
|
|
@@ -34104,20 +34135,19 @@ function registerStatus(program3) {
|
|
|
34104
34135
|
|
|
34105
34136
|
// src/commands/send.ts
|
|
34106
34137
|
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
34107
|
-
var
|
|
34108
|
-
var ACCEPTED_ASSETS_LIST = ACCEPTED_ASSETS.join(", ");
|
|
34138
|
+
var ASSET_HINT = "a registry symbol (USDC, USDsui, SUI, MANIFEST, \u2026) or a full coin type (0x\u2026::module::TYPE)";
|
|
34109
34139
|
function parseSendArgs(args) {
|
|
34110
34140
|
const filtered = args.filter((a) => a.toLowerCase() !== "to");
|
|
34111
34141
|
if (filtered.length < 2) {
|
|
34112
34142
|
throw new Error(
|
|
34113
34143
|
`Usage: t2 send <amount> <asset> <recipient>
|
|
34114
|
-
asset
|
|
34144
|
+
asset is ${ASSET_HINT}
|
|
34115
34145
|
recipient can be a 0x address or SuiNS name (alice.sui, alice.audric.sui)`
|
|
34116
34146
|
);
|
|
34117
34147
|
}
|
|
34118
34148
|
if (filtered.length === 2) {
|
|
34119
34149
|
throw new Error(
|
|
34120
|
-
`Missing required <asset> argument. Use
|
|
34150
|
+
`Missing required <asset> argument. Use ${ASSET_HINT}. Example: t2 send ${filtered[0]} USDC ${filtered[1]}`
|
|
34121
34151
|
);
|
|
34122
34152
|
}
|
|
34123
34153
|
const amount = parseFloat(filtered[0]);
|
|
@@ -34125,36 +34155,34 @@ function parseSendArgs(args) {
|
|
|
34125
34155
|
throw new Error(`Amount must be a positive number (got "${filtered[0]}").`);
|
|
34126
34156
|
}
|
|
34127
34157
|
const candidate = filtered[1];
|
|
34128
|
-
const
|
|
34129
|
-
if (!
|
|
34158
|
+
const cls = classifySendAsset(candidate);
|
|
34159
|
+
if (!cls) {
|
|
34130
34160
|
throw new Error(
|
|
34131
|
-
`
|
|
34161
|
+
`Unknown asset "${candidate}". Use ${ASSET_HINT}. Check your holdings with \`t2 balance\`.`
|
|
34132
34162
|
);
|
|
34133
34163
|
}
|
|
34134
34164
|
const recipient = filtered[2];
|
|
34135
34165
|
if (!recipient) {
|
|
34136
34166
|
throw new Error(`Missing recipient. Usage: t2 send <amount> <asset> <recipient>.`);
|
|
34137
34167
|
}
|
|
34138
|
-
|
|
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;
|
|
34168
|
+
const asset = cls.kind === "gasless-stable" || cls.kind === "sui" ? cls.symbol : candidate;
|
|
34169
|
+
return { amount, asset, recipient };
|
|
34146
34170
|
}
|
|
34147
34171
|
function registerSend(program3) {
|
|
34148
34172
|
program3.command("send").argument("<amount>", "Amount of <asset> to send (denominated in asset units, NOT USD)").argument(
|
|
34149
34173
|
"[args...]",
|
|
34150
|
-
'Asset (USDC
|
|
34151
|
-
).description(
|
|
34174
|
+
'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)'
|
|
34175
|
+
).description(
|
|
34176
|
+
"Send any held token. USDC + USDsui are gasless; everything else needs SUI for gas."
|
|
34177
|
+
).option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--force", "Override spending limits for this call (see `t2 limit`)").addHelpText(
|
|
34152
34178
|
"after",
|
|
34153
34179
|
`
|
|
34154
34180
|
Examples:
|
|
34155
34181
|
$ t2 send 5 USDC 0xabc\u2026 Send 5 USDC (gasless) to a hex address
|
|
34156
34182
|
$ t2 send 5 USDsui alice.sui Send 5 USDsui (gasless) to a SuiNS name
|
|
34157
34183
|
$ t2 send 0.1 SUI alice.audric.sui Send 0.1 SUI (gas required) to a SuiNS subname
|
|
34184
|
+
$ t2 send 10 MANIFEST 0xabc\u2026 Send any held token (gas required)
|
|
34185
|
+
$ t2 send 10 0xc466\u2026::manifest::MANIFEST 0xabc\u2026 Full coin type works too
|
|
34158
34186
|
`
|
|
34159
34187
|
).action(async (amount, args, opts) => {
|
|
34160
34188
|
try {
|
|
@@ -34163,9 +34191,8 @@ Examples:
|
|
|
34163
34191
|
const result = await agent.send({
|
|
34164
34192
|
to: recipient,
|
|
34165
34193
|
amount: parsedAmount,
|
|
34166
|
-
//
|
|
34167
|
-
//
|
|
34168
|
-
// `assertAllowedAsset('send', …)` at runtime.
|
|
34194
|
+
// [S.957] Any resolvable asset string — the SDK re-resolves via
|
|
34195
|
+
// `classifySendAsset` (same rule the parser used).
|
|
34169
34196
|
asset,
|
|
34170
34197
|
force: opts.force
|
|
34171
34198
|
});
|
|
@@ -34178,7 +34205,7 @@ Examples:
|
|
|
34178
34205
|
return;
|
|
34179
34206
|
}
|
|
34180
34207
|
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` : `${
|
|
34208
|
+
const amountDisplay = asset === "USDC" || asset === "USDsui" ? `${formatUsd(result.amount)} ${asset}` : asset === "SUI" ? `${result.amount.toFixed(4)} SUI` : `${result.amount} ${asset}`;
|
|
34182
34209
|
printBlank();
|
|
34183
34210
|
printSuccess(`Sent ${amountDisplay} \u2192 ${displayTo}`);
|
|
34184
34211
|
if (result.gasCost === 0) {
|
|
@@ -36224,10 +36251,10 @@ function parseDuration(input) {
|
|
|
36224
36251
|
}
|
|
36225
36252
|
var SHA256_HEX_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
36226
36253
|
var SPEC_STORE_MAX_BYTES = 16 * 1024;
|
|
36227
|
-
async function
|
|
36254
|
+
async function loadSpecText(input) {
|
|
36228
36255
|
const trimmed = input.trim();
|
|
36229
36256
|
if (SHA256_HEX_RE.test(trimmed)) {
|
|
36230
|
-
return { hash: trimmed.toLowerCase()
|
|
36257
|
+
return { kind: "hash", hash: trimmed.toLowerCase() };
|
|
36231
36258
|
}
|
|
36232
36259
|
let bytes;
|
|
36233
36260
|
try {
|
|
@@ -36248,7 +36275,27 @@ async function resolveSpecUpload(base, input) {
|
|
|
36248
36275
|
"Content is not UTF-8 text \u2014 the job-spec store holds text only. Upload a short note that LINKS the artifact, or pin a precomputed commitment with --hash-only 0x<sha256>."
|
|
36249
36276
|
);
|
|
36250
36277
|
}
|
|
36251
|
-
return {
|
|
36278
|
+
return { kind: "text", text };
|
|
36279
|
+
}
|
|
36280
|
+
async function resolveSpecUpload(base, input) {
|
|
36281
|
+
const loaded = await loadSpecText(input);
|
|
36282
|
+
if (loaded.kind === "hash") {
|
|
36283
|
+
return { hash: loaded.hash, uploaded: false };
|
|
36284
|
+
}
|
|
36285
|
+
return { hash: `0x${await putJobSpec(base, loaded.text)}`, uploaded: true };
|
|
36286
|
+
}
|
|
36287
|
+
async function resolveHireSpecUpload(base, input, title) {
|
|
36288
|
+
const loaded = await loadSpecText(input);
|
|
36289
|
+
if (loaded.kind === "hash") {
|
|
36290
|
+
return { hash: loaded.hash, uploaded: false };
|
|
36291
|
+
}
|
|
36292
|
+
const body2 = isCustomHireEnvelope(loaded.text) ? loaded.text : customHireEnvelope(loaded.text, title, Date.now());
|
|
36293
|
+
if (Buffer.byteLength(body2, "utf8") > SPEC_STORE_MAX_BYTES) {
|
|
36294
|
+
throw new Error(
|
|
36295
|
+
"The brief plus its envelope exceeds the 16 KiB job-spec store cap \u2014 shorten the brief, or link the long artifact (URL / IPFS)."
|
|
36296
|
+
);
|
|
36297
|
+
}
|
|
36298
|
+
return { hash: `0x${await putJobSpec(base, body2)}`, uploaded: true };
|
|
36252
36299
|
}
|
|
36253
36300
|
function stateColor(state) {
|
|
36254
36301
|
if (state === "released") return import_picocolors13.default.green(state);
|
|
@@ -36349,7 +36396,7 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36349
36396
|
ASP $ t2 job board \xB7 t2 job claim <openingId>
|
|
36350
36397
|
`
|
|
36351
36398
|
);
|
|
36352
|
-
group.command("hire").alias("create").argument("[amount]", `USDC to escrow (max ${MAX_JOB_USDC}; omit when hiring a --service listing)`).argument("[seller]", "The ASP's Sui address (omit when hiring a --service listing)").description("Hire \u2014 fund an escrow job in one transaction (buyer): a listing (--agent + --service) or your own terms (amount + seller + --spec)").option("--spec <file-or-text>", "Job spec \u2014 a file path or inline text (UPLOADED so the seller can read it; sha256 pinned on-chain), or a bare 0x\u2026 sha256 (confidential: pins without uploading)").option(
|
|
36399
|
+
group.command("hire").alias("create").argument("[amount]", `USDC to escrow (max ${MAX_JOB_USDC}; omit when hiring a --service listing)`).argument("[seller]", "The ASP's Sui address (omit when hiring a --service listing)").description("Hire \u2014 fund an escrow job in one transaction (buyer): a listing (--agent + --service) or your own terms (amount + seller + --spec)").option("--spec <file-or-text>", "Job spec \u2014 a file path or inline text (UPLOADED as the public t2-acp-custom@1 title+brief envelope so the seller and the store can read it; sha256 pinned on-chain), or a bare 0x\u2026 sha256 (confidential: pins without uploading, no envelope)").option("--title <text>", "Public job title (\u226480 chars). Custom/direct hire only; derived from the brief's first line if omitted").option(
|
|
36353
36400
|
"--agent <address|#id|@handle>",
|
|
36354
36401
|
"Hire a listing: the ASP's agent address, #id, or @handle"
|
|
36355
36402
|
).option("--service <slug>", "The service slug (see t2 services / t2 service list <agent>)").option("--requirements <file-or-json-or-text>", "What the seller asked buyers to provide \u2014 if the listing lists JSON keys, fill EVERY key (JSON object; extra keys OK)").option("--deadline <duration>", "Time the seller has to deliver (e.g. 30m, 24h, 7d)", "24h").option("--review <duration>", "Your accept/reject window after delivery", "24h").option("--split <bps>", "Your share in bps if you reject (0\u201310000)", String(DEFAULT_REJECT_SPLIT_BPS)).option("--key <path>", "Custom wallet path (default ~/.t2000/wallet.key)").option("--api <url>", `API base URL (default ${DEFAULT_API_BASE7})`).action(
|
|
@@ -36429,7 +36476,11 @@ no fund step; unclaimed openings refund fee-free):
|
|
|
36429
36476
|
seller = validateAddress(
|
|
36430
36477
|
(await resolveAgentRef(base, sellerArg)).address
|
|
36431
36478
|
);
|
|
36432
|
-
({ hash: specHash } = await
|
|
36479
|
+
({ hash: specHash } = await resolveHireSpecUpload(
|
|
36480
|
+
base,
|
|
36481
|
+
opts.spec,
|
|
36482
|
+
opts.title
|
|
36483
|
+
));
|
|
36433
36484
|
deliverByMs = Date.now() + parseDuration(opts.deadline);
|
|
36434
36485
|
reviewWindowMs = opts.review ? parseDuration(opts.review) : DEFAULT_REVIEW_WINDOW_MS;
|
|
36435
36486
|
rejectSplitBps = Number.parseInt(opts.split, 10);
|
|
@@ -36895,9 +36946,9 @@ Examples:
|
|
|
36895
36946
|
$ t2 service retire sui-market-report
|
|
36896
36947
|
`
|
|
36897
36948
|
);
|
|
36898
|
-
group.command("create").description("List a service under your Agent ID (re-run to update it)").requiredOption("--name <name>", "Service name (max 80 chars)").requiredOption("--price <usdc>", "Fixed price in USDC (0.01\u201350)").requiredOption("--sla <duration>", "Delivery SLA \u2014 e.g. 30m, 24h, 7d").requiredOption("--description <text>", "What this service is (max 2000 chars)").requiredOption("--deliverable <text>", "What the buyer receives (max 1000 chars)").option("--slug <slug>", "Machine name (default: derived from --name)").
|
|
36949
|
+
group.command("create").description("List a service under your Agent ID (re-run to update it)").requiredOption("--name <name>", "Service name (max 80 chars)").requiredOption("--price <usdc>", "Fixed price in USDC (0.01\u201350)").requiredOption("--sla <duration>", "Delivery SLA \u2014 e.g. 30m, 24h, 7d").requiredOption("--description <text>", "What this service is (max 2000 chars)").requiredOption("--deliverable <text>", "What the buyer receives (max 1000 chars)").option("--slug <slug>", "Machine name (default: derived from --name)").requiredOption(
|
|
36899
36950
|
"--requirements <file-or-json-or-text>",
|
|
36900
|
-
"
|
|
36951
|
+
"The questions buyers answer at hire (REQUIRED \u2014 the API rejects listings that ask nothing): free text, or a JSON object of field names \u2192 hints (keys enforced non-empty at hire; file path ok)"
|
|
36901
36952
|
).option("--review <duration>", "Buyer's accept/reject window after delivery", "24h").option("--split <bps>", "Buyer's share in bps if they reject (0\u201310000)", "8000").option(
|
|
36902
36953
|
"--category <category>",
|
|
36903
36954
|
`Directory category for your listing: ${AGENT_CATEGORIES.join(" | ")} (required unless already set on your profile)`
|