@t2000/sdk 3.3.0 → 4.0.1
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/README.md +19 -322
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/browser.cjs +5 -0
- package/dist/browser.cjs.map +1 -1
- package/dist/browser.d.cts +2 -1
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +5 -0
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +245 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -16
- package/dist/index.d.ts +117 -16
- package/dist/index.js +238 -113
- package/dist/index.js.map +1 -1
- package/dist/{types-C1DH4kPA.d.cts → types-CqpN8a8R.d.cts} +55 -4
- package/dist/{types-DjeENMuV.d.ts → types-_U9XXGFR.d.ts} +55 -4
- package/package.json +7 -4
package/dist/index.cjs
CHANGED
|
@@ -6,10 +6,10 @@ var BN = require('bn.js');
|
|
|
6
6
|
var eventemitter3 = require('eventemitter3');
|
|
7
7
|
var paymentKit = require('@mysten/payment-kit');
|
|
8
8
|
var jsonRpc = require('@mysten/sui/jsonRpc');
|
|
9
|
+
var grpc = require('@mysten/sui/grpc');
|
|
9
10
|
var utils = require('@mysten/sui/utils');
|
|
10
11
|
var ed25519 = require('@mysten/sui/keypairs/ed25519');
|
|
11
12
|
var cryptography = require('@mysten/sui/cryptography');
|
|
12
|
-
var crypto$1 = require('crypto');
|
|
13
13
|
var promises = require('fs/promises');
|
|
14
14
|
var path = require('path');
|
|
15
15
|
var os = require('os');
|
|
@@ -1128,7 +1128,7 @@ var OPERATION_ASSETS = {
|
|
|
1128
1128
|
borrow: ["USDC", "USDsui"],
|
|
1129
1129
|
withdraw: "*",
|
|
1130
1130
|
repay: "*",
|
|
1131
|
-
send: "
|
|
1131
|
+
send: ["USDC", "USDsui", "SUI"],
|
|
1132
1132
|
swap: "*"
|
|
1133
1133
|
};
|
|
1134
1134
|
function isAllowedAsset(op, asset) {
|
|
@@ -1142,28 +1142,62 @@ function assertAllowedAsset(op, asset) {
|
|
|
1142
1142
|
if (!isAllowedAsset(op, asset)) {
|
|
1143
1143
|
const allowed = OPERATION_ASSETS[op];
|
|
1144
1144
|
const list = Array.isArray(allowed) ? allowed.join(", ") : "any";
|
|
1145
|
+
const swapHint = op === "save" ? " Swap to USDC or USDsui first." : op === "send" ? " Swap to USDC or USDsui first, or send SUI." : "";
|
|
1145
1146
|
throw new exports.T2000Error(
|
|
1146
1147
|
"INVALID_ASSET",
|
|
1147
|
-
`${op} only supports ${list}. Cannot use ${asset}.${
|
|
1148
|
+
`${op} only supports ${list}. Cannot use ${asset}.${swapHint}`
|
|
1148
1149
|
);
|
|
1149
1150
|
}
|
|
1150
1151
|
}
|
|
1152
|
+
var SENDABLE_ASSETS = ["USDC", "USDsui", "SUI"];
|
|
1153
|
+
var GASLESS_STABLE_TYPES = {
|
|
1154
|
+
USDC: SUPPORTED_ASSETS.USDC.type,
|
|
1155
|
+
USDsui: SUPPORTED_ASSETS.USDsui.type
|
|
1156
|
+
};
|
|
1151
1157
|
var T2000_OVERLAY_FEE_WALLET = process.env.T2000_OVERLAY_FEE_WALLET ?? "0x5366efbf2b4fe5767fe2e78eb197aa5f5d138d88ac3333fbf3f80a1927da473a";
|
|
1152
1158
|
var DEFAULT_NETWORK = "mainnet";
|
|
1153
1159
|
var DEFAULT_RPC_URL = "https://fullnode.mainnet.sui.io:443";
|
|
1160
|
+
var DEFAULT_GRPC_URL = "https://fullnode.mainnet.sui.io:443";
|
|
1154
1161
|
var DEFAULT_KEY_PATH = "~/.t2000/wallet.key";
|
|
1162
|
+
var GASLESS_MIN_STABLE_AMOUNT = 0.01;
|
|
1155
1163
|
process.env.T2000_API_URL ?? "https://api.t2000.ai";
|
|
1156
1164
|
var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
|
|
1157
1165
|
var GAS_RESERVE_MIN = 0.05;
|
|
1158
1166
|
|
|
1159
1167
|
// src/utils/sui.ts
|
|
1160
1168
|
init_errors();
|
|
1161
|
-
|
|
1169
|
+
function resolveRpcUrl(rpcUrl) {
|
|
1170
|
+
if (rpcUrl) return rpcUrl;
|
|
1171
|
+
const envUrl = process.env.T2000_RPC_URL?.trim();
|
|
1172
|
+
if (envUrl) return envUrl;
|
|
1173
|
+
return DEFAULT_RPC_URL;
|
|
1174
|
+
}
|
|
1175
|
+
function resolveGrpcUrl(grpcUrl) {
|
|
1176
|
+
if (grpcUrl) return grpcUrl;
|
|
1177
|
+
const envUrl = process.env.T2000_GRPC_URL?.trim();
|
|
1178
|
+
if (envUrl) return envUrl;
|
|
1179
|
+
return DEFAULT_GRPC_URL;
|
|
1180
|
+
}
|
|
1181
|
+
var rpcClientCache = /* @__PURE__ */ new Map();
|
|
1162
1182
|
function getSuiClient(rpcUrl) {
|
|
1163
|
-
const url = rpcUrl
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1183
|
+
const url = resolveRpcUrl(rpcUrl);
|
|
1184
|
+
const cached = rpcClientCache.get(url);
|
|
1185
|
+
if (cached) return cached;
|
|
1186
|
+
const client = new jsonRpc.SuiJsonRpcClient({ url, network: "mainnet" });
|
|
1187
|
+
rpcClientCache.set(url, client);
|
|
1188
|
+
return client;
|
|
1189
|
+
}
|
|
1190
|
+
function createSuiClient(network = "mainnet") {
|
|
1191
|
+
return new jsonRpc.SuiJsonRpcClient({ url: jsonRpc.getJsonRpcFullnodeUrl(network), network });
|
|
1192
|
+
}
|
|
1193
|
+
var grpcClientCache = /* @__PURE__ */ new Map();
|
|
1194
|
+
function getSuiGrpcClient(grpcUrl) {
|
|
1195
|
+
const baseUrl = resolveGrpcUrl(grpcUrl);
|
|
1196
|
+
const cached = grpcClientCache.get(baseUrl);
|
|
1197
|
+
if (cached) return cached;
|
|
1198
|
+
const client = new grpc.SuiGrpcClient({ baseUrl, network: "mainnet" });
|
|
1199
|
+
grpcClientCache.set(baseUrl, client);
|
|
1200
|
+
return client;
|
|
1167
1201
|
}
|
|
1168
1202
|
function validateAddress(address) {
|
|
1169
1203
|
const normalized = utils.normalizeSuiAddress(address);
|
|
@@ -1186,49 +1220,10 @@ function normalizeCoinType(coinType) {
|
|
|
1186
1220
|
|
|
1187
1221
|
// src/wallet/keyManager.ts
|
|
1188
1222
|
init_errors();
|
|
1189
|
-
var ALGORITHM = "aes-256-gcm";
|
|
1190
|
-
var SCRYPT_N = 2 ** 14;
|
|
1191
|
-
var SCRYPT_R = 8;
|
|
1192
|
-
var SCRYPT_P = 1;
|
|
1193
|
-
var SALT_LENGTH = 32;
|
|
1194
|
-
var IV_LENGTH = 16;
|
|
1195
1223
|
function expandPath(p) {
|
|
1196
1224
|
if (p.startsWith("~")) return path.resolve(os.homedir(), p.slice(2));
|
|
1197
1225
|
return path.resolve(p);
|
|
1198
1226
|
}
|
|
1199
|
-
function deriveKey(passphrase, salt) {
|
|
1200
|
-
return crypto$1.scryptSync(passphrase, salt, 32, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P });
|
|
1201
|
-
}
|
|
1202
|
-
function encrypt(data, passphrase) {
|
|
1203
|
-
const salt = crypto$1.randomBytes(SALT_LENGTH);
|
|
1204
|
-
const key = deriveKey(passphrase, salt);
|
|
1205
|
-
const iv = crypto$1.randomBytes(IV_LENGTH);
|
|
1206
|
-
const cipher = crypto$1.createCipheriv(ALGORITHM, key, iv);
|
|
1207
|
-
const ciphertext = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
1208
|
-
const tag = cipher.getAuthTag();
|
|
1209
|
-
return {
|
|
1210
|
-
version: 1,
|
|
1211
|
-
algorithm: ALGORITHM,
|
|
1212
|
-
salt: salt.toString("hex"),
|
|
1213
|
-
iv: iv.toString("hex"),
|
|
1214
|
-
tag: tag.toString("hex"),
|
|
1215
|
-
ciphertext: ciphertext.toString("hex")
|
|
1216
|
-
};
|
|
1217
|
-
}
|
|
1218
|
-
function decrypt(encrypted, passphrase) {
|
|
1219
|
-
const salt = Buffer.from(encrypted.salt, "hex");
|
|
1220
|
-
const key = deriveKey(passphrase, salt);
|
|
1221
|
-
const iv = Buffer.from(encrypted.iv, "hex");
|
|
1222
|
-
const tag = Buffer.from(encrypted.tag, "hex");
|
|
1223
|
-
const ciphertext = Buffer.from(encrypted.ciphertext, "hex");
|
|
1224
|
-
const decipher = crypto$1.createDecipheriv(ALGORITHM, key, iv);
|
|
1225
|
-
decipher.setAuthTag(tag);
|
|
1226
|
-
try {
|
|
1227
|
-
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
1228
|
-
} catch {
|
|
1229
|
-
throw new exports.T2000Error("WALLET_LOCKED", "Invalid PIN");
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
1227
|
function generateKeypair() {
|
|
1233
1228
|
return ed25519.Ed25519Keypair.generate();
|
|
1234
1229
|
}
|
|
@@ -1240,7 +1235,30 @@ function keypairFromPrivateKey(privateKey) {
|
|
|
1240
1235
|
const bytes = Buffer.from(privateKey.replace(/^0x/, ""), "hex");
|
|
1241
1236
|
return ed25519.Ed25519Keypair.fromSecretKey(bytes);
|
|
1242
1237
|
}
|
|
1243
|
-
async function saveKey(keypair,
|
|
1238
|
+
async function saveKey(keypair, _passphrase, keyPath) {
|
|
1239
|
+
const filePath = expandPath(keyPath ?? DEFAULT_KEY_PATH);
|
|
1240
|
+
try {
|
|
1241
|
+
await promises.access(filePath);
|
|
1242
|
+
throw new exports.T2000Error("WALLET_EXISTS", `Wallet already exists at ${filePath}`);
|
|
1243
|
+
} catch (error) {
|
|
1244
|
+
if (error instanceof exports.T2000Error) throw error;
|
|
1245
|
+
}
|
|
1246
|
+
await promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
1247
|
+
const payload = {
|
|
1248
|
+
version: 2,
|
|
1249
|
+
secret: keypair.getSecretKey()
|
|
1250
|
+
};
|
|
1251
|
+
await promises.writeFile(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
|
|
1252
|
+
return filePath;
|
|
1253
|
+
}
|
|
1254
|
+
async function saveBech32(secret, keyPath) {
|
|
1255
|
+
if (!secret.startsWith("suiprivkey")) {
|
|
1256
|
+
throw new exports.T2000Error(
|
|
1257
|
+
"INVALID_KEY",
|
|
1258
|
+
`Secret must be a Bech32 suiprivkey1... string. Got: ${secret.slice(0, 12)}...`
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
cryptography.decodeSuiPrivateKey(secret);
|
|
1244
1262
|
const filePath = expandPath(keyPath ?? DEFAULT_KEY_PATH);
|
|
1245
1263
|
try {
|
|
1246
1264
|
await promises.access(filePath);
|
|
@@ -1249,12 +1267,11 @@ async function saveKey(keypair, passphrase, keyPath) {
|
|
|
1249
1267
|
if (error instanceof exports.T2000Error) throw error;
|
|
1250
1268
|
}
|
|
1251
1269
|
await promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
1252
|
-
const
|
|
1253
|
-
|
|
1254
|
-
await promises.writeFile(filePath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
1270
|
+
const payload = { version: 2, secret };
|
|
1271
|
+
await promises.writeFile(filePath, JSON.stringify(payload, null, 2), { mode: 384 });
|
|
1255
1272
|
return filePath;
|
|
1256
1273
|
}
|
|
1257
|
-
async function loadKey(
|
|
1274
|
+
async function loadKey(_passphrase, keyPath) {
|
|
1258
1275
|
const filePath = expandPath(keyPath ?? DEFAULT_KEY_PATH);
|
|
1259
1276
|
let content;
|
|
1260
1277
|
try {
|
|
@@ -1262,10 +1279,22 @@ async function loadKey(passphrase, keyPath) {
|
|
|
1262
1279
|
} catch {
|
|
1263
1280
|
throw new exports.T2000Error("WALLET_NOT_FOUND", `No wallet found at ${filePath}`);
|
|
1264
1281
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1282
|
+
let parsed;
|
|
1283
|
+
try {
|
|
1284
|
+
parsed = JSON.parse(content);
|
|
1285
|
+
} catch {
|
|
1286
|
+
throw new exports.T2000Error(
|
|
1287
|
+
"WALLET_CORRUPT",
|
|
1288
|
+
`Wallet file at ${filePath} is not a valid v4 wallet. Move or delete the file, then run \`t2 init\`.`
|
|
1289
|
+
);
|
|
1290
|
+
}
|
|
1291
|
+
if (!isPlainKey(parsed)) {
|
|
1292
|
+
throw new exports.T2000Error(
|
|
1293
|
+
"WALLET_CORRUPT",
|
|
1294
|
+
`Wallet file at ${filePath} is not a valid v4 wallet. Expected { version: 2, secret: "suiprivkey1..." }. Move or delete the file, then run \`t2 init\`.`
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
const decoded = cryptography.decodeSuiPrivateKey(parsed.secret);
|
|
1269
1298
|
return ed25519.Ed25519Keypair.fromSecretKey(decoded.secretKey);
|
|
1270
1299
|
}
|
|
1271
1300
|
async function walletExists(keyPath) {
|
|
@@ -1283,6 +1312,9 @@ function exportPrivateKey(keypair) {
|
|
|
1283
1312
|
function getAddress(keypair) {
|
|
1284
1313
|
return keypair.getPublicKey().toSuiAddress();
|
|
1285
1314
|
}
|
|
1315
|
+
function isPlainKey(value) {
|
|
1316
|
+
return typeof value === "object" && value !== null && value.version === 2 && typeof value.secret === "string";
|
|
1317
|
+
}
|
|
1286
1318
|
|
|
1287
1319
|
// src/wallet/keypairSigner.ts
|
|
1288
1320
|
var KeypairSigner = class {
|
|
@@ -1390,12 +1422,19 @@ async function buildSendTx({
|
|
|
1390
1422
|
address,
|
|
1391
1423
|
to,
|
|
1392
1424
|
amount,
|
|
1393
|
-
asset
|
|
1425
|
+
asset
|
|
1394
1426
|
}) {
|
|
1427
|
+
assertAllowedAsset("send", asset);
|
|
1395
1428
|
const recipient = validateAddress(to);
|
|
1396
1429
|
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
1397
1430
|
if (!assetInfo) throw new exports.T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
|
|
1398
1431
|
if (amount <= 0) throw new exports.T2000Error("INVALID_AMOUNT", "Amount must be greater than zero");
|
|
1432
|
+
if ((asset === "USDC" || asset === "USDsui") && amount < GASLESS_MIN_STABLE_AMOUNT) {
|
|
1433
|
+
throw new exports.T2000Error(
|
|
1434
|
+
"INVALID_AMOUNT",
|
|
1435
|
+
`Minimum gasless transfer is ${GASLESS_MIN_STABLE_AMOUNT} ${asset}. Got ${amount}.`
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1399
1438
|
const rawAmount = displayToRaw(amount, assetInfo.decimals);
|
|
1400
1439
|
const tx = new transactions.Transaction();
|
|
1401
1440
|
tx.setSender(address);
|
|
@@ -1407,8 +1446,20 @@ async function buildSendTx({
|
|
|
1407
1446
|
required: amount
|
|
1408
1447
|
});
|
|
1409
1448
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1449
|
+
if (asset === "SUI") {
|
|
1450
|
+
const [sendCoin] = tx.splitCoins(tx.gas, [rawAmount]);
|
|
1451
|
+
tx.transferObjects([sendCoin], recipient);
|
|
1452
|
+
return tx;
|
|
1453
|
+
}
|
|
1454
|
+
const coinType = GASLESS_STABLE_TYPES[asset];
|
|
1455
|
+
tx.moveCall({
|
|
1456
|
+
target: "0x2::balance::send_funds",
|
|
1457
|
+
typeArguments: [coinType],
|
|
1458
|
+
arguments: [
|
|
1459
|
+
tx.balance({ type: coinType, balance: rawAmount }),
|
|
1460
|
+
tx.pure.address(recipient)
|
|
1461
|
+
]
|
|
1462
|
+
});
|
|
1412
1463
|
return tx;
|
|
1413
1464
|
}
|
|
1414
1465
|
function addSendToTx(tx, coin, recipient) {
|
|
@@ -6378,8 +6429,7 @@ var ContactManager = class {
|
|
|
6378
6429
|
"CONTACT_NOT_FOUND",
|
|
6379
6430
|
`"${nameOrAddress}" is not a valid Sui address or saved contact.
|
|
6380
6431
|
Use a SuiNS name (e.g. alex.sui \u2014 register at https://suins.io)
|
|
6381
|
-
or paste the full Sui address (0x... 64 hex characters)
|
|
6382
|
-
Legacy contact aliases: \`t2000 contacts add ${nameOrAddress} 0x...\` (deprecated).`
|
|
6432
|
+
or paste the full Sui address (0x... 64 hex characters).`
|
|
6383
6433
|
);
|
|
6384
6434
|
}
|
|
6385
6435
|
validateName(name) {
|
|
@@ -6451,20 +6501,16 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
6451
6501
|
return registry;
|
|
6452
6502
|
}
|
|
6453
6503
|
static async create(options = {}) {
|
|
6454
|
-
const { keyPath,
|
|
6455
|
-
const secret = pin ?? passphrase;
|
|
6504
|
+
const { keyPath, rpcUrl } = options;
|
|
6456
6505
|
const client = getSuiClient(rpcUrl);
|
|
6457
6506
|
const exists = await walletExists(keyPath);
|
|
6458
6507
|
if (!exists) {
|
|
6459
6508
|
throw new exports.T2000Error(
|
|
6460
6509
|
"WALLET_NOT_FOUND",
|
|
6461
|
-
"No wallet found. Run `
|
|
6510
|
+
"No wallet found. Run `t2 init` to create one."
|
|
6462
6511
|
);
|
|
6463
6512
|
}
|
|
6464
|
-
|
|
6465
|
-
throw new exports.T2000Error("WALLET_LOCKED", "PIN required to unlock wallet");
|
|
6466
|
-
}
|
|
6467
|
-
const keypair = await loadKey(secret, keyPath);
|
|
6513
|
+
const keypair = await loadKey(void 0, keyPath);
|
|
6468
6514
|
return new _T2000(keypair, client, void 0, DEFAULT_CONFIG_DIR);
|
|
6469
6515
|
}
|
|
6470
6516
|
static fromPrivateKey(privateKey, options = {}) {
|
|
@@ -6472,10 +6518,9 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
6472
6518
|
const client = getSuiClient(options.rpcUrl);
|
|
6473
6519
|
return new _T2000(keypair, client);
|
|
6474
6520
|
}
|
|
6475
|
-
static async init(options) {
|
|
6476
|
-
const secret = options.pin ?? options.passphrase ?? "";
|
|
6521
|
+
static async init(options = {}) {
|
|
6477
6522
|
const keypair = generateKeypair();
|
|
6478
|
-
await saveKey(keypair,
|
|
6523
|
+
await saveKey(keypair, void 0, options.keyPath);
|
|
6479
6524
|
const client = getSuiClient();
|
|
6480
6525
|
const agent = new _T2000(keypair, client, void 0, DEFAULT_CONFIG_DIR);
|
|
6481
6526
|
const address = agent.address();
|
|
@@ -6503,7 +6548,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
6503
6548
|
this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
|
|
6504
6549
|
const { Mppx } = await import('mppx/client');
|
|
6505
6550
|
const { sui, USDC } = await import('@suimpp/mpp/client');
|
|
6506
|
-
const { SuiGrpcClient } = await import('@mysten/sui/grpc');
|
|
6551
|
+
const { SuiGrpcClient: SuiGrpcClient2 } = await import('@mysten/sui/grpc');
|
|
6507
6552
|
const client = this.client;
|
|
6508
6553
|
const signer = this._signer;
|
|
6509
6554
|
const signerAddress = signer.getAddress();
|
|
@@ -6511,7 +6556,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
6511
6556
|
let gasCostSui = 0;
|
|
6512
6557
|
const network = client.network === "testnet" ? "testnet" : "mainnet";
|
|
6513
6558
|
const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
|
|
6514
|
-
const grpcClient = new
|
|
6559
|
+
const grpcClient = new SuiGrpcClient2({ baseUrl: grpcBaseUrl, network });
|
|
6515
6560
|
const mppx = Mppx.create({
|
|
6516
6561
|
polyfill: false,
|
|
6517
6562
|
methods: [sui({
|
|
@@ -6694,23 +6739,52 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
6694
6739
|
address() {
|
|
6695
6740
|
return this._address;
|
|
6696
6741
|
}
|
|
6742
|
+
/**
|
|
6743
|
+
* Send `amount` of `asset` to `to` (hex address, SuiNS name, or
|
|
6744
|
+
* `@audric` handle / saved contact).
|
|
6745
|
+
*
|
|
6746
|
+
* [v4.0 Phase A Day 2 — SPEC_AGENT_WALLET_GREENFIELD §A]
|
|
6747
|
+
*
|
|
6748
|
+
* **Breaking changes from v3.x:**
|
|
6749
|
+
* - `asset` is now REQUIRED (no implicit `?? 'USDC'` default). Callers
|
|
6750
|
+
* must specify `'USDC' | 'USDsui' | 'SUI'`. Sending `'USDT'` /
|
|
6751
|
+
* `'USDe'` / `'WAL'` / `'ETH'` / `'NAVX'` / `'GOLD'` now errors
|
|
6752
|
+
* with `INVALID_ASSET` — swap to a stable first.
|
|
6753
|
+
* - USDC + USDsui builds go through `SuiGrpcClient` so the gRPC build
|
|
6754
|
+
* resolver auto-detects `0x2::balance::send_funds` eligibility and
|
|
6755
|
+
* zeros gas at simulate time. Result: **gasless USDC / USDsui sends
|
|
6756
|
+
* from a zero-SUI wallet.** SUI sends stay on the standard gas-paid
|
|
6757
|
+
* path.
|
|
6758
|
+
*
|
|
6759
|
+
* Submission stays on the JSON-RPC client (the rest of the SDK
|
|
6760
|
+
* expects JSON-RPC for read paths, and Sui's docs explicitly support
|
|
6761
|
+
* the "build via gRPC, execute via JSON-RPC" hybrid).
|
|
6762
|
+
*/
|
|
6697
6763
|
async send(params) {
|
|
6698
6764
|
this.enforcer.assertNotLocked();
|
|
6699
|
-
const asset = params.asset
|
|
6700
|
-
if (!
|
|
6701
|
-
throw new exports.T2000Error(
|
|
6765
|
+
const asset = params.asset;
|
|
6766
|
+
if (!asset) {
|
|
6767
|
+
throw new exports.T2000Error(
|
|
6768
|
+
"INVALID_ASSET",
|
|
6769
|
+
"send() requires an explicit asset. Use one of: USDC, USDsui, SUI."
|
|
6770
|
+
);
|
|
6702
6771
|
}
|
|
6772
|
+
assertAllowedAsset("send", asset);
|
|
6773
|
+
const sendableAsset = asset;
|
|
6703
6774
|
const resolved = await this.resolveRecipient(params.to);
|
|
6704
6775
|
const sendAmount = params.amount;
|
|
6705
6776
|
const sendTo = resolved.address;
|
|
6777
|
+
const useGrpc = sendableAsset === "USDC" || sendableAsset === "USDsui";
|
|
6778
|
+
const buildClient = useGrpc ? getSuiGrpcClient() : void 0;
|
|
6706
6779
|
const gasResult = await executeTx(
|
|
6707
6780
|
this.client,
|
|
6708
6781
|
this._signer,
|
|
6709
|
-
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset })
|
|
6782
|
+
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset: sendableAsset }),
|
|
6783
|
+
{ buildClient }
|
|
6710
6784
|
);
|
|
6711
6785
|
this.enforcer.recordUsage(sendAmount);
|
|
6712
6786
|
const balance = await this.balance();
|
|
6713
|
-
this.emitBalanceChange(
|
|
6787
|
+
this.emitBalanceChange(sendableAsset, sendAmount, "send", gasResult.digest);
|
|
6714
6788
|
return {
|
|
6715
6789
|
success: true,
|
|
6716
6790
|
tx: gasResult.digest,
|
|
@@ -6820,11 +6894,12 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
6820
6894
|
};
|
|
6821
6895
|
}
|
|
6822
6896
|
/**
|
|
6823
|
-
* [SPEC_AGENTIC_STACK P1 / SDK F2
|
|
6824
|
-
* Preferred alias of `deposit()`.
|
|
6825
|
-
*
|
|
6826
|
-
* `deposit()` stays as the canonical method name for
|
|
6827
|
-
*
|
|
6897
|
+
* [SPEC_AGENTIC_STACK P1 / SDK F2 — 2026-05-25; refreshed S.342 / 2026-05-26]
|
|
6898
|
+
* Preferred alias of `deposit()`. Was introduced to mirror the v3 `t2000 fund`
|
|
6899
|
+
* CLI command; the v4 CLI surface is `t2 receive` (deleted `fund` in the
|
|
6900
|
+
* S.332 bulk cut). `deposit()` stays as the canonical method name for
|
|
6901
|
+
* back-compat; `fund()` stays as a programmatic alias for audric + other
|
|
6902
|
+
* SDK consumers that prefer the verb.
|
|
6828
6903
|
*/
|
|
6829
6904
|
async fund() {
|
|
6830
6905
|
return this.deposit();
|
|
@@ -7159,7 +7234,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
7159
7234
|
const adapter = await this.resolveLending(params.protocol, asset, "borrow");
|
|
7160
7235
|
const maxResult = await adapter.maxBorrow(this._address, asset);
|
|
7161
7236
|
if (maxResult.maxAmount <= 0) {
|
|
7162
|
-
throw new exports.T2000Error("NO_COLLATERAL", "No collateral deposited.
|
|
7237
|
+
throw new exports.T2000Error("NO_COLLATERAL", "No collateral deposited. Make a save deposit first.");
|
|
7163
7238
|
}
|
|
7164
7239
|
if (params.amount > maxResult.maxAmount) {
|
|
7165
7240
|
throw new exports.T2000Error("HEALTH_FACTOR_TOO_LOW", `Max safe borrow: $${maxResult.maxAmount.toFixed(2)}. Only savings deposits count as borrowable collateral.`, {
|
|
@@ -7917,34 +7992,68 @@ var WRITE_APPENDER_REGISTRY = {
|
|
|
7917
7992
|
},
|
|
7918
7993
|
send_transfer: async (tx, input, ctx) => {
|
|
7919
7994
|
const recipient = validateAddress(input.to);
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7995
|
+
if (!input.asset) {
|
|
7996
|
+
throw new exports.T2000Error(
|
|
7997
|
+
"INVALID_ASSET",
|
|
7998
|
+
"send_transfer requires an explicit asset. Use one of: USDC, USDsui, SUI."
|
|
7999
|
+
);
|
|
7924
8000
|
}
|
|
8001
|
+
assertAllowedAsset("send", input.asset);
|
|
8002
|
+
const asset = input.asset;
|
|
8003
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
7925
8004
|
if (input.amount <= 0) {
|
|
7926
8005
|
throw new exports.T2000Error("INVALID_AMOUNT", "Send amount must be greater than zero");
|
|
7927
8006
|
}
|
|
7928
8007
|
const rawAmount = BigInt(Math.floor(input.amount * 10 ** assetInfo.decimals));
|
|
7929
|
-
let coin;
|
|
7930
|
-
let effectiveRaw;
|
|
7931
8008
|
if (ctx.chainedCoin) {
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
8009
|
+
addSendToTx(tx, ctx.chainedCoin, recipient);
|
|
8010
|
+
return {
|
|
8011
|
+
preview: {
|
|
8012
|
+
toolName: "send_transfer",
|
|
8013
|
+
effectiveAmount: Number(rawAmount) / 10 ** assetInfo.decimals,
|
|
8014
|
+
recipient,
|
|
8015
|
+
asset
|
|
8016
|
+
}
|
|
8017
|
+
};
|
|
8018
|
+
}
|
|
8019
|
+
if (asset === "SUI") {
|
|
7935
8020
|
const result = await selectSuiCoin(tx, ctx.client, ctx.sender, rawAmount, ctx.sponsoredContext);
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
7940
|
-
|
|
7941
|
-
|
|
8021
|
+
addSendToTx(tx, result.coin, recipient);
|
|
8022
|
+
return {
|
|
8023
|
+
preview: {
|
|
8024
|
+
toolName: "send_transfer",
|
|
8025
|
+
effectiveAmount: Number(result.effectiveAmount) / 10 ** assetInfo.decimals,
|
|
8026
|
+
recipient,
|
|
8027
|
+
asset
|
|
8028
|
+
}
|
|
8029
|
+
};
|
|
7942
8030
|
}
|
|
7943
|
-
|
|
8031
|
+
if (input.amount < GASLESS_MIN_STABLE_AMOUNT) {
|
|
8032
|
+
throw new exports.T2000Error(
|
|
8033
|
+
"INVALID_AMOUNT",
|
|
8034
|
+
`Minimum gasless transfer is ${GASLESS_MIN_STABLE_AMOUNT} ${asset}. Got ${input.amount}.`
|
|
8035
|
+
);
|
|
8036
|
+
}
|
|
8037
|
+
const balanceResp = await ctx.client.getBalance({ owner: ctx.sender, coinType: assetInfo.type });
|
|
8038
|
+
if (BigInt(balanceResp.totalBalance) < rawAmount) {
|
|
8039
|
+
throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
|
|
8040
|
+
available: Number(balanceResp.totalBalance) / 10 ** assetInfo.decimals,
|
|
8041
|
+
required: input.amount
|
|
8042
|
+
});
|
|
8043
|
+
}
|
|
8044
|
+
const coinType = GASLESS_STABLE_TYPES[asset];
|
|
8045
|
+
tx.moveCall({
|
|
8046
|
+
target: "0x2::balance::send_funds",
|
|
8047
|
+
typeArguments: [coinType],
|
|
8048
|
+
arguments: [
|
|
8049
|
+
tx.balance({ type: coinType, balance: rawAmount }),
|
|
8050
|
+
tx.pure.address(recipient)
|
|
8051
|
+
]
|
|
8052
|
+
});
|
|
7944
8053
|
return {
|
|
7945
8054
|
preview: {
|
|
7946
8055
|
toolName: "send_transfer",
|
|
7947
|
-
effectiveAmount: Number(
|
|
8056
|
+
effectiveAmount: Number(rawAmount) / 10 ** assetInfo.decimals,
|
|
7948
8057
|
recipient,
|
|
7949
8058
|
asset
|
|
7950
8059
|
}
|
|
@@ -8019,24 +8128,40 @@ var WRITE_APPENDER_REGISTRY = {
|
|
|
8019
8128
|
function deriveAllowedAddressesFromPtb(tx) {
|
|
8020
8129
|
const addresses = /* @__PURE__ */ new Set();
|
|
8021
8130
|
const data = tx.getData();
|
|
8022
|
-
|
|
8023
|
-
|
|
8024
|
-
|
|
8025
|
-
|
|
8026
|
-
if (!addressArg) continue;
|
|
8027
|
-
const addressInputIndex = addressArg.Input;
|
|
8028
|
-
if (addressInputIndex === void 0) continue;
|
|
8029
|
-
const input = data.inputs[addressInputIndex];
|
|
8030
|
-
if (!input) continue;
|
|
8131
|
+
const addAddressFromInput = (inputIndex) => {
|
|
8132
|
+
if (inputIndex === void 0) return;
|
|
8133
|
+
const input = data.inputs[inputIndex];
|
|
8134
|
+
if (!input) return;
|
|
8031
8135
|
const pureBytes = input.Pure?.bytes;
|
|
8032
|
-
if (!pureBytes)
|
|
8136
|
+
if (!pureBytes) return;
|
|
8033
8137
|
try {
|
|
8034
8138
|
const bytes = base64ToBytes(pureBytes);
|
|
8035
|
-
if (bytes.length !== 32)
|
|
8139
|
+
if (bytes.length !== 32) return;
|
|
8036
8140
|
const hex = "0x" + Array.from(bytes).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
8037
8141
|
addresses.add(hex);
|
|
8038
8142
|
} catch {
|
|
8039
8143
|
}
|
|
8144
|
+
};
|
|
8145
|
+
for (const cmd of data.commands) {
|
|
8146
|
+
const transferCmd = cmd.TransferObjects;
|
|
8147
|
+
if (transferCmd) {
|
|
8148
|
+
const addressArg = transferCmd.address;
|
|
8149
|
+
const addressInputIndex = addressArg?.Input;
|
|
8150
|
+
addAddressFromInput(addressInputIndex);
|
|
8151
|
+
continue;
|
|
8152
|
+
}
|
|
8153
|
+
const moveCall = cmd.MoveCall;
|
|
8154
|
+
if (moveCall) {
|
|
8155
|
+
const mc = moveCall;
|
|
8156
|
+
const isBalanceSendFunds = mc.module === "balance" && mc.function === "send_funds";
|
|
8157
|
+
const isCoinSendFunds = mc.module === "coin" && mc.function === "send_funds";
|
|
8158
|
+
if (isBalanceSendFunds || isCoinSendFunds) {
|
|
8159
|
+
const args = mc.arguments ?? [];
|
|
8160
|
+
const recipientArg = args[1];
|
|
8161
|
+
addAddressFromInput(recipientArg?.Input);
|
|
8162
|
+
}
|
|
8163
|
+
continue;
|
|
8164
|
+
}
|
|
8040
8165
|
}
|
|
8041
8166
|
return Array.from(addresses);
|
|
8042
8167
|
}
|
|
@@ -8370,8 +8495,11 @@ exports.BPS_DENOMINATOR = BPS_DENOMINATOR;
|
|
|
8370
8495
|
exports.CETUS_USDC_SUI_POOL = CETUS_USDC_SUI_POOL;
|
|
8371
8496
|
exports.CLOCK_ID = CLOCK_ID;
|
|
8372
8497
|
exports.ContactManager = ContactManager;
|
|
8498
|
+
exports.DEFAULT_GRPC_URL = DEFAULT_GRPC_URL;
|
|
8373
8499
|
exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
|
|
8374
8500
|
exports.DEFAULT_SAFEGUARD_CONFIG = DEFAULT_SAFEGUARD_CONFIG;
|
|
8501
|
+
exports.GASLESS_MIN_STABLE_AMOUNT = GASLESS_MIN_STABLE_AMOUNT;
|
|
8502
|
+
exports.GASLESS_STABLE_TYPES = GASLESS_STABLE_TYPES;
|
|
8375
8503
|
exports.GAS_RESERVE_MIN = GAS_RESERVE_MIN;
|
|
8376
8504
|
exports.HF_CRITICAL_THRESHOLD = HF_CRITICAL_THRESHOLD;
|
|
8377
8505
|
exports.HF_WARN_THRESHOLD = HF_WARN_THRESHOLD;
|
|
@@ -8386,6 +8514,7 @@ exports.OUTBOUND_OPS = OUTBOUND_OPS;
|
|
|
8386
8514
|
exports.ProtocolRegistry = ProtocolRegistry;
|
|
8387
8515
|
exports.SAVEABLE_ASSETS = SAVEABLE_ASSETS;
|
|
8388
8516
|
exports.SAVE_FEE_BPS = SAVE_FEE_BPS;
|
|
8517
|
+
exports.SENDABLE_ASSETS = SENDABLE_ASSETS;
|
|
8389
8518
|
exports.SPONSORED_PYTH_DEPENDENT_PROVIDERS = SPONSORED_PYTH_DEPENDENT_PROVIDERS;
|
|
8390
8519
|
exports.STABLE_ASSETS = STABLE_ASSETS;
|
|
8391
8520
|
exports.SUINS_NAME_REGEX = SUINS_NAME_REGEX;
|
|
@@ -8420,6 +8549,7 @@ exports.classifyAction = classifyAction;
|
|
|
8420
8549
|
exports.classifyLabel = classifyLabel;
|
|
8421
8550
|
exports.classifyTransaction = classifyTransaction;
|
|
8422
8551
|
exports.composeTx = composeTx;
|
|
8552
|
+
exports.createSuiClient = createSuiClient;
|
|
8423
8553
|
exports.deriveAllowedAddressesFromPtb = deriveAllowedAddressesFromPtb;
|
|
8424
8554
|
exports.deserializeCetusRoute = deserializeCetusRoute;
|
|
8425
8555
|
exports.displayHandle = displayHandle;
|
|
@@ -8445,6 +8575,8 @@ exports.getPendingRewards = getPendingRewards;
|
|
|
8445
8575
|
exports.getPendingRewardsByAddress = getPendingRewardsByAddress;
|
|
8446
8576
|
exports.getRates = getRates;
|
|
8447
8577
|
exports.getSponsoredSwapProviders = getSponsoredSwapProviders;
|
|
8578
|
+
exports.getSuiClient = getSuiClient;
|
|
8579
|
+
exports.getSuiGrpcClient = getSuiGrpcClient;
|
|
8448
8580
|
exports.getSwapQuote = getSwapQuote;
|
|
8449
8581
|
exports.getTier = getTier;
|
|
8450
8582
|
exports.isAllowedAsset = isAllowedAsset;
|
|
@@ -8473,6 +8605,7 @@ exports.resolveAddressToSuinsViaRpc = resolveAddressToSuinsViaRpc;
|
|
|
8473
8605
|
exports.resolveSuinsViaRpc = resolveSuinsViaRpc;
|
|
8474
8606
|
exports.resolveSymbol = resolveSymbol;
|
|
8475
8607
|
exports.resolveTokenType = resolveTokenType;
|
|
8608
|
+
exports.saveBech32 = saveBech32;
|
|
8476
8609
|
exports.saveKey = saveKey;
|
|
8477
8610
|
exports.selectAndSplitCoin = selectAndSplitCoin;
|
|
8478
8611
|
exports.selectSuiCoin = selectSuiCoin;
|