@t2000/sdk 2.13.1 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Transaction } from '@mysten/sui/transactions';
1
+ import { Transaction, coinWithBalance } from '@mysten/sui/transactions';
2
2
  import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
3
3
  import BN from 'bn.js';
4
4
  import { EventEmitter } from 'eventemitter3';
@@ -695,22 +695,18 @@ async function buildStakeVSuiTx(_client, address, amountMist) {
695
695
  return tx;
696
696
  }
697
697
  async function buildUnstakeVSuiTx(client, address, amountMist) {
698
- const coins = await fetchCoinsByType(client, address, VSUI_TYPE);
699
- if (coins.length === 0) {
698
+ const balResp = await client.getBalance({ owner: address, coinType: VSUI_TYPE });
699
+ const totalBalance = BigInt(balResp.totalBalance);
700
+ if (totalBalance === 0n) {
700
701
  throw new Error("No vSUI found in wallet.");
701
702
  }
702
703
  const tx = new Transaction();
703
704
  tx.setSender(address);
704
- const primary = tx.object(coins[0].coinObjectId);
705
- if (coins.length > 1) {
706
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
707
- }
708
- let vSuiCoin;
709
- if (amountMist === "all") {
710
- vSuiCoin = primary;
711
- } else {
712
- [vSuiCoin] = tx.splitCoins(primary, [amountMist]);
705
+ const requested = amountMist === "all" ? totalBalance : amountMist;
706
+ if (requested > totalBalance) {
707
+ throw new Error(`Insufficient vSUI: need ${requested} MIST, have ${totalBalance}`);
713
708
  }
709
+ const vSuiCoin = coinWithBalance({ type: VSUI_TYPE, balance: requested })(tx);
714
710
  const [suiCoin] = tx.moveCall({
715
711
  target: `${VOLO_PKG}::stake_pool::unstake`,
716
712
  arguments: [
@@ -731,19 +727,16 @@ async function addStakeVSuiToTx(tx, client, address, input) {
731
727
  if (input.inputCoin) {
732
728
  suiCoin = input.inputCoin;
733
729
  } else {
734
- const coins = await fetchCoinsByType(client, address, SUI_TYPE);
735
- if (coins.length === 0) {
736
- throw new Error("No SUI coins found in wallet");
737
- }
738
- const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
730
+ const balResp = await client.getBalance({ owner: address, coinType: SUI_TYPE });
731
+ const totalBalance = BigInt(balResp.totalBalance);
739
732
  if (totalBalance < input.amountMist) {
740
733
  throw new Error(`Insufficient SUI: need ${input.amountMist} MIST, have ${totalBalance}`);
741
734
  }
742
- const primary = tx.object(coins[0].coinObjectId);
743
- if (coins.length > 1) {
744
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
745
- }
746
- [suiCoin] = tx.splitCoins(primary, [input.amountMist]);
735
+ suiCoin = coinWithBalance({
736
+ type: SUI_TYPE,
737
+ balance: input.amountMist,
738
+ useGasCoin: false
739
+ })(tx);
747
740
  }
748
741
  const [vSuiCoin] = tx.moveCall({
749
742
  target: `${VOLO_PKG}::stake_pool::stake`,
@@ -765,19 +758,16 @@ async function addUnstakeVSuiToTx(tx, client, address, input) {
765
758
  [vSuiCoin] = tx.splitCoins(input.inputCoin, [input.amountMist]);
766
759
  }
767
760
  } else {
768
- const coins = await fetchCoinsByType(client, address, VSUI_TYPE);
769
- if (coins.length === 0) {
761
+ const balResp = await client.getBalance({ owner: address, coinType: VSUI_TYPE });
762
+ const totalBalance = BigInt(balResp.totalBalance);
763
+ if (totalBalance === 0n) {
770
764
  throw new Error("No vSUI found in wallet.");
771
765
  }
772
- const primary = tx.object(coins[0].coinObjectId);
773
- if (coins.length > 1) {
774
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
775
- }
776
- if (input.amountMist === "all") {
777
- vSuiCoin = primary;
778
- } else {
779
- [vSuiCoin] = tx.splitCoins(primary, [input.amountMist]);
766
+ const requested = input.amountMist === "all" ? totalBalance : input.amountMist;
767
+ if (requested > totalBalance) {
768
+ throw new Error(`Insufficient vSUI: need ${requested} MIST, have ${totalBalance}`);
780
769
  }
770
+ vSuiCoin = coinWithBalance({ type: VSUI_TYPE, balance: requested })(tx);
781
771
  }
782
772
  const [suiCoin] = tx.moveCall({
783
773
  target: `${VOLO_PKG}::stake_pool::unstake`,
@@ -790,22 +780,6 @@ async function addUnstakeVSuiToTx(tx, client, address, input) {
790
780
  });
791
781
  return { coin: suiCoin, effectiveAmountMist: input.amountMist };
792
782
  }
793
- async function fetchCoinsByType(client, owner, coinType) {
794
- const all = [];
795
- let cursor;
796
- let hasNext = true;
797
- while (hasNext) {
798
- const page = await client.getCoins({
799
- owner,
800
- coinType,
801
- cursor: cursor ?? void 0
802
- });
803
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
804
- cursor = page.nextCursor;
805
- hasNext = page.hasNextPage;
806
- }
807
- return all;
808
- }
809
783
  var VOLO_PKG, VOLO_POOL, VOLO_METADATA, VSUI_TYPE, SUI_SYSTEM_STATE, MIN_STAKE_MIST, VOLO_STATS_URL;
810
784
  var init_volo = __esm({
811
785
  "src/protocols/volo.ts"() {
@@ -827,52 +801,29 @@ __export(coinSelection_exports, {
827
801
  selectAndSplitCoin: () => selectAndSplitCoin,
828
802
  selectSuiCoin: () => selectSuiCoin
829
803
  });
830
- function getMergeCache(tx) {
831
- let cache = ptbMergeCache.get(tx);
832
- if (!cache) {
833
- cache = /* @__PURE__ */ new Map();
834
- ptbMergeCache.set(tx, cache);
835
- }
836
- return cache;
837
- }
838
804
  async function fetchAllCoins(client, owner, coinType) {
839
- const ids = [];
840
- let totalBalance = 0n;
841
- let cursor;
842
- let hasNext = true;
843
- while (hasNext) {
844
- const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
845
- for (const c of page.data) {
846
- ids.push(c.coinObjectId);
847
- totalBalance += BigInt(c.balance);
848
- }
849
- cursor = page.nextCursor;
850
- hasNext = page.hasNextPage;
851
- }
852
- return { ids, totalBalance };
805
+ const [balance, ids] = await Promise.all([
806
+ client.getBalance({ owner, coinType }),
807
+ (async () => {
808
+ const out = [];
809
+ let cursor;
810
+ let hasNext = true;
811
+ while (hasNext) {
812
+ const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
813
+ for (const c of page.data) out.push(c.coinObjectId);
814
+ cursor = page.nextCursor;
815
+ hasNext = page.hasNextPage;
816
+ }
817
+ return out;
818
+ })()
819
+ ]);
820
+ return { ids, totalBalance: BigInt(balance.totalBalance) };
853
821
  }
854
822
  async function selectAndSplitCoin(tx, client, owner, coinType, amount, options = {}) {
855
- const cache = getMergeCache(tx);
856
- const cacheKey = `${owner}|${coinType}`;
857
- const cached = cache.get(cacheKey);
858
- if (cached) {
859
- const allowSwapAll2 = options.allowSwapAll ?? true;
860
- if (amount !== "all" && amount > cached.remaining && !allowSwapAll2) {
861
- throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient balance for ${coinType}`, {
862
- available: cached.remaining.toString(),
863
- required: amount.toString()
864
- });
865
- }
866
- const requested2 = amount === "all" ? cached.remaining : amount;
867
- const swapAll2 = amount === "all" || requested2 >= cached.remaining;
868
- const effectiveAmount2 = swapAll2 ? cached.remaining : requested2;
869
- const coin2 = swapAll2 ? cached.primary : tx.splitCoins(cached.primary, [effectiveAmount2])[0];
870
- cached.remaining = swapAll2 ? 0n : cached.remaining - effectiveAmount2;
871
- return { coin: coin2, effectiveAmount: effectiveAmount2, swapAll: swapAll2 };
872
- }
873
- const { ids, totalBalance } = await fetchAllCoins(client, owner, coinType);
874
- if (ids.length === 0) {
875
- throw new T2000Error("INSUFFICIENT_BALANCE", `No coins found for ${coinType}`);
823
+ const balanceResp = await client.getBalance({ owner, coinType });
824
+ const totalBalance = BigInt(balanceResp.totalBalance);
825
+ if (totalBalance === 0n) {
826
+ throw new T2000Error("INSUFFICIENT_BALANCE", `No balance found for ${coinType}`);
876
827
  }
877
828
  const allowSwapAll = options.allowSwapAll ?? true;
878
829
  if (amount !== "all" && amount > totalBalance && !allowSwapAll) {
@@ -884,30 +835,29 @@ async function selectAndSplitCoin(tx, client, owner, coinType, amount, options =
884
835
  const requested = amount === "all" ? totalBalance : amount;
885
836
  const swapAll = amount === "all" || requested >= totalBalance;
886
837
  const effectiveAmount = swapAll ? totalBalance : requested;
887
- const primary = tx.object(ids[0]);
888
- if (ids.length > 1) {
889
- tx.mergeCoins(primary, ids.slice(1).map((id) => tx.object(id)));
890
- }
891
- const coin = swapAll ? primary : tx.splitCoins(primary, [effectiveAmount])[0];
892
- cache.set(cacheKey, {
893
- primary,
894
- remaining: swapAll ? 0n : totalBalance - effectiveAmount
895
- });
838
+ const coin = coinWithBalance({ type: coinType, balance: effectiveAmount })(tx);
896
839
  return { coin, effectiveAmount, swapAll };
897
840
  }
898
841
  async function selectSuiCoin(tx, client, owner, amountMist, sponsoredContext) {
899
842
  if (sponsoredContext) {
900
843
  const { SUI_TYPE: SUI_TYPE2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
901
- return selectAndSplitCoin(tx, client, owner, SUI_TYPE2, amountMist);
844
+ const balanceResp = await client.getBalance({ owner, coinType: SUI_TYPE2 });
845
+ const totalBalance = BigInt(balanceResp.totalBalance);
846
+ if (totalBalance < amountMist) {
847
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient SUI balance`, {
848
+ available: totalBalance.toString(),
849
+ required: amountMist.toString()
850
+ });
851
+ }
852
+ const coin2 = coinWithBalance({ type: SUI_TYPE2, balance: amountMist, useGasCoin: false })(tx);
853
+ return { coin: coin2, effectiveAmount: amountMist, swapAll: false };
902
854
  }
903
855
  const [coin] = tx.splitCoins(tx.gas, [amountMist]);
904
856
  return { coin, effectiveAmount: amountMist, swapAll: false };
905
857
  }
906
- var ptbMergeCache;
907
858
  var init_coinSelection = __esm({
908
859
  "src/wallet/coinSelection.ts"() {
909
860
  init_errors();
910
- ptbMergeCache = /* @__PURE__ */ new WeakMap();
911
861
  }
912
862
  });
913
863
 
@@ -1521,26 +1471,16 @@ async function buildSendTx({
1521
1471
  const rawAmount = displayToRaw(amount, assetInfo.decimals);
1522
1472
  const tx = new Transaction();
1523
1473
  tx.setSender(address);
1524
- if (asset === "SUI") {
1525
- const [coin] = tx.splitCoins(tx.gas, [rawAmount]);
1526
- tx.transferObjects([coin], recipient);
1527
- } else {
1528
- const coins = await client.getCoins({ owner: address, coinType: assetInfo.type });
1529
- if (coins.data.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
1530
- const totalBalance = coins.data.reduce((sum, c) => sum + BigInt(c.balance), 0n);
1531
- if (totalBalance < rawAmount) {
1532
- throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
1533
- available: Number(totalBalance) / 10 ** assetInfo.decimals,
1534
- required: amount
1535
- });
1536
- }
1537
- const primaryCoin = tx.object(coins.data[0].coinObjectId);
1538
- if (coins.data.length > 1) {
1539
- tx.mergeCoins(primaryCoin, coins.data.slice(1).map((c) => tx.object(c.coinObjectId)));
1540
- }
1541
- const [sendCoin] = tx.splitCoins(primaryCoin, [rawAmount]);
1542
- tx.transferObjects([sendCoin], recipient);
1474
+ const balanceResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
1475
+ const totalBalance = BigInt(balanceResp.totalBalance);
1476
+ if (totalBalance < rawAmount) {
1477
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
1478
+ available: Number(totalBalance) / 10 ** assetInfo.decimals,
1479
+ required: amount
1480
+ });
1543
1481
  }
1482
+ const sendCoin = asset === "SUI" ? tx.splitCoins(tx.gas, [rawAmount])[0] : coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx);
1483
+ tx.transferObjects([sendCoin], recipient);
1544
1484
  return tx;
1545
1485
  }
1546
1486
  function addSendToTx(tx, coin, recipient) {
@@ -5487,26 +5427,6 @@ function resolveAssetInfo(asset) {
5487
5427
  }
5488
5428
  throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
5489
5429
  }
5490
- async function fetchCoins(client, owner, coinType) {
5491
- const all = [];
5492
- let cursor;
5493
- let hasNext = true;
5494
- while (hasNext) {
5495
- const page = await client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
5496
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
5497
- cursor = page.nextCursor;
5498
- hasNext = page.hasNextPage;
5499
- }
5500
- return all;
5501
- }
5502
- function mergeCoins(tx, coins) {
5503
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
5504
- const primary = tx.object(coins[0].coinObjectId);
5505
- if (coins.length > 1) {
5506
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
5507
- }
5508
- return primary;
5509
- }
5510
5430
  async function getPositions(client, address) {
5511
5431
  try {
5512
5432
  const naviPositions = await $e(address, {
@@ -5597,13 +5517,15 @@ async function buildSaveTx(client, address, amount, options = {}) {
5597
5517
  }
5598
5518
  const asset = options.asset ?? "USDC";
5599
5519
  const assetInfo = resolveAssetInfo(asset);
5600
- const coins = await fetchCoins(client, address, assetInfo.type);
5601
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
5602
- const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
5520
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5521
+ const totalBalance = BigInt(balResp.totalBalance);
5522
+ if (totalBalance === 0n) {
5523
+ throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} balance found`);
5524
+ }
5603
5525
  const tx = new Transaction();
5604
5526
  tx.setSender(address);
5605
- const coinObj = mergeCoins(tx, coins);
5606
5527
  const rawAmount = Math.min(Number(stableToRaw(amount, assetInfo.decimals)), Number(totalBalance));
5528
+ const coinObj = coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5607
5529
  try {
5608
5530
  await Ce(tx, assetInfo.type, coinObj, {
5609
5531
  ...sdkOptions(client),
@@ -5735,18 +5657,19 @@ async function buildRepayTx(client, address, amount, options = {}) {
5735
5657
  }
5736
5658
  const asset = options.asset ?? "USDC";
5737
5659
  const assetInfo = resolveAssetInfo(asset);
5738
- const coins = await fetchCoins(client, address, assetInfo.type);
5739
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins to repay with. Withdraw some savings first to get cash.`);
5740
- const totalBalance = coins.reduce((sum, c) => sum + BigInt(c.balance), 0n);
5660
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5661
+ const totalBalance = BigInt(balResp.totalBalance);
5662
+ if (totalBalance === 0n) {
5663
+ throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} to repay with. Withdraw some savings first to get cash.`);
5664
+ }
5741
5665
  const rawRequested = Number(stableToRaw(amount, assetInfo.decimals));
5742
5666
  if (Number(totalBalance) < rawRequested && Number(totalBalance) < 1e3) {
5743
5667
  throw new T2000Error("INSUFFICIENT_BALANCE", `Not enough ${assetInfo.displayName} to repay (need $${amount.toFixed(2)}, wallet has ~$${(Number(totalBalance) / 10 ** assetInfo.decimals).toFixed(4)}). Withdraw some savings first.`);
5744
5668
  }
5745
5669
  const tx = new Transaction();
5746
5670
  tx.setSender(address);
5747
- const coinObj = mergeCoins(tx, coins);
5748
5671
  const rawAmount = Math.min(rawRequested, Number(totalBalance));
5749
- const [repayCoin] = tx.splitCoins(coinObj, [rawAmount]);
5672
+ const repayCoin = coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5750
5673
  await refreshOracle(tx, client, address, {
5751
5674
  skipOracle: options.skipOracle,
5752
5675
  skipPythUpdate: options.skipPythUpdate
@@ -6409,10 +6332,10 @@ var ContactManager = class {
6409
6332
  }
6410
6333
  };
6411
6334
  var DEFAULT_CONFIG_DIR = join$1(homedir(), ".t2000");
6412
- async function executeTx(client, signer, buildTx) {
6335
+ async function executeTx(client, signer, buildTx, options = {}) {
6413
6336
  const tx = await buildTx();
6414
6337
  tx.setSender(signer.getAddress());
6415
- const txBytes = await tx.build({ client });
6338
+ const txBytes = await tx.build({ client: options.buildClient ?? client });
6416
6339
  const { signature } = await signer.signTransaction(txBytes);
6417
6340
  const result = await client.executeTransactionBlock({
6418
6341
  transactionBlock: txBytes,
@@ -6514,9 +6437,15 @@ var T2000 = class _T2000 extends EventEmitter {
6514
6437
  this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
6515
6438
  const { Mppx } = await import('mppx/client');
6516
6439
  const { sui, USDC } = await import('@suimpp/mpp/client');
6440
+ const { SuiGrpcClient } = await import('@mysten/sui/grpc');
6517
6441
  const client = this.client;
6518
6442
  const signer = this._signer;
6519
6443
  const signerAddress = signer.getAddress();
6444
+ let paymentDigest;
6445
+ let gasCostSui = 0;
6446
+ const network = client.network === "testnet" ? "testnet" : "mainnet";
6447
+ const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
6448
+ const grpcClient = new SuiGrpcClient({ baseUrl: grpcBaseUrl, network });
6520
6449
  const mppx = Mppx.create({
6521
6450
  polyfill: false,
6522
6451
  methods: [sui({
@@ -6527,7 +6456,9 @@ var T2000 = class _T2000 extends EventEmitter {
6527
6456
  signPersonalMessage: (bytes) => signer.signPersonalMessage(bytes)
6528
6457
  },
6529
6458
  execute: async (tx) => {
6530
- const result = await executeTx(client, signer, () => tx);
6459
+ const result = await executeTx(client, signer, () => tx, { buildClient: grpcClient });
6460
+ paymentDigest = result.digest;
6461
+ gasCostSui = result.gasCostSui;
6531
6462
  return { digest: result.digest };
6532
6463
  }
6533
6464
  })]
@@ -6546,8 +6477,7 @@ var T2000 = class _T2000 extends EventEmitter {
6546
6477
  } catch {
6547
6478
  body = null;
6548
6479
  }
6549
- const receiptHeader = response.headers.get("x-payment-receipt");
6550
- const paid = !!receiptHeader;
6480
+ const paid = !!paymentDigest;
6551
6481
  if (paid) {
6552
6482
  this.enforcer.recordUsage(options.maxPrice ?? 1);
6553
6483
  }
@@ -6556,7 +6486,8 @@ var T2000 = class _T2000 extends EventEmitter {
6556
6486
  body,
6557
6487
  paid,
6558
6488
  cost: paid ? options.maxPrice ?? void 0 : void 0,
6559
- receipt: receiptHeader ? { reference: receiptHeader, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
6489
+ gasCostSui: paid ? gasCostSui : void 0,
6490
+ receipt: paymentDigest ? { reference: paymentDigest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
6560
6491
  };
6561
6492
  }
6562
6493
  // -- VOLO vSUI Staking --
@@ -6585,8 +6516,8 @@ var T2000 = class _T2000 extends EventEmitter {
6585
6516
  let vSuiAmount;
6586
6517
  if (params.amount === "all") {
6587
6518
  amountMist = "all";
6588
- const coins = await this._fetchCoins(VSUI_TYPE2);
6589
- vSuiAmount = coins.reduce((sum, c) => sum + Number(c.balance), 0) / 1e9;
6519
+ const bal = await this.client.getBalance({ owner: this._address, coinType: VSUI_TYPE2 });
6520
+ vSuiAmount = Number(bal.totalBalance) / 1e9;
6590
6521
  } else {
6591
6522
  amountMist = BigInt(Math.floor(params.amount * 1e9));
6592
6523
  vSuiAmount = params.amount;
@@ -6642,10 +6573,14 @@ var T2000 = class _T2000 extends EventEmitter {
6642
6573
  if (fromType === "0x2::sui::SUI") {
6643
6574
  [inputCoin] = tx.splitCoins(tx.gas, [rawAmount]);
6644
6575
  } else {
6645
- const coins = await this._fetchCoins(fromType);
6646
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${params.from} coins found.`);
6647
- const merged = this._mergeCoinsInTx(tx, coins);
6648
- [inputCoin] = tx.splitCoins(merged, [rawAmount]);
6576
+ const bal = await this.client.getBalance({ owner: this._address, coinType: fromType });
6577
+ if (BigInt(bal.totalBalance) < rawAmount) {
6578
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${params.from} balance.`, {
6579
+ available: Number(bal.totalBalance) / 10 ** fromDecimals,
6580
+ required: params.amount
6581
+ });
6582
+ }
6583
+ inputCoin = coinWithBalance({ type: fromType, balance: rawAmount })(tx);
6649
6584
  }
6650
6585
  const outputCoin = await buildSwapTx2({
6651
6586
  walletAddress: this._address,
@@ -6902,11 +6837,15 @@ var T2000 = class _T2000 extends EventEmitter {
6902
6837
  if (canPTB) {
6903
6838
  const tx2 = new Transaction();
6904
6839
  tx2.setSender(this._address);
6905
- const coins = await this._fetchCoins(assetInfo.type);
6906
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
6907
- const merged = this._mergeCoinsInTx(tx2, coins);
6908
6840
  const rawAmount = BigInt(Math.floor(saveAmount * 10 ** assetInfo.decimals));
6909
- const [inputCoin] = tx2.splitCoins(merged, [rawAmount]);
6841
+ const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
6842
+ if (BigInt(bal.totalBalance) < rawAmount) {
6843
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${assetInfo.displayName} balance`, {
6844
+ available: Number(bal.totalBalance) / 10 ** assetInfo.decimals,
6845
+ required: saveAmount
6846
+ });
6847
+ }
6848
+ const inputCoin = coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx2);
6910
6849
  await adapter.addSaveToTx(tx2, this._address, inputCoin, asset);
6911
6850
  return tx2;
6912
6851
  }
@@ -7101,52 +7040,6 @@ var T2000 = class _T2000 extends EventEmitter {
7101
7040
  return 0;
7102
7041
  }
7103
7042
  }
7104
- async _fetchCoins(coinType) {
7105
- const all = [];
7106
- let cursor;
7107
- let hasNext = true;
7108
- while (hasNext) {
7109
- const page = await this.client.getCoins({ owner: this._address, coinType, cursor: cursor ?? void 0 });
7110
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
7111
- cursor = page.nextCursor;
7112
- hasNext = page.hasNextPage;
7113
- }
7114
- if (all.length > 0) {
7115
- this._lastFundDigest = void 0;
7116
- return all;
7117
- }
7118
- if (this._lastFundDigest && coinType === SUPPORTED_ASSETS.USDC.type) {
7119
- const txInfo = await this.client.getTransactionBlock({
7120
- digest: this._lastFundDigest,
7121
- options: { showObjectChanges: true }
7122
- });
7123
- const coinIds = (txInfo.objectChanges ?? []).filter(
7124
- (c) => (c.type === "created" || c.type === "mutated") && "objectType" in c && typeof c.objectType === "string" && c.objectType.includes("0x2::coin::Coin") && c.objectType.includes(coinType)
7125
- ).map((c) => c.objectId);
7126
- if (coinIds.length > 0) {
7127
- const objects = await this.client.multiGetObjects({
7128
- ids: coinIds,
7129
- options: { showContent: true, showOwner: true }
7130
- });
7131
- for (const obj of objects) {
7132
- if (obj.data?.content?.dataType === "moveObject" && obj.data.owner && typeof obj.data.owner === "object" && "AddressOwner" in obj.data.owner && obj.data.owner.AddressOwner === this._address) {
7133
- const fields = obj.data.content.fields;
7134
- all.push({ coinObjectId: obj.data.objectId, balance: String(fields.balance ?? "0") });
7135
- }
7136
- }
7137
- }
7138
- }
7139
- return all;
7140
- }
7141
- _mergeCoinsInTx(tx, coins) {
7142
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
7143
- const primary = tx.object(coins[0].coinObjectId);
7144
- if (coins.length > 1) {
7145
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
7146
- }
7147
- return primary;
7148
- }
7149
- _lastFundDigest;
7150
7043
  async _autoFundFromSavings(shortfall) {
7151
7044
  const positions = await this.positions();
7152
7045
  const savingsTotal = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
@@ -7174,7 +7067,6 @@ var T2000 = class _T2000 extends EventEmitter {
7174
7067
  if (!usdcReceived) {
7175
7068
  throw new T2000Error("WITHDRAW_FAILED", "Withdraw TX did not produce USDC");
7176
7069
  }
7177
- this._lastFundDigest = result.tx;
7178
7070
  }
7179
7071
  async maxWithdraw() {
7180
7072
  const adapter = await this.resolveLending(void 0, "USDC", "withdraw");
@@ -7243,11 +7135,18 @@ var T2000 = class _T2000 extends EventEmitter {
7243
7135
  if (adapter.addRepayToTx) {
7244
7136
  const tx2 = new Transaction();
7245
7137
  tx2.setSender(this._address);
7246
- const coins = await this._fetchCoins(targetAssetInfo.type);
7247
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${targetAssetInfo.displayName} coins`);
7248
- const merged = this._mergeCoinsInTx(tx2, coins);
7249
7138
  const raw = BigInt(Math.floor(repayAmount * 10 ** targetAssetInfo.decimals));
7250
- const [repayCoin] = tx2.splitCoins(merged, [raw]);
7139
+ const bal = await this.client.getBalance({
7140
+ owner: this._address,
7141
+ coinType: targetAssetInfo.type
7142
+ });
7143
+ if (BigInt(bal.totalBalance) < raw) {
7144
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${targetAssetInfo.displayName} balance for repayment`, {
7145
+ available: Number(bal.totalBalance) / 10 ** targetAssetInfo.decimals,
7146
+ required: repayAmount
7147
+ });
7148
+ }
7149
+ const repayCoin = coinWithBalance({ type: targetAssetInfo.type, balance: raw })(tx2);
7251
7150
  await adapter.addRepayToTx(tx2, this._address, repayCoin, target.asset);
7252
7151
  return tx2;
7253
7152
  }
@@ -7278,20 +7177,23 @@ var T2000 = class _T2000 extends EventEmitter {
7278
7177
  if (canPTB) {
7279
7178
  const tx = new Transaction();
7280
7179
  tx.setSender(this._address);
7281
- const assetMerged = /* @__PURE__ */ new Map();
7282
7180
  const uniqueAssets = Array.from(new Set(entries.map((e) => e.borrow.asset)));
7283
7181
  for (const asset of uniqueAssets) {
7284
7182
  const info = SUPPORTED_ASSETS[asset];
7285
7183
  if (!info) throw new T2000Error("ASSET_NOT_SUPPORTED", `Cannot repay unknown asset: ${asset}`);
7286
- const coins = await this._fetchCoins(info.type);
7287
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${info.displayName} coins for repayment`);
7288
- assetMerged.set(asset, this._mergeCoinsInTx(tx, coins));
7184
+ const required = entries.filter((e) => e.borrow.asset === asset).reduce((sum, e) => sum + BigInt(Math.floor(e.borrow.amount * 10 ** info.decimals)), 0n);
7185
+ const bal = await this.client.getBalance({ owner: this._address, coinType: info.type });
7186
+ if (BigInt(bal.totalBalance) < required) {
7187
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${info.displayName} balance for repayment`, {
7188
+ available: Number(bal.totalBalance) / 10 ** info.decimals,
7189
+ required: Number(required) / 10 ** info.decimals
7190
+ });
7191
+ }
7289
7192
  }
7290
7193
  for (const { borrow, adapter } of entries) {
7291
7194
  const info = SUPPORTED_ASSETS[borrow.asset];
7292
- const merged = assetMerged.get(borrow.asset);
7293
7195
  const raw = BigInt(Math.floor(borrow.amount * 10 ** info.decimals));
7294
- const [repayCoin] = tx.splitCoins(merged, [raw]);
7196
+ const repayCoin = coinWithBalance({ type: info.type, balance: raw })(tx);
7295
7197
  await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
7296
7198
  totalRepaid += borrow.amount;
7297
7199
  }
@@ -7422,8 +7324,8 @@ var T2000 = class _T2000 extends EventEmitter {
7422
7324
  totalGasCost: 0
7423
7325
  };
7424
7326
  }
7425
- const preUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7426
- const preUsdcRaw = preUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7327
+ const preUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7328
+ const preUsdcRaw = BigInt(preUsdcBal.totalBalance);
7427
7329
  const claimResult = await this.claimRewards();
7428
7330
  if (!claimResult.tx) {
7429
7331
  return {
@@ -7446,8 +7348,8 @@ var T2000 = class _T2000 extends EventEmitter {
7446
7348
  const decimals = getDecimalsForCoinType(reward.coinType) ?? 9;
7447
7349
  const rawAmount = BigInt(Math.floor(reward.amount * 10 ** decimals));
7448
7350
  if (rawAmount <= 0n) continue;
7449
- const coins = await this._fetchCoins(reward.coinType);
7450
- if (coins.length === 0) continue;
7351
+ const bal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7352
+ if (BigInt(bal.totalBalance) < rawAmount) continue;
7451
7353
  const route = await findSwapRoute2({
7452
7354
  walletAddress: this._address,
7453
7355
  from: reward.coinType,
@@ -7459,10 +7361,9 @@ var T2000 = class _T2000 extends EventEmitter {
7459
7361
  const swapResult = await executeTx(this.client, this._signer, async () => {
7460
7362
  const tx = new Transaction();
7461
7363
  tx.setSender(this._address);
7462
- const freshCoins = await this._fetchCoins(reward.coinType);
7463
- if (freshCoins.length === 0) return tx;
7464
- const merged = this._mergeCoinsInTx(tx, freshCoins);
7465
- const [inputCoin] = tx.splitCoins(merged, [rawAmount]);
7364
+ const freshBal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7365
+ if (BigInt(freshBal.totalBalance) < rawAmount) return tx;
7366
+ const inputCoin = coinWithBalance({ type: reward.coinType, balance: rawAmount })(tx);
7466
7367
  const outputCoin = await buildSwapTx2({
7467
7368
  walletAddress: this._address,
7468
7369
  route,
@@ -7479,8 +7380,8 @@ var T2000 = class _T2000 extends EventEmitter {
7479
7380
  console.warn(`[compound] Failed to swap ${reward.symbol}:`, err instanceof Error ? err.message : err);
7480
7381
  }
7481
7382
  }
7482
- const postUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7483
- const postUsdcRaw = postUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7383
+ const postUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7384
+ const postUsdcRaw = BigInt(postUsdcBal.totalBalance);
7484
7385
  const gainedRaw = postUsdcRaw > preUsdcRaw ? postUsdcRaw - preUsdcRaw : 0n;
7485
7386
  const depositUsdc = Number(gainedRaw) / 10 ** USDC_DEC;
7486
7387
  let depositTx = "";
@@ -7490,10 +7391,11 @@ var T2000 = class _T2000 extends EventEmitter {
7490
7391
  const depositResult = await executeTx(this.client, this._signer, async () => {
7491
7392
  const tx = new Transaction();
7492
7393
  tx.setSender(this._address);
7493
- const coins = await this._fetchCoins(USDC_TYPE2);
7494
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins after swap");
7495
- const merged = this._mergeCoinsInTx(tx, coins);
7496
- const [inputCoin] = tx.splitCoins(merged, [gainedRaw]);
7394
+ const bal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7395
+ if (BigInt(bal.totalBalance) < gainedRaw) {
7396
+ throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC available after swap");
7397
+ }
7398
+ const inputCoin = coinWithBalance({ type: USDC_TYPE2, balance: gainedRaw })(tx);
7497
7399
  await adapter.addSaveToTx(tx, this._address, inputCoin, "USDC");
7498
7400
  return tx;
7499
7401
  });