@t2000/sdk 0.1.4 → 0.1.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/README.md +10 -5
- package/dist/index.cjs +261 -330
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -6
- package/dist/index.d.ts +0 -6
- package/dist/index.js +261 -330
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -238,39 +238,27 @@ function formatSui(amount) {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
// src/wallet/send.ts
|
|
241
|
-
async function
|
|
241
|
+
async function buildSendTx({
|
|
242
242
|
client,
|
|
243
|
-
|
|
243
|
+
address,
|
|
244
244
|
to,
|
|
245
245
|
amount,
|
|
246
246
|
asset = "USDC"
|
|
247
247
|
}) {
|
|
248
248
|
const recipient = validateAddress(to);
|
|
249
249
|
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
250
|
-
if (!assetInfo) {
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
if (amount <= 0) {
|
|
254
|
-
throw new T2000Error("INVALID_AMOUNT", "Amount must be greater than zero");
|
|
255
|
-
}
|
|
256
|
-
const senderAddress = keypair.getPublicKey().toSuiAddress();
|
|
250
|
+
if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
|
|
251
|
+
if (amount <= 0) throw new T2000Error("INVALID_AMOUNT", "Amount must be greater than zero");
|
|
257
252
|
const rawAmount = displayToRaw(amount, assetInfo.decimals);
|
|
258
253
|
const tx = new transactions.Transaction();
|
|
254
|
+
tx.setSender(address);
|
|
259
255
|
if (asset === "SUI") {
|
|
260
256
|
const [coin] = tx.splitCoins(tx.gas, [rawAmount]);
|
|
261
257
|
tx.transferObjects([coin], recipient);
|
|
262
258
|
} else {
|
|
263
|
-
const coins = await client.getCoins({
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
});
|
|
267
|
-
if (coins.data.length === 0) {
|
|
268
|
-
throw new T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
|
|
269
|
-
}
|
|
270
|
-
const totalBalance = coins.data.reduce(
|
|
271
|
-
(sum, c) => sum + BigInt(c.balance),
|
|
272
|
-
0n
|
|
273
|
-
);
|
|
259
|
+
const coins = await client.getCoins({ owner: address, coinType: assetInfo.type });
|
|
260
|
+
if (coins.data.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
|
|
261
|
+
const totalBalance = coins.data.reduce((sum, c) => sum + BigInt(c.balance), 0n);
|
|
274
262
|
if (totalBalance < rawAmount) {
|
|
275
263
|
throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
|
|
276
264
|
available: Number(totalBalance) / 10 ** assetInfo.decimals,
|
|
@@ -279,28 +267,12 @@ async function buildAndExecuteSend({
|
|
|
279
267
|
}
|
|
280
268
|
const primaryCoin = tx.object(coins.data[0].coinObjectId);
|
|
281
269
|
if (coins.data.length > 1) {
|
|
282
|
-
tx.mergeCoins(
|
|
283
|
-
primaryCoin,
|
|
284
|
-
coins.data.slice(1).map((c) => tx.object(c.coinObjectId))
|
|
285
|
-
);
|
|
270
|
+
tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
|
|
286
271
|
}
|
|
287
272
|
const [sendCoin] = tx.splitCoins(primaryCoin, [rawAmount]);
|
|
288
273
|
tx.transferObjects([sendCoin], recipient);
|
|
289
274
|
}
|
|
290
|
-
|
|
291
|
-
signer: keypair,
|
|
292
|
-
transaction: tx,
|
|
293
|
-
options: { showEffects: true }
|
|
294
|
-
});
|
|
295
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
296
|
-
const gasUsed = result.effects?.gasUsed;
|
|
297
|
-
const gasCost = gasUsed ? Math.abs(
|
|
298
|
-
(Number(gasUsed.computationCost) + Number(gasUsed.storageCost) - Number(gasUsed.storageRebate)) / 1e9
|
|
299
|
-
) : 0;
|
|
300
|
-
return {
|
|
301
|
-
digest: result.digest,
|
|
302
|
-
gasCost
|
|
303
|
-
};
|
|
275
|
+
return tx;
|
|
304
276
|
}
|
|
305
277
|
|
|
306
278
|
// src/wallet/balance.ts
|
|
@@ -398,12 +370,6 @@ var NAVI_BALANCE_DECIMALS = 9;
|
|
|
398
370
|
function clientOpt(client, fresh = false) {
|
|
399
371
|
return { client, ...ENV, ...fresh ? { disableCache: true } : {} };
|
|
400
372
|
}
|
|
401
|
-
function extractGasCost(effects) {
|
|
402
|
-
if (!effects?.gasUsed) return 0;
|
|
403
|
-
return Math.abs(
|
|
404
|
-
(Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
373
|
function rateToApy(rawRate) {
|
|
408
374
|
if (!rawRate || rawRate === "0") return 0;
|
|
409
375
|
return Number(BigInt(rawRate)) / 10 ** RATE_DECIMALS * 100;
|
|
@@ -436,8 +402,7 @@ async function updateOracle(tx, client, address) {
|
|
|
436
402
|
} catch {
|
|
437
403
|
}
|
|
438
404
|
}
|
|
439
|
-
async function
|
|
440
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
405
|
+
async function buildSaveTx(client, address, amount) {
|
|
441
406
|
const rawAmount = Number(usdcToRaw(amount));
|
|
442
407
|
const coins = await lending.getCoins(address, { coinType: USDC_TYPE, client });
|
|
443
408
|
if (!coins || coins.length === 0) {
|
|
@@ -447,25 +412,9 @@ async function save(client, keypair, amount) {
|
|
|
447
412
|
tx.setSender(address);
|
|
448
413
|
const coinObj = lending.mergeCoinsPTB(tx, coins, { balance: rawAmount });
|
|
449
414
|
await lending.depositCoinPTB(tx, USDC_TYPE, coinObj, ENV);
|
|
450
|
-
|
|
451
|
-
signer: keypair,
|
|
452
|
-
transaction: tx,
|
|
453
|
-
options: { showEffects: true }
|
|
454
|
-
});
|
|
455
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
456
|
-
const rates = await getRates();
|
|
457
|
-
return {
|
|
458
|
-
success: true,
|
|
459
|
-
tx: result.digest,
|
|
460
|
-
amount,
|
|
461
|
-
apy: rates.USDC.saveApy,
|
|
462
|
-
fee: 0,
|
|
463
|
-
gasCost: extractGasCost(result.effects),
|
|
464
|
-
gasMethod: "self-funded"
|
|
465
|
-
};
|
|
415
|
+
return tx;
|
|
466
416
|
}
|
|
467
|
-
async function
|
|
468
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
417
|
+
async function buildWithdrawTx(client, address, amount) {
|
|
469
418
|
const state = await lending.getLendingState(address, clientOpt(client, true));
|
|
470
419
|
const usdcPos = findUsdcPosition(state);
|
|
471
420
|
const deposited = usdcPos ? Number(usdcPos.supplyBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
|
|
@@ -477,47 +426,18 @@ async function withdraw(client, keypair, amount) {
|
|
|
477
426
|
await updateOracle(tx, client, address);
|
|
478
427
|
const withdrawnCoin = await lending.withdrawCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
|
|
479
428
|
tx.transferObjects([withdrawnCoin], address);
|
|
480
|
-
|
|
481
|
-
signer: keypair,
|
|
482
|
-
transaction: tx,
|
|
483
|
-
options: { showEffects: true }
|
|
484
|
-
});
|
|
485
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
486
|
-
return {
|
|
487
|
-
success: true,
|
|
488
|
-
tx: result.digest,
|
|
489
|
-
amount: effectiveAmount,
|
|
490
|
-
gasCost: extractGasCost(result.effects),
|
|
491
|
-
gasMethod: "self-funded"
|
|
492
|
-
};
|
|
429
|
+
return { tx, effectiveAmount };
|
|
493
430
|
}
|
|
494
|
-
async function
|
|
495
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
431
|
+
async function buildBorrowTx(client, address, amount) {
|
|
496
432
|
const rawAmount = Number(usdcToRaw(amount));
|
|
497
433
|
const tx = new transactions.Transaction();
|
|
498
434
|
tx.setSender(address);
|
|
499
435
|
await updateOracle(tx, client, address);
|
|
500
436
|
const borrowedCoin = await lending.borrowCoinPTB(tx, USDC_TYPE, rawAmount, ENV);
|
|
501
437
|
tx.transferObjects([borrowedCoin], address);
|
|
502
|
-
|
|
503
|
-
signer: keypair,
|
|
504
|
-
transaction: tx,
|
|
505
|
-
options: { showEffects: true }
|
|
506
|
-
});
|
|
507
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
508
|
-
const hf = await lending.getHealthFactor(address, clientOpt(client, true));
|
|
509
|
-
return {
|
|
510
|
-
success: true,
|
|
511
|
-
tx: result.digest,
|
|
512
|
-
amount,
|
|
513
|
-
fee: 0,
|
|
514
|
-
healthFactor: hf,
|
|
515
|
-
gasCost: extractGasCost(result.effects),
|
|
516
|
-
gasMethod: "self-funded"
|
|
517
|
-
};
|
|
438
|
+
return tx;
|
|
518
439
|
}
|
|
519
|
-
async function
|
|
520
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
440
|
+
async function buildRepayTx(client, address, amount) {
|
|
521
441
|
const rawAmount = Number(usdcToRaw(amount));
|
|
522
442
|
const coins = await lending.getCoins(address, { coinType: USDC_TYPE, client });
|
|
523
443
|
if (!coins || coins.length === 0) {
|
|
@@ -527,23 +447,7 @@ async function repay(client, keypair, amount) {
|
|
|
527
447
|
tx.setSender(address);
|
|
528
448
|
const coinObj = lending.mergeCoinsPTB(tx, coins, { balance: rawAmount });
|
|
529
449
|
await lending.repayCoinPTB(tx, USDC_TYPE, coinObj, { ...ENV, amount: rawAmount });
|
|
530
|
-
|
|
531
|
-
signer: keypair,
|
|
532
|
-
transaction: tx,
|
|
533
|
-
options: { showEffects: true }
|
|
534
|
-
});
|
|
535
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
536
|
-
const state = await lending.getLendingState(address, clientOpt(client, true));
|
|
537
|
-
const usdcPos = findUsdcPosition(state);
|
|
538
|
-
const remainingDebt = usdcPos ? Number(usdcPos.borrowBalance) / 10 ** NAVI_BALANCE_DECIMALS : 0;
|
|
539
|
-
return {
|
|
540
|
-
success: true,
|
|
541
|
-
tx: result.digest,
|
|
542
|
-
amount,
|
|
543
|
-
remainingDebt,
|
|
544
|
-
gasCost: extractGasCost(result.effects),
|
|
545
|
-
gasMethod: "self-funded"
|
|
546
|
-
};
|
|
450
|
+
return tx;
|
|
547
451
|
}
|
|
548
452
|
async function getHealthFactor(client, keypair) {
|
|
549
453
|
const address = keypair.getPublicKey().toSuiAddress();
|
|
@@ -635,10 +539,6 @@ async function maxBorrowAmount(client, keypair) {
|
|
|
635
539
|
};
|
|
636
540
|
}
|
|
637
541
|
var DEFAULT_SLIPPAGE_BPS = 300;
|
|
638
|
-
function extractGasCost2(effects) {
|
|
639
|
-
if (!effects?.gasUsed) return 0;
|
|
640
|
-
return (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9;
|
|
641
|
-
}
|
|
642
542
|
function isA2B(from) {
|
|
643
543
|
return from === "USDC";
|
|
644
544
|
}
|
|
@@ -649,9 +549,8 @@ function getCetusSDK() {
|
|
|
649
549
|
}
|
|
650
550
|
return _cetusSDK;
|
|
651
551
|
}
|
|
652
|
-
async function
|
|
653
|
-
const {
|
|
654
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
552
|
+
async function buildSwapTx(params) {
|
|
553
|
+
const { address, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
|
|
655
554
|
const a2b = isA2B(fromAsset);
|
|
656
555
|
const fromInfo = SUPPORTED_ASSETS[fromAsset];
|
|
657
556
|
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
@@ -682,32 +581,10 @@ async function executeSwap(params) {
|
|
|
682
581
|
amount: preSwapResult.amount.toString(),
|
|
683
582
|
amount_limit: amountLimit.toString()
|
|
684
583
|
});
|
|
685
|
-
const result = await client.signAndExecuteTransaction({
|
|
686
|
-
signer: keypair,
|
|
687
|
-
transaction: swapPayload,
|
|
688
|
-
options: { showEffects: true, showBalanceChanges: true }
|
|
689
|
-
});
|
|
690
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
691
|
-
let actualReceived = 0;
|
|
692
|
-
if (result.balanceChanges) {
|
|
693
|
-
for (const change of result.balanceChanges) {
|
|
694
|
-
if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
|
|
695
|
-
const amt = Number(change.amount) / 10 ** toInfo.decimals;
|
|
696
|
-
if (amt > 0) actualReceived += amt;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const expectedOutput = estimatedOut / 10 ** toInfo.decimals;
|
|
701
|
-
if (actualReceived === 0) actualReceived = expectedOutput;
|
|
702
|
-
const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
|
|
703
584
|
return {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
toAmount: actualReceived,
|
|
708
|
-
toAsset,
|
|
709
|
-
priceImpact,
|
|
710
|
-
gasCost: extractGasCost2(result.effects)
|
|
585
|
+
tx: swapPayload,
|
|
586
|
+
estimatedOut,
|
|
587
|
+
toDecimals: toInfo.decimals
|
|
711
588
|
};
|
|
712
589
|
}
|
|
713
590
|
async function getPoolPrice(client) {
|
|
@@ -984,12 +861,127 @@ async function executeAutoTopUp(client, keypair) {
|
|
|
984
861
|
};
|
|
985
862
|
}
|
|
986
863
|
|
|
864
|
+
// src/gas/manager.ts
|
|
865
|
+
function extractGasCost(effects) {
|
|
866
|
+
if (!effects?.gasUsed) return 0;
|
|
867
|
+
return (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9;
|
|
868
|
+
}
|
|
869
|
+
async function getSuiBalance(client, address) {
|
|
870
|
+
const bal = await client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type });
|
|
871
|
+
return BigInt(bal.totalBalance);
|
|
872
|
+
}
|
|
873
|
+
async function trySelfFunded(client, keypair, tx) {
|
|
874
|
+
const address = keypair.getPublicKey().toSuiAddress();
|
|
875
|
+
const suiBalance = await getSuiBalance(client, address);
|
|
876
|
+
if (suiBalance < AUTO_TOPUP_THRESHOLD) return null;
|
|
877
|
+
tx.setSender(address);
|
|
878
|
+
const result = await client.signAndExecuteTransaction({
|
|
879
|
+
signer: keypair,
|
|
880
|
+
transaction: tx,
|
|
881
|
+
options: { showEffects: true }
|
|
882
|
+
});
|
|
883
|
+
await client.waitForTransaction({ digest: result.digest });
|
|
884
|
+
return {
|
|
885
|
+
digest: result.digest,
|
|
886
|
+
effects: result.effects,
|
|
887
|
+
gasMethod: "self-funded",
|
|
888
|
+
gasCostSui: extractGasCost(result.effects)
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
|
|
892
|
+
const address = keypair.getPublicKey().toSuiAddress();
|
|
893
|
+
const canTopUp = await shouldAutoTopUp(client, address);
|
|
894
|
+
if (!canTopUp) return null;
|
|
895
|
+
try {
|
|
896
|
+
await executeAutoTopUp(client, keypair);
|
|
897
|
+
} catch {
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
tx.setSender(address);
|
|
901
|
+
const result = await client.signAndExecuteTransaction({
|
|
902
|
+
signer: keypair,
|
|
903
|
+
transaction: tx,
|
|
904
|
+
options: { showEffects: true }
|
|
905
|
+
});
|
|
906
|
+
await client.waitForTransaction({ digest: result.digest });
|
|
907
|
+
return {
|
|
908
|
+
digest: result.digest,
|
|
909
|
+
effects: result.effects,
|
|
910
|
+
gasMethod: "auto-topup",
|
|
911
|
+
gasCostSui: extractGasCost(result.effects)
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
async function trySponsored(client, keypair, tx) {
|
|
915
|
+
const address = keypair.getPublicKey().toSuiAddress();
|
|
916
|
+
tx.setSender(address);
|
|
917
|
+
let txBytes;
|
|
918
|
+
try {
|
|
919
|
+
txBytes = await tx.build({ client, onlyTransactionKind: true });
|
|
920
|
+
} catch {
|
|
921
|
+
return null;
|
|
922
|
+
}
|
|
923
|
+
const txBytesBase64 = Buffer.from(txBytes).toString("base64");
|
|
924
|
+
let sponsoredResult;
|
|
925
|
+
try {
|
|
926
|
+
sponsoredResult = await requestGasSponsorship(txBytesBase64, address);
|
|
927
|
+
} catch {
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
|
|
931
|
+
const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
|
|
932
|
+
const result = await client.executeTransactionBlock({
|
|
933
|
+
transactionBlock: sponsoredResult.txBytes,
|
|
934
|
+
signature: [agentSig, sponsoredResult.sponsorSignature],
|
|
935
|
+
options: { showEffects: true }
|
|
936
|
+
});
|
|
937
|
+
await client.waitForTransaction({ digest: result.digest });
|
|
938
|
+
const gasCost = extractGasCost(result.effects);
|
|
939
|
+
reportGasUsage(address, result.digest, gasCost, 0, sponsoredResult.type);
|
|
940
|
+
return {
|
|
941
|
+
digest: result.digest,
|
|
942
|
+
effects: result.effects,
|
|
943
|
+
gasMethod: "sponsored",
|
|
944
|
+
gasCostSui: gasCost
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
async function executeWithGas(client, keypair, buildTx) {
|
|
948
|
+
const errors = [];
|
|
949
|
+
try {
|
|
950
|
+
const tx = await buildTx();
|
|
951
|
+
const result = await trySelfFunded(client, keypair, tx);
|
|
952
|
+
if (result) return result;
|
|
953
|
+
errors.push("self-funded: SUI below threshold");
|
|
954
|
+
} catch (err) {
|
|
955
|
+
errors.push(`self-funded: ${err instanceof Error ? err.message : String(err)}`);
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
const tx = await buildTx();
|
|
959
|
+
const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
|
|
960
|
+
if (result) return result;
|
|
961
|
+
errors.push("auto-topup: not eligible (low USDC or sufficient SUI)");
|
|
962
|
+
} catch (err) {
|
|
963
|
+
errors.push(`auto-topup: ${err instanceof Error ? err.message : String(err)}`);
|
|
964
|
+
}
|
|
965
|
+
try {
|
|
966
|
+
const tx = await buildTx();
|
|
967
|
+
const result = await trySponsored(client, keypair, tx);
|
|
968
|
+
if (result) return result;
|
|
969
|
+
errors.push("sponsored: returned null");
|
|
970
|
+
} catch (err) {
|
|
971
|
+
errors.push(`sponsored: ${err instanceof Error ? err.message : String(err)}`);
|
|
972
|
+
}
|
|
973
|
+
throw new T2000Error(
|
|
974
|
+
"INSUFFICIENT_GAS",
|
|
975
|
+
`No SUI for gas and Gas Station unavailable. Fund your wallet with SUI or USDC. [${errors.join(" | ")}]`,
|
|
976
|
+
{ reason: "all_gas_methods_exhausted", errors }
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
987
980
|
// src/t2000.ts
|
|
988
981
|
var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
989
982
|
keypair;
|
|
990
983
|
client;
|
|
991
984
|
_address;
|
|
992
|
-
_lastGasMethod = "self-funded";
|
|
993
985
|
constructor(keypair, client) {
|
|
994
986
|
super();
|
|
995
987
|
this.keypair = keypair;
|
|
@@ -1043,29 +1035,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1043
1035
|
return { agent, address, sponsored };
|
|
1044
1036
|
}
|
|
1045
1037
|
// -- Gas --
|
|
1046
|
-
/**
|
|
1047
|
-
* Ensure the agent has enough SUI for gas.
|
|
1048
|
-
* If SUI is low and USDC is available, auto-swaps $1 USDC → SUI.
|
|
1049
|
-
*/
|
|
1050
|
-
async ensureGas() {
|
|
1051
|
-
this._lastGasMethod = "self-funded";
|
|
1052
|
-
const needsTopUp = await shouldAutoTopUp(this.client, this._address);
|
|
1053
|
-
if (!needsTopUp) return;
|
|
1054
|
-
try {
|
|
1055
|
-
const result = await executeAutoTopUp(this.client, this.keypair);
|
|
1056
|
-
this._lastGasMethod = "auto-topup";
|
|
1057
|
-
this.emit("gasAutoTopUp", {
|
|
1058
|
-
usdcSpent: result.usdcSpent,
|
|
1059
|
-
suiReceived: result.suiReceived
|
|
1060
|
-
});
|
|
1061
|
-
} catch {
|
|
1062
|
-
this.emit("gasStationFallback", {
|
|
1063
|
-
reason: "auto-topup failed",
|
|
1064
|
-
method: "self-funded",
|
|
1065
|
-
suiUsed: 0
|
|
1066
|
-
});
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
1038
|
/** SuiClient used by this agent — exposed for x402 and other integrations. */
|
|
1070
1039
|
get suiClient() {
|
|
1071
1040
|
return this.client;
|
|
@@ -1083,24 +1052,23 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1083
1052
|
if (!(asset in SUPPORTED_ASSETS)) {
|
|
1084
1053
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
|
|
1085
1054
|
}
|
|
1086
|
-
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
});
|
|
1055
|
+
const sendAmount = params.amount;
|
|
1056
|
+
const sendTo = params.to;
|
|
1057
|
+
const gasResult = await executeWithGas(
|
|
1058
|
+
this.client,
|
|
1059
|
+
this.keypair,
|
|
1060
|
+
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset })
|
|
1061
|
+
);
|
|
1094
1062
|
const balance = await this.balance();
|
|
1095
|
-
this.emitBalanceChange(asset,
|
|
1063
|
+
this.emitBalanceChange(asset, sendAmount, "send", gasResult.digest);
|
|
1096
1064
|
return {
|
|
1097
1065
|
success: true,
|
|
1098
|
-
tx:
|
|
1099
|
-
amount:
|
|
1066
|
+
tx: gasResult.digest,
|
|
1067
|
+
amount: sendAmount,
|
|
1100
1068
|
to: params.to,
|
|
1101
|
-
gasCost:
|
|
1069
|
+
gasCost: gasResult.gasCostSui,
|
|
1102
1070
|
gasCostUnit: "SUI",
|
|
1103
|
-
gasMethod:
|
|
1071
|
+
gasMethod: gasResult.gasMethod,
|
|
1104
1072
|
balance
|
|
1105
1073
|
};
|
|
1106
1074
|
}
|
|
@@ -1156,11 +1124,24 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1156
1124
|
amount = params.amount;
|
|
1157
1125
|
}
|
|
1158
1126
|
const fee = calculateFee("save", amount);
|
|
1159
|
-
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1127
|
+
const saveAmount = amount;
|
|
1128
|
+
const gasResult = await executeWithGas(
|
|
1129
|
+
this.client,
|
|
1130
|
+
this.keypair,
|
|
1131
|
+
() => buildSaveTx(this.client, this._address, saveAmount)
|
|
1132
|
+
);
|
|
1133
|
+
const rates = await getRates(this.client);
|
|
1134
|
+
reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
|
|
1135
|
+
this.emitBalanceChange("USDC", saveAmount, "save", gasResult.digest);
|
|
1136
|
+
return {
|
|
1137
|
+
success: true,
|
|
1138
|
+
tx: gasResult.digest,
|
|
1139
|
+
amount: saveAmount,
|
|
1140
|
+
apy: rates.USDC.saveApy,
|
|
1141
|
+
fee: fee.amount,
|
|
1142
|
+
gasCost: gasResult.gasCostSui,
|
|
1143
|
+
gasMethod: gasResult.gasMethod
|
|
1144
|
+
};
|
|
1164
1145
|
}
|
|
1165
1146
|
async withdraw(params) {
|
|
1166
1147
|
let amount;
|
|
@@ -1188,10 +1169,21 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1188
1169
|
}
|
|
1189
1170
|
}
|
|
1190
1171
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
this.
|
|
1194
|
-
|
|
1172
|
+
const withdrawAmount = amount;
|
|
1173
|
+
let effectiveAmount = withdrawAmount;
|
|
1174
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1175
|
+
const built = await buildWithdrawTx(this.client, this._address, withdrawAmount);
|
|
1176
|
+
effectiveAmount = built.effectiveAmount;
|
|
1177
|
+
return built.tx;
|
|
1178
|
+
});
|
|
1179
|
+
this.emitBalanceChange("USDC", effectiveAmount, "withdraw", gasResult.digest);
|
|
1180
|
+
return {
|
|
1181
|
+
success: true,
|
|
1182
|
+
tx: gasResult.digest,
|
|
1183
|
+
amount: effectiveAmount,
|
|
1184
|
+
gasCost: gasResult.gasCostSui,
|
|
1185
|
+
gasMethod: gasResult.gasMethod
|
|
1186
|
+
};
|
|
1195
1187
|
}
|
|
1196
1188
|
async maxWithdraw() {
|
|
1197
1189
|
return maxWithdrawAmount(this.client, this.keypair);
|
|
@@ -1206,27 +1198,52 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1206
1198
|
});
|
|
1207
1199
|
}
|
|
1208
1200
|
const fee = calculateFee("borrow", params.amount);
|
|
1209
|
-
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1201
|
+
const borrowAmount = params.amount;
|
|
1202
|
+
const gasResult = await executeWithGas(
|
|
1203
|
+
this.client,
|
|
1204
|
+
this.keypair,
|
|
1205
|
+
() => buildBorrowTx(this.client, this._address, borrowAmount)
|
|
1206
|
+
);
|
|
1207
|
+
const hf = await getHealthFactor(this.client, this.keypair);
|
|
1208
|
+
reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
|
|
1209
|
+
this.emitBalanceChange("USDC", borrowAmount, "borrow", gasResult.digest);
|
|
1210
|
+
return {
|
|
1211
|
+
success: true,
|
|
1212
|
+
tx: gasResult.digest,
|
|
1213
|
+
amount: borrowAmount,
|
|
1214
|
+
fee: fee.amount,
|
|
1215
|
+
healthFactor: hf.healthFactor,
|
|
1216
|
+
gasCost: gasResult.gasCostSui,
|
|
1217
|
+
gasMethod: gasResult.gasMethod
|
|
1218
|
+
};
|
|
1214
1219
|
}
|
|
1215
1220
|
async repay(params) {
|
|
1216
1221
|
let amount;
|
|
1217
1222
|
if (params.amount === "all") {
|
|
1218
|
-
const
|
|
1219
|
-
amount =
|
|
1223
|
+
const hf2 = await this.healthFactor();
|
|
1224
|
+
amount = hf2.borrowed;
|
|
1220
1225
|
if (amount <= 0) {
|
|
1221
1226
|
throw new T2000Error("NO_COLLATERAL", "No outstanding borrow to repay");
|
|
1222
1227
|
}
|
|
1223
1228
|
} else {
|
|
1224
1229
|
amount = params.amount;
|
|
1225
1230
|
}
|
|
1226
|
-
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1231
|
+
const repayAmount = amount;
|
|
1232
|
+
const gasResult = await executeWithGas(
|
|
1233
|
+
this.client,
|
|
1234
|
+
this.keypair,
|
|
1235
|
+
() => buildRepayTx(this.client, this._address, repayAmount)
|
|
1236
|
+
);
|
|
1237
|
+
const hf = await getHealthFactor(this.client, this.keypair);
|
|
1238
|
+
this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
|
|
1239
|
+
return {
|
|
1240
|
+
success: true,
|
|
1241
|
+
tx: gasResult.digest,
|
|
1242
|
+
amount: repayAmount,
|
|
1243
|
+
remainingDebt: hf.borrowed,
|
|
1244
|
+
gasCost: gasResult.gasCostSui,
|
|
1245
|
+
gasMethod: gasResult.gasMethod
|
|
1246
|
+
};
|
|
1230
1247
|
}
|
|
1231
1248
|
async maxBorrow() {
|
|
1232
1249
|
return maxBorrowAmount(this.client, this.keypair);
|
|
@@ -1251,28 +1268,51 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1251
1268
|
throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
|
|
1252
1269
|
}
|
|
1253
1270
|
const fee = calculateFee("swap", params.amount);
|
|
1254
|
-
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1271
|
+
const swapAmount = params.amount;
|
|
1272
|
+
const slippageBps = params.maxSlippage ? params.maxSlippage * 100 : void 0;
|
|
1273
|
+
let swapMeta = { estimatedOut: 0, toDecimals: 0 };
|
|
1274
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1275
|
+
const built = await buildSwapTx({
|
|
1276
|
+
client: this.client,
|
|
1277
|
+
address: this._address,
|
|
1278
|
+
fromAsset,
|
|
1279
|
+
toAsset,
|
|
1280
|
+
amount: swapAmount,
|
|
1281
|
+
maxSlippageBps: slippageBps
|
|
1282
|
+
});
|
|
1283
|
+
swapMeta = { estimatedOut: built.estimatedOut, toDecimals: built.toDecimals };
|
|
1284
|
+
return built.tx;
|
|
1262
1285
|
});
|
|
1263
|
-
|
|
1264
|
-
|
|
1286
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
1287
|
+
const txDetail = await this.client.getTransactionBlock({
|
|
1288
|
+
digest: gasResult.digest,
|
|
1289
|
+
options: { showBalanceChanges: true }
|
|
1290
|
+
});
|
|
1291
|
+
let actualReceived = 0;
|
|
1292
|
+
if (txDetail.balanceChanges) {
|
|
1293
|
+
for (const change of txDetail.balanceChanges) {
|
|
1294
|
+
if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === this._address) {
|
|
1295
|
+
const amt = Number(change.amount) / 10 ** toInfo.decimals;
|
|
1296
|
+
if (amt > 0) actualReceived += amt;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
const expectedOutput = swapMeta.estimatedOut / 10 ** swapMeta.toDecimals;
|
|
1301
|
+
if (actualReceived === 0) actualReceived = expectedOutput;
|
|
1302
|
+
const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
|
|
1303
|
+
reportFee(this._address, "swap", fee.amount, fee.rate, gasResult.digest);
|
|
1304
|
+
this.emitBalanceChange(fromAsset, swapAmount, "swap", gasResult.digest);
|
|
1265
1305
|
return {
|
|
1266
1306
|
success: true,
|
|
1267
|
-
tx:
|
|
1268
|
-
fromAmount:
|
|
1269
|
-
fromAsset
|
|
1270
|
-
toAmount:
|
|
1271
|
-
toAsset
|
|
1272
|
-
priceImpact
|
|
1307
|
+
tx: gasResult.digest,
|
|
1308
|
+
fromAmount: swapAmount,
|
|
1309
|
+
fromAsset,
|
|
1310
|
+
toAmount: actualReceived,
|
|
1311
|
+
toAsset,
|
|
1312
|
+
priceImpact,
|
|
1273
1313
|
fee: fee.amount,
|
|
1274
|
-
gasCost:
|
|
1275
|
-
gasMethod:
|
|
1314
|
+
gasCost: gasResult.gasCostSui,
|
|
1315
|
+
gasMethod: gasResult.gasMethod
|
|
1276
1316
|
};
|
|
1277
1317
|
}
|
|
1278
1318
|
async swapQuote(params) {
|
|
@@ -1403,115 +1443,6 @@ function parseMoveAbort(errorStr) {
|
|
|
1403
1443
|
return { reason: errorStr };
|
|
1404
1444
|
}
|
|
1405
1445
|
|
|
1406
|
-
// src/gas/manager.ts
|
|
1407
|
-
function extractGasCost3(effects) {
|
|
1408
|
-
if (!effects?.gasUsed) return 0;
|
|
1409
|
-
return (Number(effects.gasUsed.computationCost) + Number(effects.gasUsed.storageCost) - Number(effects.gasUsed.storageRebate)) / 1e9;
|
|
1410
|
-
}
|
|
1411
|
-
async function getSuiBalance(client, address) {
|
|
1412
|
-
const bal = await client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type });
|
|
1413
|
-
return BigInt(bal.totalBalance);
|
|
1414
|
-
}
|
|
1415
|
-
async function trySelfFunded(client, keypair, tx) {
|
|
1416
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
1417
|
-
const suiBalance = await getSuiBalance(client, address);
|
|
1418
|
-
if (suiBalance < AUTO_TOPUP_THRESHOLD) return null;
|
|
1419
|
-
tx.setSender(address);
|
|
1420
|
-
const result = await client.signAndExecuteTransaction({
|
|
1421
|
-
signer: keypair,
|
|
1422
|
-
transaction: tx,
|
|
1423
|
-
options: { showEffects: true }
|
|
1424
|
-
});
|
|
1425
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
1426
|
-
return {
|
|
1427
|
-
digest: result.digest,
|
|
1428
|
-
effects: result.effects,
|
|
1429
|
-
gasMethod: "self-funded",
|
|
1430
|
-
gasCostSui: extractGasCost3(result.effects)
|
|
1431
|
-
};
|
|
1432
|
-
}
|
|
1433
|
-
async function tryAutoTopUpThenSelfFund(client, keypair, tx) {
|
|
1434
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
1435
|
-
const canTopUp = await shouldAutoTopUp(client, address);
|
|
1436
|
-
if (!canTopUp) return null;
|
|
1437
|
-
try {
|
|
1438
|
-
await executeAutoTopUp(client, keypair);
|
|
1439
|
-
} catch {
|
|
1440
|
-
return null;
|
|
1441
|
-
}
|
|
1442
|
-
tx.setSender(address);
|
|
1443
|
-
const result = await client.signAndExecuteTransaction({
|
|
1444
|
-
signer: keypair,
|
|
1445
|
-
transaction: tx,
|
|
1446
|
-
options: { showEffects: true }
|
|
1447
|
-
});
|
|
1448
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
1449
|
-
return {
|
|
1450
|
-
digest: result.digest,
|
|
1451
|
-
effects: result.effects,
|
|
1452
|
-
gasMethod: "auto-topup",
|
|
1453
|
-
gasCostSui: extractGasCost3(result.effects)
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
async function trySponsored(client, keypair, tx) {
|
|
1457
|
-
const address = keypair.getPublicKey().toSuiAddress();
|
|
1458
|
-
tx.setSender(address);
|
|
1459
|
-
let txBytes;
|
|
1460
|
-
try {
|
|
1461
|
-
txBytes = await tx.build({ client, onlyTransactionKind: true });
|
|
1462
|
-
} catch {
|
|
1463
|
-
return null;
|
|
1464
|
-
}
|
|
1465
|
-
const txBytesBase64 = Buffer.from(txBytes).toString("base64");
|
|
1466
|
-
let sponsoredResult;
|
|
1467
|
-
try {
|
|
1468
|
-
sponsoredResult = await requestGasSponsorship(txBytesBase64, address);
|
|
1469
|
-
} catch {
|
|
1470
|
-
return null;
|
|
1471
|
-
}
|
|
1472
|
-
const sponsoredTxBytes = Buffer.from(sponsoredResult.txBytes, "base64");
|
|
1473
|
-
const { signature: agentSig } = await keypair.signTransaction(sponsoredTxBytes);
|
|
1474
|
-
const result = await client.executeTransactionBlock({
|
|
1475
|
-
transactionBlock: sponsoredResult.txBytes,
|
|
1476
|
-
signature: [agentSig, sponsoredResult.sponsorSignature],
|
|
1477
|
-
options: { showEffects: true }
|
|
1478
|
-
});
|
|
1479
|
-
await client.waitForTransaction({ digest: result.digest });
|
|
1480
|
-
const gasCost = extractGasCost3(result.effects);
|
|
1481
|
-
reportGasUsage(address, result.digest, gasCost, 0, sponsoredResult.type);
|
|
1482
|
-
return {
|
|
1483
|
-
digest: result.digest,
|
|
1484
|
-
effects: result.effects,
|
|
1485
|
-
gasMethod: "sponsored",
|
|
1486
|
-
gasCostSui: gasCost
|
|
1487
|
-
};
|
|
1488
|
-
}
|
|
1489
|
-
async function executeWithGas(client, keypair, buildTx) {
|
|
1490
|
-
try {
|
|
1491
|
-
const tx = await buildTx();
|
|
1492
|
-
const result = await trySelfFunded(client, keypair, tx);
|
|
1493
|
-
if (result) return result;
|
|
1494
|
-
} catch {
|
|
1495
|
-
}
|
|
1496
|
-
try {
|
|
1497
|
-
const tx = await buildTx();
|
|
1498
|
-
const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
|
|
1499
|
-
if (result) return result;
|
|
1500
|
-
} catch {
|
|
1501
|
-
}
|
|
1502
|
-
try {
|
|
1503
|
-
const tx = await buildTx();
|
|
1504
|
-
const result = await trySponsored(client, keypair, tx);
|
|
1505
|
-
if (result) return result;
|
|
1506
|
-
} catch {
|
|
1507
|
-
}
|
|
1508
|
-
throw new T2000Error(
|
|
1509
|
-
"INSUFFICIENT_GAS",
|
|
1510
|
-
"No SUI for gas and Gas Station unavailable. Fund your wallet with SUI or USDC.",
|
|
1511
|
-
{ reason: "all_gas_methods_exhausted" }
|
|
1512
|
-
);
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
1446
|
exports.BPS_DENOMINATOR = BPS_DENOMINATOR;
|
|
1516
1447
|
exports.CLOCK_ID = CLOCK_ID;
|
|
1517
1448
|
exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
|