@t2000/sdk 2.13.2 → 2.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
 
@@ -1115,6 +1065,17 @@ async function addSwapToTx(tx, client, address, input) {
1115
1065
  if (input.inputCoin) {
1116
1066
  inputCoin = input.inputCoin;
1117
1067
  effectiveRaw = requestedRaw;
1068
+ } else if (fromType === "0x2::sui::SUI") {
1069
+ const { selectSuiCoin: selectSuiCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
1070
+ const result = await selectSuiCoin2(
1071
+ tx,
1072
+ client,
1073
+ address,
1074
+ requestedRaw,
1075
+ input.sponsoredContext ?? false
1076
+ );
1077
+ inputCoin = result.coin;
1078
+ effectiveRaw = result.effectiveAmount;
1118
1079
  } else {
1119
1080
  const { selectAndSplitCoin: selectAndSplitCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
1120
1081
  const result = await selectAndSplitCoin2(tx, client, address, fromType, requestedRaw);
@@ -1521,26 +1482,16 @@ async function buildSendTx({
1521
1482
  const rawAmount = displayToRaw(amount, assetInfo.decimals);
1522
1483
  const tx = new Transaction();
1523
1484
  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);
1485
+ const balanceResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
1486
+ const totalBalance = BigInt(balanceResp.totalBalance);
1487
+ if (totalBalance < rawAmount) {
1488
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} balance`, {
1489
+ available: Number(totalBalance) / 10 ** assetInfo.decimals,
1490
+ required: amount
1491
+ });
1543
1492
  }
1493
+ const sendCoin = asset === "SUI" ? tx.splitCoins(tx.gas, [rawAmount])[0] : coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx);
1494
+ tx.transferObjects([sendCoin], recipient);
1544
1495
  return tx;
1545
1496
  }
1546
1497
  function addSendToTx(tx, coin, recipient) {
@@ -5487,26 +5438,6 @@ function resolveAssetInfo(asset) {
5487
5438
  }
5488
5439
  throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
5489
5440
  }
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
5441
  async function getPositions(client, address) {
5511
5442
  try {
5512
5443
  const naviPositions = await $e(address, {
@@ -5597,13 +5528,15 @@ async function buildSaveTx(client, address, amount, options = {}) {
5597
5528
  }
5598
5529
  const asset = options.asset ?? "USDC";
5599
5530
  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);
5531
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5532
+ const totalBalance = BigInt(balResp.totalBalance);
5533
+ if (totalBalance === 0n) {
5534
+ throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} balance found`);
5535
+ }
5603
5536
  const tx = new Transaction();
5604
5537
  tx.setSender(address);
5605
- const coinObj = mergeCoins(tx, coins);
5606
5538
  const rawAmount = Math.min(Number(stableToRaw(amount, assetInfo.decimals)), Number(totalBalance));
5539
+ const coinObj = coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5607
5540
  try {
5608
5541
  await Ce(tx, assetInfo.type, coinObj, {
5609
5542
  ...sdkOptions(client),
@@ -5735,18 +5668,19 @@ async function buildRepayTx(client, address, amount, options = {}) {
5735
5668
  }
5736
5669
  const asset = options.asset ?? "USDC";
5737
5670
  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);
5671
+ const balResp = await client.getBalance({ owner: address, coinType: assetInfo.type });
5672
+ const totalBalance = BigInt(balResp.totalBalance);
5673
+ if (totalBalance === 0n) {
5674
+ throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} to repay with. Withdraw some savings first to get cash.`);
5675
+ }
5741
5676
  const rawRequested = Number(stableToRaw(amount, assetInfo.decimals));
5742
5677
  if (Number(totalBalance) < rawRequested && Number(totalBalance) < 1e3) {
5743
5678
  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
5679
  }
5745
5680
  const tx = new Transaction();
5746
5681
  tx.setSender(address);
5747
- const coinObj = mergeCoins(tx, coins);
5748
5682
  const rawAmount = Math.min(rawRequested, Number(totalBalance));
5749
- const [repayCoin] = tx.splitCoins(coinObj, [rawAmount]);
5683
+ const repayCoin = coinWithBalance({ type: assetInfo.type, balance: BigInt(rawAmount) })(tx);
5750
5684
  await refreshOracle(tx, client, address, {
5751
5685
  skipOracle: options.skipOracle,
5752
5686
  skipPythUpdate: options.skipPythUpdate
@@ -6409,10 +6343,10 @@ var ContactManager = class {
6409
6343
  }
6410
6344
  };
6411
6345
  var DEFAULT_CONFIG_DIR = join$1(homedir(), ".t2000");
6412
- async function executeTx(client, signer, buildTx) {
6346
+ async function executeTx(client, signer, buildTx, options = {}) {
6413
6347
  const tx = await buildTx();
6414
6348
  tx.setSender(signer.getAddress());
6415
- const txBytes = await tx.build({ client });
6349
+ const txBytes = await tx.build({ client: options.buildClient ?? client });
6416
6350
  const { signature } = await signer.signTransaction(txBytes);
6417
6351
  const result = await client.executeTransactionBlock({
6418
6352
  transactionBlock: txBytes,
@@ -6514,10 +6448,15 @@ var T2000 = class _T2000 extends EventEmitter {
6514
6448
  this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
6515
6449
  const { Mppx } = await import('mppx/client');
6516
6450
  const { sui, USDC } = await import('@suimpp/mpp/client');
6451
+ const { SuiGrpcClient } = await import('@mysten/sui/grpc');
6517
6452
  const client = this.client;
6518
6453
  const signer = this._signer;
6519
6454
  const signerAddress = signer.getAddress();
6520
6455
  let paymentDigest;
6456
+ let gasCostSui = 0;
6457
+ const network = client.network === "testnet" ? "testnet" : "mainnet";
6458
+ const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
6459
+ const grpcClient = new SuiGrpcClient({ baseUrl: grpcBaseUrl, network });
6521
6460
  const mppx = Mppx.create({
6522
6461
  polyfill: false,
6523
6462
  methods: [sui({
@@ -6528,8 +6467,9 @@ var T2000 = class _T2000 extends EventEmitter {
6528
6467
  signPersonalMessage: (bytes) => signer.signPersonalMessage(bytes)
6529
6468
  },
6530
6469
  execute: async (tx) => {
6531
- const result = await executeTx(client, signer, () => tx);
6470
+ const result = await executeTx(client, signer, () => tx, { buildClient: grpcClient });
6532
6471
  paymentDigest = result.digest;
6472
+ gasCostSui = result.gasCostSui;
6533
6473
  return { digest: result.digest };
6534
6474
  }
6535
6475
  })]
@@ -6557,6 +6497,7 @@ var T2000 = class _T2000 extends EventEmitter {
6557
6497
  body,
6558
6498
  paid,
6559
6499
  cost: paid ? options.maxPrice ?? void 0 : void 0,
6500
+ gasCostSui: paid ? gasCostSui : void 0,
6560
6501
  receipt: paymentDigest ? { reference: paymentDigest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
6561
6502
  };
6562
6503
  }
@@ -6586,8 +6527,8 @@ var T2000 = class _T2000 extends EventEmitter {
6586
6527
  let vSuiAmount;
6587
6528
  if (params.amount === "all") {
6588
6529
  amountMist = "all";
6589
- const coins = await this._fetchCoins(VSUI_TYPE2);
6590
- vSuiAmount = coins.reduce((sum, c) => sum + Number(c.balance), 0) / 1e9;
6530
+ const bal = await this.client.getBalance({ owner: this._address, coinType: VSUI_TYPE2 });
6531
+ vSuiAmount = Number(bal.totalBalance) / 1e9;
6591
6532
  } else {
6592
6533
  amountMist = BigInt(Math.floor(params.amount * 1e9));
6593
6534
  vSuiAmount = params.amount;
@@ -6643,10 +6584,14 @@ var T2000 = class _T2000 extends EventEmitter {
6643
6584
  if (fromType === "0x2::sui::SUI") {
6644
6585
  [inputCoin] = tx.splitCoins(tx.gas, [rawAmount]);
6645
6586
  } else {
6646
- const coins = await this._fetchCoins(fromType);
6647
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${params.from} coins found.`);
6648
- const merged = this._mergeCoinsInTx(tx, coins);
6649
- [inputCoin] = tx.splitCoins(merged, [rawAmount]);
6587
+ const bal = await this.client.getBalance({ owner: this._address, coinType: fromType });
6588
+ if (BigInt(bal.totalBalance) < rawAmount) {
6589
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${params.from} balance.`, {
6590
+ available: Number(bal.totalBalance) / 10 ** fromDecimals,
6591
+ required: params.amount
6592
+ });
6593
+ }
6594
+ inputCoin = coinWithBalance({ type: fromType, balance: rawAmount })(tx);
6650
6595
  }
6651
6596
  const outputCoin = await buildSwapTx2({
6652
6597
  walletAddress: this._address,
@@ -6903,11 +6848,15 @@ var T2000 = class _T2000 extends EventEmitter {
6903
6848
  if (canPTB) {
6904
6849
  const tx2 = new Transaction();
6905
6850
  tx2.setSender(this._address);
6906
- const coins = await this._fetchCoins(assetInfo.type);
6907
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
6908
- const merged = this._mergeCoinsInTx(tx2, coins);
6909
6851
  const rawAmount = BigInt(Math.floor(saveAmount * 10 ** assetInfo.decimals));
6910
- const [inputCoin] = tx2.splitCoins(merged, [rawAmount]);
6852
+ const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
6853
+ if (BigInt(bal.totalBalance) < rawAmount) {
6854
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${assetInfo.displayName} balance`, {
6855
+ available: Number(bal.totalBalance) / 10 ** assetInfo.decimals,
6856
+ required: saveAmount
6857
+ });
6858
+ }
6859
+ const inputCoin = coinWithBalance({ type: assetInfo.type, balance: rawAmount })(tx2);
6911
6860
  await adapter.addSaveToTx(tx2, this._address, inputCoin, asset);
6912
6861
  return tx2;
6913
6862
  }
@@ -7102,52 +7051,6 @@ var T2000 = class _T2000 extends EventEmitter {
7102
7051
  return 0;
7103
7052
  }
7104
7053
  }
7105
- async _fetchCoins(coinType) {
7106
- const all = [];
7107
- let cursor;
7108
- let hasNext = true;
7109
- while (hasNext) {
7110
- const page = await this.client.getCoins({ owner: this._address, coinType, cursor: cursor ?? void 0 });
7111
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
7112
- cursor = page.nextCursor;
7113
- hasNext = page.hasNextPage;
7114
- }
7115
- if (all.length > 0) {
7116
- this._lastFundDigest = void 0;
7117
- return all;
7118
- }
7119
- if (this._lastFundDigest && coinType === SUPPORTED_ASSETS.USDC.type) {
7120
- const txInfo = await this.client.getTransactionBlock({
7121
- digest: this._lastFundDigest,
7122
- options: { showObjectChanges: true }
7123
- });
7124
- const coinIds = (txInfo.objectChanges ?? []).filter(
7125
- (c) => (c.type === "created" || c.type === "mutated") && "objectType" in c && typeof c.objectType === "string" && c.objectType.includes("0x2::coin::Coin") && c.objectType.includes(coinType)
7126
- ).map((c) => c.objectId);
7127
- if (coinIds.length > 0) {
7128
- const objects = await this.client.multiGetObjects({
7129
- ids: coinIds,
7130
- options: { showContent: true, showOwner: true }
7131
- });
7132
- for (const obj of objects) {
7133
- 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) {
7134
- const fields = obj.data.content.fields;
7135
- all.push({ coinObjectId: obj.data.objectId, balance: String(fields.balance ?? "0") });
7136
- }
7137
- }
7138
- }
7139
- }
7140
- return all;
7141
- }
7142
- _mergeCoinsInTx(tx, coins) {
7143
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No coins to merge");
7144
- const primary = tx.object(coins[0].coinObjectId);
7145
- if (coins.length > 1) {
7146
- tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
7147
- }
7148
- return primary;
7149
- }
7150
- _lastFundDigest;
7151
7054
  async _autoFundFromSavings(shortfall) {
7152
7055
  const positions = await this.positions();
7153
7056
  const savingsTotal = positions.positions.filter((p) => p.type === "save").reduce((sum, p) => sum + p.amount, 0);
@@ -7175,7 +7078,6 @@ var T2000 = class _T2000 extends EventEmitter {
7175
7078
  if (!usdcReceived) {
7176
7079
  throw new T2000Error("WITHDRAW_FAILED", "Withdraw TX did not produce USDC");
7177
7080
  }
7178
- this._lastFundDigest = result.tx;
7179
7081
  }
7180
7082
  async maxWithdraw() {
7181
7083
  const adapter = await this.resolveLending(void 0, "USDC", "withdraw");
@@ -7244,11 +7146,18 @@ var T2000 = class _T2000 extends EventEmitter {
7244
7146
  if (adapter.addRepayToTx) {
7245
7147
  const tx2 = new Transaction();
7246
7148
  tx2.setSender(this._address);
7247
- const coins = await this._fetchCoins(targetAssetInfo.type);
7248
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${targetAssetInfo.displayName} coins`);
7249
- const merged = this._mergeCoinsInTx(tx2, coins);
7250
7149
  const raw = BigInt(Math.floor(repayAmount * 10 ** targetAssetInfo.decimals));
7251
- const [repayCoin] = tx2.splitCoins(merged, [raw]);
7150
+ const bal = await this.client.getBalance({
7151
+ owner: this._address,
7152
+ coinType: targetAssetInfo.type
7153
+ });
7154
+ if (BigInt(bal.totalBalance) < raw) {
7155
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${targetAssetInfo.displayName} balance for repayment`, {
7156
+ available: Number(bal.totalBalance) / 10 ** targetAssetInfo.decimals,
7157
+ required: repayAmount
7158
+ });
7159
+ }
7160
+ const repayCoin = coinWithBalance({ type: targetAssetInfo.type, balance: raw })(tx2);
7252
7161
  await adapter.addRepayToTx(tx2, this._address, repayCoin, target.asset);
7253
7162
  return tx2;
7254
7163
  }
@@ -7279,20 +7188,23 @@ var T2000 = class _T2000 extends EventEmitter {
7279
7188
  if (canPTB) {
7280
7189
  const tx = new Transaction();
7281
7190
  tx.setSender(this._address);
7282
- const assetMerged = /* @__PURE__ */ new Map();
7283
7191
  const uniqueAssets = Array.from(new Set(entries.map((e) => e.borrow.asset)));
7284
7192
  for (const asset of uniqueAssets) {
7285
7193
  const info = SUPPORTED_ASSETS[asset];
7286
7194
  if (!info) throw new T2000Error("ASSET_NOT_SUPPORTED", `Cannot repay unknown asset: ${asset}`);
7287
- const coins = await this._fetchCoins(info.type);
7288
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${info.displayName} coins for repayment`);
7289
- assetMerged.set(asset, this._mergeCoinsInTx(tx, coins));
7195
+ const required = entries.filter((e) => e.borrow.asset === asset).reduce((sum, e) => sum + BigInt(Math.floor(e.borrow.amount * 10 ** info.decimals)), 0n);
7196
+ const bal = await this.client.getBalance({ owner: this._address, coinType: info.type });
7197
+ if (BigInt(bal.totalBalance) < required) {
7198
+ throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${info.displayName} balance for repayment`, {
7199
+ available: Number(bal.totalBalance) / 10 ** info.decimals,
7200
+ required: Number(required) / 10 ** info.decimals
7201
+ });
7202
+ }
7290
7203
  }
7291
7204
  for (const { borrow, adapter } of entries) {
7292
7205
  const info = SUPPORTED_ASSETS[borrow.asset];
7293
- const merged = assetMerged.get(borrow.asset);
7294
7206
  const raw = BigInt(Math.floor(borrow.amount * 10 ** info.decimals));
7295
- const [repayCoin] = tx.splitCoins(merged, [raw]);
7207
+ const repayCoin = coinWithBalance({ type: info.type, balance: raw })(tx);
7296
7208
  await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
7297
7209
  totalRepaid += borrow.amount;
7298
7210
  }
@@ -7423,8 +7335,8 @@ var T2000 = class _T2000 extends EventEmitter {
7423
7335
  totalGasCost: 0
7424
7336
  };
7425
7337
  }
7426
- const preUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7427
- const preUsdcRaw = preUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7338
+ const preUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7339
+ const preUsdcRaw = BigInt(preUsdcBal.totalBalance);
7428
7340
  const claimResult = await this.claimRewards();
7429
7341
  if (!claimResult.tx) {
7430
7342
  return {
@@ -7447,8 +7359,8 @@ var T2000 = class _T2000 extends EventEmitter {
7447
7359
  const decimals = getDecimalsForCoinType(reward.coinType) ?? 9;
7448
7360
  const rawAmount = BigInt(Math.floor(reward.amount * 10 ** decimals));
7449
7361
  if (rawAmount <= 0n) continue;
7450
- const coins = await this._fetchCoins(reward.coinType);
7451
- if (coins.length === 0) continue;
7362
+ const bal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7363
+ if (BigInt(bal.totalBalance) < rawAmount) continue;
7452
7364
  const route = await findSwapRoute2({
7453
7365
  walletAddress: this._address,
7454
7366
  from: reward.coinType,
@@ -7460,10 +7372,9 @@ var T2000 = class _T2000 extends EventEmitter {
7460
7372
  const swapResult = await executeTx(this.client, this._signer, async () => {
7461
7373
  const tx = new Transaction();
7462
7374
  tx.setSender(this._address);
7463
- const freshCoins = await this._fetchCoins(reward.coinType);
7464
- if (freshCoins.length === 0) return tx;
7465
- const merged = this._mergeCoinsInTx(tx, freshCoins);
7466
- const [inputCoin] = tx.splitCoins(merged, [rawAmount]);
7375
+ const freshBal = await this.client.getBalance({ owner: this._address, coinType: reward.coinType });
7376
+ if (BigInt(freshBal.totalBalance) < rawAmount) return tx;
7377
+ const inputCoin = coinWithBalance({ type: reward.coinType, balance: rawAmount })(tx);
7467
7378
  const outputCoin = await buildSwapTx2({
7468
7379
  walletAddress: this._address,
7469
7380
  route,
@@ -7480,8 +7391,8 @@ var T2000 = class _T2000 extends EventEmitter {
7480
7391
  console.warn(`[compound] Failed to swap ${reward.symbol}:`, err instanceof Error ? err.message : err);
7481
7392
  }
7482
7393
  }
7483
- const postUsdcCoins = await this._fetchCoins(USDC_TYPE2);
7484
- const postUsdcRaw = postUsdcCoins.reduce((s, c) => s + BigInt(c.balance), 0n);
7394
+ const postUsdcBal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7395
+ const postUsdcRaw = BigInt(postUsdcBal.totalBalance);
7485
7396
  const gainedRaw = postUsdcRaw > preUsdcRaw ? postUsdcRaw - preUsdcRaw : 0n;
7486
7397
  const depositUsdc = Number(gainedRaw) / 10 ** USDC_DEC;
7487
7398
  let depositTx = "";
@@ -7491,10 +7402,11 @@ var T2000 = class _T2000 extends EventEmitter {
7491
7402
  const depositResult = await executeTx(this.client, this._signer, async () => {
7492
7403
  const tx = new Transaction();
7493
7404
  tx.setSender(this._address);
7494
- const coins = await this._fetchCoins(USDC_TYPE2);
7495
- if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins after swap");
7496
- const merged = this._mergeCoinsInTx(tx, coins);
7497
- const [inputCoin] = tx.splitCoins(merged, [gainedRaw]);
7405
+ const bal = await this.client.getBalance({ owner: this._address, coinType: USDC_TYPE2 });
7406
+ if (BigInt(bal.totalBalance) < gainedRaw) {
7407
+ throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC available after swap");
7408
+ }
7409
+ const inputCoin = coinWithBalance({ type: USDC_TYPE2, balance: gainedRaw })(tx);
7498
7410
  await adapter.addSaveToTx(tx, this._address, inputCoin, "USDC");
7499
7411
  return tx;
7500
7412
  });
@@ -7990,7 +7902,8 @@ var WRITE_APPENDER_REGISTRY = {
7990
7902
  overlayFee: ctx.overlayFee,
7991
7903
  providers,
7992
7904
  inputCoin: ctx.chainedCoin,
7993
- precomputedRoute: input.precomputedRoute
7905
+ precomputedRoute: input.precomputedRoute,
7906
+ sponsoredContext: ctx.sponsoredContext
7994
7907
  });
7995
7908
  if (!ctx.isOutputConsumed) {
7996
7909
  tx.transferObjects([result.coin], ctx.sender);