@t2000/sdk 0.19.23 → 0.20.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,159 +1,36 @@
1
- import { EventEmitter } from 'eventemitter3';
2
1
  import { Transaction } from '@mysten/sui/transactions';
2
+ import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
3
+ import { EventEmitter } from 'eventemitter3';
3
4
  import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
4
- import { normalizeSuiAddress, isValidSuiAddress, normalizeStructTag } from '@mysten/sui/utils';
5
+ import { normalizeSuiAddress, isValidSuiAddress } from '@mysten/sui/utils';
5
6
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
6
7
  import { decodeSuiPrivateKey } from '@mysten/sui/cryptography';
7
- import { createHash, randomUUID, randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
8
+ import { createHash, randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
8
9
  import { access, mkdir, writeFile, readFile } from 'fs/promises';
9
10
  import { join, dirname, resolve } from 'path';
10
11
  import { homedir } from 'os';
11
12
  import { getPools, getLendingPositions, getHealthFactor as getHealthFactor$1, depositCoinPTB, withdrawCoinPTB, borrowCoinPTB, repayCoinPTB, getUserAvailableLendingRewards, summaryLendingRewards, claimLendingRewardsPTB, updateOraclePriceBeforeUserOperationPTB } from '@naviprotocol/lending';
12
- import { AggregatorClient, Env } from '@cetusprotocol/aggregator-sdk';
13
- import { SuilendClient, LENDING_MARKET_ID, LENDING_MARKET_TYPE } from '@suilend/sdk/client';
14
- import { initializeSuilend, initializeObligations } from '@suilend/sdk/lib/initialize';
15
- import { Side } from '@suilend/sdk/lib/types';
16
13
  import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
17
14
 
18
- // src/t2000.ts
19
-
20
- // src/constants.ts
21
- var MIST_PER_SUI = 1000000000n;
22
- var SUI_DECIMALS = 9;
23
- var USDC_DECIMALS = 6;
24
- var BPS_DENOMINATOR = 10000n;
25
- var AUTO_TOPUP_THRESHOLD = 50000000n;
26
- var GAS_RESERVE_TARGET = 150000000n;
27
- var AUTO_TOPUP_AMOUNT = 1000000n;
28
- var AUTO_TOPUP_MIN_USDC = 2000000n;
29
- var SAVE_FEE_BPS = 10n;
30
- var SWAP_FEE_BPS = 0n;
31
- var BORROW_FEE_BPS = 5n;
32
- var CLOCK_ID = "0x6";
33
- var SUPPORTED_ASSETS = {
34
- USDC: {
35
- type: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
36
- decimals: 6,
37
- symbol: "USDC",
38
- displayName: "USDC"
39
- },
40
- USDT: {
41
- type: "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT",
42
- decimals: 6,
43
- symbol: "USDT",
44
- displayName: "suiUSDT"
45
- },
46
- USDe: {
47
- type: "0x41d587e5336f1c86cad50d38a7136db99333bb9bda91cea4ba69115defeb1402::sui_usde::SUI_USDE",
48
- decimals: 6,
49
- symbol: "USDe",
50
- displayName: "suiUSDe"
51
- },
52
- USDsui: {
53
- type: "0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI",
54
- decimals: 6,
55
- symbol: "USDsui",
56
- displayName: "USDsui"
57
- },
58
- SUI: {
59
- type: "0x2::sui::SUI",
60
- decimals: 9,
61
- symbol: "SUI",
62
- displayName: "SUI"
63
- },
64
- BTC: {
65
- type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC",
66
- decimals: 8,
67
- symbol: "BTC",
68
- displayName: "Bitcoin"
69
- },
70
- ETH: {
71
- type: "0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH",
72
- decimals: 8,
73
- symbol: "ETH",
74
- displayName: "Ethereum"
75
- },
76
- GOLD: {
77
- type: "0x9d297676e7a4b771ab023291377b2adfaa4938fb9080b8d12430e4b108b836a9::xaum::XAUM",
78
- decimals: 9,
79
- symbol: "GOLD",
80
- displayName: "Gold"
81
- }
82
- };
83
- var STABLE_ASSETS = ["USDC", "USDT", "USDe", "USDsui"];
84
- var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
85
- var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
86
- var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
87
- var DEFAULT_NETWORK = "mainnet";
88
- var DEFAULT_RPC_URL = "https://fullnode.mainnet.sui.io:443";
89
- var DEFAULT_KEY_PATH = "~/.t2000/wallet.key";
90
- var API_BASE_URL = process.env.T2000_API_URL ?? "https://api.t2000.ai";
91
- var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
92
- var CETUS_PACKAGE = "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb";
93
- var INVESTMENT_ASSETS = {
94
- SUI: SUPPORTED_ASSETS.SUI,
95
- BTC: SUPPORTED_ASSETS.BTC,
96
- ETH: SUPPORTED_ASSETS.ETH,
97
- GOLD: SUPPORTED_ASSETS.GOLD
15
+ var __defProp = Object.defineProperty;
16
+ var __getOwnPropNames = Object.getOwnPropertyNames;
17
+ var __esm = (fn, res) => function __init() {
18
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
98
19
  };
99
- var DEFAULT_STRATEGIES = {
100
- bluechip: {
101
- name: "Bluechip / Large-Cap",
102
- allocations: { BTC: 50, ETH: 30, SUI: 20 },
103
- description: "Large-cap crypto index",
104
- custom: false
105
- },
106
- layer1: {
107
- name: "Smart Contract Platforms",
108
- allocations: { ETH: 50, SUI: 50 },
109
- description: "Smart contract platforms",
110
- custom: false
111
- },
112
- "sui-heavy": {
113
- name: "Sui-Weighted Portfolio",
114
- allocations: { BTC: 20, ETH: 20, SUI: 60 },
115
- description: "Sui-weighted portfolio",
116
- custom: false
117
- },
118
- "all-weather": {
119
- name: "All-Weather Portfolio",
120
- allocations: { BTC: 30, ETH: 20, SUI: 20, GOLD: 30 },
121
- description: "Crypto and commodities",
122
- custom: false
123
- },
124
- "safe-haven": {
125
- name: "Safe Haven",
126
- allocations: { BTC: 50, GOLD: 50 },
127
- description: "Store-of-value assets",
128
- custom: false
129
- }
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
130
23
  };
131
- var PERPS_MARKETS = ["SUI-PERP"];
132
- var DEFAULT_MAX_LEVERAGE = 5;
133
- var DEFAULT_MAX_POSITION_SIZE = 1e3;
134
- var GAS_RESERVE_MIN = 0.05;
135
24
 
136
25
  // src/errors.ts
137
- var T2000Error = class extends Error {
138
- code;
139
- data;
140
- retryable;
141
- constructor(code, message, data, retryable = false) {
142
- super(message);
143
- this.name = "T2000Error";
144
- this.code = code;
145
- this.data = data;
146
- this.retryable = retryable;
147
- }
148
- toJSON() {
149
- return {
150
- error: this.code,
151
- message: this.message,
152
- ...this.data && { data: this.data },
153
- retryable: this.retryable
154
- };
155
- }
156
- };
26
+ var errors_exports = {};
27
+ __export(errors_exports, {
28
+ T2000Error: () => T2000Error,
29
+ isMoveAbort: () => isMoveAbort,
30
+ mapMoveAbortCode: () => mapMoveAbortCode,
31
+ mapWalletError: () => mapWalletError,
32
+ parseMoveAbortMessage: () => parseMoveAbortMessage
33
+ });
157
34
  function mapWalletError(error) {
158
35
  const msg = error instanceof Error ? error.message : String(error);
159
36
  if (msg.includes("rejected") || msg.includes("cancelled")) {
@@ -182,9 +59,7 @@ function mapMoveAbortCode(code) {
182
59
  1600: "Health factor too low \u2014 withdrawal would risk liquidation",
183
60
  1605: "Asset borrowing is disabled or at capacity on this protocol",
184
61
  // NAVI utils abort codes
185
- 46e3: "Insufficient balance to repay \u2014 withdraw some savings first to get cash",
186
- // Cetus DEX abort codes
187
- 46001: "Swap failed \u2014 the DEX pool rejected the trade (liquidity or routing issue). Try again."
62
+ 46e3: "Insufficient balance to repay \u2014 withdraw some savings first to get cash"
188
63
  };
189
64
  return abortMessages[code] ?? `Move abort code: ${code}`;
190
65
  }
@@ -200,7 +75,7 @@ function parseMoveAbortMessage(msg) {
200
75
  const context = `${moduleMatch?.[1] ?? ""}${fnMatch ? `::${fnMatch[1]}` : ""}`.toLowerCase();
201
76
  const suffix = moduleMatch ? ` [${moduleMatch[1]}${fnMatch ? `::${fnMatch[1]}` : ""}]` : "";
202
77
  if (context.includes("slippage")) {
203
- return `Swap slippage too high \u2014 price moved during execution${suffix}`;
78
+ return `Slippage too high \u2014 price moved during execution${suffix}`;
204
79
  }
205
80
  if (context.includes("balance::split") || context.includes("balance::ENotEnough")) {
206
81
  return `Insufficient on-chain balance${suffix}`;
@@ -210,8 +85,296 @@ function parseMoveAbortMessage(msg) {
210
85
  }
211
86
  return msg;
212
87
  }
88
+ var T2000Error;
89
+ var init_errors = __esm({
90
+ "src/errors.ts"() {
91
+ T2000Error = class extends Error {
92
+ code;
93
+ data;
94
+ retryable;
95
+ constructor(code, message, data, retryable = false) {
96
+ super(message);
97
+ this.name = "T2000Error";
98
+ this.code = code;
99
+ this.data = data;
100
+ this.retryable = retryable;
101
+ }
102
+ toJSON() {
103
+ return {
104
+ error: this.code,
105
+ message: this.message,
106
+ ...this.data && { data: this.data },
107
+ retryable: this.retryable
108
+ };
109
+ }
110
+ };
111
+ }
112
+ });
113
+
114
+ // src/protocols/volo.ts
115
+ var volo_exports = {};
116
+ __export(volo_exports, {
117
+ MIN_STAKE_MIST: () => MIN_STAKE_MIST,
118
+ SUI_SYSTEM_STATE: () => SUI_SYSTEM_STATE,
119
+ VOLO_METADATA: () => VOLO_METADATA,
120
+ VOLO_PKG: () => VOLO_PKG,
121
+ VOLO_POOL: () => VOLO_POOL,
122
+ VSUI_TYPE: () => VSUI_TYPE,
123
+ buildStakeVSuiTx: () => buildStakeVSuiTx,
124
+ buildUnstakeVSuiTx: () => buildUnstakeVSuiTx,
125
+ getVoloStats: () => getVoloStats
126
+ });
127
+ async function getVoloStats() {
128
+ const res = await fetch(VOLO_STATS_URL, {
129
+ signal: AbortSignal.timeout(8e3)
130
+ });
131
+ if (!res.ok) {
132
+ throw new Error(`VOLO stats API error: HTTP ${res.status}`);
133
+ }
134
+ const data = await res.json();
135
+ const d = data.data ?? data;
136
+ return {
137
+ apy: d.apy ?? 0,
138
+ exchangeRate: d.exchange_rate ?? d.exchangeRate ?? 1,
139
+ tvl: d.tvl ?? 0
140
+ };
141
+ }
142
+ async function buildStakeVSuiTx(_client, address, amountMist) {
143
+ if (amountMist < MIN_STAKE_MIST) {
144
+ throw new Error(`Minimum stake is 1 SUI (${MIN_STAKE_MIST} MIST). Got: ${amountMist}`);
145
+ }
146
+ const tx = new Transaction();
147
+ tx.setSender(address);
148
+ const [suiCoin] = tx.splitCoins(tx.gas, [amountMist]);
149
+ const [vSuiCoin] = tx.moveCall({
150
+ target: `${VOLO_PKG}::stake_pool::stake`,
151
+ arguments: [
152
+ tx.object(VOLO_POOL),
153
+ tx.object(VOLO_METADATA),
154
+ tx.object(SUI_SYSTEM_STATE),
155
+ suiCoin
156
+ ]
157
+ });
158
+ tx.transferObjects([vSuiCoin], address);
159
+ return tx;
160
+ }
161
+ async function buildUnstakeVSuiTx(client, address, amountMist) {
162
+ const coins = await fetchVSuiCoins(client, address);
163
+ if (coins.length === 0) {
164
+ throw new Error("No vSUI found in wallet.");
165
+ }
166
+ const tx = new Transaction();
167
+ tx.setSender(address);
168
+ const primary = tx.object(coins[0].coinObjectId);
169
+ if (coins.length > 1) {
170
+ tx.mergeCoins(primary, coins.slice(1).map((c) => tx.object(c.coinObjectId)));
171
+ }
172
+ let vSuiCoin;
173
+ if (amountMist === "all") {
174
+ vSuiCoin = primary;
175
+ } else {
176
+ [vSuiCoin] = tx.splitCoins(primary, [amountMist]);
177
+ }
178
+ const [suiCoin] = tx.moveCall({
179
+ target: `${VOLO_PKG}::stake_pool::unstake`,
180
+ arguments: [
181
+ tx.object(VOLO_POOL),
182
+ tx.object(VOLO_METADATA),
183
+ tx.object(SUI_SYSTEM_STATE),
184
+ vSuiCoin
185
+ ]
186
+ });
187
+ tx.transferObjects([suiCoin], address);
188
+ return tx;
189
+ }
190
+ async function fetchVSuiCoins(client, address) {
191
+ const all = [];
192
+ let cursor;
193
+ let hasNext = true;
194
+ while (hasNext) {
195
+ const page = await client.getCoins({
196
+ owner: address,
197
+ coinType: VSUI_TYPE,
198
+ cursor: cursor ?? void 0
199
+ });
200
+ all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
201
+ cursor = page.nextCursor;
202
+ hasNext = page.hasNextPage;
203
+ }
204
+ return all;
205
+ }
206
+ var VOLO_PKG, VOLO_POOL, VOLO_METADATA, VSUI_TYPE, SUI_SYSTEM_STATE, MIN_STAKE_MIST, VOLO_STATS_URL;
207
+ var init_volo = __esm({
208
+ "src/protocols/volo.ts"() {
209
+ VOLO_PKG = "0x68d22cf8bdbcd11ecba1e094922873e4080d4d11133e2443fddda0bfd11dae20";
210
+ VOLO_POOL = "0x2d914e23d82fedef1b5f56a32d5c64bdcc3087ccfea2b4d6ea51a71f587840e5";
211
+ VOLO_METADATA = "0x680cd26af32b2bde8d3361e804c53ec1d1cfe24c7f039eb7f549e8dfde389a60";
212
+ VSUI_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
213
+ SUI_SYSTEM_STATE = "0x05";
214
+ MIN_STAKE_MIST = 1000000000n;
215
+ VOLO_STATS_URL = "https://open-api.naviprotocol.io/api/volo/stats";
216
+ }
217
+ });
218
+
219
+ // src/protocols/cetus-swap.ts
220
+ var cetus_swap_exports = {};
221
+ __export(cetus_swap_exports, {
222
+ TOKEN_MAP: () => TOKEN_MAP,
223
+ buildSwapTx: () => buildSwapTx,
224
+ findSwapRoute: () => findSwapRoute,
225
+ resolveTokenType: () => resolveTokenType,
226
+ simulateSwap: () => simulateSwap
227
+ });
228
+ function getClient(walletAddress) {
229
+ if (clientInstance) return clientInstance;
230
+ clientInstance = new AggregatorClient({
231
+ signer: walletAddress,
232
+ env: Env.Mainnet
233
+ });
234
+ return clientInstance;
235
+ }
236
+ async function findSwapRoute(params) {
237
+ const client = getClient(params.walletAddress);
238
+ const findParams = {
239
+ from: params.from,
240
+ target: params.to,
241
+ amount: params.amount.toString(),
242
+ byAmountIn: params.byAmountIn
243
+ };
244
+ const routerData = await client.findRouters(findParams);
245
+ if (!routerData) return null;
246
+ if (routerData.insufficientLiquidity) {
247
+ return {
248
+ routerData,
249
+ amountIn: routerData.amountIn.toString(),
250
+ amountOut: routerData.amountOut.toString(),
251
+ byAmountIn: params.byAmountIn,
252
+ priceImpact: routerData.deviationRatio,
253
+ insufficientLiquidity: true
254
+ };
255
+ }
256
+ if (routerData.error) {
257
+ const { T2000Error: T2000Error2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
258
+ throw new T2000Error2("SWAP_FAILED", `Cetus routing error: ${routerData.error.msg} (code ${routerData.error.code})`);
259
+ }
260
+ return {
261
+ routerData,
262
+ amountIn: routerData.amountIn.toString(),
263
+ amountOut: routerData.amountOut.toString(),
264
+ byAmountIn: params.byAmountIn,
265
+ priceImpact: routerData.deviationRatio,
266
+ insufficientLiquidity: false
267
+ };
268
+ }
269
+ async function buildSwapTx(params) {
270
+ const client = getClient(params.walletAddress);
271
+ const clampedSlippage = Math.max(1e-3, Math.min(params.slippage, 0.05));
272
+ const outputCoin = await client.routerSwap({
273
+ router: params.route.routerData,
274
+ inputCoin: params.inputCoin,
275
+ slippage: clampedSlippage,
276
+ txb: params.tx
277
+ });
278
+ return outputCoin;
279
+ }
280
+ async function simulateSwap(params) {
281
+ const client = getClient(params.walletAddress);
282
+ try {
283
+ await client.devInspectTransactionBlock(params.tx);
284
+ return { success: true };
285
+ } catch (err) {
286
+ return { success: false, error: err instanceof Error ? err.message : String(err) };
287
+ }
288
+ }
289
+ function resolveTokenType(nameOrType) {
290
+ if (nameOrType.includes("::")) return nameOrType;
291
+ return TOKEN_MAP[nameOrType.toUpperCase()] ?? null;
292
+ }
293
+ var clientInstance, TOKEN_MAP;
294
+ var init_cetus_swap = __esm({
295
+ "src/protocols/cetus-swap.ts"() {
296
+ clientInstance = null;
297
+ TOKEN_MAP = {
298
+ SUI: "0x2::sui::SUI",
299
+ USDC: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
300
+ USDT: "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT",
301
+ CETUS: "0x06864a6f921804860930db6ddbe2e16acdf8504495ea7481637a1c8b9a8fe54b::cetus::CETUS",
302
+ DEEP: "0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270::deep::DEEP",
303
+ NAVX: "0xa99b8952d4f7d947ea77fe0ecdcc9e5fc0bcab2841d6e2a5aa00c3044e5544b5::navx::NAVX",
304
+ vSUI: "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT",
305
+ haSUI: "0xbde4ba4c2e274a60ce15c1cfff9e5c42e136a8bc::hasui::HASUI",
306
+ afSUI: "0xf325ce1300e8dac124071d3152c5c5ee6174914f8bc2161e88329cf579246efc::afsui::AFSUI",
307
+ WAL: "0x356a26eb9e012a68958082340d4c4116e7f55615cf27affcff209cf0ae544f59::wal::WAL",
308
+ ETH: "0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH",
309
+ wBTC: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC",
310
+ FDUSD: "0xf16e6b723f242ec745dfd7634ad072c42d5c1d9ac9d62a39c381303eaa57693a::fdusd::FDUSD",
311
+ AUSD: "0x2053d08c1e2bd02791056171aab0fd12bd7cd7efad2ab8f6b9c8902f14df2ff2::ausd::AUSD",
312
+ BUCK: "0xce7ff77a83ea0cb6fd39bd8748e2ec89a3f41e8efdc3f4eb123e0ca37b184db2::buck::BUCK",
313
+ USDe: "0x41d587e5336f1c86cad50d38a7136db99333bb9bda91cea4ba69115defeb1402::sui_usde::SUI_USDE",
314
+ NS: "0x5145494a5f5100e645e4b0aa950fa6b68f614e8c59e17bc5ded3495123a79178::ns::NS",
315
+ BLUB: "0xfa7ac3951fdca12c1b6d18eb19e1aa2fbc31e4d45773c8e45b4ded3ef8d83f8a::blub::BLUB",
316
+ SCA: "0x7016aae72cfc67f2fadf55769c0a7dd54291a583b63051a5ed71081cce836ac6::sca::SCA",
317
+ TURBOS: "0x5d1f47ea69bb0de31c313d7acf89b890dbb8991ea8e03c6c355171f84bb1ba4a::turbos::TURBOS"
318
+ };
319
+ }
320
+ });
321
+
322
+ // src/constants.ts
323
+ var MIST_PER_SUI = 1000000000n;
324
+ var SUI_DECIMALS = 9;
325
+ var USDC_DECIMALS = 6;
326
+ var BPS_DENOMINATOR = 10000n;
327
+ var AUTO_TOPUP_THRESHOLD = 50000000n;
328
+ var GAS_RESERVE_TARGET = 150000000n;
329
+ var AUTO_TOPUP_MIN_USDC = 2000000n;
330
+ var SAVE_FEE_BPS = 10n;
331
+ var BORROW_FEE_BPS = 5n;
332
+ var CLOCK_ID = "0x6";
333
+ var SUPPORTED_ASSETS = {
334
+ USDC: {
335
+ type: "0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC",
336
+ decimals: 6,
337
+ symbol: "USDC",
338
+ displayName: "USDC"
339
+ },
340
+ USDT: {
341
+ type: "0x375f70cf2ae4c00bf37117d0c85a2c71545e6ee05c4a5c7d282cd66a4504b068::usdt::USDT",
342
+ decimals: 6,
343
+ symbol: "USDT",
344
+ displayName: "suiUSDT"
345
+ },
346
+ USDe: {
347
+ type: "0x41d587e5336f1c86cad50d38a7136db99333bb9bda91cea4ba69115defeb1402::sui_usde::SUI_USDE",
348
+ decimals: 6,
349
+ symbol: "USDe",
350
+ displayName: "suiUSDe"
351
+ },
352
+ USDsui: {
353
+ type: "0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::USDSUI",
354
+ decimals: 6,
355
+ symbol: "USDsui",
356
+ displayName: "USDsui"
357
+ },
358
+ SUI: {
359
+ type: "0x2::sui::SUI",
360
+ decimals: 9,
361
+ symbol: "SUI",
362
+ displayName: "SUI"
363
+ }
364
+ };
365
+ var STABLE_ASSETS = ["USDC"];
366
+ var T2000_PACKAGE_ID = process.env.T2000_PACKAGE_ID ?? "0xab92e9f1fe549ad3d6a52924a73181b45791e76120b975138fac9ec9b75db9f3";
367
+ var T2000_CONFIG_ID = process.env.T2000_CONFIG_ID ?? "0x408add9aa9322f93cfd87523d8f603006eb8713894f4c460283c58a6888dae8a";
368
+ var T2000_TREASURY_ID = process.env.T2000_TREASURY_ID ?? "0x3bb501b8300125dca59019247941a42af6b292a150ce3cfcce9449456be2ec91";
369
+ var DEFAULT_NETWORK = "mainnet";
370
+ var DEFAULT_RPC_URL = "https://fullnode.mainnet.sui.io:443";
371
+ var DEFAULT_KEY_PATH = "~/.t2000/wallet.key";
372
+ var API_BASE_URL = process.env.T2000_API_URL ?? "https://api.t2000.ai";
373
+ var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
374
+ var GAS_RESERVE_MIN = 0.05;
213
375
 
214
376
  // src/utils/sui.ts
377
+ init_errors();
215
378
  var cachedClient = null;
216
379
  function getSuiClient(rpcUrl) {
217
380
  const url = rpcUrl ?? DEFAULT_RPC_URL;
@@ -230,6 +393,9 @@ function truncateAddress(address) {
230
393
  if (address.length <= 10) return address;
231
394
  return `${address.slice(0, 6)}...${address.slice(-4)}`;
232
395
  }
396
+
397
+ // src/wallet/keyManager.ts
398
+ init_errors();
233
399
  var ALGORITHM = "aes-256-gcm";
234
400
  var SCRYPT_N = 2 ** 14;
235
401
  var SCRYPT_R = 8;
@@ -371,6 +537,7 @@ var ZkLoginSigner = class {
371
537
  return currentEpoch >= this.maxEpoch;
372
538
  }
373
539
  };
540
+ init_errors();
374
541
 
375
542
  // src/utils/format.ts
376
543
  function mistToSui(mist) {
@@ -496,18 +663,11 @@ async function queryBalance(client, address) {
496
663
  const stableBalancePromises = STABLE_ASSETS.map(
497
664
  (asset) => client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS[asset].type }).then((b) => ({ asset, amount: Number(b.totalBalance) / 10 ** SUPPORTED_ASSETS[asset].decimals })).catch(() => ({ asset, amount: 0 }))
498
665
  );
499
- const nonSuiInvestmentAssets = Object.keys(INVESTMENT_ASSETS).filter((a) => a !== "SUI");
500
- const investBalancePromises = nonSuiInvestmentAssets.map(
501
- (asset) => client.getBalance({ owner: address, coinType: INVESTMENT_ASSETS[asset].type }).then((b) => ({ asset, amount: Number(b.totalBalance) / 10 ** INVESTMENT_ASSETS[asset].decimals })).catch(() => ({ asset, amount: 0 }))
502
- );
503
- const [suiBalance, suiPriceUsd, ...rest] = await Promise.all([
666
+ const [suiBalance, suiPriceUsd, ...stableResults] = await Promise.all([
504
667
  client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
505
668
  fetchSuiPrice(client),
506
- ...stableBalancePromises,
507
- ...investBalancePromises
669
+ ...stableBalancePromises
508
670
  ]);
509
- const stableResults = rest.slice(0, STABLE_ASSETS.length);
510
- const investResults = rest.slice(STABLE_ASSETS.length);
511
671
  const stables = {};
512
672
  let totalStables = 0;
513
673
  for (const { asset, amount } of stableResults) {
@@ -518,27 +678,17 @@ async function queryBalance(client, address) {
518
678
  const savings = 0;
519
679
  const usdEquiv = suiAmount * suiPriceUsd;
520
680
  const total = totalStables + savings + usdEquiv;
521
- const assets = {
522
- USDC: stables.USDC ?? 0,
523
- SUI: suiAmount
524
- };
525
- for (const { asset, amount } of investResults) {
526
- assets[asset] = amount;
527
- }
528
681
  return {
529
682
  available: totalStables,
530
683
  savings,
531
684
  debt: 0,
532
- investment: 0,
533
- investmentPnL: 0,
534
685
  pendingRewards: 0,
535
686
  gasReserve: {
536
687
  sui: suiAmount,
537
688
  usdEquiv
538
689
  },
539
690
  total,
540
- stables,
541
- assets
691
+ stables
542
692
  };
543
693
  }
544
694
 
@@ -645,12 +795,10 @@ function classifyAction(targets, commandTypes) {
645
795
  // src/protocols/protocolFee.ts
646
796
  var FEE_RATES = {
647
797
  save: SAVE_FEE_BPS,
648
- swap: SWAP_FEE_BPS,
649
798
  borrow: BORROW_FEE_BPS
650
799
  };
651
800
  var OP_CODES = {
652
801
  save: 0,
653
- swap: 1,
654
802
  borrow: 2
655
803
  };
656
804
  function calculateFee(operation, amount) {
@@ -694,6 +842,7 @@ async function reportFee(agentAddress, operation, feeAmount, feeRate, txDigest)
694
842
  } catch {
695
843
  }
696
844
  }
845
+ init_errors();
697
846
  var MIN_HEALTH_FACTOR = 1.5;
698
847
  var NAVI_SUPPORTED_ASSETS = [...STABLE_ASSETS, "SUI", "ETH", "GOLD"];
699
848
  function sdkOptions(client) {
@@ -1127,15 +1276,12 @@ async function getFundStatus(client, address) {
1127
1276
  }
1128
1277
 
1129
1278
  // src/adapters/registry.ts
1279
+ init_errors();
1130
1280
  var ProtocolRegistry = class {
1131
1281
  lending = /* @__PURE__ */ new Map();
1132
- swap = /* @__PURE__ */ new Map();
1133
1282
  registerLending(adapter) {
1134
1283
  this.lending.set(adapter.id, adapter);
1135
1284
  }
1136
- registerSwap(adapter) {
1137
- this.swap.set(adapter.id, adapter);
1138
- }
1139
1285
  async bestSaveRate(asset) {
1140
1286
  const candidates = [];
1141
1287
  for (const adapter of this.lending.values()) {
@@ -1171,23 +1317,6 @@ var ProtocolRegistry = class {
1171
1317
  candidates.sort((a, b) => a.rate.borrowApy - b.rate.borrowApy);
1172
1318
  return candidates[0];
1173
1319
  }
1174
- async bestSwapQuote(from, to, amount) {
1175
- const candidates = [];
1176
- for (const adapter of this.swap.values()) {
1177
- const pairs = adapter.getSupportedPairs();
1178
- if (!pairs.some((p) => p.from === from && p.to === to)) continue;
1179
- try {
1180
- const quote = await adapter.getQuote(from, to, amount);
1181
- candidates.push({ adapter, quote });
1182
- } catch {
1183
- }
1184
- }
1185
- if (candidates.length === 0) {
1186
- throw new T2000Error("ASSET_NOT_SUPPORTED", `No swap adapter supports ${from} \u2192 ${to}`);
1187
- }
1188
- candidates.sort((a, b) => b.quote.expectedOutput - a.quote.expectedOutput);
1189
- return candidates[0];
1190
- }
1191
1320
  async bestSaveRateAcrossAssets() {
1192
1321
  const candidates = [];
1193
1322
  for (const asset of STABLE_ASSETS) {
@@ -1209,9 +1338,8 @@ var ProtocolRegistry = class {
1209
1338
  }
1210
1339
  async allRatesAcrossAssets() {
1211
1340
  const results = [];
1212
- const allAssets = [...STABLE_ASSETS, ...Object.keys(INVESTMENT_ASSETS)];
1213
1341
  const seen = /* @__PURE__ */ new Set();
1214
- for (const asset of allAssets) {
1342
+ for (const asset of STABLE_ASSETS) {
1215
1343
  if (seen.has(asset)) continue;
1216
1344
  seen.add(asset);
1217
1345
  for (const adapter of this.lending.values()) {
@@ -1260,19 +1388,15 @@ var ProtocolRegistry = class {
1260
1388
  getLending(id) {
1261
1389
  return this.lending.get(id);
1262
1390
  }
1263
- getSwap(id) {
1264
- return this.swap.get(id);
1265
- }
1266
1391
  listLending() {
1267
1392
  return [...this.lending.values()];
1268
1393
  }
1269
- listSwap() {
1270
- return [...this.swap.values()];
1271
- }
1272
1394
  };
1273
1395
 
1396
+ // src/adapters/navi.ts
1397
+ init_errors();
1398
+
1274
1399
  // src/adapters/descriptors.ts
1275
- var SUILEND_PACKAGE = "0xf95b06141ed4a174f239417323bde3f209b972f5930d8521ea38a52aff3a6ddf";
1276
1400
  var naviDescriptor = {
1277
1401
  id: "navi",
1278
1402
  name: "NAVI Protocol",
@@ -1289,39 +1413,8 @@ var naviDescriptor = {
1289
1413
  "incentive_v3::repay": "repay"
1290
1414
  }
1291
1415
  };
1292
- var suilendDescriptor = {
1293
- id: "suilend",
1294
- name: "Suilend",
1295
- packages: [SUILEND_PACKAGE],
1296
- actionMap: {
1297
- "lending_market::deposit_liquidity_and_mint_ctokens": "save",
1298
- "lending_market::deposit_ctokens_into_obligation": "save",
1299
- "lending_market::create_obligation": "save",
1300
- "lending_market::withdraw_ctokens": "withdraw",
1301
- "lending_market::redeem_ctokens_and_withdraw_liquidity": "withdraw",
1302
- "lending_market::redeem_ctokens_and_withdraw_liquidity_request": "withdraw",
1303
- "lending_market::fulfill_liquidity_request": "withdraw",
1304
- "lending_market::unstake_sui_from_staker": "withdraw",
1305
- "lending_market::borrow": "borrow",
1306
- "lending_market::repay": "repay"
1307
- }
1308
- };
1309
- var cetusDescriptor = {
1310
- id: "cetus",
1311
- name: "Cetus DEX",
1312
- packages: [CETUS_PACKAGE],
1313
- actionMap: {
1314
- "router::swap": "swap",
1315
- "router::swap_ab_bc": "swap",
1316
- "router::swap_ab_cb": "swap",
1317
- "router::swap_ba_bc": "swap",
1318
- "router::swap_ba_cb": "swap"
1319
- }
1320
- };
1321
1416
  var allDescriptors = [
1322
- naviDescriptor,
1323
- suilendDescriptor,
1324
- cetusDescriptor
1417
+ naviDescriptor
1325
1418
  ];
1326
1419
 
1327
1420
  // src/adapters/navi.ts
@@ -1405,712 +1498,52 @@ var NaviAdapter = class {
1405
1498
  return addClaimRewardsToTx(tx, this.client, address);
1406
1499
  }
1407
1500
  };
1408
- var DEFAULT_SLIPPAGE_BPS = 300;
1409
- function createAggregatorClient(client, signer) {
1410
- return new AggregatorClient({
1411
- client,
1412
- signer,
1413
- env: Env.Mainnet
1414
- });
1415
- }
1416
- async function buildSwapTx(params) {
1417
- const { client, address, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
1418
- const fromInfo = SUPPORTED_ASSETS[fromAsset];
1419
- const toInfo = SUPPORTED_ASSETS[toAsset];
1420
- if (!fromInfo || !toInfo) {
1421
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
1422
- }
1423
- const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
1424
- const aggClient = createAggregatorClient(client, address);
1425
- const _origLog = console.log;
1426
- console.log = () => {
1427
- };
1428
- let result;
1429
- try {
1430
- result = await aggClient.findRouters({
1431
- from: fromInfo.type,
1432
- target: toInfo.type,
1433
- amount: rawAmount,
1434
- byAmountIn: true
1435
- });
1436
- } finally {
1437
- console.log = _origLog;
1438
- }
1439
- if (!result || result.insufficientLiquidity) {
1440
- throw new T2000Error(
1441
- "ASSET_NOT_SUPPORTED",
1442
- `No swap route found for ${fromAsset} \u2192 ${toAsset}`
1443
- );
1501
+ function hasLeadingZeroBits(hash, bits) {
1502
+ const fullBytes = Math.floor(bits / 8);
1503
+ const remainingBits = bits % 8;
1504
+ for (let i = 0; i < fullBytes; i++) {
1505
+ if (hash[i] !== 0) return false;
1444
1506
  }
1445
- const tx = new Transaction();
1446
- const slippage = maxSlippageBps / 1e4;
1447
- console.log = () => {
1448
- };
1449
- try {
1450
- await aggClient.fastRouterSwap({
1451
- router: result,
1452
- txb: tx,
1453
- slippage
1454
- });
1455
- } finally {
1456
- console.log = _origLog;
1507
+ if (remainingBits > 0) {
1508
+ const mask = 255 << 8 - remainingBits;
1509
+ if ((hash[fullBytes] & mask) !== 0) return false;
1457
1510
  }
1458
- const estimatedOut = Number(result.amountOut.toString());
1459
- return {
1460
- tx,
1461
- estimatedOut,
1462
- toDecimals: toInfo.decimals
1463
- };
1511
+ return true;
1464
1512
  }
1465
- async function addSwapToTx(params) {
1466
- const { tx, client, address, inputCoin, fromAsset, toAsset, amount, maxSlippageBps = DEFAULT_SLIPPAGE_BPS } = params;
1467
- const fromInfo = SUPPORTED_ASSETS[fromAsset];
1468
- const toInfo = SUPPORTED_ASSETS[toAsset];
1469
- if (!fromInfo || !toInfo) {
1470
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
1471
- }
1472
- const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
1473
- const aggClient = createAggregatorClient(client, address);
1474
- const _origLog = console.log;
1475
- console.log = () => {
1476
- };
1477
- let result;
1478
- try {
1479
- result = await aggClient.findRouters({
1480
- from: fromInfo.type,
1481
- target: toInfo.type,
1482
- amount: rawAmount,
1483
- byAmountIn: true
1484
- });
1485
- } finally {
1486
- console.log = _origLog;
1513
+ function solveHashcash(challenge) {
1514
+ const bits = parseInt(challenge.split(":")[1], 10);
1515
+ let counter = 0;
1516
+ while (true) {
1517
+ const stamp = `${challenge}${counter.toString(16)}`;
1518
+ const hash = createHash("sha256").update(stamp).digest();
1519
+ if (hasLeadingZeroBits(hash, bits)) return stamp;
1520
+ counter++;
1487
1521
  }
1488
- if (!result || result.insufficientLiquidity) {
1489
- throw new T2000Error(
1490
- "ASSET_NOT_SUPPORTED",
1491
- `No swap route found for ${fromAsset} \u2192 ${toAsset}`
1492
- );
1493
- }
1494
- const slippage = maxSlippageBps / 1e4;
1495
- console.log = () => {
1496
- };
1497
- let outputCoin;
1498
- try {
1499
- outputCoin = await aggClient.routerSwap({
1500
- router: result,
1501
- txb: tx,
1502
- inputCoin,
1503
- slippage
1504
- });
1505
- } finally {
1506
- console.log = _origLog;
1507
- }
1508
- const estimatedOut = Number(result.amountOut.toString());
1509
- return {
1510
- outputCoin,
1511
- estimatedOut,
1512
- toDecimals: toInfo.decimals
1513
- };
1514
- }
1515
- async function buildRawSwapTx(params) {
1516
- const { client, address, fromCoinType, toCoinType, amount, toDecimals, maxSlippageBps = 500 } = params;
1517
- const aggClient = createAggregatorClient(client, address);
1518
- const _origLog = console.log;
1519
- console.log = () => {
1520
- };
1521
- let result;
1522
- try {
1523
- result = await aggClient.findRouters({
1524
- from: fromCoinType,
1525
- target: toCoinType,
1526
- amount,
1527
- byAmountIn: true
1528
- });
1529
- } finally {
1530
- console.log = _origLog;
1531
- }
1532
- if (!result || result.insufficientLiquidity) {
1533
- throw new T2000Error("ASSET_NOT_SUPPORTED", `No swap route for reward token \u2192 USDC`);
1534
- }
1535
- const tx = new Transaction();
1536
- const slippage = maxSlippageBps / 1e4;
1537
- console.log = () => {
1538
- };
1539
- try {
1540
- await aggClient.fastRouterSwap({
1541
- router: result,
1542
- txb: tx,
1543
- slippage
1544
- });
1545
- } finally {
1546
- console.log = _origLog;
1547
- }
1548
- return {
1549
- tx,
1550
- estimatedOut: Number(result.amountOut.toString()),
1551
- toDecimals
1552
- };
1553
- }
1554
- async function getPoolPrice(client) {
1555
- try {
1556
- const pool = await client.getObject({
1557
- id: CETUS_USDC_SUI_POOL,
1558
- options: { showContent: true }
1559
- });
1560
- if (pool.data?.content?.dataType === "moveObject") {
1561
- const fields = pool.data.content.fields;
1562
- const currentSqrtPrice = BigInt(String(fields.current_sqrt_price ?? "0"));
1563
- if (currentSqrtPrice > 0n) {
1564
- const Q64 = 2n ** 64n;
1565
- const sqrtPriceFloat = Number(currentSqrtPrice) / Number(Q64);
1566
- const rawPrice = sqrtPriceFloat * sqrtPriceFloat;
1567
- const suiPriceUsd = 1e3 / rawPrice;
1568
- if (suiPriceUsd > 0.01 && suiPriceUsd < 1e3) return suiPriceUsd;
1569
- }
1570
- }
1571
- } catch {
1572
- }
1573
- return 3.5;
1574
- }
1575
- async function getSwapQuote(client, fromAsset, toAsset, amount) {
1576
- const fromInfo = SUPPORTED_ASSETS[fromAsset];
1577
- const toInfo = SUPPORTED_ASSETS[toAsset];
1578
- if (!fromInfo || !toInfo) {
1579
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
1580
- }
1581
- const rawAmount = BigInt(Math.floor(amount * 10 ** fromInfo.decimals));
1582
- const poolPrice = await getPoolPrice(client);
1583
- try {
1584
- const aggClient = createAggregatorClient(client);
1585
- const result = await aggClient.findRouters({
1586
- from: fromInfo.type,
1587
- target: toInfo.type,
1588
- amount: rawAmount,
1589
- byAmountIn: true
1590
- });
1591
- if (!result || result.insufficientLiquidity) {
1592
- return fallbackQuote(fromAsset, amount, poolPrice);
1593
- }
1594
- const expectedOutput = Number(result.amountOut.toString()) / 10 ** toInfo.decimals;
1595
- const priceImpact = result.deviationRatio ?? 0;
1596
- return { expectedOutput, priceImpact, poolPrice };
1597
- } catch {
1598
- return fallbackQuote(fromAsset, amount, poolPrice);
1599
- }
1600
- }
1601
- function fallbackQuote(fromAsset, amount, poolPrice) {
1602
- const expectedOutput = fromAsset === "USDC" ? amount / poolPrice : amount * poolPrice;
1603
- return { expectedOutput, priceImpact: 0, poolPrice };
1604
1522
  }
1605
1523
 
1606
- // src/adapters/cetus.ts
1607
- var CetusAdapter = class {
1608
- id = "cetus";
1609
- name = "Cetus";
1610
- version = "1.0.0";
1611
- capabilities = ["swap"];
1612
- client;
1613
- async init(client) {
1614
- this.client = client;
1615
- }
1616
- initSync(client) {
1617
- this.client = client;
1618
- }
1619
- async getQuote(from, to, amount) {
1620
- return getSwapQuote(this.client, from, to, amount);
1621
- }
1622
- async buildSwapTx(address, from, to, amount, maxSlippageBps) {
1623
- const result = await buildSwapTx({
1624
- client: this.client,
1625
- address,
1626
- fromAsset: from,
1627
- toAsset: to,
1628
- amount,
1629
- maxSlippageBps
1630
- });
1631
- return {
1632
- tx: result.tx,
1633
- estimatedOut: result.estimatedOut,
1634
- toDecimals: result.toDecimals
1635
- };
1636
- }
1637
- getSupportedPairs() {
1638
- const pairs = [];
1639
- for (const asset of Object.keys(INVESTMENT_ASSETS)) {
1640
- pairs.push({ from: "USDC", to: asset }, { from: asset, to: "USDC" });
1641
- }
1642
- for (const a of STABLE_ASSETS) {
1643
- for (const b of STABLE_ASSETS) {
1644
- if (a !== b) pairs.push({ from: a, to: b });
1645
- }
1646
- }
1647
- return pairs;
1648
- }
1649
- async getPoolPrice() {
1650
- return getPoolPrice(this.client);
1651
- }
1652
- async addSwapToTx(tx, address, inputCoin, from, to, amount, maxSlippageBps) {
1653
- return addSwapToTx({
1654
- tx,
1655
- client: this.client,
1656
- address,
1657
- inputCoin,
1658
- fromAsset: from,
1659
- toAsset: to,
1660
- amount,
1661
- maxSlippageBps
1662
- });
1663
- }
1664
- };
1665
- var MIN_HEALTH_FACTOR2 = 1.5;
1666
- async function quietSuilend(fn) {
1667
- const origLog = console.log;
1668
- const origWarn = console.warn;
1669
- const filter = (...args) => typeof args[0] === "string" && (args[0].includes("PythEndpoint") || args[0].includes("PythConnection"));
1670
- console.log = (...args) => {
1671
- if (!filter(...args)) origLog.apply(console, args);
1672
- };
1673
- console.warn = (...args) => {
1674
- if (!filter(...args)) origWarn.apply(console, args);
1675
- };
1676
- return fn().finally(() => {
1677
- console.log = origLog;
1678
- console.warn = origWarn;
1679
- });
1680
- }
1681
- var SuilendAdapter = class {
1682
- id = "suilend";
1683
- name = "Suilend";
1684
- version = "3.0.0";
1685
- capabilities = ["save", "withdraw", "borrow", "repay"];
1686
- supportedAssets = [...STABLE_ASSETS, "SUI", "ETH", "BTC", "GOLD"];
1687
- supportsSameAssetBorrow = false;
1688
- client;
1689
- sdkClient = null;
1690
- async init(client) {
1691
- this.client = client;
1692
- }
1693
- initSync(client) {
1694
- this.client = client;
1695
- }
1696
- async getSdkClient() {
1697
- if (!this.sdkClient) {
1698
- this.sdkClient = await SuilendClient.initialize(
1699
- LENDING_MARKET_ID,
1700
- LENDING_MARKET_TYPE,
1701
- this.client,
1702
- false
1703
- );
1704
- }
1705
- return this.sdkClient;
1706
- }
1707
- resolveSymbol(coinType) {
1708
- try {
1709
- const normalized = normalizeStructTag(coinType);
1710
- for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
1711
- try {
1712
- if (normalizeStructTag(info.type) === normalized) return key;
1713
- } catch {
1714
- }
1715
- }
1716
- } catch {
1717
- }
1718
- const parts = coinType.split("::");
1719
- return parts[parts.length - 1] || "UNKNOWN";
1720
- }
1721
- async getRates(asset) {
1722
- try {
1723
- const sdk = await this.getSdkClient();
1724
- const { reserveMap } = await quietSuilend(() => initializeSuilend(this.client, sdk));
1725
- const assetInfo = SUPPORTED_ASSETS[asset];
1726
- if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1727
- const normalized = normalizeStructTag(assetInfo.type);
1728
- const reserve = Object.values(reserveMap).find((r) => {
1729
- try {
1730
- return normalizeStructTag(r.coinType) === normalized;
1731
- } catch {
1732
- return false;
1733
- }
1734
- });
1735
- if (!reserve) throw new T2000Error("ASSET_NOT_SUPPORTED", `Suilend does not support ${asset}`);
1736
- return {
1737
- asset,
1738
- saveApy: reserve.depositAprPercent.toNumber(),
1739
- borrowApy: reserve.borrowAprPercent.toNumber()
1740
- };
1741
- } catch (err) {
1742
- if (err instanceof T2000Error) throw err;
1743
- const msg = err instanceof Error ? err.message : String(err);
1744
- throw new T2000Error("PROTOCOL_UNAVAILABLE", `Suilend getRates failed: ${msg}`);
1745
- }
1746
- }
1747
- async getPositions(address) {
1748
- const supplies = [];
1749
- const borrows = [];
1750
- try {
1751
- const sdk = await this.getSdkClient();
1752
- const { reserveMap, refreshedRawReserves } = await quietSuilend(() => initializeSuilend(this.client, sdk));
1753
- const { obligations, obligationOwnerCaps } = await initializeObligations(
1754
- this.client,
1755
- sdk,
1756
- refreshedRawReserves,
1757
- reserveMap,
1758
- address
1759
- );
1760
- if (obligationOwnerCaps.length === 0 || obligations.length === 0) {
1761
- return { supplies, borrows };
1762
- }
1763
- const obligation = obligations[0];
1764
- for (const dep of obligation.deposits) {
1765
- const symbol = this.resolveSymbol(dep.coinType);
1766
- const amount = dep.depositedAmount.toNumber();
1767
- const amountUsd = dep.depositedAmountUsd.toNumber();
1768
- const apy = dep.reserve.depositAprPercent.toNumber();
1769
- if (amountUsd > 0.01) {
1770
- supplies.push({ asset: symbol, amount, amountUsd, apy });
1771
- }
1772
- }
1773
- for (const bor of obligation.borrows) {
1774
- const symbol = this.resolveSymbol(bor.coinType);
1775
- const amount = bor.borrowedAmount.toNumber();
1776
- const amountUsd = bor.borrowedAmountUsd.toNumber();
1777
- const apy = bor.reserve.borrowAprPercent.toNumber();
1778
- if (amountUsd > 0.01) {
1779
- borrows.push({ asset: symbol, amount, amountUsd, apy });
1780
- }
1781
- }
1782
- } catch (err) {
1783
- if (err instanceof T2000Error) throw err;
1784
- const msg = err instanceof Error ? err.message : String(err);
1785
- throw new T2000Error("PROTOCOL_UNAVAILABLE", `Suilend getPositions failed: ${msg}`);
1786
- }
1787
- return { supplies, borrows };
1788
- }
1789
- async getHealth(address) {
1790
- try {
1791
- const sdk = await this.getSdkClient();
1792
- const { reserveMap, refreshedRawReserves } = await quietSuilend(() => initializeSuilend(this.client, sdk));
1793
- const { obligations, obligationOwnerCaps } = await initializeObligations(
1794
- this.client,
1795
- sdk,
1796
- refreshedRawReserves,
1797
- reserveMap,
1798
- address
1799
- );
1800
- if (obligationOwnerCaps.length === 0 || obligations.length === 0) {
1801
- return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
1802
- }
1803
- const ob = obligations[0];
1804
- const supplied = ob.depositedAmountUsd.toNumber();
1805
- const borrowed = ob.borrowedAmountUsd.toNumber();
1806
- const borrowLimit = ob.borrowLimitUsd.toNumber();
1807
- const unhealthy = ob.unhealthyBorrowValueUsd.toNumber();
1808
- const liqThreshold = supplied > 0 ? unhealthy / supplied : 0.75;
1809
- const healthFactor = borrowed > 0 ? unhealthy / borrowed : Infinity;
1810
- const maxBorrow = Math.max(0, borrowLimit - borrowed);
1811
- return { healthFactor, supplied, borrowed, maxBorrow, liquidationThreshold: liqThreshold };
1812
- } catch {
1813
- return { healthFactor: Infinity, supplied: 0, borrowed: 0, maxBorrow: 0, liquidationThreshold: 0 };
1814
- }
1815
- }
1816
- async buildSaveTx(address, amount, asset, options) {
1817
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1818
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1819
- const sdk = await this.getSdkClient();
1820
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1821
- const tx = new Transaction();
1822
- tx.setSender(address);
1823
- const rawValue = stableToRaw(amount, assetInfo.decimals).toString();
1824
- if (caps.length > 0) {
1825
- if (options?.collectFee) {
1826
- const allCoins = await this.fetchAllCoins(address, assetInfo.type);
1827
- if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
1828
- const primaryCoinId = allCoins[0].coinObjectId;
1829
- if (allCoins.length > 1) {
1830
- tx.mergeCoins(tx.object(primaryCoinId), allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
1831
- }
1832
- const [depositCoin] = tx.splitCoins(tx.object(primaryCoinId), [rawValue]);
1833
- addCollectFeeToTx(tx, depositCoin, "save");
1834
- }
1835
- await sdk.depositIntoObligation(address, assetInfo.type, rawValue, tx, caps[0].id);
1836
- } else {
1837
- const newCap = sdk.createObligation(tx);
1838
- let depositCoin;
1839
- if (assetKey === "SUI") {
1840
- [depositCoin] = tx.splitCoins(tx.gas, [rawValue]);
1841
- } else {
1842
- const allCoins = await this.fetchAllCoins(address, assetInfo.type);
1843
- if (allCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${assetInfo.displayName} coins found`);
1844
- const primaryCoin = tx.object(allCoins[0].coinObjectId);
1845
- if (allCoins.length > 1) {
1846
- tx.mergeCoins(primaryCoin, allCoins.slice(1).map((c) => tx.object(c.coinObjectId)));
1847
- }
1848
- [depositCoin] = tx.splitCoins(primaryCoin, [rawValue]);
1849
- }
1850
- if (options?.collectFee) {
1851
- addCollectFeeToTx(tx, depositCoin, "save");
1852
- }
1853
- sdk.deposit(depositCoin, assetInfo.type, newCap, tx);
1854
- tx.transferObjects([newCap], address);
1855
- }
1856
- return { tx };
1857
- }
1858
- async buildWithdrawTx(address, amount, asset) {
1859
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1860
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1861
- const sdk = await this.getSdkClient();
1862
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1863
- if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
1864
- const { ctokenRaw, effectiveAmount, obligationIndex } = await this.resolveWithdrawCTokens(sdk, address, assetKey, assetInfo, amount);
1865
- const cap = caps[obligationIndex] ?? caps[0];
1866
- const tx = new Transaction();
1867
- tx.setSender(address);
1868
- await sdk.withdrawAndSendToUser(address, cap.id, cap.obligationId, assetInfo.type, ctokenRaw, tx);
1869
- return { tx, effectiveAmount };
1870
- }
1871
- async addWithdrawToTx(tx, address, amount, asset) {
1872
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1873
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1874
- const sdk = await this.getSdkClient();
1875
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1876
- if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found");
1877
- const { ctokenRaw, effectiveAmount, obligationIndex } = await this.resolveWithdrawCTokens(sdk, address, assetKey, assetInfo, amount);
1878
- const cap = caps[obligationIndex] ?? caps[0];
1879
- const coin = await sdk.withdraw(cap.id, cap.obligationId, assetInfo.type, ctokenRaw, tx);
1880
- return { coin, effectiveAmount };
1881
- }
1882
- async addSaveToTx(tx, address, coin, asset, options) {
1883
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1884
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1885
- const sdk = await this.getSdkClient();
1886
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1887
- let capRef;
1888
- if (caps.length === 0) {
1889
- const newCap = sdk.createObligation(tx);
1890
- capRef = newCap;
1891
- tx.transferObjects([newCap], address);
1892
- } else {
1893
- capRef = caps[0].id;
1894
- }
1895
- if (options?.collectFee) {
1896
- addCollectFeeToTx(tx, coin, "save");
1897
- }
1898
- sdk.deposit(coin, assetInfo.type, capRef, tx);
1899
- }
1900
- async buildBorrowTx(address, amount, asset, options) {
1901
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1902
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1903
- const sdk = await this.getSdkClient();
1904
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1905
- if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend position found. Deposit collateral first with: t2000 save <amount>");
1906
- const rawValue = stableToRaw(amount, assetInfo.decimals).toString();
1907
- const tx = new Transaction();
1908
- tx.setSender(address);
1909
- if (options?.collectFee) {
1910
- const coin = await sdk.borrow(caps[0].id, caps[0].obligationId, assetInfo.type, rawValue, tx);
1911
- addCollectFeeToTx(tx, coin, "borrow");
1912
- tx.transferObjects([coin], address);
1913
- } else {
1914
- await sdk.borrowAndSendToUser(address, caps[0].id, caps[0].obligationId, assetInfo.type, rawValue, tx);
1915
- }
1916
- return { tx };
1917
- }
1918
- async buildRepayTx(address, amount, asset) {
1919
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1920
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1921
- const sdk = await this.getSdkClient();
1922
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1923
- if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
1924
- const rawValue = stableToRaw(amount, assetInfo.decimals).toString();
1925
- const tx = new Transaction();
1926
- tx.setSender(address);
1927
- await sdk.repayIntoObligation(address, caps[0].obligationId, assetInfo.type, rawValue, tx);
1928
- return { tx };
1929
- }
1930
- async addRepayToTx(tx, address, coin, asset) {
1931
- const assetKey = asset in SUPPORTED_ASSETS ? asset : "USDC";
1932
- const assetInfo = SUPPORTED_ASSETS[assetKey];
1933
- const sdk = await this.getSdkClient();
1934
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
1935
- if (caps.length === 0) throw new T2000Error("NO_COLLATERAL", "No Suilend obligation found");
1936
- sdk.repay(caps[0].obligationId, assetInfo.type, coin, tx);
1937
- }
1938
- async resolveWithdrawCTokens(sdk, address, assetKey, assetInfo, amount) {
1939
- const { reserveMap, refreshedRawReserves } = await quietSuilend(() => initializeSuilend(this.client, sdk));
1940
- const { obligations } = await initializeObligations(
1941
- this.client,
1942
- sdk,
1943
- refreshedRawReserves,
1944
- reserveMap,
1945
- address
1946
- );
1947
- if (obligations.length === 0) throw new T2000Error("NO_COLLATERAL", `Nothing to withdraw for ${assetInfo.displayName} on Suilend \u2014 no obligations found`);
1948
- let dep;
1949
- let matchedObligation = 0;
1950
- for (let oi = 0; oi < obligations.length; oi++) {
1951
- dep = obligations[oi].deposits.find((d) => {
1952
- const resolved = this.resolveSymbol(d.coinType);
1953
- if (resolved === assetKey) return true;
1954
- try {
1955
- return normalizeStructTag(d.coinType) === normalizeStructTag(assetInfo.type);
1956
- } catch {
1957
- return false;
1958
- }
1959
- });
1960
- if (dep && dep.depositedAmount.toNumber() > 1e-10) {
1961
- matchedObligation = oi;
1962
- break;
1963
- }
1964
- dep = void 0;
1965
- }
1966
- if (!dep) {
1967
- const foundDeposits = obligations.flatMap(
1968
- (o, i) => o.deposits.map((d) => `ob${i}:${this.resolveSymbol(d.coinType)}=${d.depositedAmount.toFixed(8)}`)
1969
- );
1970
- throw new T2000Error(
1971
- "NO_COLLATERAL",
1972
- `Nothing to withdraw for ${assetInfo.displayName} (${assetKey}) on Suilend. Found deposits: [${foundDeposits.join(", ")}]`
1973
- );
1974
- }
1975
- const deposited = dep.depositedAmount.toNumber();
1976
- const effectiveAmount = Math.min(amount, deposited);
1977
- const proportion = effectiveAmount / deposited;
1978
- const ctokenRaw = proportion >= 0.999 ? dep.depositedCtokenAmount.toFixed(0) : dep.depositedCtokenAmount.times(proportion).integerValue(1).toFixed(0);
1979
- return { ctokenRaw, effectiveAmount, obligationIndex: matchedObligation };
1980
- }
1981
- async maxWithdraw(address, _asset) {
1982
- const health = await this.getHealth(address);
1983
- let maxAmount;
1984
- if (health.borrowed === 0) {
1985
- maxAmount = health.supplied;
1986
- } else {
1987
- maxAmount = Math.max(0, health.supplied - health.borrowed * MIN_HEALTH_FACTOR2 / health.liquidationThreshold);
1988
- }
1989
- const remainingSupply = health.supplied - maxAmount;
1990
- const hfAfter = health.borrowed > 0 ? remainingSupply * health.liquidationThreshold / health.borrowed : Infinity;
1991
- return { maxAmount, healthFactorAfter: hfAfter, currentHF: health.healthFactor };
1992
- }
1993
- async maxBorrow(address, _asset) {
1994
- const health = await this.getHealth(address);
1995
- return { maxAmount: health.maxBorrow, healthFactorAfter: MIN_HEALTH_FACTOR2, currentHF: health.healthFactor };
1996
- }
1997
- async fetchAllCoins(owner, coinType) {
1998
- const all = [];
1999
- let cursor = null;
2000
- let hasNext = true;
2001
- while (hasNext) {
2002
- const page = await this.client.getCoins({ owner, coinType, cursor: cursor ?? void 0 });
2003
- all.push(...page.data.map((c) => ({ coinObjectId: c.coinObjectId, balance: c.balance })));
2004
- cursor = page.nextCursor;
2005
- hasNext = page.hasNextPage;
2006
- }
2007
- return all;
2008
- }
2009
- async getPendingRewards(address) {
2010
- try {
2011
- const sdk = await this.getSdkClient();
2012
- const { reserveMap, refreshedRawReserves } = await quietSuilend(() => initializeSuilend(this.client, sdk));
2013
- const { obligations, obligationOwnerCaps } = await initializeObligations(
2014
- this.client,
2015
- sdk,
2016
- refreshedRawReserves,
2017
- reserveMap,
2018
- address
2019
- );
2020
- if (obligationOwnerCaps.length === 0 || obligations.length === 0) return [];
2021
- const ob = obligations[0];
2022
- const rewards = [];
2023
- const WAD = 1e18;
2024
- for (const dep of ob.deposits) {
2025
- const urm = dep.userRewardManager;
2026
- for (const rw of dep.reserve.depositsPoolRewardManager?.poolRewards ?? []) {
2027
- if (rw.endTimeMs <= Date.now()) continue;
2028
- let claimableAmount = 0;
2029
- const userReward = urm?.rewards?.[rw.rewardIndex];
2030
- if (userReward?.earnedRewards) {
2031
- claimableAmount = Number(BigInt(userReward.earnedRewards.value.toString())) / WAD / 10 ** rw.mintDecimals;
2032
- }
2033
- const symbol = rw.symbol || rw.coinType.split("::").pop() || "UNKNOWN";
2034
- rewards.push({
2035
- protocol: "suilend",
2036
- asset: this.resolveSymbol(dep.coinType),
2037
- coinType: rw.coinType,
2038
- symbol,
2039
- amount: claimableAmount,
2040
- estimatedValueUsd: 0
2041
- });
2042
- }
2043
- }
2044
- return rewards;
2045
- } catch {
2046
- return [];
2047
- }
2048
- }
2049
- async addClaimRewardsToTx(tx, address) {
2050
- try {
2051
- const sdk = await this.getSdkClient();
2052
- const caps = await SuilendClient.getObligationOwnerCaps(address, [LENDING_MARKET_TYPE], this.client);
2053
- if (caps.length === 0) return [];
2054
- const { reserveMap, refreshedRawReserves } = await quietSuilend(() => initializeSuilend(this.client, sdk));
2055
- const { obligations } = await initializeObligations(
2056
- this.client,
2057
- sdk,
2058
- refreshedRawReserves,
2059
- reserveMap,
2060
- address
2061
- );
2062
- if (obligations.length === 0) return [];
2063
- const ob = obligations[0];
2064
- const claimRewards = [];
2065
- for (const dep of ob.deposits) {
2066
- for (const rw of dep.reserve.depositsPoolRewardManager.poolRewards) {
2067
- if (rw.endTimeMs <= Date.now()) continue;
2068
- claimRewards.push({
2069
- reserveArrayIndex: dep.reserveArrayIndex,
2070
- rewardIndex: BigInt(rw.rewardIndex),
2071
- rewardCoinType: rw.coinType,
2072
- side: Side.DEPOSIT
2073
- });
2074
- }
2075
- }
2076
- if (claimRewards.length === 0) return [];
2077
- sdk.claimRewardsAndSendToUser(address, caps[0].id, claimRewards, tx);
2078
- return claimRewards.map((r) => ({
2079
- protocol: "suilend",
2080
- asset: "",
2081
- coinType: r.rewardCoinType,
2082
- symbol: r.rewardCoinType.split("::").pop() ?? "UNKNOWN",
2083
- amount: 0,
2084
- estimatedValueUsd: 0
2085
- }));
2086
- } catch {
2087
- return [];
2088
- }
2089
- }
2090
- };
2091
- function hasLeadingZeroBits(hash, bits) {
2092
- const fullBytes = Math.floor(bits / 8);
2093
- const remainingBits = bits % 8;
2094
- for (let i = 0; i < fullBytes; i++) {
2095
- if (hash[i] !== 0) return false;
2096
- }
2097
- if (remainingBits > 0) {
2098
- const mask = 255 << 8 - remainingBits;
2099
- if ((hash[fullBytes] & mask) !== 0) return false;
1524
+ // src/gas/manager.ts
1525
+ init_errors();
1526
+
1527
+ // src/gas/autoTopUp.ts
1528
+ async function shouldAutoTopUp(client, address) {
1529
+ const [suiBalance, usdcBalance] = await Promise.all([
1530
+ client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
1531
+ client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.USDC.type })
1532
+ ]);
1533
+ const suiRaw = BigInt(suiBalance.totalBalance);
1534
+ const usdcRaw = BigInt(usdcBalance.totalBalance);
1535
+ if (suiRaw < GAS_RESERVE_TARGET && usdcRaw >= AUTO_TOPUP_MIN_USDC) {
1536
+ return false;
2100
1537
  }
2101
- return true;
1538
+ return false;
2102
1539
  }
2103
- function solveHashcash(challenge) {
2104
- const bits = parseInt(challenge.split(":")[1], 10);
2105
- let counter = 0;
2106
- while (true) {
2107
- const stamp = `${challenge}${counter.toString(16)}`;
2108
- const hash = createHash("sha256").update(stamp).digest();
2109
- if (hasLeadingZeroBits(hash, bits)) return stamp;
2110
- counter++;
2111
- }
1540
+ async function executeAutoTopUp(_client, _signer) {
1541
+ return { success: false, tx: "", usdcSpent: 0, suiReceived: 0 };
2112
1542
  }
2113
1543
 
1544
+ // src/gas/gasStation.ts
1545
+ init_errors();
1546
+
2114
1547
  // src/utils/base64.ts
2115
1548
  function toBase64(bytes) {
2116
1549
  let binary = "";
@@ -2186,92 +1619,6 @@ async function getGasStatus(address) {
2186
1619
  return await res.json();
2187
1620
  }
2188
1621
 
2189
- // src/gas/autoTopUp.ts
2190
- async function shouldAutoTopUp(client, address) {
2191
- const [suiBalance, usdcBalance] = await Promise.all([
2192
- client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.SUI.type }),
2193
- client.getBalance({ owner: address, coinType: SUPPORTED_ASSETS.USDC.type })
2194
- ]);
2195
- const suiRaw = BigInt(suiBalance.totalBalance);
2196
- const usdcRaw = BigInt(usdcBalance.totalBalance);
2197
- return suiRaw < GAS_RESERVE_TARGET && usdcRaw >= AUTO_TOPUP_MIN_USDC;
2198
- }
2199
- async function executeAutoTopUp(client, signer) {
2200
- const address = signer.getAddress();
2201
- const topupAmountHuman = Number(AUTO_TOPUP_AMOUNT) / 1e6;
2202
- const { tx } = await buildSwapTx({
2203
- client,
2204
- address,
2205
- fromAsset: "USDC",
2206
- toAsset: "SUI",
2207
- amount: topupAmountHuman
2208
- });
2209
- tx.setSender(address);
2210
- let result;
2211
- try {
2212
- const builtBytes = await tx.build({ client });
2213
- const { signature } = await signer.signTransaction(builtBytes);
2214
- result = await client.executeTransactionBlock({
2215
- transactionBlock: toBase64(builtBytes),
2216
- signature: [signature],
2217
- options: { showEffects: true, showBalanceChanges: true }
2218
- });
2219
- } catch {
2220
- const { tx: freshTx } = await buildSwapTx({
2221
- client,
2222
- address,
2223
- fromAsset: "USDC",
2224
- toAsset: "SUI",
2225
- amount: topupAmountHuman
2226
- });
2227
- freshTx.setSender(address);
2228
- let txJson;
2229
- let txBcsBase64;
2230
- try {
2231
- txJson = freshTx.serialize();
2232
- } catch {
2233
- const bcsBytes = await freshTx.build({ client });
2234
- txBcsBase64 = toBase64(bcsBytes);
2235
- }
2236
- const sponsored = await requestGasSponsorship(
2237
- txJson ?? "",
2238
- address,
2239
- "auto-topup",
2240
- txBcsBase64
2241
- );
2242
- const sponsoredTxBytes = fromBase64(sponsored.txBytes);
2243
- const { signature: agentSig } = await signer.signTransaction(sponsoredTxBytes);
2244
- result = await client.executeTransactionBlock({
2245
- transactionBlock: sponsored.txBytes,
2246
- signature: [agentSig, sponsored.sponsorSignature],
2247
- options: { showEffects: true, showBalanceChanges: true }
2248
- });
2249
- reportGasUsage(address, result.digest, 0, 0, "auto-topup");
2250
- }
2251
- await client.waitForTransaction({ digest: result.digest });
2252
- const eff = result.effects;
2253
- if (eff?.status?.status === "failure") {
2254
- throw new T2000Error(
2255
- "TRANSACTION_FAILED",
2256
- `Auto-topup swap failed on-chain: ${eff.status.error ?? "unknown"}`
2257
- );
2258
- }
2259
- let suiReceived = 0;
2260
- if (result.balanceChanges) {
2261
- for (const change of result.balanceChanges) {
2262
- if (change.coinType === SUPPORTED_ASSETS.SUI.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === address) {
2263
- suiReceived += Number(change.amount) / Number(MIST_PER_SUI);
2264
- }
2265
- }
2266
- }
2267
- return {
2268
- success: true,
2269
- tx: result.digest,
2270
- usdcSpent: topupAmountHuman,
2271
- suiReceived: Math.abs(suiReceived)
2272
- };
2273
- }
2274
-
2275
1622
  // src/gas/manager.ts
2276
1623
  function extractGasCost(effects) {
2277
1624
  if (!effects?.gasUsed) return 0;
@@ -2317,7 +1664,7 @@ async function tryAutoTopUpThenSelfFund(client, signer, buildTx) {
2317
1664
  const address = signer.getAddress();
2318
1665
  const canTopUp = await shouldAutoTopUp(client, address);
2319
1666
  if (!canTopUp) return null;
2320
- await executeAutoTopUp(client, signer);
1667
+ await executeAutoTopUp();
2321
1668
  const tx = await buildTx();
2322
1669
  tx.setSender(address);
2323
1670
  const suiAfterTopUp = await getSuiBalance(client, address);
@@ -2482,6 +1829,9 @@ async function resolveGas(client, signer, buildTx) {
2482
1829
  );
2483
1830
  }
2484
1831
 
1832
+ // src/t2000.ts
1833
+ init_errors();
1834
+
2485
1835
  // src/safeguards/types.ts
2486
1836
  var OUTBOUND_OPS = /* @__PURE__ */ new Set([
2487
1837
  "send",
@@ -2496,6 +1846,7 @@ var DEFAULT_SAFEGUARD_CONFIG = {
2496
1846
  };
2497
1847
 
2498
1848
  // src/safeguards/errors.ts
1849
+ init_errors();
2499
1850
  var SafeguardError = class extends T2000Error {
2500
1851
  rule;
2501
1852
  details;
@@ -2642,6 +1993,7 @@ var SafeguardEnforcer = class {
2642
1993
  }
2643
1994
  }
2644
1995
  };
1996
+ init_errors();
2645
1997
  var RESERVED_NAMES = /* @__PURE__ */ new Set(["to", "all", "address"]);
2646
1998
  var ContactManager = class {
2647
1999
  contacts = {};
@@ -2719,489 +2071,6 @@ var ContactManager = class {
2719
2071
  }
2720
2072
  }
2721
2073
  };
2722
- var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2723
- function emptyData() {
2724
- return { positions: {}, strategies: {}, realizedPnL: 0 };
2725
- }
2726
- var PortfolioManager = class {
2727
- data = emptyData();
2728
- filePath;
2729
- dir;
2730
- constructor(configDir) {
2731
- this.dir = configDir ?? join(homedir(), ".t2000");
2732
- this.filePath = join(this.dir, "portfolio.json");
2733
- this.load();
2734
- }
2735
- load() {
2736
- try {
2737
- if (existsSync(this.filePath)) {
2738
- this.data = JSON.parse(readFileSync(this.filePath, "utf-8"));
2739
- if (!this.data.strategies) this.data.strategies = {};
2740
- }
2741
- } catch {
2742
- this.data = emptyData();
2743
- }
2744
- }
2745
- save() {
2746
- if (!existsSync(this.dir)) mkdirSync(this.dir, { recursive: true });
2747
- writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
2748
- }
2749
- recordBuy(trade) {
2750
- this.load();
2751
- const pos = this.data.positions[trade.asset] ?? { totalAmount: 0, costBasis: 0, avgPrice: 0, trades: [] };
2752
- pos.totalAmount += trade.amount;
2753
- pos.costBasis += trade.usdValue;
2754
- pos.avgPrice = pos.costBasis / pos.totalAmount;
2755
- pos.trades.push(trade);
2756
- this.data.positions[trade.asset] = pos;
2757
- this.save();
2758
- }
2759
- recordSell(trade) {
2760
- this.load();
2761
- const pos = this.data.positions[trade.asset];
2762
- if (!pos || pos.totalAmount <= 0) {
2763
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${trade.asset} position to sell`);
2764
- }
2765
- const sellAmount = Math.min(trade.amount, pos.totalAmount);
2766
- const effectiveUsdValue = trade.amount > 0 && sellAmount < trade.amount ? trade.usdValue * (sellAmount / trade.amount) : trade.usdValue;
2767
- const costOfSold = pos.avgPrice * sellAmount;
2768
- const realizedPnL = effectiveUsdValue - costOfSold;
2769
- pos.totalAmount -= sellAmount;
2770
- pos.costBasis -= costOfSold;
2771
- if (pos.totalAmount < 1e-6) {
2772
- pos.totalAmount = 0;
2773
- pos.costBasis = 0;
2774
- pos.avgPrice = 0;
2775
- }
2776
- pos.trades.push(trade);
2777
- this.data.realizedPnL += realizedPnL;
2778
- this.data.positions[trade.asset] = pos;
2779
- this.save();
2780
- return realizedPnL;
2781
- }
2782
- getPosition(asset) {
2783
- this.load();
2784
- return this.data.positions[asset];
2785
- }
2786
- getPositions() {
2787
- this.load();
2788
- return Object.entries(this.data.positions).filter(([, pos]) => pos.totalAmount > 0).map(([asset, pos]) => ({ asset, ...pos }));
2789
- }
2790
- recordEarn(asset, protocol, apy) {
2791
- this.load();
2792
- const pos = this.data.positions[asset];
2793
- if (!pos || pos.totalAmount <= 0) {
2794
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${asset} position to earn on`);
2795
- }
2796
- pos.earning = true;
2797
- pos.earningProtocol = protocol;
2798
- pos.earningApy = apy;
2799
- this.data.positions[asset] = pos;
2800
- this.save();
2801
- }
2802
- recordUnearn(asset) {
2803
- this.load();
2804
- const pos = this.data.positions[asset];
2805
- if (!pos || !pos.earning) {
2806
- throw new T2000Error("INVEST_NOT_EARNING", `${asset} is not currently earning`);
2807
- }
2808
- pos.earning = false;
2809
- pos.earningProtocol = void 0;
2810
- pos.earningApy = void 0;
2811
- this.data.positions[asset] = pos;
2812
- this.save();
2813
- }
2814
- getStrategyAmountForAsset(asset) {
2815
- this.load();
2816
- let total = 0;
2817
- for (const bucket of Object.values(this.data.strategies)) {
2818
- const pos = bucket[asset];
2819
- if (pos && pos.totalAmount > 0) total += pos.totalAmount;
2820
- }
2821
- return total;
2822
- }
2823
- getDirectAmount(asset) {
2824
- this.load();
2825
- const aggregate = this.data.positions[asset]?.totalAmount ?? 0;
2826
- const strategyAmount = this.getStrategyAmountForAsset(asset);
2827
- return Math.max(0, aggregate - strategyAmount);
2828
- }
2829
- deductFromStrategies(asset, amount) {
2830
- this.load();
2831
- let remaining = amount;
2832
- for (const [stratKey, bucket] of Object.entries(this.data.strategies)) {
2833
- if (remaining <= 0) break;
2834
- const pos = bucket[asset];
2835
- if (!pos || pos.totalAmount <= 0) continue;
2836
- const deduct = Math.min(pos.totalAmount, remaining);
2837
- const costDeduct = pos.avgPrice * deduct;
2838
- pos.totalAmount -= deduct;
2839
- pos.costBasis -= costDeduct;
2840
- if (pos.totalAmount < 1e-6) {
2841
- pos.totalAmount = 0;
2842
- pos.costBasis = 0;
2843
- pos.avgPrice = 0;
2844
- }
2845
- remaining -= deduct;
2846
- const hasPositions = Object.values(bucket).some((p) => p.totalAmount > 0);
2847
- if (!hasPositions) {
2848
- delete this.data.strategies[stratKey];
2849
- }
2850
- }
2851
- this.save();
2852
- }
2853
- closePosition(asset) {
2854
- this.load();
2855
- const pos = this.data.positions[asset];
2856
- if (pos) {
2857
- pos.totalAmount = 0;
2858
- pos.costBasis = 0;
2859
- pos.avgPrice = 0;
2860
- pos.earning = false;
2861
- pos.earningProtocol = void 0;
2862
- pos.earningApy = void 0;
2863
- this.data.positions[asset] = pos;
2864
- this.save();
2865
- }
2866
- }
2867
- isEarning(asset) {
2868
- this.load();
2869
- const pos = this.data.positions[asset];
2870
- return pos?.earning === true;
2871
- }
2872
- getRealizedPnL() {
2873
- this.load();
2874
- return this.data.realizedPnL;
2875
- }
2876
- // --- Strategy position tracking ---
2877
- recordStrategyBuy(strategyKey, trade) {
2878
- if (UNSAFE_KEYS.has(strategyKey) || UNSAFE_KEYS.has(trade.asset)) {
2879
- throw new T2000Error("ASSET_NOT_SUPPORTED", "Invalid strategy key or asset name");
2880
- }
2881
- this.load();
2882
- if (!Object.hasOwn(this.data.strategies, strategyKey)) {
2883
- this.data.strategies[strategyKey] = /* @__PURE__ */ Object.create(null);
2884
- }
2885
- const bucket = this.data.strategies[strategyKey];
2886
- const pos = Object.hasOwn(bucket, trade.asset) ? bucket[trade.asset] : { totalAmount: 0, costBasis: 0, avgPrice: 0, trades: [] };
2887
- pos.totalAmount += trade.amount;
2888
- pos.costBasis += trade.usdValue;
2889
- pos.avgPrice = pos.costBasis / pos.totalAmount;
2890
- pos.trades.push(trade);
2891
- Object.defineProperty(bucket, trade.asset, { value: pos, writable: true, enumerable: true, configurable: true });
2892
- this.save();
2893
- }
2894
- recordStrategySell(strategyKey, trade) {
2895
- if (UNSAFE_KEYS.has(strategyKey) || UNSAFE_KEYS.has(trade.asset)) {
2896
- throw new T2000Error("ASSET_NOT_SUPPORTED", "Invalid strategy key or asset name");
2897
- }
2898
- this.load();
2899
- const bucket = this.data.strategies[strategyKey];
2900
- if (!bucket) {
2901
- throw new T2000Error("STRATEGY_NOT_FOUND", `No positions for strategy '${strategyKey}'`);
2902
- }
2903
- if (!Object.hasOwn(bucket, trade.asset)) {
2904
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${trade.asset} position in strategy '${strategyKey}'`);
2905
- }
2906
- const pos = bucket[trade.asset];
2907
- if (!pos || pos.totalAmount <= 0) {
2908
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${trade.asset} position in strategy '${strategyKey}'`);
2909
- }
2910
- const sellAmount = Math.min(trade.amount, pos.totalAmount);
2911
- const effectiveUsdValue = trade.amount > 0 && sellAmount < trade.amount ? trade.usdValue * (sellAmount / trade.amount) : trade.usdValue;
2912
- const costOfSold = pos.avgPrice * sellAmount;
2913
- const realizedPnL = effectiveUsdValue - costOfSold;
2914
- pos.totalAmount -= sellAmount;
2915
- pos.costBasis -= costOfSold;
2916
- if (pos.totalAmount < 1e-6) {
2917
- pos.totalAmount = 0;
2918
- pos.costBasis = 0;
2919
- pos.avgPrice = 0;
2920
- }
2921
- pos.trades.push(trade);
2922
- Object.defineProperty(bucket, trade.asset, { value: pos, writable: true, enumerable: true, configurable: true });
2923
- const hasPositions = Object.values(bucket).some((p) => p.totalAmount > 0);
2924
- if (!hasPositions) {
2925
- delete this.data.strategies[strategyKey];
2926
- }
2927
- this.save();
2928
- return realizedPnL;
2929
- }
2930
- getStrategyPositions(strategyKey) {
2931
- this.load();
2932
- const bucket = this.data.strategies[strategyKey];
2933
- if (!bucket) return [];
2934
- return Object.entries(bucket).filter(([, pos]) => pos.totalAmount > 0).map(([asset, pos]) => ({ asset, ...pos }));
2935
- }
2936
- getAllStrategyKeys() {
2937
- this.load();
2938
- return Object.keys(this.data.strategies);
2939
- }
2940
- clearStrategy(strategyKey) {
2941
- this.load();
2942
- delete this.data.strategies[strategyKey];
2943
- this.save();
2944
- }
2945
- hasStrategyPositions(strategyKey) {
2946
- this.load();
2947
- const bucket = this.data.strategies[strategyKey];
2948
- if (!bucket) return false;
2949
- return Object.values(bucket).some((p) => p.totalAmount > 0);
2950
- }
2951
- closeStrategyPosition(strategyKey, asset) {
2952
- this.load();
2953
- const bucket = this.data.strategies[strategyKey];
2954
- if (!bucket?.[asset]) return;
2955
- bucket[asset].totalAmount = 0;
2956
- bucket[asset].costBasis = 0;
2957
- bucket[asset].avgPrice = 0;
2958
- const hasPositions = Object.values(bucket).some((p) => p.totalAmount > 0);
2959
- if (!hasPositions) {
2960
- delete this.data.strategies[strategyKey];
2961
- }
2962
- this.save();
2963
- }
2964
- };
2965
- function emptyData2() {
2966
- return { strategies: {} };
2967
- }
2968
- var StrategyManager = class {
2969
- data = emptyData2();
2970
- filePath;
2971
- dir;
2972
- seeded = false;
2973
- constructor(configDir) {
2974
- this.dir = configDir ?? join(homedir(), ".t2000");
2975
- this.filePath = join(this.dir, "strategies.json");
2976
- this.load();
2977
- }
2978
- load() {
2979
- try {
2980
- if (existsSync(this.filePath)) {
2981
- this.data = JSON.parse(readFileSync(this.filePath, "utf-8"));
2982
- }
2983
- } catch {
2984
- this.data = emptyData2();
2985
- }
2986
- if (!this.seeded) {
2987
- this.seedDefaults();
2988
- }
2989
- }
2990
- save() {
2991
- if (!existsSync(this.dir)) mkdirSync(this.dir, { recursive: true });
2992
- writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
2993
- }
2994
- seedDefaults() {
2995
- this.seeded = true;
2996
- let changed = false;
2997
- for (const [key, def] of Object.entries(DEFAULT_STRATEGIES)) {
2998
- if (!this.data.strategies[key]) {
2999
- this.data.strategies[key] = { ...def, allocations: { ...def.allocations } };
3000
- changed = true;
3001
- }
3002
- }
3003
- if (changed) this.save();
3004
- }
3005
- getAll() {
3006
- this.load();
3007
- return { ...this.data.strategies };
3008
- }
3009
- get(name) {
3010
- this.load();
3011
- const strategy = this.data.strategies[name];
3012
- if (!strategy) {
3013
- throw new T2000Error("STRATEGY_NOT_FOUND", `Strategy '${name}' not found`);
3014
- }
3015
- return strategy;
3016
- }
3017
- create(params) {
3018
- this.load();
3019
- const key = params.name.toLowerCase().replace(/\s+/g, "-");
3020
- if (this.data.strategies[key]) {
3021
- throw new T2000Error("STRATEGY_INVALID_ALLOCATIONS", `Strategy '${key}' already exists`);
3022
- }
3023
- this.validateAllocations(params.allocations);
3024
- const definition = {
3025
- name: params.name,
3026
- allocations: { ...params.allocations },
3027
- description: params.description ?? `Custom strategy: ${params.name}`,
3028
- custom: true
3029
- };
3030
- this.data.strategies[key] = definition;
3031
- this.save();
3032
- return definition;
3033
- }
3034
- delete(name) {
3035
- this.load();
3036
- const strategy = this.data.strategies[name];
3037
- if (!strategy) {
3038
- throw new T2000Error("STRATEGY_NOT_FOUND", `Strategy '${name}' not found`);
3039
- }
3040
- if (!strategy.custom) {
3041
- throw new T2000Error("STRATEGY_BUILTIN", `Cannot delete built-in strategy '${name}'`);
3042
- }
3043
- delete this.data.strategies[name];
3044
- this.save();
3045
- }
3046
- validateAllocations(allocations) {
3047
- const total = Object.values(allocations).reduce((sum, pct) => sum + pct, 0);
3048
- if (Math.abs(total - 100) > 0.01) {
3049
- throw new T2000Error("STRATEGY_INVALID_ALLOCATIONS", `Allocations must sum to 100 (got ${total})`);
3050
- }
3051
- for (const asset of Object.keys(allocations)) {
3052
- if (!(asset in INVESTMENT_ASSETS)) {
3053
- throw new T2000Error("STRATEGY_INVALID_ALLOCATIONS", `${asset} is not an investment asset`);
3054
- }
3055
- if (allocations[asset] <= 0) {
3056
- throw new T2000Error("STRATEGY_INVALID_ALLOCATIONS", `Allocation for ${asset} must be > 0`);
3057
- }
3058
- }
3059
- }
3060
- validateMinAmount(allocations, totalUsd) {
3061
- const smallestPct = Math.min(...Object.values(allocations));
3062
- const minRequired = Math.ceil(100 / smallestPct);
3063
- if (totalUsd < minRequired) {
3064
- const smallestAsset = Object.entries(allocations).find(([, p]) => p === smallestPct)?.[0] ?? "?";
3065
- throw new T2000Error(
3066
- "STRATEGY_MIN_AMOUNT",
3067
- `Minimum $${minRequired} for this strategy (${smallestAsset} at ${smallestPct}% needs at least $1)`
3068
- );
3069
- }
3070
- }
3071
- };
3072
- function emptyData3() {
3073
- return { schedules: [] };
3074
- }
3075
- function computeNextRun(frequency, dayOfWeek, dayOfMonth, from) {
3076
- const base = /* @__PURE__ */ new Date();
3077
- const next = new Date(base);
3078
- switch (frequency) {
3079
- case "daily":
3080
- next.setDate(next.getDate() + 1);
3081
- next.setHours(0, 0, 0, 0);
3082
- break;
3083
- case "weekly": {
3084
- const dow = dayOfWeek ?? 1;
3085
- next.setDate(next.getDate() + ((7 - next.getDay() + dow) % 7 || 7));
3086
- next.setHours(0, 0, 0, 0);
3087
- break;
3088
- }
3089
- case "monthly": {
3090
- const dom = dayOfMonth ?? 1;
3091
- next.setMonth(next.getMonth() + 1, dom);
3092
- next.setHours(0, 0, 0, 0);
3093
- break;
3094
- }
3095
- }
3096
- return next.toISOString();
3097
- }
3098
- var AutoInvestManager = class {
3099
- data = emptyData3();
3100
- filePath;
3101
- dir;
3102
- constructor(configDir) {
3103
- this.dir = configDir ?? join(homedir(), ".t2000");
3104
- this.filePath = join(this.dir, "auto-invest.json");
3105
- this.load();
3106
- }
3107
- load() {
3108
- try {
3109
- if (existsSync(this.filePath)) {
3110
- this.data = JSON.parse(readFileSync(this.filePath, "utf-8"));
3111
- }
3112
- } catch {
3113
- this.data = emptyData3();
3114
- }
3115
- if (!this.data.schedules) {
3116
- this.data.schedules = [];
3117
- }
3118
- }
3119
- save() {
3120
- if (!existsSync(this.dir)) mkdirSync(this.dir, { recursive: true });
3121
- writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
3122
- }
3123
- setup(params) {
3124
- this.load();
3125
- if (!params.strategy && !params.asset) {
3126
- throw new T2000Error("AUTO_INVEST_NOT_FOUND", "Either strategy or asset must be specified");
3127
- }
3128
- if (params.amount < 1) {
3129
- throw new T2000Error("AUTO_INVEST_INSUFFICIENT", "Auto-invest amount must be at least $1");
3130
- }
3131
- const schedule = {
3132
- id: randomUUID().slice(0, 8),
3133
- strategy: params.strategy,
3134
- asset: params.asset,
3135
- amount: params.amount,
3136
- frequency: params.frequency,
3137
- dayOfWeek: params.dayOfWeek,
3138
- dayOfMonth: params.dayOfMonth,
3139
- nextRun: computeNextRun(params.frequency, params.dayOfWeek, params.dayOfMonth),
3140
- enabled: true,
3141
- totalInvested: 0,
3142
- runCount: 0
3143
- };
3144
- this.data.schedules.push(schedule);
3145
- this.save();
3146
- return schedule;
3147
- }
3148
- getStatus() {
3149
- this.load();
3150
- const now = /* @__PURE__ */ new Date();
3151
- const pending = this.data.schedules.filter(
3152
- (s) => s.enabled && new Date(s.nextRun) <= now
3153
- );
3154
- return {
3155
- schedules: [...this.data.schedules],
3156
- pendingRuns: pending
3157
- };
3158
- }
3159
- getSchedule(id) {
3160
- this.load();
3161
- const schedule = this.data.schedules.find((s) => s.id === id);
3162
- if (!schedule) {
3163
- throw new T2000Error("AUTO_INVEST_NOT_FOUND", `Schedule '${id}' not found`);
3164
- }
3165
- return schedule;
3166
- }
3167
- recordRun(id, amountInvested) {
3168
- this.load();
3169
- const schedule = this.data.schedules.find((s) => s.id === id);
3170
- if (!schedule) return;
3171
- schedule.lastRun = (/* @__PURE__ */ new Date()).toISOString();
3172
- schedule.nextRun = computeNextRun(schedule.frequency, schedule.dayOfWeek, schedule.dayOfMonth);
3173
- schedule.totalInvested += amountInvested;
3174
- schedule.runCount += 1;
3175
- this.save();
3176
- }
3177
- stop(id) {
3178
- this.load();
3179
- const schedule = this.data.schedules.find((s) => s.id === id);
3180
- if (!schedule) {
3181
- throw new T2000Error("AUTO_INVEST_NOT_FOUND", `Schedule '${id}' not found`);
3182
- }
3183
- schedule.enabled = false;
3184
- this.save();
3185
- }
3186
- remove(id) {
3187
- this.load();
3188
- const idx = this.data.schedules.findIndex((s) => s.id === id);
3189
- if (idx === -1) {
3190
- throw new T2000Error("AUTO_INVEST_NOT_FOUND", `Schedule '${id}' not found`);
3191
- }
3192
- this.data.schedules.splice(idx, 1);
3193
- this.save();
3194
- }
3195
- };
3196
- var LOW_LIQUIDITY_ASSETS = /* @__PURE__ */ new Set(["GOLD"]);
3197
- var REWARD_TOKEN_DECIMALS = {
3198
- "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT": 9,
3199
- "0xdeeb7a4662eec9f2f3def03fb937a663dddaa2e215b8078a284d026b7946c270::deep::DEEP": 6,
3200
- "0x83556891f4a0f233ce7b05cfe7f957d4020492a34f5405b2cb9377d060bef4bf::spring_sui::SPRING_SUI": 9
3201
- };
3202
- function defaultSlippage(asset) {
3203
- return LOW_LIQUIDITY_ASSETS.has(asset) ? 0.05 : 0.03;
3204
- }
3205
2074
  var DEFAULT_CONFIG_DIR = join(homedir(), ".t2000");
3206
2075
  var T2000 = class _T2000 extends EventEmitter {
3207
2076
  _signer;
@@ -3211,9 +2080,6 @@ var T2000 = class _T2000 extends EventEmitter {
3211
2080
  registry;
3212
2081
  enforcer;
3213
2082
  contacts;
3214
- portfolio;
3215
- strategies;
3216
- autoInvest;
3217
2083
  constructor(keypairOrSigner, client, registry, configDir, isSignerMode) {
3218
2084
  super();
3219
2085
  if (isSignerMode) {
@@ -3231,21 +2097,12 @@ var T2000 = class _T2000 extends EventEmitter {
3231
2097
  this.enforcer = new SafeguardEnforcer(configDir);
3232
2098
  this.enforcer.load();
3233
2099
  this.contacts = new ContactManager(configDir);
3234
- this.portfolio = new PortfolioManager(configDir);
3235
- this.strategies = new StrategyManager(configDir);
3236
- this.autoInvest = new AutoInvestManager(configDir);
3237
2100
  }
3238
2101
  static createDefaultRegistry(client) {
3239
2102
  const registry = new ProtocolRegistry();
3240
2103
  const naviAdapter = new NaviAdapter();
3241
2104
  naviAdapter.initSync(client);
3242
2105
  registry.registerLending(naviAdapter);
3243
- const cetusAdapter = new CetusAdapter();
3244
- cetusAdapter.initSync(client);
3245
- registry.registerSwap(cetusAdapter);
3246
- const suilendAdapter = new SuilendAdapter();
3247
- suilendAdapter.initSync(client);
3248
- registry.registerLending(suilendAdapter);
3249
2106
  return registry;
3250
2107
  }
3251
2108
  static async create(options = {}) {
@@ -3321,14 +2178,15 @@ var T2000 = class _T2000 extends EventEmitter {
3321
2178
  this.enforcer.assertNotLocked();
3322
2179
  this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
3323
2180
  const { Mppx } = await import('mppx/client');
3324
- const { sui } = await import('@t2000/mpp-sui/client');
2181
+ const { sui } = await import('@suimpp/mpp/client');
3325
2182
  const client = this.client;
3326
2183
  const signer = this._signer;
2184
+ const signerAddress = signer.getAddress();
3327
2185
  const mppx = Mppx.create({
3328
2186
  polyfill: false,
3329
2187
  methods: [sui({
3330
2188
  client,
3331
- signer,
2189
+ signer: { toSuiAddress: () => signerAddress },
3332
2190
  execute: async (tx) => {
3333
2191
  const result = await executeWithGas(client, signer, () => tx);
3334
2192
  return { digest: result.digest, effects: result.effects };
@@ -3355,11 +2213,156 @@ var T2000 = class _T2000 extends EventEmitter {
3355
2213
  this.enforcer.recordUsage(options.maxPrice ?? 1);
3356
2214
  }
3357
2215
  return {
3358
- status: response.status,
3359
- body,
3360
- paid,
3361
- cost: paid ? options.maxPrice ?? void 0 : void 0,
3362
- receipt: receiptHeader ? { reference: receiptHeader, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
2216
+ status: response.status,
2217
+ body,
2218
+ paid,
2219
+ cost: paid ? options.maxPrice ?? void 0 : void 0,
2220
+ receipt: receiptHeader ? { reference: receiptHeader, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
2221
+ };
2222
+ }
2223
+ // -- VOLO vSUI Staking --
2224
+ async stakeVSui(params) {
2225
+ this.enforcer.assertNotLocked();
2226
+ const { buildStakeVSuiTx: buildStakeVSuiTx2, getVoloStats: getVoloStats2 } = await Promise.resolve().then(() => (init_volo(), volo_exports));
2227
+ const amountMist = BigInt(Math.floor(params.amount * Number(MIST_PER_SUI)));
2228
+ const stats = await getVoloStats2();
2229
+ const gasResult = await executeWithGas(this.client, this._signer, async () => {
2230
+ return buildStakeVSuiTx2(this.client, this._address, amountMist);
2231
+ });
2232
+ const vSuiReceived = params.amount / stats.exchangeRate;
2233
+ return {
2234
+ success: true,
2235
+ tx: gasResult.digest,
2236
+ amountSui: params.amount,
2237
+ vSuiReceived,
2238
+ apy: stats.apy,
2239
+ gasCost: gasResult.gasCostSui,
2240
+ gasMethod: gasResult.gasMethod
2241
+ };
2242
+ }
2243
+ async unstakeVSui(params) {
2244
+ this.enforcer.assertNotLocked();
2245
+ const { buildUnstakeVSuiTx: buildUnstakeVSuiTx2, getVoloStats: getVoloStats2, VSUI_TYPE: VSUI_TYPE2 } = await Promise.resolve().then(() => (init_volo(), volo_exports));
2246
+ let amountMist;
2247
+ let vSuiAmount;
2248
+ if (params.amount === "all") {
2249
+ amountMist = "all";
2250
+ const coins = await this._fetchCoins(VSUI_TYPE2);
2251
+ vSuiAmount = coins.reduce((sum, c) => sum + Number(c.balance), 0) / 1e9;
2252
+ } else {
2253
+ amountMist = BigInt(Math.floor(params.amount * 1e9));
2254
+ vSuiAmount = params.amount;
2255
+ }
2256
+ const stats = await getVoloStats2();
2257
+ const gasResult = await executeWithGas(this.client, this._signer, async () => {
2258
+ return buildUnstakeVSuiTx2(this.client, this._address, amountMist);
2259
+ });
2260
+ const suiReceived = vSuiAmount * stats.exchangeRate;
2261
+ return {
2262
+ success: true,
2263
+ tx: gasResult.digest,
2264
+ vSuiAmount,
2265
+ suiReceived,
2266
+ gasCost: gasResult.gasCostSui,
2267
+ gasMethod: gasResult.gasMethod
2268
+ };
2269
+ }
2270
+ // -- Swap --
2271
+ async swap(params) {
2272
+ this.enforcer.assertNotLocked();
2273
+ const { findSwapRoute: findSwapRoute2, buildSwapTx: buildSwapTx2, resolveTokenType: resolveTokenType2, TOKEN_MAP: TOKEN_MAP2 } = await Promise.resolve().then(() => (init_cetus_swap(), cetus_swap_exports));
2274
+ const fromType = resolveTokenType2(params.from);
2275
+ const toType = resolveTokenType2(params.to);
2276
+ if (!fromType) throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown token: ${params.from}. Provide the full coin type.`);
2277
+ if (!toType) throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown token: ${params.to}. Provide the full coin type.`);
2278
+ const byAmountIn = params.byAmountIn ?? true;
2279
+ const slippage = Math.min(params.slippage ?? 0.01, 0.05);
2280
+ const fromEntry = Object.values(TOKEN_MAP2).includes(fromType) ? Object.entries(SUPPORTED_ASSETS).find(([, v]) => v.type === fromType) : null;
2281
+ const fromDecimals = fromEntry ? fromEntry[1].decimals : fromType === "0x2::sui::SUI" ? 9 : 6;
2282
+ const rawAmount = BigInt(Math.floor(params.amount * 10 ** fromDecimals));
2283
+ const route = await findSwapRoute2({
2284
+ walletAddress: this._address,
2285
+ from: fromType,
2286
+ to: toType,
2287
+ amount: rawAmount,
2288
+ byAmountIn
2289
+ });
2290
+ if (!route) throw new T2000Error("SWAP_NO_ROUTE", `No swap route found for ${params.from} -> ${params.to}.`);
2291
+ if (route.insufficientLiquidity) throw new T2000Error("SWAP_NO_ROUTE", `Insufficient liquidity for ${params.from} -> ${params.to}.`);
2292
+ if (route.priceImpact > 0.05) {
2293
+ console.warn(`[swap] High price impact: ${(route.priceImpact * 100).toFixed(2)}%`);
2294
+ }
2295
+ const gasResult = await executeWithGas(this.client, this._signer, async () => {
2296
+ const tx = new Transaction();
2297
+ tx.setSender(this._address);
2298
+ let inputCoin;
2299
+ if (fromType === "0x2::sui::SUI") {
2300
+ [inputCoin] = tx.splitCoins(tx.gas, [rawAmount]);
2301
+ } else {
2302
+ const coins = await this._fetchCoins(fromType);
2303
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${params.from} coins found.`);
2304
+ const merged = this._mergeCoinsInTx(tx, coins);
2305
+ [inputCoin] = tx.splitCoins(merged, [rawAmount]);
2306
+ }
2307
+ const outputCoin = await buildSwapTx2({
2308
+ walletAddress: this._address,
2309
+ route,
2310
+ tx,
2311
+ inputCoin,
2312
+ slippage
2313
+ });
2314
+ tx.transferObjects([outputCoin], this._address);
2315
+ return tx;
2316
+ });
2317
+ const toEntry = Object.entries(SUPPORTED_ASSETS).find(([, v]) => v.type === toType);
2318
+ const toDecimals = toEntry ? toEntry[1].decimals : toType === "0x2::sui::SUI" ? 9 : 6;
2319
+ const fromAmount = Number(route.amountIn) / 10 ** fromDecimals;
2320
+ const toAmount = Number(route.amountOut) / 10 ** toDecimals;
2321
+ const routeDesc = route.routerData.paths?.map((p) => p.provider).filter(Boolean).slice(0, 3).join(" + ") ?? "Cetus Aggregator";
2322
+ return {
2323
+ success: true,
2324
+ tx: gasResult.digest,
2325
+ fromToken: params.from,
2326
+ toToken: params.to,
2327
+ fromAmount,
2328
+ toAmount,
2329
+ priceImpact: route.priceImpact,
2330
+ route: routeDesc,
2331
+ gasCost: gasResult.gasCostSui,
2332
+ gasMethod: gasResult.gasMethod
2333
+ };
2334
+ }
2335
+ async swapQuote(params) {
2336
+ const { findSwapRoute: findSwapRoute2, resolveTokenType: resolveTokenType2, TOKEN_MAP: TOKEN_MAP2 } = await Promise.resolve().then(() => (init_cetus_swap(), cetus_swap_exports));
2337
+ const fromType = resolveTokenType2(params.from);
2338
+ const toType = resolveTokenType2(params.to);
2339
+ if (!fromType) throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown token: ${params.from}. Provide the full coin type.`);
2340
+ if (!toType) throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown token: ${params.to}. Provide the full coin type.`);
2341
+ const byAmountIn = params.byAmountIn ?? true;
2342
+ const fromEntry = Object.values(TOKEN_MAP2).includes(fromType) ? Object.entries(SUPPORTED_ASSETS).find(([, v]) => v.type === fromType) : null;
2343
+ const fromDecimals = fromEntry ? fromEntry[1].decimals : fromType === "0x2::sui::SUI" ? 9 : 6;
2344
+ const rawAmount = BigInt(Math.floor(params.amount * 10 ** fromDecimals));
2345
+ const route = await findSwapRoute2({
2346
+ walletAddress: this._address,
2347
+ from: fromType,
2348
+ to: toType,
2349
+ amount: rawAmount,
2350
+ byAmountIn
2351
+ });
2352
+ if (!route) throw new T2000Error("SWAP_NO_ROUTE", `No swap route found for ${params.from} -> ${params.to}.`);
2353
+ if (route.insufficientLiquidity) throw new T2000Error("SWAP_NO_ROUTE", `Insufficient liquidity for ${params.from} -> ${params.to}.`);
2354
+ const toEntry = Object.entries(SUPPORTED_ASSETS).find(([, v]) => v.type === toType);
2355
+ const toDecimals = toEntry ? toEntry[1].decimals : toType === "0x2::sui::SUI" ? 9 : 6;
2356
+ const fromAmount = Number(route.amountIn) / 10 ** fromDecimals;
2357
+ const toAmount = Number(route.amountOut) / 10 ** toDecimals;
2358
+ const routeDesc = route.routerData.paths?.map((p) => p.provider).filter(Boolean).slice(0, 3).join(" + ") ?? "Cetus Aggregator";
2359
+ return {
2360
+ fromToken: params.from,
2361
+ toToken: params.to,
2362
+ fromAmount,
2363
+ toAmount,
2364
+ priceImpact: route.priceImpact,
2365
+ route: routeDesc
3363
2366
  };
3364
2367
  }
3365
2368
  // -- Wallet --
@@ -3398,38 +2401,14 @@ var T2000 = class _T2000 extends EventEmitter {
3398
2401
  }
3399
2402
  async balance() {
3400
2403
  const bal = await queryBalance(this.client, this._address);
3401
- const portfolioPositions = this.portfolio.getPositions();
3402
- const earningAssets = new Set(
3403
- portfolioPositions.filter((p) => p.earning).map((p) => p.asset)
3404
- );
3405
- const suiPrice = bal.gasReserve.sui > 0 ? bal.gasReserve.usdEquiv / bal.gasReserve.sui : 0;
3406
- const assetPrices = { SUI: suiPrice };
3407
- const swapAdapter = this.registry.listSwap()[0];
3408
- for (const asset of Object.keys(INVESTMENT_ASSETS)) {
3409
- if (asset === "SUI") continue;
3410
- try {
3411
- if (swapAdapter) {
3412
- const quote = await swapAdapter.getQuote("USDC", asset, 1);
3413
- assetPrices[asset] = quote.expectedOutput > 0 ? 1 / quote.expectedOutput : 0;
3414
- }
3415
- } catch {
3416
- assetPrices[asset] = 0;
3417
- }
3418
- }
3419
2404
  let chainTotal = bal.available + bal.gasReserve.usdEquiv;
3420
- for (const asset of Object.keys(INVESTMENT_ASSETS)) {
3421
- if (asset === "SUI") continue;
3422
- chainTotal += (bal.assets[asset] ?? 0) * (assetPrices[asset] ?? 0);
3423
- }
3424
2405
  try {
3425
2406
  const positions = await this.positions();
3426
2407
  for (const pos of positions.positions) {
3427
2408
  const usdValue = pos.amountUsd ?? pos.amount;
3428
2409
  if (pos.type === "save") {
3429
2410
  chainTotal += usdValue;
3430
- if (!earningAssets.has(pos.asset)) {
3431
- bal.savings += usdValue;
3432
- }
2411
+ bal.savings += usdValue;
3433
2412
  } else if (pos.type === "borrow") {
3434
2413
  chainTotal -= usdValue;
3435
2414
  bal.debt += usdValue;
@@ -3437,50 +2416,6 @@ var T2000 = class _T2000 extends EventEmitter {
3437
2416
  }
3438
2417
  } catch {
3439
2418
  }
3440
- try {
3441
- const trackedAmounts = {};
3442
- const trackedCostBasis = {};
3443
- const earningAssetSet = /* @__PURE__ */ new Set();
3444
- for (const pos of portfolioPositions) {
3445
- if (!(pos.asset in INVESTMENT_ASSETS)) continue;
3446
- trackedAmounts[pos.asset] = (trackedAmounts[pos.asset] ?? 0) + pos.totalAmount;
3447
- trackedCostBasis[pos.asset] = (trackedCostBasis[pos.asset] ?? 0) + pos.costBasis;
3448
- if (pos.earning) earningAssetSet.add(pos.asset);
3449
- }
3450
- let investmentValue = 0;
3451
- let investmentCostBasis = 0;
3452
- let trackedValue = 0;
3453
- for (const asset of Object.keys(INVESTMENT_ASSETS)) {
3454
- const price = assetPrices[asset] ?? 0;
3455
- const tracked = trackedAmounts[asset] ?? 0;
3456
- const costBasis = trackedCostBasis[asset] ?? 0;
3457
- if (asset === "SUI") {
3458
- const actualSui = earningAssetSet.has("SUI") ? tracked : Math.min(tracked, bal.gasReserve.sui);
3459
- investmentValue += actualSui * price;
3460
- trackedValue += actualSui * price;
3461
- if (actualSui < tracked && tracked > 0) {
3462
- investmentCostBasis += costBasis * (actualSui / tracked);
3463
- } else {
3464
- investmentCostBasis += costBasis;
3465
- }
3466
- if (!earningAssetSet.has("SUI")) {
3467
- const gasSui = Math.max(0, bal.gasReserve.sui - tracked);
3468
- bal.gasReserve = { sui: gasSui, usdEquiv: gasSui * price };
3469
- }
3470
- } else {
3471
- const onChainAmount = bal.assets[asset] ?? 0;
3472
- const effectiveAmount = Math.max(tracked, onChainAmount);
3473
- investmentValue += effectiveAmount * price;
3474
- trackedValue += tracked * price;
3475
- investmentCostBasis += costBasis;
3476
- }
3477
- }
3478
- bal.investment = investmentValue;
3479
- bal.investmentPnL = trackedValue - investmentCostBasis;
3480
- } catch {
3481
- bal.investment = 0;
3482
- bal.investmentPnL = 0;
3483
- }
3484
2419
  try {
3485
2420
  const pendingRewards = await this.getPendingRewards();
3486
2421
  bal.pendingRewards = pendingRewards.reduce((s, r) => s + r.estimatedValueUsd, 0);
@@ -3499,25 +2434,19 @@ var T2000 = class _T2000 extends EventEmitter {
3499
2434
  async deposit() {
3500
2435
  return {
3501
2436
  address: this._address,
3502
- network: "Sui (mainnet)",
3503
- supportedAssets: ["USDC"],
2437
+ network: "mainnet",
2438
+ supportedAssets: ["USDC", "USDT", "SUI"],
3504
2439
  instructions: [
3505
- `Send USDC on Sui to: ${this._address}`,
3506
- "",
3507
- "From a CEX (Coinbase, Binance):",
3508
- ` 1. Withdraw USDC`,
3509
- ` 2. Select "Sui" network`,
3510
- ` 3. Paste address: ${truncateAddress(this._address)}`,
3511
- "",
3512
- "From another Sui wallet:",
3513
- ` Transfer USDC to ${truncateAddress(this._address)}`
2440
+ `Send USDC to: ${this._address}`,
2441
+ `Network: Sui Mainnet`,
2442
+ `Or buy USDC on an exchange and withdraw to this address.`,
2443
+ `USDC contract: ${SUPPORTED_ASSETS.USDC.type}`
3514
2444
  ].join("\n")
3515
2445
  };
3516
2446
  }
3517
2447
  exportKey() {
3518
2448
  return exportPrivateKey(this.keypair);
3519
2449
  }
3520
- /** Create a T2000 instance from zkLogin credentials (for web app). */
3521
2450
  static fromZkLogin(opts) {
3522
2451
  const signer = new ZkLoginSigner(opts.ephemeralKeypair, opts.zkProof, opts.userAddress, opts.maxEpoch);
3523
2452
  const client = getSuiClient(opts.rpcUrl);
@@ -3525,16 +2454,15 @@ var T2000 = class _T2000 extends EventEmitter {
3525
2454
  }
3526
2455
  async registerAdapter(adapter) {
3527
2456
  await adapter.init(this.client);
3528
- if ("buildSaveTx" in adapter) this.registry.registerLending(adapter);
3529
- if ("buildSwapTx" in adapter) this.registry.registerSwap(adapter);
2457
+ this.registry.registerLending(adapter);
3530
2458
  }
3531
2459
  // -- Savings --
3532
2460
  async save(params) {
3533
2461
  this.enforcer.assertNotLocked();
3534
- const asset = "USDC";
2462
+ const asset = params.asset ?? "USDC";
2463
+ const assetInfo = SUPPORTED_ASSETS[asset];
2464
+ if (!assetInfo) throw new T2000Error("ASSET_NOT_SUPPORTED", `Unsupported asset: ${asset}`);
3535
2465
  const bal = await queryBalance(this.client, this._address);
3536
- const usdcBalance = bal.stables.USDC ?? 0;
3537
- const needsAutoConvert = params.amount === "all" ? Object.entries(bal.stables).some(([k, v]) => k !== "USDC" && v > 0.01) : typeof params.amount === "number" && params.amount > usdcBalance;
3538
2466
  let amount;
3539
2467
  if (params.amount === "all") {
3540
2468
  amount = (bal.available ?? 0) - 1;
@@ -3553,54 +2481,25 @@ var T2000 = class _T2000 extends EventEmitter {
3553
2481
  const fee = calculateFee("save", amount);
3554
2482
  const saveAmount = amount;
3555
2483
  const adapter = await this.resolveLending(params.protocol, asset, "save");
3556
- const swapAdapter = this.registry.listSwap()[0];
3557
- const canPTB = adapter.addSaveToTx && (!needsAutoConvert || swapAdapter?.addSwapToTx);
2484
+ const canPTB = !!adapter.addSaveToTx;
3558
2485
  const gasResult = await executeWithGas(this.client, this._signer, async () => {
3559
- if (canPTB && needsAutoConvert) {
2486
+ if (canPTB) {
3560
2487
  const tx2 = new Transaction();
3561
2488
  tx2.setSender(this._address);
3562
- const usdcCoins = [];
3563
- for (const [stableAsset, stableAmount] of Object.entries(bal.stables)) {
3564
- if (stableAsset === "USDC" || stableAmount <= 0.01) continue;
3565
- const assetInfo = SUPPORTED_ASSETS[stableAsset];
3566
- if (!assetInfo) continue;
2489
+ let inputCoin;
2490
+ if (asset === "SUI") {
2491
+ const rawAmount = BigInt(Math.floor(saveAmount * 10 ** assetInfo.decimals));
2492
+ [inputCoin] = tx2.splitCoins(tx2.gas, [rawAmount]);
2493
+ } else {
3567
2494
  const coins = await this._fetchCoins(assetInfo.type);
3568
- if (coins.length === 0) continue;
2495
+ if (coins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", `No ${asset} coins found`);
3569
2496
  const merged = this._mergeCoinsInTx(tx2, coins);
3570
- const { outputCoin } = await swapAdapter.addSwapToTx(
3571
- tx2,
3572
- this._address,
3573
- merged,
3574
- stableAsset,
3575
- "USDC",
3576
- stableAmount
3577
- );
3578
- usdcCoins.push(outputCoin);
3579
- }
3580
- const existingUsdc = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
3581
- if (existingUsdc.length > 0) {
3582
- usdcCoins.push(this._mergeCoinsInTx(tx2, existingUsdc));
2497
+ const rawAmount = BigInt(Math.floor(saveAmount * 10 ** assetInfo.decimals));
2498
+ [inputCoin] = tx2.splitCoins(merged, [rawAmount]);
3583
2499
  }
3584
- if (usdcCoins.length > 1) {
3585
- tx2.mergeCoins(usdcCoins[0], usdcCoins.slice(1));
3586
- }
3587
- await adapter.addSaveToTx(tx2, this._address, usdcCoins[0], asset, { collectFee: true });
3588
- return tx2;
3589
- }
3590
- if (canPTB && !needsAutoConvert) {
3591
- const tx2 = new Transaction();
3592
- tx2.setSender(this._address);
3593
- const existingUsdc = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
3594
- if (existingUsdc.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
3595
- const merged = this._mergeCoinsInTx(tx2, existingUsdc);
3596
- const rawAmount = BigInt(Math.floor(saveAmount * 10 ** SUPPORTED_ASSETS.USDC.decimals));
3597
- const [depositCoin] = tx2.splitCoins(merged, [rawAmount]);
3598
- await adapter.addSaveToTx(tx2, this._address, depositCoin, asset, { collectFee: true });
2500
+ await adapter.addSaveToTx(tx2, this._address, inputCoin, asset, { collectFee: true });
3599
2501
  return tx2;
3600
2502
  }
3601
- if (needsAutoConvert) {
3602
- await this._convertWalletStablesToUsdc(bal, params.amount === "all" ? void 0 : amount - usdcBalance);
3603
- }
3604
2503
  const { tx } = await adapter.buildSaveTx(this._address, saveAmount, asset, { collectFee: true });
3605
2504
  return tx;
3606
2505
  });
@@ -3633,24 +2532,22 @@ var T2000 = class _T2000 extends EventEmitter {
3633
2532
  }
3634
2533
  async withdraw(params) {
3635
2534
  this.enforcer.assertNotLocked();
3636
- if (params.amount === "all" && !params.protocol) {
2535
+ if (params.amount === "all" && !params.protocol && !params.asset) {
3637
2536
  return this.withdrawAllProtocols();
3638
2537
  }
3639
2538
  const allPositions = await this.registry.allPositions(this._address);
3640
- const earningAssets = new Set(
3641
- this.portfolio.getPositions().filter((p) => p.earning).map((p) => p.asset)
3642
- );
3643
2539
  const supplies = [];
3644
2540
  for (const pos of allPositions) {
3645
2541
  if (params.protocol && pos.protocolId !== params.protocol) continue;
3646
2542
  for (const s of pos.positions.supplies) {
3647
- if (s.amount > 1e-3 && !earningAssets.has(s.asset)) {
2543
+ if (s.amount > 1e-3) {
2544
+ if (params.asset && s.asset !== params.asset) continue;
3648
2545
  supplies.push({ protocolId: pos.protocolId, asset: s.asset, amount: s.amount, apy: s.apy });
3649
2546
  }
3650
2547
  }
3651
2548
  }
3652
2549
  if (supplies.length === 0) {
3653
- throw new T2000Error("NO_COLLATERAL", "No savings to withdraw");
2550
+ throw new T2000Error("NO_COLLATERAL", params.asset ? `No ${params.asset} savings to withdraw` : "No savings to withdraw");
3654
2551
  }
3655
2552
  supplies.sort((a, b) => {
3656
2553
  const aIsUsdc = a.asset === "USDC" ? 0 : 1;
@@ -3688,42 +2585,19 @@ var T2000 = class _T2000 extends EventEmitter {
3688
2585
  }
3689
2586
  const withdrawAmount = amount;
3690
2587
  let finalAmount = withdrawAmount;
3691
- const swapAdapter = target.asset !== "USDC" ? this.registry.listSwap()[0] : void 0;
3692
- const canPTB = adapter.addWithdrawToTx && (!swapAdapter || swapAdapter.addSwapToTx);
3693
2588
  const gasResult = await executeWithGas(this.client, this._signer, async () => {
3694
- if (canPTB) {
2589
+ if (adapter.addWithdrawToTx) {
3695
2590
  const tx = new Transaction();
3696
2591
  tx.setSender(this._address);
3697
2592
  const { coin, effectiveAmount } = await adapter.addWithdrawToTx(tx, this._address, withdrawAmount, target.asset);
3698
2593
  finalAmount = effectiveAmount;
3699
- if (target.asset !== "USDC" && swapAdapter?.addSwapToTx) {
3700
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
3701
- tx,
3702
- this._address,
3703
- coin,
3704
- target.asset,
3705
- "USDC",
3706
- effectiveAmount,
3707
- 500
3708
- );
3709
- finalAmount = estimatedOut / 10 ** toDecimals;
3710
- tx.transferObjects([outputCoin], this._address);
3711
- } else {
3712
- tx.transferObjects([coin], this._address);
3713
- }
2594
+ tx.transferObjects([coin], this._address);
3714
2595
  return tx;
3715
2596
  }
3716
2597
  const built = await adapter.buildWithdrawTx(this._address, withdrawAmount, target.asset);
3717
2598
  finalAmount = built.effectiveAmount;
3718
2599
  return built.tx;
3719
2600
  });
3720
- if (target.asset !== "USDC") {
3721
- try {
3722
- const postBal = await queryBalance(this.client, this._address);
3723
- await this._convertWalletStablesToUsdc(postBal);
3724
- } catch {
3725
- }
3726
- }
3727
2601
  this.emitBalanceChange("USDC", finalAmount, "withdraw", gasResult.digest);
3728
2602
  return {
3729
2603
  success: true,
@@ -3735,13 +2609,10 @@ var T2000 = class _T2000 extends EventEmitter {
3735
2609
  }
3736
2610
  async withdrawAllProtocols() {
3737
2611
  const allPositions = await this.registry.allPositions(this._address);
3738
- const earningAssets = new Set(
3739
- this.portfolio.getPositions().filter((p) => p.earning).map((p) => p.asset)
3740
- );
3741
2612
  const withdrawable = [];
3742
2613
  for (const pos of allPositions) {
3743
2614
  for (const supply of pos.positions.supplies) {
3744
- if (supply.amount > 0.01 && !earningAssets.has(supply.asset)) {
2615
+ if (supply.amount > 0.01) {
3745
2616
  withdrawable.push({ protocolId: pos.protocolId, asset: supply.asset, amount: supply.amount });
3746
2617
  }
3747
2618
  }
@@ -3768,87 +2639,39 @@ var T2000 = class _T2000 extends EventEmitter {
3768
2639
  if (entries.length === 0) {
3769
2640
  throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
3770
2641
  }
3771
- const DUST_SWAP_THRESHOLD = 1;
3772
- const swappableEntries = entries.filter((e) => e.asset === "USDC" || e.maxAmount >= DUST_SWAP_THRESHOLD);
3773
- const dustEntries = entries.filter((e) => e.asset !== "USDC" && e.maxAmount < DUST_SWAP_THRESHOLD);
3774
- const hasNonUsdc = swappableEntries.some((e) => e.asset !== "USDC");
3775
- const swapAdapter = hasNonUsdc ? this.registry.listSwap()[0] : void 0;
3776
- const canPTB = swappableEntries.every((e) => e.adapter.addWithdrawToTx) && (!swapAdapter || swapAdapter.addSwapToTx);
3777
- let totalUsdcReceived = 0;
2642
+ let totalReceived = 0;
2643
+ const canPTB = entries.every((e) => e.adapter.addWithdrawToTx);
3778
2644
  const gasResult = await executeWithGas(this.client, this._signer, async () => {
3779
2645
  if (canPTB) {
3780
2646
  const tx = new Transaction();
3781
2647
  tx.setSender(this._address);
3782
- const usdcCoins = [];
3783
- for (const entry of swappableEntries) {
2648
+ for (const entry of entries) {
3784
2649
  const { coin, effectiveAmount } = await entry.adapter.addWithdrawToTx(
3785
2650
  tx,
3786
2651
  this._address,
3787
2652
  entry.maxAmount,
3788
2653
  entry.asset
3789
2654
  );
3790
- if (entry.asset === "USDC") {
3791
- totalUsdcReceived += effectiveAmount;
3792
- usdcCoins.push(coin);
3793
- } else if (swapAdapter?.addSwapToTx) {
3794
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
3795
- tx,
3796
- this._address,
3797
- coin,
3798
- entry.asset,
3799
- "USDC",
3800
- effectiveAmount,
3801
- 500
3802
- );
3803
- totalUsdcReceived += estimatedOut / 10 ** toDecimals;
3804
- usdcCoins.push(outputCoin);
3805
- } else {
3806
- totalUsdcReceived += effectiveAmount;
3807
- tx.transferObjects([coin], this._address);
3808
- }
3809
- }
3810
- for (const dust of dustEntries) {
3811
- if (dust.adapter.addWithdrawToTx) {
3812
- const { coin, effectiveAmount } = await dust.adapter.addWithdrawToTx(
3813
- tx,
3814
- this._address,
3815
- dust.maxAmount,
3816
- dust.asset
3817
- );
3818
- totalUsdcReceived += effectiveAmount;
3819
- tx.transferObjects([coin], this._address);
3820
- }
3821
- }
3822
- if (usdcCoins.length > 1) {
3823
- tx.mergeCoins(usdcCoins[0], usdcCoins.slice(1));
3824
- }
3825
- if (usdcCoins.length > 0) {
3826
- tx.transferObjects([usdcCoins[0]], this._address);
2655
+ totalReceived += effectiveAmount;
2656
+ tx.transferObjects([coin], this._address);
3827
2657
  }
3828
2658
  return tx;
3829
2659
  }
3830
2660
  let lastTx;
3831
2661
  for (const entry of entries) {
3832
2662
  const built = await entry.adapter.buildWithdrawTx(this._address, entry.maxAmount, entry.asset);
3833
- totalUsdcReceived += built.effectiveAmount;
2663
+ totalReceived += built.effectiveAmount;
3834
2664
  lastTx = built.tx;
3835
2665
  }
3836
2666
  return lastTx;
3837
2667
  });
3838
- if (hasNonUsdc) {
3839
- try {
3840
- const postBal = await queryBalance(this.client, this._address);
3841
- await this._convertWalletStablesToUsdc(postBal);
3842
- } catch {
3843
- }
3844
- }
3845
- if (totalUsdcReceived <= 0) {
2668
+ if (totalReceived <= 0) {
3846
2669
  throw new T2000Error("NO_COLLATERAL", "No savings to withdraw across any protocol");
3847
2670
  }
3848
2671
  return {
3849
2672
  success: true,
3850
2673
  tx: gasResult.digest,
3851
- amount: totalUsdcReceived,
2674
+ amount: totalReceived,
3852
2675
  gasCost: gasResult.gasCostSui,
3853
2676
  gasMethod: gasResult.gasMethod
3854
2677
  };
@@ -3898,39 +2721,6 @@ var T2000 = class _T2000 extends EventEmitter {
3898
2721
  }
3899
2722
  return primary;
3900
2723
  }
3901
- async _swapToUsdc(asset, amount) {
3902
- const swapAdapter = this.registry.listSwap()[0];
3903
- if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
3904
- let estimatedOut = 0;
3905
- let toDecimals = 6;
3906
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
3907
- const built = await swapAdapter.buildSwapTx(this._address, asset, "USDC", amount);
3908
- estimatedOut = built.estimatedOut;
3909
- toDecimals = built.toDecimals;
3910
- return built.tx;
3911
- });
3912
- const usdcReceived = estimatedOut / 10 ** toDecimals;
3913
- return { usdcReceived, digest: gasResult.digest, gasCost: gasResult.gasCostSui };
3914
- }
3915
- async _swapFromUsdc(toAsset, amount) {
3916
- const swapAdapter = this.registry.listSwap()[0];
3917
- if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
3918
- let estimatedOut = 0;
3919
- let toDecimals = 6;
3920
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
3921
- const built = await swapAdapter.buildSwapTx(this._address, "USDC", toAsset, amount);
3922
- estimatedOut = built.estimatedOut;
3923
- toDecimals = built.toDecimals;
3924
- return built.tx;
3925
- });
3926
- const received = estimatedOut / 10 ** toDecimals;
3927
- return { received, digest: gasResult.digest, gasCost: gasResult.gasCostSui };
3928
- }
3929
- /**
3930
- * Auto-withdraw from savings when checking balance is insufficient for an
3931
- * operation. Handles non-USDC savings (e.g. USDe) via the standard withdraw
3932
- * path which swaps back to USDC. Throws if savings are also insufficient.
3933
- */
3934
2724
  _lastFundDigest;
3935
2725
  async _autoFundFromSavings(shortfall) {
3936
2726
  const positions = await this.positions();
@@ -3961,69 +2751,17 @@ var T2000 = class _T2000 extends EventEmitter {
3961
2751
  }
3962
2752
  this._lastFundDigest = result.tx;
3963
2753
  }
3964
- async _convertWalletStablesToUsdc(bal, amountNeeded) {
3965
- const nonUsdcStables = [];
3966
- for (const [asset, amount] of Object.entries(bal.stables)) {
3967
- if (asset !== "USDC" && amount > 0.01) {
3968
- nonUsdcStables.push({ asset, amount });
3969
- }
3970
- }
3971
- if (nonUsdcStables.length === 0) return;
3972
- nonUsdcStables.sort((a, b) => b.amount - a.amount);
3973
- let converted = 0;
3974
- for (const entry of nonUsdcStables) {
3975
- if (amountNeeded !== void 0 && converted >= amountNeeded) break;
3976
- try {
3977
- await this._swapToUsdc(entry.asset, entry.amount);
3978
- converted += entry.amount;
3979
- } catch {
3980
- }
3981
- }
3982
- }
3983
2754
  async maxWithdraw() {
3984
2755
  const adapter = await this.resolveLending(void 0, "USDC", "withdraw");
3985
2756
  return adapter.maxWithdraw(this._address, "USDC");
3986
2757
  }
3987
2758
  // -- Borrowing --
3988
- async adjustMaxBorrowForInvestments(adapter, maxResult) {
3989
- const earningPositions = this.portfolio.getPositions().filter((p) => p.earning);
3990
- if (earningPositions.length === 0) return maxResult;
3991
- let investmentCollateralUsd = 0;
3992
- const swapAdapter = this.registry.listSwap()[0];
3993
- for (const pos of earningPositions) {
3994
- if (pos.earningProtocol !== adapter.id) continue;
3995
- try {
3996
- let price = 0;
3997
- if (pos.asset === "SUI" && swapAdapter) {
3998
- price = await swapAdapter.getPoolPrice();
3999
- } else if (swapAdapter) {
4000
- const quote = await swapAdapter.getQuote("USDC", pos.asset, 1);
4001
- price = quote.expectedOutput > 0 ? 1 / quote.expectedOutput : 0;
4002
- }
4003
- investmentCollateralUsd += pos.totalAmount * price;
4004
- } catch {
4005
- }
4006
- }
4007
- if (investmentCollateralUsd <= 0) return maxResult;
4008
- const CONSERVATIVE_LTV = 0.6;
4009
- const investmentBorrowCapacity = investmentCollateralUsd * CONSERVATIVE_LTV;
4010
- const adjustedMax = Math.max(0, maxResult.maxAmount - investmentBorrowCapacity);
4011
- return { ...maxResult, maxAmount: adjustedMax };
4012
- }
4013
2759
  async borrow(params) {
4014
2760
  this.enforcer.assertNotLocked();
4015
2761
  const asset = "USDC";
4016
2762
  const adapter = await this.resolveLending(params.protocol, asset, "borrow");
4017
- const rawMax = await adapter.maxBorrow(this._address, asset);
4018
- const maxResult = await this.adjustMaxBorrowForInvestments(adapter, rawMax);
2763
+ const maxResult = await adapter.maxBorrow(this._address, asset);
4019
2764
  if (maxResult.maxAmount <= 0) {
4020
- const hasInvestmentEarning = this.portfolio.getPositions().some((p) => p.earning && p.earningProtocol === adapter.id);
4021
- if (hasInvestmentEarning) {
4022
- throw new T2000Error(
4023
- "BORROW_GUARD_INVESTMENT",
4024
- "Max safe borrow: $0.00. Only savings deposits (stablecoins) count as borrowable collateral. Investment collateral (SUI, ETH, BTC) is excluded."
4025
- );
4026
- }
4027
2765
  throw new T2000Error("NO_COLLATERAL", "No collateral deposited. Save first with `t2000 save <amount>`.");
4028
2766
  }
4029
2767
  if (params.amount > maxResult.maxAmount) {
@@ -4072,30 +2810,8 @@ var T2000 = class _T2000 extends EventEmitter {
4072
2810
  const adapter = this.registry.getLending(target.protocolId);
4073
2811
  if (!adapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${target.protocolId} not found`);
4074
2812
  const repayAmount = Math.min(params.amount, target.amount);
4075
- const swapAdapter = target.asset !== "USDC" ? this.registry.listSwap()[0] : void 0;
4076
- const canPTB = adapter.addRepayToTx && (!swapAdapter || swapAdapter.addSwapToTx);
4077
2813
  const gasResult = await executeWithGas(this.client, this._signer, async () => {
4078
- if (canPTB && target.asset !== "USDC" && swapAdapter?.addSwapToTx) {
4079
- const tx2 = new Transaction();
4080
- tx2.setSender(this._address);
4081
- const buffer = repayAmount * 1.005;
4082
- const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
4083
- if (usdcCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins for swap");
4084
- const merged = this._mergeCoinsInTx(tx2, usdcCoins);
4085
- const rawSwap = BigInt(Math.floor(buffer * 10 ** SUPPORTED_ASSETS.USDC.decimals));
4086
- const [splitCoin] = tx2.splitCoins(merged, [rawSwap]);
4087
- const { outputCoin } = await swapAdapter.addSwapToTx(
4088
- tx2,
4089
- this._address,
4090
- splitCoin,
4091
- "USDC",
4092
- target.asset,
4093
- buffer
4094
- );
4095
- await adapter.addRepayToTx(tx2, this._address, outputCoin, target.asset);
4096
- return tx2;
4097
- }
4098
- if (canPTB && target.asset === "USDC") {
2814
+ if (adapter.addRepayToTx) {
4099
2815
  const tx2 = new Transaction();
4100
2816
  tx2.setSender(this._address);
4101
2817
  const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
@@ -4106,9 +2822,6 @@ var T2000 = class _T2000 extends EventEmitter {
4106
2822
  await adapter.addRepayToTx(tx2, this._address, repayCoin, target.asset);
4107
2823
  return tx2;
4108
2824
  }
4109
- if (target.asset !== "USDC") {
4110
- await this._swapFromUsdc(target.asset, repayAmount * 1.005);
4111
- }
4112
2825
  const { tx } = await adapter.buildRepayTx(this._address, repayAmount, target.asset);
4113
2826
  return tx;
4114
2827
  });
@@ -4130,8 +2843,7 @@ var T2000 = class _T2000 extends EventEmitter {
4130
2843
  const adapter = this.registry.getLending(borrow.protocolId);
4131
2844
  if (adapter) entries.push({ borrow, adapter });
4132
2845
  }
4133
- const swapAdapter = this.registry.listSwap()[0];
4134
- const canPTB = entries.every((e) => e.adapter.addRepayToTx) && (entries.every((e) => e.borrow.asset === "USDC") || swapAdapter?.addSwapToTx);
2846
+ const canPTB = entries.every((e) => e.adapter.addRepayToTx);
4135
2847
  let totalRepaid = 0;
4136
2848
  const gasResult = await executeWithGas(this.client, this._signer, async () => {
4137
2849
  if (canPTB) {
@@ -4143,35 +2855,16 @@ var T2000 = class _T2000 extends EventEmitter {
4143
2855
  usdcMerged = this._mergeCoinsInTx(tx, usdcCoins);
4144
2856
  }
4145
2857
  for (const { borrow, adapter } of entries) {
4146
- if (borrow.asset !== "USDC" && swapAdapter?.addSwapToTx) {
4147
- const buffer = borrow.amount * 1.005;
4148
- const rawSwap = BigInt(Math.floor(buffer * 10 ** SUPPORTED_ASSETS.USDC.decimals));
4149
- if (!usdcMerged) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC for swap");
4150
- const [splitCoin] = tx.splitCoins(usdcMerged, [rawSwap]);
4151
- const { outputCoin } = await swapAdapter.addSwapToTx(
4152
- tx,
4153
- this._address,
4154
- splitCoin,
4155
- "USDC",
4156
- borrow.asset,
4157
- buffer
4158
- );
4159
- await adapter.addRepayToTx(tx, this._address, outputCoin, borrow.asset);
4160
- } else {
4161
- const raw = BigInt(Math.floor(borrow.amount * 10 ** SUPPORTED_ASSETS.USDC.decimals));
4162
- if (!usdcMerged) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC for repayment");
4163
- const [repayCoin] = tx.splitCoins(usdcMerged, [raw]);
4164
- await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
4165
- }
2858
+ const raw = BigInt(Math.floor(borrow.amount * 10 ** SUPPORTED_ASSETS.USDC.decimals));
2859
+ if (!usdcMerged) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC for repayment");
2860
+ const [repayCoin] = tx.splitCoins(usdcMerged, [raw]);
2861
+ await adapter.addRepayToTx(tx, this._address, repayCoin, borrow.asset);
4166
2862
  totalRepaid += borrow.amount;
4167
2863
  }
4168
2864
  return tx;
4169
2865
  }
4170
2866
  let lastTx;
4171
2867
  for (const { borrow, adapter } of entries) {
4172
- if (borrow.asset !== "USDC") {
4173
- await this._swapFromUsdc(borrow.asset, borrow.amount * 1.005);
4174
- }
4175
2868
  const { tx } = await adapter.buildRepayTx(this._address, borrow.amount, borrow.asset);
4176
2869
  lastTx = tx;
4177
2870
  totalRepaid += borrow.amount;
@@ -4182,536 +2875,27 @@ var T2000 = class _T2000 extends EventEmitter {
4182
2875
  const hf = firstAdapter ? await firstAdapter.getHealth(this._address) : { borrowed: 0 };
4183
2876
  this.emitBalanceChange("USDC", totalRepaid, "repay", gasResult.digest);
4184
2877
  return {
4185
- success: true,
4186
- tx: gasResult.digest,
4187
- amount: totalRepaid,
4188
- remainingDebt: hf.borrowed,
4189
- gasCost: gasResult.gasCostSui,
4190
- gasMethod: gasResult.gasMethod
4191
- };
4192
- }
4193
- async maxBorrow() {
4194
- const adapter = await this.resolveLending(void 0, "USDC", "borrow");
4195
- const rawMax = await adapter.maxBorrow(this._address, "USDC");
4196
- return this.adjustMaxBorrowForInvestments(adapter, rawMax);
4197
- }
4198
- async healthFactor() {
4199
- const adapter = await this.resolveLending(void 0, "USDC", "save");
4200
- const hf = await adapter.getHealth(this._address);
4201
- if (hf.healthFactor < 1.2) {
4202
- this.emit("healthCritical", { healthFactor: hf.healthFactor, threshold: 1.5, severity: "critical" });
4203
- } else if (hf.healthFactor < 2) {
4204
- this.emit("healthWarning", { healthFactor: hf.healthFactor, threshold: 2, severity: "warning" });
4205
- }
4206
- return hf;
4207
- }
4208
- // -- Swap (formerly Exchange) --
4209
- async swap(params) {
4210
- this.enforcer.assertNotLocked();
4211
- const fromAsset = params.from;
4212
- const toAsset = params.to;
4213
- if (!(fromAsset in SUPPORTED_ASSETS) || !(toAsset in SUPPORTED_ASSETS)) {
4214
- throw new T2000Error("ASSET_NOT_SUPPORTED", `Swap pair ${fromAsset}/${toAsset} is not supported`);
4215
- }
4216
- if (fromAsset === toAsset) {
4217
- throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
4218
- }
4219
- const best = await this.registry.bestSwapQuote(fromAsset, toAsset, params.amount);
4220
- const adapter = best.adapter;
4221
- const fee = calculateFee("swap", params.amount);
4222
- const swapAmount = params.amount;
4223
- const slippageBps = params.maxSlippage ? Math.round(params.maxSlippage * 1e4) : void 0;
4224
- let swapMeta = { estimatedOut: 0, toDecimals: 0 };
4225
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
4226
- const built = await adapter.buildSwapTx(this._address, fromAsset, toAsset, swapAmount, slippageBps);
4227
- swapMeta = { estimatedOut: built.estimatedOut, toDecimals: built.toDecimals };
4228
- return built.tx;
4229
- });
4230
- const toInfo = SUPPORTED_ASSETS[toAsset];
4231
- await this.client.waitForTransaction({ digest: gasResult.digest });
4232
- const txDetail = await this.client.getTransactionBlock({
4233
- digest: gasResult.digest,
4234
- options: { showBalanceChanges: true }
4235
- });
4236
- let actualReceived = 0;
4237
- if (txDetail.balanceChanges) {
4238
- for (const change of txDetail.balanceChanges) {
4239
- if (change.coinType === toInfo.type && change.owner && typeof change.owner === "object" && "AddressOwner" in change.owner && change.owner.AddressOwner === this._address) {
4240
- const amt = Number(change.amount) / 10 ** toInfo.decimals;
4241
- if (amt > 0) actualReceived += amt;
4242
- }
4243
- }
4244
- }
4245
- const expectedOutput = swapMeta.estimatedOut / 10 ** swapMeta.toDecimals;
4246
- if (actualReceived === 0) actualReceived = expectedOutput;
4247
- const priceImpact = expectedOutput > 0 ? Math.abs(actualReceived - expectedOutput) / expectedOutput : 0;
4248
- reportFee(this._address, "swap", fee.amount, fee.rate, gasResult.digest);
4249
- this.emitBalanceChange(fromAsset, swapAmount, "swap", gasResult.digest);
4250
- const stableSet = new Set(STABLE_ASSETS);
4251
- if (!params._skipPortfolioRecord && stableSet.has(fromAsset) && toAsset in INVESTMENT_ASSETS && actualReceived > 0) {
4252
- const price = swapAmount / actualReceived;
4253
- this.portfolio.recordBuy({
4254
- id: `swap_${Date.now()}`,
4255
- type: "buy",
4256
- asset: toAsset,
4257
- amount: actualReceived,
4258
- price,
4259
- usdValue: swapAmount,
4260
- fee: fee.amount,
4261
- tx: gasResult.digest,
4262
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4263
- });
4264
- } else if (!params._skipPortfolioRecord && fromAsset in INVESTMENT_ASSETS && stableSet.has(toAsset) && actualReceived > 0) {
4265
- const price = actualReceived / swapAmount;
4266
- this.portfolio.recordSell({
4267
- id: `swap_${Date.now()}`,
4268
- type: "sell",
4269
- asset: fromAsset,
4270
- amount: swapAmount,
4271
- price,
4272
- usdValue: actualReceived,
4273
- fee: fee.amount,
4274
- tx: gasResult.digest,
4275
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4276
- });
4277
- }
4278
- return {
4279
- success: true,
4280
- tx: gasResult.digest,
4281
- fromAmount: swapAmount,
4282
- fromAsset,
4283
- toAmount: actualReceived,
4284
- toAsset,
4285
- priceImpact,
4286
- fee: fee.amount,
4287
- gasCost: gasResult.gasCostSui,
4288
- gasMethod: gasResult.gasMethod
4289
- };
4290
- }
4291
- async swapQuote(params) {
4292
- const fromAsset = params.from;
4293
- const toAsset = params.to;
4294
- const best = await this.registry.bestSwapQuote(fromAsset, toAsset, params.amount);
4295
- const fee = calculateFee("swap", params.amount);
4296
- return { ...best.quote, fee: { amount: fee.amount, rate: fee.rate } };
4297
- }
4298
- /** @deprecated Use swap() instead */
4299
- async exchange(params) {
4300
- return this.swap(params);
4301
- }
4302
- /** @deprecated Use swapQuote() instead */
4303
- async exchangeQuote(params) {
4304
- return this.swapQuote(params);
4305
- }
4306
- // -- Investment (strategies only — individual buy/sell deprecated, use swap()) --
4307
- async investBuy(params) {
4308
- this.enforcer.assertNotLocked();
4309
- if (!params.usdAmount || params.usdAmount <= 0 || !isFinite(params.usdAmount)) {
4310
- throw new T2000Error("INVALID_AMOUNT", "Investment amount must be greater than $0");
4311
- }
4312
- this.enforcer.check({ operation: "invest", amount: params.usdAmount });
4313
- if (!(params.asset in INVESTMENT_ASSETS)) {
4314
- throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
4315
- }
4316
- const bal = await queryBalance(this.client, this._address);
4317
- const totalFunds = bal.available + bal.savings;
4318
- if (params.usdAmount > totalFunds * 1.05) {
4319
- throw new T2000Error(
4320
- "INSUFFICIENT_BALANCE",
4321
- `Insufficient funds. You have $${totalFunds.toFixed(2)} total (checking: $${bal.available.toFixed(2)}, savings: $${bal.savings.toFixed(2)}) but need $${params.usdAmount.toFixed(2)}.`
4322
- );
4323
- }
4324
- if (bal.available < params.usdAmount) {
4325
- await this._autoFundFromSavings(params.usdAmount - bal.available);
4326
- }
4327
- let swapResult;
4328
- const maxRetries = 3;
4329
- for (let attempt = 0; ; attempt++) {
4330
- try {
4331
- swapResult = await this.swap({
4332
- from: "USDC",
4333
- to: params.asset,
4334
- amount: params.usdAmount,
4335
- maxSlippage: params.maxSlippage ?? defaultSlippage(params.asset),
4336
- _skipPortfolioRecord: true
4337
- });
4338
- break;
4339
- } catch (err) {
4340
- const msg = err instanceof Error ? err.message : String(err);
4341
- const isSlippage = msg.includes("slippage") || msg.includes("amount_out_slippage");
4342
- if (isSlippage && attempt < maxRetries) {
4343
- await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
4344
- continue;
4345
- }
4346
- throw err;
4347
- }
4348
- }
4349
- if (swapResult.toAmount === 0) {
4350
- throw new T2000Error("SWAP_FAILED", "Swap returned zero tokens \u2014 try a different amount or check liquidity");
4351
- }
4352
- const price = params.usdAmount / swapResult.toAmount;
4353
- this.portfolio.recordBuy({
4354
- id: `inv_${Date.now()}`,
4355
- type: "buy",
4356
- asset: params.asset,
4357
- amount: swapResult.toAmount,
4358
- price,
4359
- usdValue: params.usdAmount,
4360
- fee: swapResult.fee,
4361
- tx: swapResult.tx,
4362
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4363
- });
4364
- const pos = this.portfolio.getPosition(params.asset);
4365
- const currentPrice = price;
4366
- const position = {
4367
- asset: params.asset,
4368
- totalAmount: pos?.totalAmount ?? swapResult.toAmount,
4369
- costBasis: pos?.costBasis ?? params.usdAmount,
4370
- avgPrice: pos?.avgPrice ?? price,
4371
- currentPrice,
4372
- currentValue: (pos?.totalAmount ?? swapResult.toAmount) * currentPrice,
4373
- unrealizedPnL: 0,
4374
- unrealizedPnLPct: 0,
4375
- trades: pos?.trades ?? []
4376
- };
4377
- return {
4378
- success: true,
4379
- tx: swapResult.tx,
4380
- type: "buy",
4381
- asset: params.asset,
4382
- amount: swapResult.toAmount,
4383
- price,
4384
- usdValue: params.usdAmount,
4385
- fee: swapResult.fee,
4386
- gasCost: swapResult.gasCost,
4387
- gasMethod: swapResult.gasMethod,
4388
- position
4389
- };
4390
- }
4391
- async investSell(params) {
4392
- this.enforcer.assertNotLocked();
4393
- if (params.usdAmount !== "all") {
4394
- if (!params.usdAmount || params.usdAmount <= 0 || !isFinite(params.usdAmount)) {
4395
- throw new T2000Error("INVALID_AMOUNT", "Sell amount must be greater than $0");
4396
- }
4397
- }
4398
- if (!(params.asset in INVESTMENT_ASSETS)) {
4399
- throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
4400
- }
4401
- let pos = this.portfolio.getPosition(params.asset);
4402
- const didAutoWithdraw = !!(pos?.earning && pos.earningProtocol);
4403
- if (didAutoWithdraw) {
4404
- const unearnResult = await this.investUnearn({ asset: params.asset });
4405
- if (unearnResult.tx) {
4406
- await this.client.waitForTransaction({ digest: unearnResult.tx, options: { showEffects: true } });
4407
- }
4408
- pos = this.portfolio.getPosition(params.asset);
4409
- }
4410
- const assetInfo = SUPPORTED_ASSETS[params.asset];
4411
- const gasReserve = params.asset === "SUI" ? GAS_RESERVE_MIN : 0;
4412
- let walletAmount = 0;
4413
- for (let attempt = 0; ; attempt++) {
4414
- const assetBalance = await this.client.getBalance({
4415
- owner: this._address,
4416
- coinType: assetInfo.type
4417
- });
4418
- walletAmount = Number(assetBalance.totalBalance) / 10 ** assetInfo.decimals;
4419
- if (!didAutoWithdraw || walletAmount > gasReserve || attempt >= 5) break;
4420
- await new Promise((r) => setTimeout(r, 1500));
4421
- }
4422
- const maxSellable = Math.max(0, walletAmount - gasReserve);
4423
- const trackedAmount = pos ? pos.totalAmount : maxSellable;
4424
- if (trackedAmount <= 0) {
4425
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${params.asset} position to sell`);
4426
- }
4427
- let sellAmountAsset;
4428
- if (params.usdAmount === "all") {
4429
- sellAmountAsset = Math.min(trackedAmount, maxSellable);
4430
- } else {
4431
- const swapAdapter = this.registry.listSwap()[0];
4432
- if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
4433
- const quote = await swapAdapter.getQuote("USDC", params.asset, 1);
4434
- const assetPrice = 1 / quote.expectedOutput;
4435
- sellAmountAsset = params.usdAmount / assetPrice;
4436
- const maxPosition = params._strategyOnly ? maxSellable : trackedAmount;
4437
- sellAmountAsset = Math.min(sellAmountAsset, maxPosition);
4438
- if (sellAmountAsset > maxSellable) {
4439
- throw new T2000Error(
4440
- "INSUFFICIENT_INVESTMENT",
4441
- `Cannot sell $${params.usdAmount.toFixed(2)} \u2014 max sellable: $${(maxSellable * assetPrice).toFixed(2)} (gas reserve: ${gasReserve} ${params.asset})`
4442
- );
4443
- }
4444
- }
4445
- if (sellAmountAsset <= 0) {
4446
- throw new T2000Error("INSUFFICIENT_INVESTMENT", "Nothing to sell after gas reserve");
4447
- }
4448
- let swapResult;
4449
- const maxRetries = 3;
4450
- for (let attempt = 0; ; attempt++) {
4451
- try {
4452
- swapResult = await this.swap({
4453
- from: params.asset,
4454
- to: "USDC",
4455
- amount: sellAmountAsset,
4456
- maxSlippage: params.maxSlippage ?? defaultSlippage(params.asset),
4457
- _skipPortfolioRecord: true
4458
- });
4459
- break;
4460
- } catch (err) {
4461
- const msg = err instanceof Error ? err.message : String(err);
4462
- const isSlippage = msg.includes("slippage") || msg.includes("amount_out_slippage");
4463
- if (isSlippage && attempt < maxRetries) {
4464
- await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
4465
- continue;
4466
- }
4467
- throw err;
4468
- }
4469
- }
4470
- const price = swapResult.toAmount / sellAmountAsset;
4471
- const directAmountBeforeSell = this.portfolio.getDirectAmount(params.asset);
4472
- let realizedPnL = 0;
4473
- try {
4474
- realizedPnL = this.portfolio.recordSell({
4475
- id: `inv_${Date.now()}`,
4476
- type: "sell",
4477
- asset: params.asset,
4478
- amount: sellAmountAsset,
4479
- price,
4480
- usdValue: swapResult.toAmount,
4481
- fee: swapResult.fee,
4482
- tx: swapResult.tx,
4483
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
4484
- });
4485
- } catch {
4486
- }
4487
- if (params.usdAmount === "all" && !params._strategyOnly) {
4488
- this.portfolio.closePosition(params.asset);
4489
- this.portfolio.deductFromStrategies(params.asset, sellAmountAsset);
4490
- } else if (!params._strategyOnly && sellAmountAsset > 0) {
4491
- const overflowIntoStrategy = sellAmountAsset - directAmountBeforeSell;
4492
- if (overflowIntoStrategy > 0) {
4493
- this.portfolio.deductFromStrategies(params.asset, overflowIntoStrategy);
4494
- }
4495
- }
4496
- const updatedPos = this.portfolio.getPosition(params.asset);
4497
- const position = {
4498
- asset: params.asset,
4499
- totalAmount: updatedPos?.totalAmount ?? 0,
4500
- costBasis: updatedPos?.costBasis ?? 0,
4501
- avgPrice: updatedPos?.avgPrice ?? 0,
4502
- currentPrice: price,
4503
- currentValue: (updatedPos?.totalAmount ?? 0) * price,
4504
- unrealizedPnL: 0,
4505
- unrealizedPnLPct: 0,
4506
- trades: updatedPos?.trades ?? []
4507
- };
4508
- return {
4509
- success: true,
4510
- tx: swapResult.tx,
4511
- type: "sell",
4512
- asset: params.asset,
4513
- amount: sellAmountAsset,
4514
- price,
4515
- usdValue: swapResult.toAmount,
4516
- fee: swapResult.fee,
4517
- gasCost: swapResult.gasCost,
4518
- gasMethod: swapResult.gasMethod,
4519
- realizedPnL,
4520
- position
4521
- };
4522
- }
4523
- async investEarn(params) {
4524
- this.enforcer.assertNotLocked();
4525
- if (!(params.asset in INVESTMENT_ASSETS)) {
4526
- throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
4527
- }
4528
- const pos = this.portfolio.getPosition(params.asset);
4529
- if (!pos || pos.totalAmount <= 0) {
4530
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${params.asset} position to earn on`);
4531
- }
4532
- const assetInfo = SUPPORTED_ASSETS[params.asset];
4533
- const assetBalance = await this.client.getBalance({
4534
- owner: this._address,
4535
- coinType: assetInfo.type
4536
- });
4537
- const walletAmount = Number(assetBalance.totalBalance) / 10 ** assetInfo.decimals;
4538
- const gasReserve = params.asset === "SUI" ? GAS_RESERVE_MIN : 0;
4539
- const depositAmount = Math.min(pos.totalAmount, Math.max(0, walletAmount - gasReserve));
4540
- if (pos.earning) {
4541
- throw new T2000Error("INVEST_ALREADY_EARNING", `${params.asset} is already earning via ${pos.earningProtocol ?? "lending"}`);
4542
- }
4543
- if (depositAmount <= 0) {
4544
- throw new T2000Error("INSUFFICIENT_BALANCE", `No ${params.asset} available to deposit (wallet: ${walletAmount}, gas reserve: ${gasReserve})`);
4545
- }
4546
- let adapter;
4547
- let rate;
4548
- if (params.protocol) {
4549
- const specific = this.registry.getLending(params.protocol);
4550
- if (!specific) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${params.protocol} not found`);
4551
- adapter = specific;
4552
- rate = await specific.getRates(params.asset);
4553
- } else {
4554
- ({ adapter, rate } = await this.registry.bestSaveRate(params.asset));
4555
- }
4556
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
4557
- const { tx } = await adapter.buildSaveTx(this._address, depositAmount, params.asset);
4558
- return tx;
4559
- });
4560
- this.portfolio.recordEarn(params.asset, adapter.id, rate.saveApy);
4561
- return {
4562
- success: true,
4563
- tx: gasResult.digest,
4564
- asset: params.asset,
4565
- amount: depositAmount,
4566
- protocol: adapter.name,
4567
- apy: rate.saveApy,
4568
- gasCost: gasResult.gasCostSui,
4569
- gasMethod: gasResult.gasMethod
4570
- };
4571
- }
4572
- async investUnearn(params) {
4573
- this.enforcer.assertNotLocked();
4574
- if (!(params.asset in INVESTMENT_ASSETS)) {
4575
- throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
4576
- }
4577
- let pos = this.portfolio.getPosition(params.asset);
4578
- let adapter;
4579
- let withdrawAmount;
4580
- if (pos?.earning && pos.earningProtocol) {
4581
- adapter = this.registry.getLending(pos.earningProtocol);
4582
- withdrawAmount = pos.totalAmount;
4583
- } else {
4584
- const onChain = await this.registry.allPositions(this._address);
4585
- let found;
4586
- for (const entry of onChain) {
4587
- const supply = entry.positions.supplies.find((s) => s.asset === params.asset);
4588
- if (supply && supply.amount > 1e-10) {
4589
- found = { protocolId: entry.protocolId, amount: supply.amount };
4590
- break;
4591
- }
4592
- }
4593
- if (!found) {
4594
- throw new T2000Error("INVEST_NOT_EARNING", `${params.asset} is not currently earning (checked on-chain)`);
4595
- }
4596
- adapter = this.registry.getLending(found.protocolId);
4597
- withdrawAmount = found.amount;
4598
- }
4599
- if (!adapter) {
4600
- throw new T2000Error("PROTOCOL_UNAVAILABLE", `Lending protocol not found for ${params.asset}`);
4601
- }
4602
- const protocolName = adapter.name;
4603
- let effectiveAmount = withdrawAmount;
4604
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
4605
- const result = await adapter.buildWithdrawTx(this._address, withdrawAmount, params.asset);
4606
- effectiveAmount = result.effectiveAmount;
4607
- return result.tx;
4608
- });
4609
- try {
4610
- this.portfolio.recordUnearn(params.asset);
4611
- } catch {
4612
- }
4613
- return {
4614
- success: true,
4615
- tx: gasResult.digest,
4616
- asset: params.asset,
4617
- amount: effectiveAmount,
4618
- protocol: protocolName,
4619
- apy: 0,
4620
- gasCost: gasResult.gasCostSui,
4621
- gasMethod: gasResult.gasMethod
4622
- };
4623
- }
4624
- // -- Invest Rebalance --
4625
- async investRebalance(opts = {}) {
4626
- this.enforcer.assertNotLocked();
4627
- const minDiff = opts.minYieldDiff ?? 0.1;
4628
- const positions = this.portfolio.getPositions().filter((p) => p.earning && p.earningProtocol);
4629
- if (positions.length === 0) {
4630
- return { executed: false, moves: [], totalGasCost: 0, skipped: [] };
4631
- }
4632
- const moves = [];
4633
- const skipped = [];
4634
- let totalGasCost = 0;
4635
- for (const pos of positions) {
4636
- const currentProtocol = pos.earningProtocol;
4637
- let best;
4638
- try {
4639
- best = await this.registry.bestSaveRate(pos.asset);
4640
- } catch {
4641
- const currentApy2 = pos.earningApy ?? 0;
4642
- skipped.push({ asset: pos.asset, protocol: currentProtocol, apy: currentApy2, bestApy: currentApy2, reason: "no_rates" });
4643
- continue;
4644
- }
4645
- let currentApy = pos.earningApy ?? 0;
4646
- try {
4647
- const currentAdapter = this.registry.getLending(currentProtocol);
4648
- if (currentAdapter) {
4649
- const liveRate = await currentAdapter.getRates(pos.asset);
4650
- currentApy = liveRate.saveApy;
4651
- }
4652
- } catch {
4653
- }
4654
- const apyGain = best.rate.saveApy - currentApy;
4655
- if (best.adapter.id === currentProtocol || apyGain <= 0) {
4656
- skipped.push({ asset: pos.asset, protocol: currentProtocol, apy: currentApy, bestApy: best.rate.saveApy, reason: "already_best" });
4657
- continue;
4658
- }
4659
- if (apyGain < minDiff) {
4660
- skipped.push({ asset: pos.asset, protocol: currentProtocol, apy: currentApy, bestApy: best.rate.saveApy, reason: "below_threshold" });
4661
- continue;
4662
- }
4663
- if (opts.dryRun) {
4664
- moves.push({
4665
- asset: pos.asset,
4666
- fromProtocol: this.registry.getLending(currentProtocol)?.name ?? currentProtocol,
4667
- toProtocol: best.adapter.name,
4668
- amount: pos.totalAmount,
4669
- oldApy: currentApy,
4670
- newApy: best.rate.saveApy,
4671
- txDigests: [],
4672
- gasCost: 0
4673
- });
4674
- continue;
4675
- }
4676
- const txDigests = [];
4677
- let moveGasCost = 0;
4678
- const fromAdapter = this.registry.getLending(currentProtocol);
4679
- if (!fromAdapter) {
4680
- skipped.push({ asset: pos.asset, protocol: currentProtocol, apy: currentApy, bestApy: best.rate.saveApy, reason: "protocol_unavailable" });
4681
- continue;
4682
- }
4683
- const withdrawResult = await executeWithGas(this.client, this._signer, async () => {
4684
- const result = await fromAdapter.buildWithdrawTx(this._address, pos.totalAmount, pos.asset);
4685
- return result.tx;
4686
- });
4687
- txDigests.push(withdrawResult.digest);
4688
- moveGasCost += withdrawResult.gasCostSui;
4689
- const depositResult = await executeWithGas(this.client, this._signer, async () => {
4690
- const assetInfo = SUPPORTED_ASSETS[pos.asset];
4691
- const balance = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
4692
- const available = Number(balance.totalBalance) / 10 ** assetInfo.decimals;
4693
- const gasReserve = pos.asset === "SUI" ? GAS_RESERVE_MIN : 0;
4694
- const depositAmount = Math.max(0, available - gasReserve);
4695
- const { tx } = await best.adapter.buildSaveTx(this._address, depositAmount, pos.asset);
4696
- return tx;
4697
- });
4698
- txDigests.push(depositResult.digest);
4699
- moveGasCost += depositResult.gasCostSui;
4700
- this.portfolio.recordUnearn(pos.asset);
4701
- this.portfolio.recordEarn(pos.asset, best.adapter.id, best.rate.saveApy);
4702
- moves.push({
4703
- asset: pos.asset,
4704
- fromProtocol: fromAdapter.name,
4705
- toProtocol: best.adapter.name,
4706
- amount: pos.totalAmount,
4707
- oldApy: currentApy,
4708
- newApy: best.rate.saveApy,
4709
- txDigests,
4710
- gasCost: moveGasCost
4711
- });
4712
- totalGasCost += moveGasCost;
2878
+ success: true,
2879
+ tx: gasResult.digest,
2880
+ amount: totalRepaid,
2881
+ remainingDebt: hf.borrowed,
2882
+ gasCost: gasResult.gasCostSui,
2883
+ gasMethod: gasResult.gasMethod
2884
+ };
2885
+ }
2886
+ async maxBorrow() {
2887
+ const adapter = await this.resolveLending(void 0, "USDC", "borrow");
2888
+ return adapter.maxBorrow(this._address, "USDC");
2889
+ }
2890
+ async healthFactor() {
2891
+ const adapter = await this.resolveLending(void 0, "USDC", "save");
2892
+ const hf = await adapter.getHealth(this._address);
2893
+ if (hf.healthFactor < 1.2) {
2894
+ this.emit("healthCritical", { healthFactor: hf.healthFactor, threshold: 1.5, severity: "critical" });
2895
+ } else if (hf.healthFactor < 2) {
2896
+ this.emit("healthWarning", { healthFactor: hf.healthFactor, threshold: 2, severity: "warning" });
4713
2897
  }
4714
- return { executed: !opts.dryRun && moves.length > 0, moves, totalGasCost, skipped };
2898
+ return hf;
4715
2899
  }
4716
2900
  // -- Claim Rewards --
4717
2901
  async getPendingRewards() {
@@ -4729,7 +2913,7 @@ var T2000 = class _T2000 extends EventEmitter {
4729
2913
  this.enforcer.assertNotLocked();
4730
2914
  const adapters = this.registry.listLending().filter((a) => a.addClaimRewardsToTx);
4731
2915
  if (adapters.length === 0) {
4732
- return { success: true, tx: "", rewards: [], totalValueUsd: 0, usdcReceived: 0, gasCost: 0, gasMethod: "none" };
2916
+ return { success: true, tx: "", rewards: [], totalValueUsd: 0, gasCost: 0, gasMethod: "none" };
4733
2917
  }
4734
2918
  const tx = new Transaction();
4735
2919
  tx.setSender(this._address);
@@ -4742,698 +2926,20 @@ var T2000 = class _T2000 extends EventEmitter {
4742
2926
  }
4743
2927
  }
4744
2928
  if (allRewards.length === 0) {
4745
- return { success: true, tx: "", rewards: [], totalValueUsd: 0, usdcReceived: 0, gasCost: 0, gasMethod: "none" };
2929
+ return { success: true, tx: "", rewards: [], totalValueUsd: 0, gasCost: 0, gasMethod: "none" };
4746
2930
  }
4747
2931
  const claimResult = await executeWithGas(this.client, this._signer, async () => tx);
4748
2932
  await this.client.waitForTransaction({ digest: claimResult.digest });
4749
- const usdcReceived = await this.swapRewardTokensToUsdc(allRewards);
2933
+ const totalValueUsd = allRewards.reduce((s, r) => s + r.estimatedValueUsd, 0);
4750
2934
  return {
4751
2935
  success: true,
4752
2936
  tx: claimResult.digest,
4753
2937
  rewards: allRewards,
4754
- totalValueUsd: usdcReceived,
4755
- usdcReceived,
2938
+ totalValueUsd,
4756
2939
  gasCost: claimResult.gasCostSui,
4757
2940
  gasMethod: claimResult.gasMethod
4758
2941
  };
4759
2942
  }
4760
- async swapRewardTokensToUsdc(rewards) {
4761
- const uniqueTokens = [...new Set(rewards.map((r) => r.coinType))];
4762
- const usdcType = SUPPORTED_ASSETS.USDC.type;
4763
- const usdcDecimals = SUPPORTED_ASSETS.USDC.decimals;
4764
- let totalUsdc = 0;
4765
- for (const coinType of uniqueTokens) {
4766
- try {
4767
- const balResult = await this.client.getBalance({
4768
- owner: this._address,
4769
- coinType
4770
- });
4771
- const rawBalance = BigInt(balResult.totalBalance);
4772
- if (rawBalance <= 0n) continue;
4773
- const decimals = REWARD_TOKEN_DECIMALS[coinType] ?? 9;
4774
- const swapResult = await buildRawSwapTx({
4775
- client: this.client,
4776
- address: this._address,
4777
- fromCoinType: coinType,
4778
- fromDecimals: decimals,
4779
- toCoinType: usdcType,
4780
- toDecimals: usdcDecimals,
4781
- amount: rawBalance
4782
- });
4783
- const gasResult = await executeWithGas(this.client, this._signer, async () => swapResult.tx);
4784
- await this.client.waitForTransaction({ digest: gasResult.digest });
4785
- totalUsdc += swapResult.estimatedOut / 10 ** usdcDecimals;
4786
- } catch {
4787
- }
4788
- }
4789
- return totalUsdc;
4790
- }
4791
- // -- Strategies --
4792
- async investStrategy(params) {
4793
- this.enforcer.assertNotLocked();
4794
- const definition = this.strategies.get(params.strategy);
4795
- this.strategies.validateMinAmount(definition.allocations, params.usdAmount);
4796
- if (!params.usdAmount || params.usdAmount <= 0) {
4797
- throw new T2000Error("INVALID_AMOUNT", "Strategy investment must be > $0");
4798
- }
4799
- this.enforcer.check({ operation: "invest", amount: params.usdAmount });
4800
- const bal = await queryBalance(this.client, this._address);
4801
- const totalFunds = bal.available + bal.savings;
4802
- if (params.usdAmount > totalFunds * 1.05) {
4803
- throw new T2000Error(
4804
- "INSUFFICIENT_BALANCE",
4805
- `Insufficient funds. You have $${totalFunds.toFixed(2)} total (checking: $${bal.available.toFixed(2)}, savings: $${bal.savings.toFixed(2)}) but need $${params.usdAmount.toFixed(2)}.`
4806
- );
4807
- }
4808
- if (bal.available < params.usdAmount && !params.dryRun) {
4809
- await this._autoFundFromSavings(params.usdAmount - bal.available);
4810
- }
4811
- const buys = [];
4812
- const allocEntries = Object.entries(definition.allocations);
4813
- if (params.dryRun) {
4814
- const swapAdapter2 = this.registry.listSwap()[0];
4815
- for (const [asset, pct] of allocEntries) {
4816
- const assetUsd = params.usdAmount * (pct / 100);
4817
- let estAmount = 0;
4818
- let estPrice = 0;
4819
- try {
4820
- if (swapAdapter2) {
4821
- const quote = await swapAdapter2.getQuote("USDC", asset, assetUsd);
4822
- estAmount = quote.expectedOutput;
4823
- estPrice = assetUsd / estAmount;
4824
- }
4825
- } catch {
4826
- }
4827
- buys.push({ asset, usdAmount: assetUsd, amount: estAmount, price: estPrice, tx: "" });
4828
- }
4829
- return { success: true, strategy: params.strategy, totalInvested: params.usdAmount, buys, gasCost: 0, gasMethod: "self-funded" };
4830
- }
4831
- const swapAdapter = this.registry.listSwap()[0];
4832
- if (!swapAdapter?.addSwapToTx) {
4833
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "Swap adapter does not support composable PTB");
4834
- }
4835
- let swapMetas = [];
4836
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
4837
- swapMetas = [];
4838
- const tx = new Transaction();
4839
- tx.setSender(this._address);
4840
- const usdcCoins = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
4841
- if (usdcCoins.length === 0) throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC coins found");
4842
- const mergedUsdc = this._mergeCoinsInTx(tx, usdcCoins);
4843
- const splitAmounts = allocEntries.map(
4844
- ([, pct]) => BigInt(Math.floor(params.usdAmount * (pct / 100) * 10 ** SUPPORTED_ASSETS.USDC.decimals))
4845
- );
4846
- const splitCoins = tx.splitCoins(mergedUsdc, splitAmounts);
4847
- const outputCoins = [];
4848
- for (let i = 0; i < allocEntries.length; i++) {
4849
- const [asset] = allocEntries[i];
4850
- const assetUsd = params.usdAmount * (allocEntries[i][1] / 100);
4851
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
4852
- tx,
4853
- this._address,
4854
- splitCoins[i],
4855
- "USDC",
4856
- asset,
4857
- assetUsd
4858
- );
4859
- outputCoins.push(outputCoin);
4860
- swapMetas.push({ asset, usdAmount: assetUsd, estimatedOut, toDecimals });
4861
- }
4862
- tx.transferObjects(outputCoins, this._address);
4863
- return tx;
4864
- });
4865
- const digest = gasResult.digest;
4866
- const now = (/* @__PURE__ */ new Date()).toISOString();
4867
- for (const meta of swapMetas) {
4868
- const amount = meta.estimatedOut / 10 ** meta.toDecimals;
4869
- const price = meta.usdAmount / amount;
4870
- this.portfolio.recordBuy({
4871
- id: `inv_${Date.now()}_${meta.asset}`,
4872
- type: "buy",
4873
- asset: meta.asset,
4874
- amount,
4875
- price,
4876
- usdValue: meta.usdAmount,
4877
- fee: 0,
4878
- tx: digest,
4879
- timestamp: now
4880
- });
4881
- this.portfolio.recordStrategyBuy(params.strategy, {
4882
- id: `strat_${Date.now()}_${meta.asset}`,
4883
- type: "buy",
4884
- asset: meta.asset,
4885
- amount,
4886
- price,
4887
- usdValue: meta.usdAmount,
4888
- fee: 0,
4889
- tx: digest,
4890
- timestamp: now
4891
- });
4892
- buys.push({ asset: meta.asset, usdAmount: meta.usdAmount, amount, price, tx: digest });
4893
- }
4894
- return {
4895
- success: true,
4896
- strategy: params.strategy,
4897
- totalInvested: params.usdAmount,
4898
- buys,
4899
- gasCost: gasResult.gasCostSui,
4900
- gasMethod: gasResult.gasMethod
4901
- };
4902
- }
4903
- async sellStrategy(params) {
4904
- this.enforcer.assertNotLocked();
4905
- this.strategies.get(params.strategy);
4906
- const stratPositions = this.portfolio.getStrategyPositions(params.strategy);
4907
- if (stratPositions.length === 0) {
4908
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No positions in strategy '${params.strategy}'`);
4909
- }
4910
- const swapAdapter = this.registry.listSwap()[0];
4911
- if (!swapAdapter?.addSwapToTx) {
4912
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "Swap adapter does not support composable PTB");
4913
- }
4914
- const unearnFailures = [];
4915
- for (const pos of stratPositions) {
4916
- try {
4917
- await this.investUnearn({ asset: pos.asset });
4918
- await new Promise((r) => setTimeout(r, 1500));
4919
- } catch (err) {
4920
- const msg = err instanceof Error ? err.message : String(err);
4921
- if (!msg.includes("not currently earning")) {
4922
- unearnFailures.push({ asset: pos.asset, error: msg });
4923
- }
4924
- }
4925
- }
4926
- let swapMetas = [];
4927
- const buildSellPtb = async () => {
4928
- swapMetas = [];
4929
- const tx = new Transaction();
4930
- tx.setSender(this._address);
4931
- const usdcOutputs = [];
4932
- for (const pos of stratPositions) {
4933
- const assetInfo = SUPPORTED_ASSETS[pos.asset];
4934
- const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
4935
- const walletAmount = Number(bal.totalBalance) / 10 ** assetInfo.decimals;
4936
- const gasReserve = pos.asset === "SUI" ? GAS_RESERVE_MIN : 0;
4937
- const sellAmount = Math.max(0, Math.min(pos.totalAmount, walletAmount) - gasReserve);
4938
- if (sellAmount <= 0) continue;
4939
- const rawAmount = BigInt(Math.floor(sellAmount * 10 ** assetInfo.decimals));
4940
- let splitCoin;
4941
- if (pos.asset === "SUI") {
4942
- [splitCoin] = tx.splitCoins(tx.gas, [rawAmount]);
4943
- } else {
4944
- const coins = await this._fetchCoins(assetInfo.type);
4945
- if (coins.length === 0) continue;
4946
- const merged = this._mergeCoinsInTx(tx, coins);
4947
- [splitCoin] = tx.splitCoins(merged, [rawAmount]);
4948
- }
4949
- const slippageBps = LOW_LIQUIDITY_ASSETS.has(pos.asset) ? 500 : 300;
4950
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
4951
- tx,
4952
- this._address,
4953
- splitCoin,
4954
- pos.asset,
4955
- "USDC",
4956
- sellAmount,
4957
- slippageBps
4958
- );
4959
- usdcOutputs.push(outputCoin);
4960
- swapMetas.push({ asset: pos.asset, amount: sellAmount, estimatedOut, toDecimals });
4961
- }
4962
- if (usdcOutputs.length === 0) {
4963
- throw new T2000Error("INSUFFICIENT_BALANCE", "No assets available to sell");
4964
- }
4965
- if (usdcOutputs.length > 1) {
4966
- tx.mergeCoins(usdcOutputs[0], usdcOutputs.slice(1));
4967
- }
4968
- tx.transferObjects([usdcOutputs[0]], this._address);
4969
- return tx;
4970
- };
4971
- let gasResult;
4972
- const MAX_RETRIES = 3;
4973
- for (let attempt = 0; ; attempt++) {
4974
- try {
4975
- gasResult = await executeWithGas(this.client, this._signer, buildSellPtb);
4976
- break;
4977
- } catch (err) {
4978
- const msg = err instanceof Error ? err.message : String(err);
4979
- const isSlippage = msg.includes("slippage") || msg.includes("amount_out_slippage");
4980
- if (isSlippage && attempt < MAX_RETRIES) {
4981
- await new Promise((r) => setTimeout(r, 2e3 * (attempt + 1)));
4982
- continue;
4983
- }
4984
- throw err;
4985
- }
4986
- }
4987
- const digest = gasResult.digest;
4988
- const now = (/* @__PURE__ */ new Date()).toISOString();
4989
- const sells = [];
4990
- let totalProceeds = 0;
4991
- let totalPnL = 0;
4992
- for (const meta of swapMetas) {
4993
- const usdValue = meta.estimatedOut / 10 ** meta.toDecimals;
4994
- const price = meta.amount > 0 ? usdValue / meta.amount : 0;
4995
- let pnl = 0;
4996
- try {
4997
- pnl = this.portfolio.recordStrategySell(params.strategy, {
4998
- id: `strat_sell_${Date.now()}_${meta.asset}`,
4999
- type: "sell",
5000
- asset: meta.asset,
5001
- amount: meta.amount,
5002
- price,
5003
- usdValue,
5004
- fee: 0,
5005
- tx: digest,
5006
- timestamp: now
5007
- });
5008
- } catch {
5009
- }
5010
- try {
5011
- this.portfolio.recordSell({
5012
- id: `inv_sell_${Date.now()}_${meta.asset}`,
5013
- type: "sell",
5014
- asset: meta.asset,
5015
- amount: meta.amount,
5016
- price,
5017
- usdValue,
5018
- fee: 0,
5019
- tx: digest,
5020
- timestamp: now
5021
- });
5022
- } catch {
5023
- }
5024
- sells.push({ asset: meta.asset, amount: meta.amount, usdValue, realizedPnL: pnl, tx: digest });
5025
- totalProceeds += usdValue;
5026
- totalPnL += pnl;
5027
- }
5028
- if (this.portfolio.hasStrategyPositions(params.strategy)) {
5029
- if (unearnFailures.length === 0) {
5030
- this.portfolio.clearStrategy(params.strategy);
5031
- } else {
5032
- for (const s of sells) {
5033
- this.portfolio.closeStrategyPosition(params.strategy, s.asset);
5034
- }
5035
- }
5036
- }
5037
- const failed = unearnFailures.map((f) => ({ asset: f.asset, reason: f.error }));
5038
- return {
5039
- success: true,
5040
- strategy: params.strategy,
5041
- totalProceeds,
5042
- realizedPnL: totalPnL,
5043
- sells,
5044
- failed: failed.length > 0 ? failed : void 0,
5045
- gasCost: gasResult.gasCostSui,
5046
- gasMethod: gasResult.gasMethod
5047
- };
5048
- }
5049
- async rebalanceStrategy(params) {
5050
- this.enforcer.assertNotLocked();
5051
- const definition = this.strategies.get(params.strategy);
5052
- const stratPositions = this.portfolio.getStrategyPositions(params.strategy);
5053
- if (stratPositions.length === 0) {
5054
- throw new T2000Error("INSUFFICIENT_INVESTMENT", `No positions in strategy '${params.strategy}'`);
5055
- }
5056
- const swapAdapter = this.registry.listSwap()[0];
5057
- const prices = {};
5058
- for (const pos of stratPositions) {
5059
- try {
5060
- if (pos.asset === "SUI" && swapAdapter) {
5061
- prices[pos.asset] = await swapAdapter.getPoolPrice();
5062
- } else if (swapAdapter) {
5063
- const q = await swapAdapter.getQuote("USDC", pos.asset, 1);
5064
- prices[pos.asset] = q.expectedOutput > 0 ? 1 / q.expectedOutput : 0;
5065
- }
5066
- } catch {
5067
- prices[pos.asset] = 0;
5068
- }
5069
- }
5070
- const totalValue = stratPositions.reduce((s, p) => s + p.totalAmount * (prices[p.asset] ?? 0), 0);
5071
- if (totalValue <= 0) {
5072
- throw new T2000Error("INSUFFICIENT_INVESTMENT", "Strategy has no value to rebalance");
5073
- }
5074
- const currentWeights = {};
5075
- const beforeWeights = {};
5076
- for (const pos of stratPositions) {
5077
- const w = pos.totalAmount * (prices[pos.asset] ?? 0) / totalValue * 100;
5078
- currentWeights[pos.asset] = w;
5079
- beforeWeights[pos.asset] = w;
5080
- }
5081
- const threshold = 3;
5082
- const sellOps = [];
5083
- const buyOps = [];
5084
- for (const [asset, targetPct] of Object.entries(definition.allocations)) {
5085
- const currentPct = currentWeights[asset] ?? 0;
5086
- const diff = targetPct - currentPct;
5087
- if (Math.abs(diff) < threshold) continue;
5088
- const usdDiff = totalValue * (Math.abs(diff) / 100);
5089
- if (usdDiff < 1) continue;
5090
- if (diff > 0) {
5091
- buyOps.push({ asset, usdAmount: usdDiff });
5092
- } else {
5093
- const price = prices[asset] ?? 1;
5094
- const assetAmount = price > 0 ? usdDiff / price : 0;
5095
- sellOps.push({ asset, usdAmount: usdDiff, assetAmount });
5096
- }
5097
- }
5098
- if (sellOps.length === 0 && buyOps.length === 0) {
5099
- return { success: true, strategy: params.strategy, trades: [], beforeWeights, afterWeights: { ...beforeWeights }, targetWeights: { ...definition.allocations } };
5100
- }
5101
- if (!swapAdapter?.addSwapToTx) {
5102
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "Swap adapter does not support composable PTB");
5103
- }
5104
- const tradeMetas = [];
5105
- const gasResult = await executeWithGas(this.client, this._signer, async () => {
5106
- tradeMetas.length = 0;
5107
- const tx = new Transaction();
5108
- tx.setSender(this._address);
5109
- const usdcCoins = [];
5110
- for (const sell of sellOps) {
5111
- const assetInfo = SUPPORTED_ASSETS[sell.asset];
5112
- const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
5113
- const walletAmount = Number(bal.totalBalance) / 10 ** assetInfo.decimals;
5114
- const gasReserve = sell.asset === "SUI" ? GAS_RESERVE_MIN : 0;
5115
- const sellAmount = Math.max(0, Math.min(sell.assetAmount, walletAmount) - gasReserve);
5116
- if (sellAmount <= 0) continue;
5117
- const rawAmount = BigInt(Math.floor(sellAmount * 10 ** assetInfo.decimals));
5118
- let splitCoin;
5119
- if (sell.asset === "SUI") {
5120
- [splitCoin] = tx.splitCoins(tx.gas, [rawAmount]);
5121
- } else {
5122
- const coins = await this._fetchCoins(assetInfo.type);
5123
- if (coins.length === 0) continue;
5124
- const merged = this._mergeCoinsInTx(tx, coins);
5125
- [splitCoin] = tx.splitCoins(merged, [rawAmount]);
5126
- }
5127
- const slippageBps = LOW_LIQUIDITY_ASSETS.has(sell.asset) ? 500 : 300;
5128
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
5129
- tx,
5130
- this._address,
5131
- splitCoin,
5132
- sell.asset,
5133
- "USDC",
5134
- sellAmount,
5135
- slippageBps
5136
- );
5137
- usdcCoins.push(outputCoin);
5138
- tradeMetas.push({ action: "sell", asset: sell.asset, usdAmount: sell.usdAmount, estimatedOut, toDecimals });
5139
- }
5140
- if (buyOps.length > 0) {
5141
- const walletUsdc = await this._fetchCoins(SUPPORTED_ASSETS.USDC.type);
5142
- if (walletUsdc.length > 0) {
5143
- usdcCoins.push(this._mergeCoinsInTx(tx, walletUsdc));
5144
- }
5145
- if (usdcCoins.length === 0) {
5146
- throw new T2000Error("INSUFFICIENT_BALANCE", "No USDC available for rebalance buys");
5147
- }
5148
- if (usdcCoins.length > 1) {
5149
- tx.mergeCoins(usdcCoins[0], usdcCoins.slice(1));
5150
- }
5151
- const mergedUsdc = usdcCoins[0];
5152
- const splitAmounts = buyOps.map(
5153
- (b) => BigInt(Math.floor(b.usdAmount * 10 ** SUPPORTED_ASSETS.USDC.decimals))
5154
- );
5155
- const splitCoins = tx.splitCoins(mergedUsdc, splitAmounts);
5156
- const outputCoins = [];
5157
- for (let i = 0; i < buyOps.length; i++) {
5158
- const buy = buyOps[i];
5159
- const slippageBps = LOW_LIQUIDITY_ASSETS.has(buy.asset) ? 500 : 300;
5160
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
5161
- tx,
5162
- this._address,
5163
- splitCoins[i],
5164
- "USDC",
5165
- buy.asset,
5166
- buy.usdAmount,
5167
- slippageBps
5168
- );
5169
- outputCoins.push(outputCoin);
5170
- tradeMetas.push({ action: "buy", asset: buy.asset, usdAmount: buy.usdAmount, estimatedOut, toDecimals });
5171
- }
5172
- tx.transferObjects(outputCoins, this._address);
5173
- }
5174
- return tx;
5175
- });
5176
- const digest = gasResult.digest;
5177
- const now = (/* @__PURE__ */ new Date()).toISOString();
5178
- const trades = [];
5179
- for (const meta of tradeMetas) {
5180
- const rawAmount = meta.estimatedOut / 10 ** meta.toDecimals;
5181
- if (meta.action === "sell") {
5182
- const price = meta.usdAmount > 0 && rawAmount > 0 ? meta.usdAmount / rawAmount : prices[meta.asset] ?? 0;
5183
- const assetAmount = prices[meta.asset] > 0 ? meta.usdAmount / prices[meta.asset] : 0;
5184
- this.portfolio.recordStrategySell(params.strategy, {
5185
- id: `strat_rebal_${Date.now()}_${meta.asset}`,
5186
- type: "sell",
5187
- asset: meta.asset,
5188
- amount: assetAmount,
5189
- price,
5190
- usdValue: meta.usdAmount,
5191
- fee: 0,
5192
- tx: digest,
5193
- timestamp: now
5194
- });
5195
- this.portfolio.recordSell({
5196
- id: `inv_rebal_${Date.now()}_${meta.asset}`,
5197
- type: "sell",
5198
- asset: meta.asset,
5199
- amount: assetAmount,
5200
- price,
5201
- usdValue: meta.usdAmount,
5202
- fee: 0,
5203
- tx: digest,
5204
- timestamp: now
5205
- });
5206
- trades.push({ action: "sell", asset: meta.asset, usdAmount: meta.usdAmount, amount: assetAmount, tx: digest });
5207
- } else {
5208
- const amount = rawAmount;
5209
- const price = meta.usdAmount / amount;
5210
- this.portfolio.recordBuy({
5211
- id: `inv_rebal_${Date.now()}_${meta.asset}`,
5212
- type: "buy",
5213
- asset: meta.asset,
5214
- amount,
5215
- price,
5216
- usdValue: meta.usdAmount,
5217
- fee: 0,
5218
- tx: digest,
5219
- timestamp: now
5220
- });
5221
- this.portfolio.recordStrategyBuy(params.strategy, {
5222
- id: `strat_rebal_${Date.now()}_${meta.asset}`,
5223
- type: "buy",
5224
- asset: meta.asset,
5225
- amount,
5226
- price,
5227
- usdValue: meta.usdAmount,
5228
- fee: 0,
5229
- tx: digest,
5230
- timestamp: now
5231
- });
5232
- trades.push({ action: "buy", asset: meta.asset, usdAmount: meta.usdAmount, amount, tx: digest });
5233
- }
5234
- }
5235
- const afterWeights = {};
5236
- const updatedPositions = this.portfolio.getStrategyPositions(params.strategy);
5237
- const newTotal = updatedPositions.reduce((s, p) => s + p.totalAmount * (prices[p.asset] ?? 0), 0);
5238
- for (const p of updatedPositions) {
5239
- afterWeights[p.asset] = newTotal > 0 ? p.totalAmount * (prices[p.asset] ?? 0) / newTotal * 100 : 0;
5240
- }
5241
- return { success: true, strategy: params.strategy, trades, beforeWeights, afterWeights, targetWeights: { ...definition.allocations } };
5242
- }
5243
- async getStrategyStatus(name) {
5244
- const definition = this.strategies.get(name);
5245
- const stratPositions = this.portfolio.getStrategyPositions(name);
5246
- const swapAdapter = this.registry.listSwap()[0];
5247
- const prices = {};
5248
- for (const asset of Object.keys(definition.allocations)) {
5249
- try {
5250
- if (asset === "SUI" && swapAdapter) {
5251
- prices[asset] = await swapAdapter.getPoolPrice();
5252
- } else if (swapAdapter) {
5253
- const q = await swapAdapter.getQuote("USDC", asset, 1);
5254
- prices[asset] = q.expectedOutput > 0 ? 1 / q.expectedOutput : 0;
5255
- }
5256
- } catch {
5257
- prices[asset] = 0;
5258
- }
5259
- }
5260
- const positions = stratPositions.map((sp) => {
5261
- const price = prices[sp.asset] ?? 0;
5262
- const currentValue = sp.totalAmount * price;
5263
- const pnl = currentValue - sp.costBasis;
5264
- return {
5265
- asset: sp.asset,
5266
- totalAmount: sp.totalAmount,
5267
- costBasis: sp.costBasis,
5268
- avgPrice: sp.avgPrice,
5269
- currentPrice: price,
5270
- currentValue,
5271
- unrealizedPnL: pnl,
5272
- unrealizedPnLPct: sp.costBasis > 0 ? pnl / sp.costBasis * 100 : 0,
5273
- trades: sp.trades
5274
- };
5275
- });
5276
- const totalValue = positions.reduce((s, p) => s + p.currentValue, 0);
5277
- const currentWeights = {};
5278
- for (const p of positions) {
5279
- currentWeights[p.asset] = totalValue > 0 ? p.currentValue / totalValue * 100 : 0;
5280
- }
5281
- return { definition, positions, currentWeights, totalValue };
5282
- }
5283
- // -- Auto-Invest --
5284
- setupAutoInvest(params) {
5285
- if (params.strategy) this.strategies.get(params.strategy);
5286
- if (params.asset && !(params.asset in INVESTMENT_ASSETS)) {
5287
- throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not an investment asset`);
5288
- }
5289
- return this.autoInvest.setup(params);
5290
- }
5291
- getAutoInvestStatus() {
5292
- return this.autoInvest.getStatus();
5293
- }
5294
- async runAutoInvest() {
5295
- this.enforcer.assertNotLocked();
5296
- const status = this.autoInvest.getStatus();
5297
- const executed = [];
5298
- const skipped = [];
5299
- for (const schedule of status.pendingRuns) {
5300
- try {
5301
- const bal = await queryBalance(this.client, this._address);
5302
- if (bal.available < schedule.amount) {
5303
- skipped.push({ scheduleId: schedule.id, reason: `Insufficient balance ($${bal.available.toFixed(2)} < $${schedule.amount})` });
5304
- continue;
5305
- }
5306
- if (schedule.strategy) {
5307
- const result = await this.investStrategy({ strategy: schedule.strategy, usdAmount: schedule.amount });
5308
- this.autoInvest.recordRun(schedule.id, schedule.amount);
5309
- executed.push({ scheduleId: schedule.id, strategy: schedule.strategy, amount: schedule.amount, result });
5310
- } else if (schedule.asset) {
5311
- const result = await this.investBuy({ asset: schedule.asset, usdAmount: schedule.amount });
5312
- this.autoInvest.recordRun(schedule.id, schedule.amount);
5313
- executed.push({ scheduleId: schedule.id, asset: schedule.asset, amount: schedule.amount, result });
5314
- }
5315
- } catch (err) {
5316
- const msg = err instanceof Error ? err.message : String(err);
5317
- skipped.push({ scheduleId: schedule.id, reason: msg });
5318
- }
5319
- }
5320
- return { executed, skipped };
5321
- }
5322
- stopAutoInvest(id) {
5323
- this.autoInvest.stop(id);
5324
- }
5325
- async getPortfolio() {
5326
- const positions = this.portfolio.getPositions();
5327
- const realizedPnL = this.portfolio.getRealizedPnL();
5328
- const prices = {};
5329
- const swapAdapter = this.registry.listSwap()[0];
5330
- for (const asset of Object.keys(INVESTMENT_ASSETS)) {
5331
- try {
5332
- if (asset === "SUI" && swapAdapter) {
5333
- prices[asset] = await swapAdapter.getPoolPrice();
5334
- } else if (swapAdapter) {
5335
- const quote = await swapAdapter.getQuote("USDC", asset, 1);
5336
- prices[asset] = quote.expectedOutput > 0 ? 1 / quote.expectedOutput : 0;
5337
- }
5338
- } catch {
5339
- prices[asset] = 0;
5340
- }
5341
- }
5342
- const enrichPosition = async (pos, adjustWallet) => {
5343
- const currentPrice = prices[pos.asset] ?? 0;
5344
- let totalAmount = pos.totalAmount;
5345
- let costBasis = pos.costBasis;
5346
- if (adjustWallet && pos.asset in INVESTMENT_ASSETS && !pos.earning) {
5347
- try {
5348
- const assetInfo = SUPPORTED_ASSETS[pos.asset];
5349
- const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
5350
- const walletAmount = Number(bal.totalBalance) / 10 ** assetInfo.decimals;
5351
- const gasReserve = pos.asset === "SUI" ? GAS_RESERVE_MIN : 0;
5352
- const actualHeld = Math.max(0, walletAmount - gasReserve);
5353
- if (actualHeld < totalAmount) {
5354
- const ratio = totalAmount > 0 ? actualHeld / totalAmount : 0;
5355
- costBasis *= ratio;
5356
- totalAmount = actualHeld;
5357
- }
5358
- } catch {
5359
- }
5360
- }
5361
- const currentValue = totalAmount * currentPrice;
5362
- const unrealizedPnL = currentPrice > 0 ? currentValue - costBasis : 0;
5363
- const unrealizedPnLPct = currentPrice > 0 && costBasis > 0 ? unrealizedPnL / costBasis * 100 : 0;
5364
- return {
5365
- asset: pos.asset,
5366
- totalAmount,
5367
- costBasis,
5368
- avgPrice: pos.avgPrice,
5369
- currentPrice,
5370
- currentValue,
5371
- unrealizedPnL,
5372
- unrealizedPnLPct,
5373
- trades: pos.trades,
5374
- earning: pos.earning,
5375
- earningProtocol: pos.earningProtocol,
5376
- earningApy: pos.earningApy
5377
- };
5378
- };
5379
- const enriched = [];
5380
- for (const pos of positions) {
5381
- enriched.push(await enrichPosition(pos, true));
5382
- }
5383
- const strategyPositions = {};
5384
- for (const key of this.portfolio.getAllStrategyKeys()) {
5385
- const sps = this.portfolio.getStrategyPositions(key);
5386
- const enrichedStrat = [];
5387
- for (const sp of sps) {
5388
- enrichedStrat.push(await enrichPosition(sp, false));
5389
- }
5390
- if (enrichedStrat.length > 0) {
5391
- strategyPositions[key] = enrichedStrat;
5392
- }
5393
- }
5394
- const strategyAmountByAsset = {};
5395
- for (const strats of Object.values(strategyPositions)) {
5396
- for (const sp of strats) {
5397
- const prev = strategyAmountByAsset[sp.asset] ?? { amount: 0, costBasis: 0 };
5398
- strategyAmountByAsset[sp.asset] = {
5399
- amount: prev.amount + sp.totalAmount,
5400
- costBasis: prev.costBasis + sp.costBasis
5401
- };
5402
- }
5403
- }
5404
- const directOnly = enriched.map((pos) => {
5405
- const strat = strategyAmountByAsset[pos.asset];
5406
- if (!strat) return pos;
5407
- const directAmt = pos.totalAmount - strat.amount;
5408
- if (directAmt <= 1e-6) return null;
5409
- const directCost = pos.costBasis - strat.costBasis;
5410
- const currentValue = directAmt * (pos.currentPrice ?? 0);
5411
- return {
5412
- ...pos,
5413
- totalAmount: directAmt,
5414
- costBasis: directCost,
5415
- currentValue,
5416
- unrealizedPnL: currentValue - directCost,
5417
- unrealizedPnLPct: directCost > 0 ? (currentValue - directCost) / directCost * 100 : 0
5418
- };
5419
- }).filter((p) => p !== null);
5420
- const totalInvested = enriched.reduce((sum, p) => sum + p.costBasis, 0);
5421
- const totalValue = enriched.reduce((sum, p) => sum + p.currentValue, 0);
5422
- const totalUnrealizedPnL = totalValue - totalInvested;
5423
- const totalUnrealizedPnLPct = totalInvested > 0 ? totalUnrealizedPnL / totalInvested * 100 : 0;
5424
- const result = {
5425
- positions: directOnly,
5426
- totalInvested,
5427
- totalValue,
5428
- unrealizedPnL: totalUnrealizedPnL,
5429
- unrealizedPnLPct: totalUnrealizedPnLPct,
5430
- realizedPnL
5431
- };
5432
- if (Object.keys(strategyPositions).length > 0) {
5433
- result.strategyPositions = strategyPositions;
5434
- }
5435
- return result;
5436
- }
5437
2943
  // -- Info --
5438
2944
  async positions() {
5439
2945
  const allPositions = await this.registry.allPositions(this._address);
@@ -5476,274 +2982,6 @@ var T2000 = class _T2000 extends EventEmitter {
5476
2982
  async allRatesAcrossAssets() {
5477
2983
  return this.registry.allRatesAcrossAssets();
5478
2984
  }
5479
- async rebalance(opts = {}) {
5480
- this.enforcer.assertNotLocked();
5481
- const dryRun = opts.dryRun ?? false;
5482
- const minYieldDiff = opts.minYieldDiff ?? 0.5;
5483
- const maxBreakEven = opts.maxBreakEven ?? 30;
5484
- const [allPositions, allRates] = await Promise.all([
5485
- this.registry.allPositions(this._address),
5486
- this.registry.allRatesAcrossAssets()
5487
- ]);
5488
- const earningAssets = new Set(
5489
- this.portfolio.getPositions().filter((p) => p.earning).map((p) => p.asset)
5490
- );
5491
- const savePositions = allPositions.flatMap(
5492
- (p) => p.positions.supplies.filter((s) => s.amount > 0.01).filter((s) => !earningAssets.has(s.asset)).filter((s) => !(s.asset in INVESTMENT_ASSETS)).map((s) => ({
5493
- protocolId: p.protocolId,
5494
- protocol: p.protocol,
5495
- asset: s.asset,
5496
- amount: s.amount,
5497
- apy: s.apy
5498
- }))
5499
- );
5500
- if (savePositions.length === 0) {
5501
- throw new T2000Error("NO_COLLATERAL", "No savings positions to rebalance. Use `t2000 save <amount>` first.");
5502
- }
5503
- const borrowPositions = allPositions.flatMap(
5504
- (p) => p.positions.borrows.filter((b) => b.amount > 0.01)
5505
- );
5506
- if (borrowPositions.length > 0) {
5507
- const healthResults = await Promise.all(
5508
- allPositions.filter((p) => p.positions.borrows.some((b) => b.amount > 0.01)).map(async (p) => {
5509
- const adapter = this.registry.getLending(p.protocolId);
5510
- if (!adapter) return null;
5511
- return adapter.getHealth(this._address);
5512
- })
5513
- );
5514
- for (const hf of healthResults) {
5515
- if (hf && hf.healthFactor < 1.5) {
5516
- throw new T2000Error(
5517
- "HEALTH_FACTOR_TOO_LOW",
5518
- `Cannot rebalance \u2014 health factor is ${hf.healthFactor.toFixed(2)} (minimum 1.5). Repay some debt first.`,
5519
- { healthFactor: hf.healthFactor }
5520
- );
5521
- }
5522
- }
5523
- }
5524
- const stableSet = new Set(STABLE_ASSETS);
5525
- const stableRates = allRates.filter((r) => stableSet.has(r.asset));
5526
- if (stableRates.length === 0) {
5527
- throw new T2000Error("PROTOCOL_UNAVAILABLE", "No stablecoin lending rates available for rebalance");
5528
- }
5529
- const bestRate = stableRates.reduce(
5530
- (best, r) => r.rates.saveApy > best.rates.saveApy ? r : best
5531
- );
5532
- const current = savePositions.reduce(
5533
- (worst, p) => p.apy < worst.apy ? p : worst
5534
- );
5535
- const withdrawAdapter = this.registry.getLending(current.protocolId);
5536
- if (withdrawAdapter) {
5537
- try {
5538
- const maxResult = await withdrawAdapter.maxWithdraw(this._address, current.asset);
5539
- if (maxResult.maxAmount < current.amount) {
5540
- current.amount = Math.max(0, maxResult.maxAmount - 0.01);
5541
- }
5542
- } catch {
5543
- }
5544
- }
5545
- if (current.amount <= 0.01) {
5546
- throw new T2000Error(
5547
- "HEALTH_FACTOR_TOO_LOW",
5548
- "Cannot rebalance \u2014 active borrows prevent safe withdrawal. Repay some debt first."
5549
- );
5550
- }
5551
- const apyDiff = bestRate.rates.saveApy - current.apy;
5552
- const isSameProtocol = current.protocolId === bestRate.protocolId;
5553
- const isSameAsset = current.asset === bestRate.asset;
5554
- if (apyDiff < minYieldDiff) {
5555
- return {
5556
- executed: false,
5557
- steps: [],
5558
- fromProtocol: current.protocol,
5559
- fromAsset: current.asset,
5560
- toProtocol: bestRate.protocol,
5561
- toAsset: bestRate.asset,
5562
- amount: current.amount,
5563
- currentApy: current.apy,
5564
- newApy: bestRate.rates.saveApy,
5565
- annualGain: current.amount * apyDiff / 100,
5566
- estimatedSwapCost: 0,
5567
- breakEvenDays: Infinity,
5568
- txDigests: [],
5569
- totalGasCost: 0
5570
- };
5571
- }
5572
- if (isSameProtocol && isSameAsset) {
5573
- return {
5574
- executed: false,
5575
- steps: [],
5576
- fromProtocol: current.protocol,
5577
- fromAsset: current.asset,
5578
- toProtocol: bestRate.protocol,
5579
- toAsset: bestRate.asset,
5580
- amount: current.amount,
5581
- currentApy: current.apy,
5582
- newApy: bestRate.rates.saveApy,
5583
- annualGain: 0,
5584
- estimatedSwapCost: 0,
5585
- breakEvenDays: Infinity,
5586
- txDigests: [],
5587
- totalGasCost: 0
5588
- };
5589
- }
5590
- const steps = [];
5591
- let estimatedSwapCost = 0;
5592
- steps.push({
5593
- action: "withdraw",
5594
- protocol: current.protocolId,
5595
- fromAsset: current.asset,
5596
- amount: current.amount
5597
- });
5598
- let amountToDeposit = current.amount;
5599
- if (!isSameAsset) {
5600
- try {
5601
- const quote = await this.registry.bestSwapQuote(current.asset, bestRate.asset, current.amount);
5602
- amountToDeposit = quote.quote.expectedOutput;
5603
- estimatedSwapCost = Math.abs(current.amount - amountToDeposit);
5604
- } catch {
5605
- estimatedSwapCost = current.amount * 3e-3;
5606
- amountToDeposit = current.amount - estimatedSwapCost;
5607
- }
5608
- steps.push({
5609
- action: "swap",
5610
- fromAsset: current.asset,
5611
- toAsset: bestRate.asset,
5612
- amount: current.amount,
5613
- estimatedOutput: amountToDeposit
5614
- });
5615
- }
5616
- steps.push({
5617
- action: "deposit",
5618
- protocol: bestRate.protocolId,
5619
- toAsset: bestRate.asset,
5620
- amount: amountToDeposit
5621
- });
5622
- const annualGain = amountToDeposit * apyDiff / 100;
5623
- const breakEvenDays = estimatedSwapCost > 0 ? Math.ceil(estimatedSwapCost / annualGain * 365) : 0;
5624
- if (breakEvenDays > maxBreakEven && estimatedSwapCost > 0) {
5625
- return {
5626
- executed: false,
5627
- steps,
5628
- fromProtocol: current.protocol,
5629
- fromAsset: current.asset,
5630
- toProtocol: bestRate.protocol,
5631
- toAsset: bestRate.asset,
5632
- amount: current.amount,
5633
- currentApy: current.apy,
5634
- newApy: bestRate.rates.saveApy,
5635
- annualGain,
5636
- estimatedSwapCost,
5637
- breakEvenDays,
5638
- txDigests: [],
5639
- totalGasCost: 0
5640
- };
5641
- }
5642
- if (dryRun) {
5643
- return {
5644
- executed: false,
5645
- steps,
5646
- fromProtocol: current.protocol,
5647
- fromAsset: current.asset,
5648
- toProtocol: bestRate.protocol,
5649
- toAsset: bestRate.asset,
5650
- amount: current.amount,
5651
- currentApy: current.apy,
5652
- newApy: bestRate.rates.saveApy,
5653
- annualGain,
5654
- estimatedSwapCost,
5655
- breakEvenDays,
5656
- txDigests: [],
5657
- totalGasCost: 0
5658
- };
5659
- }
5660
- if (!withdrawAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${current.protocolId} not found`);
5661
- const depositAdapter = this.registry.getLending(bestRate.protocolId);
5662
- if (!depositAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", `Protocol ${bestRate.protocolId} not found`);
5663
- const canComposePTB = withdrawAdapter.addWithdrawToTx && depositAdapter.addSaveToTx && (isSameAsset || this.registry.listSwap()[0]?.addSwapToTx);
5664
- let txDigests;
5665
- let totalGasCost;
5666
- if (canComposePTB) {
5667
- const result = await executeWithGas(this.client, this._signer, async () => {
5668
- const tx = new Transaction();
5669
- tx.setSender(this._address);
5670
- const { coin: withdrawnCoin, effectiveAmount } = await withdrawAdapter.addWithdrawToTx(
5671
- tx,
5672
- this._address,
5673
- current.amount,
5674
- current.asset
5675
- );
5676
- amountToDeposit = effectiveAmount;
5677
- let depositCoin = withdrawnCoin;
5678
- if (!isSameAsset) {
5679
- const swapAdapter = this.registry.listSwap()[0];
5680
- const { outputCoin, estimatedOut, toDecimals } = await swapAdapter.addSwapToTx(
5681
- tx,
5682
- this._address,
5683
- withdrawnCoin,
5684
- current.asset,
5685
- bestRate.asset,
5686
- amountToDeposit
5687
- );
5688
- depositCoin = outputCoin;
5689
- amountToDeposit = estimatedOut / 10 ** toDecimals;
5690
- }
5691
- await depositAdapter.addSaveToTx(
5692
- tx,
5693
- this._address,
5694
- depositCoin,
5695
- bestRate.asset,
5696
- { collectFee: bestRate.asset === "USDC" }
5697
- );
5698
- return tx;
5699
- });
5700
- txDigests = [result.digest];
5701
- totalGasCost = result.gasCostSui;
5702
- } else {
5703
- txDigests = [];
5704
- totalGasCost = 0;
5705
- const withdrawResult = await executeWithGas(this.client, this._signer, async () => {
5706
- const built = await withdrawAdapter.buildWithdrawTx(this._address, current.amount, current.asset);
5707
- amountToDeposit = built.effectiveAmount;
5708
- return built.tx;
5709
- });
5710
- txDigests.push(withdrawResult.digest);
5711
- totalGasCost += withdrawResult.gasCostSui;
5712
- if (!isSameAsset) {
5713
- const swapAdapter = this.registry.listSwap()[0];
5714
- if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
5715
- const swapResult = await executeWithGas(this.client, this._signer, async () => {
5716
- const built = await swapAdapter.buildSwapTx(this._address, current.asset, bestRate.asset, amountToDeposit);
5717
- amountToDeposit = built.estimatedOut / 10 ** built.toDecimals;
5718
- return built.tx;
5719
- });
5720
- txDigests.push(swapResult.digest);
5721
- totalGasCost += swapResult.gasCostSui;
5722
- }
5723
- const depositResult = await executeWithGas(this.client, this._signer, async () => {
5724
- const { tx } = await depositAdapter.buildSaveTx(this._address, amountToDeposit, bestRate.asset, { collectFee: bestRate.asset === "USDC" });
5725
- return tx;
5726
- });
5727
- txDigests.push(depositResult.digest);
5728
- totalGasCost += depositResult.gasCostSui;
5729
- }
5730
- return {
5731
- executed: true,
5732
- steps,
5733
- fromProtocol: current.protocol,
5734
- fromAsset: current.asset,
5735
- toProtocol: bestRate.protocol,
5736
- toAsset: bestRate.asset,
5737
- amount: current.amount,
5738
- currentApy: current.apy,
5739
- newApy: bestRate.rates.saveApy,
5740
- annualGain,
5741
- estimatedSwapCost,
5742
- breakEvenDays,
5743
- txDigests,
5744
- totalGasCost
5745
- };
5746
- }
5747
2985
  async earnings() {
5748
2986
  const result = await getEarnings(this.client, this._address);
5749
2987
  if (result.totalYieldEarned > 0) {
@@ -5760,17 +2998,6 @@ var T2000 = class _T2000 extends EventEmitter {
5760
2998
  return getFundStatus(this.client, this._address);
5761
2999
  }
5762
3000
  // -- Helpers --
5763
- async getFreeBalance(asset) {
5764
- if (!(asset in INVESTMENT_ASSETS)) return Infinity;
5765
- const pos = this.portfolio.getPosition(asset);
5766
- const walletInvested = pos && pos.totalAmount > 0 && !pos.earning ? pos.totalAmount : 0;
5767
- if (walletInvested <= 0) return Infinity;
5768
- const assetInfo = SUPPORTED_ASSETS[asset];
5769
- const balance = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
5770
- const walletAmount = Number(balance.totalBalance) / 10 ** assetInfo.decimals;
5771
- const gasReserve = asset === "SUI" ? GAS_RESERVE_MIN : 0;
5772
- return Math.max(0, walletAmount - walletInvested - gasReserve);
5773
- }
5774
3001
  async resolveLending(protocol, asset, capability) {
5775
3002
  if (protocol) {
5776
3003
  const adapter = this.registry.getLending(protocol);
@@ -5865,7 +3092,12 @@ async function callUsdcSponsorApi(address) {
5865
3092
  }
5866
3093
  }
5867
3094
 
3095
+ // src/index.ts
3096
+ init_errors();
3097
+
5868
3098
  // src/utils/simulate.ts
3099
+ init_errors();
3100
+ init_errors();
5869
3101
  async function simulateTransaction(client, tx, sender) {
5870
3102
  tx.setSender(sender);
5871
3103
  try {
@@ -5935,6 +3167,6 @@ function parseMoveAbort(errorStr) {
5935
3167
  return { reason: errorStr };
5936
3168
  }
5937
3169
 
5938
- export { AutoInvestManager, BPS_DENOMINATOR, CLOCK_ID, CetusAdapter, ContactManager, DEFAULT_MAX_LEVERAGE, DEFAULT_MAX_POSITION_SIZE, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, DEFAULT_STRATEGIES, GAS_RESERVE_MIN, INVESTMENT_ASSETS, KeypairSigner, MIST_PER_SUI, NaviAdapter, OUTBOUND_OPS, PERPS_MARKETS, PortfolioManager, ProtocolRegistry, STABLE_ASSETS, SUI_DECIMALS, SUPPORTED_ASSETS, SafeguardEnforcer, SafeguardError, StrategyManager, SuilendAdapter, T2000, T2000Error, USDC_DECIMALS, ZkLoginSigner, addCollectFeeToTx, allDescriptors, calculateFee, cetusDescriptor, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, naviDescriptor, rawToStable, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, suiToMist, suilendDescriptor, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
3170
+ export { BPS_DENOMINATOR, CETUS_USDC_SUI_POOL, CLOCK_ID, ContactManager, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, GAS_RESERVE_MIN, KeypairSigner, MIST_PER_SUI, NaviAdapter, OUTBOUND_OPS, ProtocolRegistry, STABLE_ASSETS, SUI_DECIMALS, SUPPORTED_ASSETS, SafeguardEnforcer, SafeguardError, T2000, T2000Error, USDC_DECIMALS, ZkLoginSigner, addCollectFeeToTx, allDescriptors, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getRates, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, naviDescriptor, rawToStable, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
5939
3171
  //# sourceMappingURL=index.js.map
5940
3172
  //# sourceMappingURL=index.js.map