@t2000/sdk 0.1.4 → 0.1.5
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 +254 -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 +254 -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,120 @@ 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
|
+
try {
|
|
949
|
+
const tx = await buildTx();
|
|
950
|
+
const result = await trySelfFunded(client, keypair, tx);
|
|
951
|
+
if (result) return result;
|
|
952
|
+
} catch {
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
const tx = await buildTx();
|
|
956
|
+
const result = await tryAutoTopUpThenSelfFund(client, keypair, tx);
|
|
957
|
+
if (result) return result;
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
try {
|
|
961
|
+
const tx = await buildTx();
|
|
962
|
+
const result = await trySponsored(client, keypair, tx);
|
|
963
|
+
if (result) return result;
|
|
964
|
+
} catch {
|
|
965
|
+
}
|
|
966
|
+
throw new T2000Error(
|
|
967
|
+
"INSUFFICIENT_GAS",
|
|
968
|
+
"No SUI for gas and Gas Station unavailable. Fund your wallet with SUI or USDC.",
|
|
969
|
+
{ reason: "all_gas_methods_exhausted" }
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
|
|
987
973
|
// src/t2000.ts
|
|
988
974
|
var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
989
975
|
keypair;
|
|
990
976
|
client;
|
|
991
977
|
_address;
|
|
992
|
-
_lastGasMethod = "self-funded";
|
|
993
978
|
constructor(keypair, client) {
|
|
994
979
|
super();
|
|
995
980
|
this.keypair = keypair;
|
|
@@ -1043,29 +1028,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1043
1028
|
return { agent, address, sponsored };
|
|
1044
1029
|
}
|
|
1045
1030
|
// -- 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
1031
|
/** SuiClient used by this agent — exposed for x402 and other integrations. */
|
|
1070
1032
|
get suiClient() {
|
|
1071
1033
|
return this.client;
|
|
@@ -1083,24 +1045,23 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1083
1045
|
if (!(asset in SUPPORTED_ASSETS)) {
|
|
1084
1046
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
|
|
1085
1047
|
}
|
|
1086
|
-
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
});
|
|
1048
|
+
const sendAmount = params.amount;
|
|
1049
|
+
const sendTo = params.to;
|
|
1050
|
+
const gasResult = await executeWithGas(
|
|
1051
|
+
this.client,
|
|
1052
|
+
this.keypair,
|
|
1053
|
+
() => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset })
|
|
1054
|
+
);
|
|
1094
1055
|
const balance = await this.balance();
|
|
1095
|
-
this.emitBalanceChange(asset,
|
|
1056
|
+
this.emitBalanceChange(asset, sendAmount, "send", gasResult.digest);
|
|
1096
1057
|
return {
|
|
1097
1058
|
success: true,
|
|
1098
|
-
tx:
|
|
1099
|
-
amount:
|
|
1059
|
+
tx: gasResult.digest,
|
|
1060
|
+
amount: sendAmount,
|
|
1100
1061
|
to: params.to,
|
|
1101
|
-
gasCost:
|
|
1062
|
+
gasCost: gasResult.gasCostSui,
|
|
1102
1063
|
gasCostUnit: "SUI",
|
|
1103
|
-
gasMethod:
|
|
1064
|
+
gasMethod: gasResult.gasMethod,
|
|
1104
1065
|
balance
|
|
1105
1066
|
};
|
|
1106
1067
|
}
|
|
@@ -1156,11 +1117,24 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1156
1117
|
amount = params.amount;
|
|
1157
1118
|
}
|
|
1158
1119
|
const fee = calculateFee("save", amount);
|
|
1159
|
-
|
|
1160
|
-
const
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1120
|
+
const saveAmount = amount;
|
|
1121
|
+
const gasResult = await executeWithGas(
|
|
1122
|
+
this.client,
|
|
1123
|
+
this.keypair,
|
|
1124
|
+
() => buildSaveTx(this.client, this._address, saveAmount)
|
|
1125
|
+
);
|
|
1126
|
+
const rates = await getRates(this.client);
|
|
1127
|
+
reportFee(this._address, "save", fee.amount, fee.rate, gasResult.digest);
|
|
1128
|
+
this.emitBalanceChange("USDC", saveAmount, "save", gasResult.digest);
|
|
1129
|
+
return {
|
|
1130
|
+
success: true,
|
|
1131
|
+
tx: gasResult.digest,
|
|
1132
|
+
amount: saveAmount,
|
|
1133
|
+
apy: rates.USDC.saveApy,
|
|
1134
|
+
fee: fee.amount,
|
|
1135
|
+
gasCost: gasResult.gasCostSui,
|
|
1136
|
+
gasMethod: gasResult.gasMethod
|
|
1137
|
+
};
|
|
1164
1138
|
}
|
|
1165
1139
|
async withdraw(params) {
|
|
1166
1140
|
let amount;
|
|
@@ -1188,10 +1162,21 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1188
1162
|
}
|
|
1189
1163
|
}
|
|
1190
1164
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
this.
|
|
1194
|
-
|
|
1165
|
+
const withdrawAmount = amount;
|
|
1166
|
+
let effectiveAmount = withdrawAmount;
|
|
1167
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1168
|
+
const built = await buildWithdrawTx(this.client, this._address, withdrawAmount);
|
|
1169
|
+
effectiveAmount = built.effectiveAmount;
|
|
1170
|
+
return built.tx;
|
|
1171
|
+
});
|
|
1172
|
+
this.emitBalanceChange("USDC", effectiveAmount, "withdraw", gasResult.digest);
|
|
1173
|
+
return {
|
|
1174
|
+
success: true,
|
|
1175
|
+
tx: gasResult.digest,
|
|
1176
|
+
amount: effectiveAmount,
|
|
1177
|
+
gasCost: gasResult.gasCostSui,
|
|
1178
|
+
gasMethod: gasResult.gasMethod
|
|
1179
|
+
};
|
|
1195
1180
|
}
|
|
1196
1181
|
async maxWithdraw() {
|
|
1197
1182
|
return maxWithdrawAmount(this.client, this.keypair);
|
|
@@ -1206,27 +1191,52 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1206
1191
|
});
|
|
1207
1192
|
}
|
|
1208
1193
|
const fee = calculateFee("borrow", params.amount);
|
|
1209
|
-
|
|
1210
|
-
const
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1194
|
+
const borrowAmount = params.amount;
|
|
1195
|
+
const gasResult = await executeWithGas(
|
|
1196
|
+
this.client,
|
|
1197
|
+
this.keypair,
|
|
1198
|
+
() => buildBorrowTx(this.client, this._address, borrowAmount)
|
|
1199
|
+
);
|
|
1200
|
+
const hf = await getHealthFactor(this.client, this.keypair);
|
|
1201
|
+
reportFee(this._address, "borrow", fee.amount, fee.rate, gasResult.digest);
|
|
1202
|
+
this.emitBalanceChange("USDC", borrowAmount, "borrow", gasResult.digest);
|
|
1203
|
+
return {
|
|
1204
|
+
success: true,
|
|
1205
|
+
tx: gasResult.digest,
|
|
1206
|
+
amount: borrowAmount,
|
|
1207
|
+
fee: fee.amount,
|
|
1208
|
+
healthFactor: hf.healthFactor,
|
|
1209
|
+
gasCost: gasResult.gasCostSui,
|
|
1210
|
+
gasMethod: gasResult.gasMethod
|
|
1211
|
+
};
|
|
1214
1212
|
}
|
|
1215
1213
|
async repay(params) {
|
|
1216
1214
|
let amount;
|
|
1217
1215
|
if (params.amount === "all") {
|
|
1218
|
-
const
|
|
1219
|
-
amount =
|
|
1216
|
+
const hf2 = await this.healthFactor();
|
|
1217
|
+
amount = hf2.borrowed;
|
|
1220
1218
|
if (amount <= 0) {
|
|
1221
1219
|
throw new T2000Error("NO_COLLATERAL", "No outstanding borrow to repay");
|
|
1222
1220
|
}
|
|
1223
1221
|
} else {
|
|
1224
1222
|
amount = params.amount;
|
|
1225
1223
|
}
|
|
1226
|
-
|
|
1227
|
-
const
|
|
1228
|
-
|
|
1229
|
-
|
|
1224
|
+
const repayAmount = amount;
|
|
1225
|
+
const gasResult = await executeWithGas(
|
|
1226
|
+
this.client,
|
|
1227
|
+
this.keypair,
|
|
1228
|
+
() => buildRepayTx(this.client, this._address, repayAmount)
|
|
1229
|
+
);
|
|
1230
|
+
const hf = await getHealthFactor(this.client, this.keypair);
|
|
1231
|
+
this.emitBalanceChange("USDC", repayAmount, "repay", gasResult.digest);
|
|
1232
|
+
return {
|
|
1233
|
+
success: true,
|
|
1234
|
+
tx: gasResult.digest,
|
|
1235
|
+
amount: repayAmount,
|
|
1236
|
+
remainingDebt: hf.borrowed,
|
|
1237
|
+
gasCost: gasResult.gasCostSui,
|
|
1238
|
+
gasMethod: gasResult.gasMethod
|
|
1239
|
+
};
|
|
1230
1240
|
}
|
|
1231
1241
|
async maxBorrow() {
|
|
1232
1242
|
return maxBorrowAmount(this.client, this.keypair);
|
|
@@ -1251,28 +1261,51 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
|
|
|
1251
1261
|
throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
|
|
1252
1262
|
}
|
|
1253
1263
|
const fee = calculateFee("swap", params.amount);
|
|
1254
|
-
|
|
1255
|
-
const
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1264
|
+
const swapAmount = params.amount;
|
|
1265
|
+
const slippageBps = params.maxSlippage ? params.maxSlippage * 100 : void 0;
|
|
1266
|
+
let swapMeta = { estimatedOut: 0, toDecimals: 0 };
|
|
1267
|
+
const gasResult = await executeWithGas(this.client, this.keypair, async () => {
|
|
1268
|
+
const built = await buildSwapTx({
|
|
1269
|
+
client: this.client,
|
|
1270
|
+
address: this._address,
|
|
1271
|
+
fromAsset,
|
|
1272
|
+
toAsset,
|
|
1273
|
+
amount: swapAmount,
|
|
1274
|
+
maxSlippageBps: slippageBps
|
|
1275
|
+
});
|
|
1276
|
+
swapMeta = { estimatedOut: built.estimatedOut, toDecimals: built.toDecimals };
|
|
1277
|
+
return built.tx;
|
|
1278
|
+
});
|
|
1279
|
+
const toInfo = SUPPORTED_ASSETS[toAsset];
|
|
1280
|
+
const txDetail = await this.client.getTransactionBlock({
|
|
1281
|
+
digest: gasResult.digest,
|
|
1282
|
+
options: { showBalanceChanges: true }
|
|
1262
1283
|
});
|
|
1263
|
-
|
|
1264
|
-
|
|
1284
|
+
let actualReceived = 0;
|
|
1285
|
+
if (txDetail.balanceChanges) {
|
|
1286
|
+
for (const change of txDetail.balanceChanges) {
|
|
1287
|
+
if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === this._address) {
|
|
1288
|
+
const amt = Number(change.amount) / 10 ** toInfo.decimals;
|
|
1289
|
+
if (amt > 0) actualReceived += amt;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
const expectedOutput = swapMeta.estimatedOut / 10 ** swapMeta.toDecimals;
|
|
1294
|
+
if (actualReceived === 0) actualReceived = expectedOutput;
|
|
1295
|
+
const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
|
|
1296
|
+
reportFee(this._address, "swap", fee.amount, fee.rate, gasResult.digest);
|
|
1297
|
+
this.emitBalanceChange(fromAsset, swapAmount, "swap", gasResult.digest);
|
|
1265
1298
|
return {
|
|
1266
1299
|
success: true,
|
|
1267
|
-
tx:
|
|
1268
|
-
fromAmount:
|
|
1269
|
-
fromAsset
|
|
1270
|
-
toAmount:
|
|
1271
|
-
toAsset
|
|
1272
|
-
priceImpact
|
|
1300
|
+
tx: gasResult.digest,
|
|
1301
|
+
fromAmount: swapAmount,
|
|
1302
|
+
fromAsset,
|
|
1303
|
+
toAmount: actualReceived,
|
|
1304
|
+
toAsset,
|
|
1305
|
+
priceImpact,
|
|
1273
1306
|
fee: fee.amount,
|
|
1274
|
-
gasCost:
|
|
1275
|
-
gasMethod:
|
|
1307
|
+
gasCost: gasResult.gasCostSui,
|
|
1308
|
+
gasMethod: gasResult.gasMethod
|
|
1276
1309
|
};
|
|
1277
1310
|
}
|
|
1278
1311
|
async swapQuote(params) {
|
|
@@ -1403,115 +1436,6 @@ function parseMoveAbort(errorStr) {
|
|
|
1403
1436
|
return { reason: errorStr };
|
|
1404
1437
|
}
|
|
1405
1438
|
|
|
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
1439
|
exports.BPS_DENOMINATOR = BPS_DENOMINATOR;
|
|
1516
1440
|
exports.CLOCK_ID = CLOCK_ID;
|
|
1517
1441
|
exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
|