@t2000/sdk 0.16.1 → 0.16.6
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/LICENSE +21 -0
- package/README.md +39 -2
- package/dist/index.cjs +136 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +136 -76
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.d.cts
CHANGED
|
@@ -582,6 +582,13 @@ interface AutoTopUpResult {
|
|
|
582
582
|
suiReceived: number;
|
|
583
583
|
}
|
|
584
584
|
declare function shouldAutoTopUp(client: SuiJsonRpcClient, address: string): Promise<boolean>;
|
|
585
|
+
/**
|
|
586
|
+
* Self-fund a USDC→SUI swap to replenish gas.
|
|
587
|
+
*
|
|
588
|
+
* Uses the agent's remaining SUI to pay for the swap gas (~0.007 SUI).
|
|
589
|
+
* This avoids the chicken-and-egg problem of needing gas station sponsorship
|
|
590
|
+
* to get gas, and works even when the gas station is down.
|
|
591
|
+
*/
|
|
585
592
|
declare function executeAutoTopUp(client: SuiJsonRpcClient, keypair: Ed25519Keypair): Promise<AutoTopUpResult>;
|
|
586
593
|
|
|
587
594
|
type GasRequestType = 'bootstrap' | 'auto-topup' | 'fallback';
|
package/dist/index.d.ts
CHANGED
|
@@ -582,6 +582,13 @@ interface AutoTopUpResult {
|
|
|
582
582
|
suiReceived: number;
|
|
583
583
|
}
|
|
584
584
|
declare function shouldAutoTopUp(client: SuiJsonRpcClient, address: string): Promise<boolean>;
|
|
585
|
+
/**
|
|
586
|
+
* Self-fund a USDC→SUI swap to replenish gas.
|
|
587
|
+
*
|
|
588
|
+
* Uses the agent's remaining SUI to pay for the swap gas (~0.007 SUI).
|
|
589
|
+
* This avoids the chicken-and-egg problem of needing gas station sponsorship
|
|
590
|
+
* to get gas, and works even when the gas station is down.
|
|
591
|
+
*/
|
|
585
592
|
declare function executeAutoTopUp(client: SuiJsonRpcClient, keypair: Ed25519Keypair): Promise<AutoTopUpResult>;
|
|
586
593
|
|
|
587
594
|
type GasRequestType = 'bootstrap' | 'auto-topup' | 'fallback';
|
package/dist/index.js
CHANGED
|
@@ -2386,13 +2386,62 @@ function solveHashcash(challenge) {
|
|
|
2386
2386
|
}
|
|
2387
2387
|
}
|
|
2388
2388
|
|
|
2389
|
+
// src/gas/autoTopUp.ts
|
|
2390
|
+
var AUTO_TOPUP_MIN_SUI_FOR_GAS = 5000000n;
|
|
2391
|
+
async function shouldAutoTopUp(client, address) {
|
|
2392
|
+
const [suiBalance, usdcBalance] = await Promise.all([
|
|
2393
|
+
client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
|
|
2394
|
+
client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.USDC.type })
|
|
2395
|
+
]);
|
|
2396
|
+
const suiRaw = BigInt(suiBalance.totalBalance);
|
|
2397
|
+
const usdcRaw = BigInt(usdcBalance.totalBalance);
|
|
2398
|
+
return suiRaw < AUTO_TOPUP_THRESHOLD && suiRaw >= AUTO_TOPUP_MIN_SUI_FOR_GAS && usdcRaw >= AUTO_TOPUP_MIN_USDC;
|
|
2399
|
+
}
|
|
2400
|
+
async function executeAutoTopUp(client, keypair) {
|
|
2401
|
+
const address = keypair.getPublicKey().toSuiAddress();
|
|
2402
|
+
const topupAmountHuman = Number(AUTO_TOPUP_AMOUNT) / 1e6;
|
|
2403
|
+
const { tx } = await buildSwapTx({
|
|
2404
|
+
client,
|
|
2405
|
+
address,
|
|
2406
|
+
fromAsset: "USDC",
|
|
2407
|
+
toAsset: "SUI",
|
|
2408
|
+
amount: topupAmountHuman
|
|
2409
|
+
});
|
|
2410
|
+
const result = await client.signAndExecuteTransaction({
|
|
2411
|
+
signer: keypair,
|
|
2412
|
+
transaction: tx,
|
|
2413
|
+
options: { showEffects: true, showBalanceChanges: true }
|
|
2414
|
+
});
|
|
2415
|
+
await client.waitForTransaction({ digest: result.digest });
|
|
2416
|
+
let suiReceived = 0;
|
|
2417
|
+
if (result.balanceChanges) {
|
|
2418
|
+
for (const change of result.balanceChanges) {
|
|
2419
|
+
if (change.coinType === SUPPORTED_ASSETS.SUI.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
|
|
2420
|
+
suiReceived += Number(change.amount) / Number(MIST_PER_SUI);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return {
|
|
2425
|
+
success: true,
|
|
2426
|
+
tx: result.digest,
|
|
2427
|
+
usdcSpent: topupAmountHuman,
|
|
2428
|
+
suiReceived: Math.abs(suiReceived)
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2389
2432
|
// src/gas/gasStation.ts
|
|
2390
|
-
async function requestGasSponsorship(txJson, sender, type) {
|
|
2391
|
-
const
|
|
2433
|
+
async function requestGasSponsorship(txJson, sender, type, txBcsBytes) {
|
|
2434
|
+
const payload = { sender, type };
|
|
2435
|
+
if (txBcsBytes) {
|
|
2436
|
+
payload.txBcsBytes = txBcsBytes;
|
|
2437
|
+
} else {
|
|
2438
|
+
payload.txJson = txJson;
|
|
2439
|
+
payload.txBytes = Buffer.from(txJson).toString("base64");
|
|
2440
|
+
}
|
|
2392
2441
|
const res = await fetch(`${API_BASE_URL}/api/gas`, {
|
|
2393
2442
|
method: "POST",
|
|
2394
2443
|
headers: { "Content-Type": "application/json" },
|
|
2395
|
-
body: JSON.stringify(
|
|
2444
|
+
body: JSON.stringify(payload)
|
|
2396
2445
|
});
|
|
2397
2446
|
const data = await res.json();
|
|
2398
2447
|
if (!res.ok) {
|
|
@@ -2442,53 +2491,6 @@ async function getGasStatus(address) {
|
|
|
2442
2491
|
return await res.json();
|
|
2443
2492
|
}
|
|
2444
2493
|
|
|
2445
|
-
// src/gas/autoTopUp.ts
|
|
2446
|
-
async function shouldAutoTopUp(client, address) {
|
|
2447
|
-
const [suiBalance, usdcBalance] = await Promise.all([
|
|
2448
|
-
client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
|
|
2449
|
-
client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.USDC.type })
|
|
2450
|
-
]);
|
|
2451
|
-
const suiRaw = BigInt(suiBalance.totalBalance);
|
|
2452
|
-
const usdcRaw = BigInt(usdcBalance.totalBalance);
|
|
2453
|
-
return suiRaw < AUTO_TOPUP_THRESHOLD && usdcRaw >= AUTO_TOPUP_MIN_USDC;
|
|
2454
|
-
}
|
|
2455
|
-
async function executeAutoTopUp(client, keypair) {
|
|
2456
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
2457
|
-
const topupAmountHuman = Number(AUTO_TOPUP_AMOUNT) / 1e6;
|
|
2458
|
-
const { tx } = await buildSwapTx({
|
|
2459
|
-
client,
|
|
2460
|
-
address,
|
|
2461
|
-
fromAsset: "USDC",
|
|
2462
|
-
toAsset: "SUI",
|
|
2463
|
-
amount: topupAmountHuman
|
|
2464
|
-
});
|
|
2465
|
-
const txJson = tx.serialize();
|
|
2466
|
-
const sponsoredResult = await requestGasSponsorship(txJson, address, "auto-topup");
|
|
2467
|
-
const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
|
|
2468
|
-
const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
|
|
2469
|
-
const result = await client.executeTransactionBlock({
|
|
2470
|
-
transactionBlock: sponsoredResult.txBytes,
|
|
2471
|
-
signature: [agentSig, sponsoredResult.sponsorSignature],
|
|
2472
|
-
options: { showEffects: true, showBalanceChanges: true }
|
|
2473
|
-
});
|
|
2474
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
2475
|
-
let suiReceived = 0;
|
|
2476
|
-
if (result.balanceChanges) {
|
|
2477
|
-
for (const change of result.balanceChanges) {
|
|
2478
|
-
if (change.coinType === SUPPORTED_ASSETS.SUI.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
|
|
2479
|
-
suiReceived += Number(change.amount) / Number(MIST_PER_SUI);
|
|
2480
|
-
}
|
|
2481
|
-
}
|
|
2482
|
-
}
|
|
2483
|
-
reportGasUsage(address, result.digest, 0, 0, "auto-topup");
|
|
2484
|
-
return {
|
|
2485
|
-
success: true,
|
|
2486
|
-
tx: result.digest,
|
|
2487
|
-
usdcSpent: topupAmountHuman,
|
|
2488
|
-
suiReceived: Math.abs(suiReceived)
|
|
2489
|
-
};
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
2494
|
// src/gas/manager.ts
|
|
2493
2495
|
function extractGasCost(effects) {
|
|
2494
2496
|
if (!effects?.gasUsed) return 0;
|
|
@@ -2516,11 +2518,12 @@ async function trySelfFunded(client, keypair, tx) {
|
|
|
2516
2518
|
gasCostSui: extractGasCost(result.effects)
|
|
2517
2519
|
};
|
|
2518
2520
|
}
|
|
2519
|
-
async function tryAutoTopUpThenSelfFund(client, keypair,
|
|
2521
|
+
async function tryAutoTopUpThenSelfFund(client, keypair, buildTx) {
|
|
2520
2522
|
const address = keypair.getPublicKey().toSuiAddress();
|
|
2521
2523
|
const canTopUp = await shouldAutoTopUp(client, address);
|
|
2522
2524
|
if (!canTopUp) return null;
|
|
2523
2525
|
await executeAutoTopUp(client, keypair);
|
|
2526
|
+
const tx = await buildTx();
|
|
2524
2527
|
tx.setSender(address);
|
|
2525
2528
|
const result = await client.signAndExecuteTransaction({
|
|
2526
2529
|
signer: keypair,
|
|
@@ -2538,8 +2541,15 @@ async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
|
|
|
2538
2541
|
async function trySponsored(client, keypair, tx) {
|
|
2539
2542
|
const address = keypair.getPublicKey().toSuiAddress();
|
|
2540
2543
|
tx.setSender(address);
|
|
2541
|
-
|
|
2542
|
-
|
|
2544
|
+
let txJson;
|
|
2545
|
+
let txBcsBase64;
|
|
2546
|
+
try {
|
|
2547
|
+
txJson = tx.serialize();
|
|
2548
|
+
} catch {
|
|
2549
|
+
const bcsBytes = await tx.build({ client });
|
|
2550
|
+
txBcsBase64 = Buffer.from(bcsBytes).toString("base64");
|
|
2551
|
+
}
|
|
2552
|
+
const sponsoredResult = await requestGasSponsorship(txJson ?? "", address, void 0, txBcsBase64);
|
|
2543
2553
|
const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
|
|
2544
2554
|
const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
|
|
2545
2555
|
const result = await client.executeTransactionBlock({
|
|
@@ -2575,8 +2585,7 @@ async function executeWithGas(client, keypair, buildTx, options) {
|
|
|
2575
2585
|
errors.push(`self-funded: ${msg}`);
|
|
2576
2586
|
}
|
|
2577
2587
|
try {
|
|
2578
|
-
const
|
|
2579
|
-
const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
|
|
2588
|
+
const result = await tryAutoTopUpThenSelfFund(client, keypair, buildTx);
|
|
2580
2589
|
if (result) return result;
|
|
2581
2590
|
errors.push("auto-topup: not eligible (low USDC or sufficient SUI)");
|
|
2582
2591
|
} catch (err) {
|
|
@@ -4338,20 +4347,22 @@ To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
|
|
|
4338
4347
|
if (!params.usdAmount || params.usdAmount <= 0) {
|
|
4339
4348
|
throw new T2000Error("INVALID_AMOUNT", "Strategy investment must be > $0");
|
|
4340
4349
|
}
|
|
4350
|
+
this.enforcer.check({ operation: "invest", amount: params.usdAmount });
|
|
4341
4351
|
const bal = await queryBalance(this.client, this._address);
|
|
4342
4352
|
if (bal.available < params.usdAmount) {
|
|
4343
4353
|
throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance. Available: $${bal.available.toFixed(2)}, requested: $${params.usdAmount.toFixed(2)}`);
|
|
4344
4354
|
}
|
|
4345
4355
|
const buys = [];
|
|
4356
|
+
const allocEntries = Object.entries(definition.allocations);
|
|
4346
4357
|
if (params.dryRun) {
|
|
4347
|
-
const
|
|
4348
|
-
for (const [asset, pct] of
|
|
4358
|
+
const swapAdapter2 = this.registry.listSwap()[0];
|
|
4359
|
+
for (const [asset, pct] of allocEntries) {
|
|
4349
4360
|
const assetUsd = params.usdAmount * (pct / 100);
|
|
4350
4361
|
let estAmount = 0;
|
|
4351
4362
|
let estPrice = 0;
|
|
4352
4363
|
try {
|
|
4353
|
-
if (
|
|
4354
|
-
const quote = await
|
|
4364
|
+
if (swapAdapter2) {
|
|
4365
|
+
const quote = await swapAdapter2.getQuote("USDC", asset, assetUsd);
|
|
4355
4366
|
estAmount = quote.expectedOutput;
|
|
4356
4367
|
estPrice = assetUsd / estAmount;
|
|
4357
4368
|
}
|
|
@@ -4361,27 +4372,76 @@ To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
|
|
|
4361
4372
|
}
|
|
4362
4373
|
return { success: true, strategy: params.strategy, totalInvested: params.usdAmount, buys, gasCost: 0, gasMethod: "self-funded" };
|
|
4363
4374
|
}
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4375
|
+
const swapAdapter = this.registry.listSwap()[0];
|
|
4376
|
+
if (!swapAdapter?.addSwapToTx) {
|
|
4377
|
+
throw new T2000Error("PROTOCOL_UNAVAILABLE", "Swap adapter does not support composable PTB");
|
|
4378
|
+
}
|
|
4379
|
+
const swapMetas = [];
|
|
4380
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
4381
|
+
const tx = new Transaction();
|
|
4382
|
+
tx.setSender(this._address);
|
|
4383
|
+
const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
|
|
4384
|
+
if (usdcCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
|
|
4385
|
+
const mergedUsdc = this._mergeCoinsInTx(tx, usdcCoins);
|
|
4386
|
+
const splitAmounts = allocEntries.map(
|
|
4387
|
+
([, pct]) => BigInt(Math.floor(params.usdAmount * (pct / 100) * 10 ** SUPPORTED_ASSETS.USDC.decimals))
|
|
4388
|
+
);
|
|
4389
|
+
const splitCoins = tx.splitCoins(mergedUsdc, splitAmounts);
|
|
4390
|
+
const outputCoins = [];
|
|
4391
|
+
for (let i = 0; i < allocEntries.length; i++) {
|
|
4392
|
+
const [asset] = allocEntries[i];
|
|
4393
|
+
const assetUsd = params.usdAmount * (allocEntries[i][1] / 100);
|
|
4394
|
+
const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
|
|
4395
|
+
tx,
|
|
4396
|
+
this._address,
|
|
4397
|
+
splitCoins[i],
|
|
4398
|
+
"USDC",
|
|
4399
|
+
asset,
|
|
4400
|
+
assetUsd
|
|
4401
|
+
);
|
|
4402
|
+
outputCoins.push(outputCoin);
|
|
4403
|
+
swapMetas.push({ asset, usdAmount: assetUsd, estimatedOut, toDecimals });
|
|
4404
|
+
}
|
|
4405
|
+
tx.transferObjects(outputCoins, this._address);
|
|
4406
|
+
return tx;
|
|
4407
|
+
});
|
|
4408
|
+
const digest = gasResult.digest;
|
|
4409
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4410
|
+
for (const meta of swapMetas) {
|
|
4411
|
+
const amount = meta.estimatedOut / 10 ** meta.toDecimals;
|
|
4412
|
+
const price = meta.usdAmount / amount;
|
|
4413
|
+
this.portfolio.recordBuy({
|
|
4414
|
+
id: `inv_${Date.now()}_${meta.asset}`,
|
|
4415
|
+
type: "buy",
|
|
4416
|
+
asset: meta.asset,
|
|
4417
|
+
amount,
|
|
4418
|
+
price,
|
|
4419
|
+
usdValue: meta.usdAmount,
|
|
4420
|
+
fee: 0,
|
|
4421
|
+
tx: digest,
|
|
4422
|
+
timestamp: now
|
|
4423
|
+
});
|
|
4369
4424
|
this.portfolio.recordStrategyBuy(params.strategy, {
|
|
4370
|
-
id: `strat_${Date.now()}_${asset}`,
|
|
4425
|
+
id: `strat_${Date.now()}_${meta.asset}`,
|
|
4371
4426
|
type: "buy",
|
|
4372
|
-
asset,
|
|
4373
|
-
amount
|
|
4374
|
-
price
|
|
4375
|
-
usdValue:
|
|
4376
|
-
fee:
|
|
4377
|
-
tx:
|
|
4378
|
-
timestamp:
|
|
4427
|
+
asset: meta.asset,
|
|
4428
|
+
amount,
|
|
4429
|
+
price,
|
|
4430
|
+
usdValue: meta.usdAmount,
|
|
4431
|
+
fee: 0,
|
|
4432
|
+
tx: digest,
|
|
4433
|
+
timestamp: now
|
|
4379
4434
|
});
|
|
4380
|
-
buys.push({ asset, usdAmount:
|
|
4381
|
-
totalGas += result.gasCost;
|
|
4382
|
-
gasMethod = result.gasMethod;
|
|
4435
|
+
buys.push({ asset: meta.asset, usdAmount: meta.usdAmount, amount, price, tx: digest });
|
|
4383
4436
|
}
|
|
4384
|
-
return {
|
|
4437
|
+
return {
|
|
4438
|
+
success: true,
|
|
4439
|
+
strategy: params.strategy,
|
|
4440
|
+
totalInvested: params.usdAmount,
|
|
4441
|
+
buys,
|
|
4442
|
+
gasCost: gasResult.gasCostSui,
|
|
4443
|
+
gasMethod: gasResult.gasMethod
|
|
4444
|
+
};
|
|
4385
4445
|
}
|
|
4386
4446
|
async sellStrategy(params) {
|
|
4387
4447
|
this.enforcer.assertNotLocked();
|