@t2000/sdk 0.14.1 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -1,11 +1,11 @@
1
+ import { I as InvestmentTrade, T as T2000Options, S as SendResult, B as BalanceResponse, a as TransactionRecord, D as DepositInfo, L as LendingAdapter, b as SwapAdapter, c as SaveResult, W as WithdrawResult, M as MaxWithdrawResult, d as BorrowResult, R as RepayResult, e as MaxBorrowResult, H as HealthFactorResult, f as SwapResult, g as InvestResult, h as InvestEarnResult, P as PortfolioResult, i as PositionsResult, j as RatesResult, k as LendingRates, l as RebalanceResult, E as EarningsResult, F as FundStatusResult, m as SentinelAgent, n as SentinelAttackResult, G as GasMethod } from './index-BykavuDO.cjs';
2
+ export { A as AdapterCapability, o as AdapterPositions, p as AdapterTxResult, q as AssetRates, C as CetusAdapter, r as GasReserve, s as HealthInfo, t as InvestmentPosition, N as NaviAdapter, u as PerpsAdapter, v as PerpsPosition, w as PositionEntry, x as PositionSide, y as ProtocolDescriptor, z as ProtocolRegistry, J as RebalanceStep, K as SentinelVerdict, O as SuilendAdapter, Q as SwapQuote, U as TradePositionsResult, V as TradeResult, X as allDescriptors, Y as cetusDescriptor, Z as getSentinelInfo, _ as listSentinels, $ as naviDescriptor, a0 as requestAttack, a1 as sentinelAttack, a2 as sentinelDescriptor, a3 as settleAttack, a4 as submitPrompt, a5 as suilendDescriptor } from './index-BykavuDO.cjs';
1
3
  import { EventEmitter } from 'eventemitter3';
2
4
  import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
3
5
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
4
- import { I as InvestmentTrade, T as T2000Options, S as SendResult, B as BalanceResponse, a as TransactionRecord, D as DepositInfo, L as LendingAdapter, b as SwapAdapter, c as SaveResult, W as WithdrawResult, M as MaxWithdrawResult, d as BorrowResult, R as RepayResult, e as MaxBorrowResult, H as HealthFactorResult, f as SwapResult, g as InvestResult, P as PortfolioResult, h as PositionsResult, i as RatesResult, j as LendingRates, k as RebalanceResult, E as EarningsResult, F as FundStatusResult, l as SentinelAgent, m as SentinelAttackResult, G as GasMethod } from './index-B14ZyQZt.cjs';
5
- export { A as AdapterCapability, n as AdapterPositions, o as AdapterTxResult, p as AssetRates, C as CetusAdapter, q as GasReserve, r as HealthInfo, s as InvestmentPosition, N as NaviAdapter, t as PerpsAdapter, u as PerpsPosition, v as PositionEntry, w as PositionSide, x as ProtocolDescriptor, y as ProtocolRegistry, z as RebalanceStep, J as SentinelVerdict, K as SuilendAdapter, O as SwapQuote, Q as TradePositionsResult, U as TradeResult, V as allDescriptors, X as cetusDescriptor, Y as getSentinelInfo, Z as listSentinels, _ as naviDescriptor, $ as requestAttack, a0 as sentinelAttack, a1 as sentinelDescriptor, a2 as settleAttack, a3 as submitPrompt, a4 as suilendDescriptor } from './index-B14ZyQZt.cjs';
6
6
  import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
7
7
 
8
- type T2000ErrorCode = 'INSUFFICIENT_BALANCE' | 'INSUFFICIENT_GAS' | 'INVALID_ADDRESS' | 'INVALID_AMOUNT' | 'WALLET_NOT_FOUND' | 'WALLET_LOCKED' | 'WALLET_EXISTS' | 'SPONSOR_FAILED' | 'SPONSOR_RATE_LIMITED' | 'GAS_STATION_UNAVAILABLE' | 'GAS_FEE_EXCEEDED' | 'SIMULATION_FAILED' | 'TRANSACTION_FAILED' | 'ASSET_NOT_SUPPORTED' | 'SWAP_FAILED' | 'SLIPPAGE_EXCEEDED' | 'HEALTH_FACTOR_TOO_LOW' | 'WITHDRAW_WOULD_LIQUIDATE' | 'NO_COLLATERAL' | 'PROTOCOL_PAUSED' | 'PROTOCOL_UNAVAILABLE' | 'RPC_ERROR' | 'RPC_UNREACHABLE' | 'SPONSOR_UNAVAILABLE' | 'AUTO_TOPUP_FAILED' | 'PRICE_EXCEEDS_LIMIT' | 'UNSUPPORTED_NETWORK' | 'PAYMENT_EXPIRED' | 'DUPLICATE_PAYMENT' | 'FACILITATOR_REJECTION' | 'CONTACT_NOT_FOUND' | 'INVALID_CONTACT_NAME' | 'FACILITATOR_TIMEOUT' | 'SENTINEL_API_ERROR' | 'SENTINEL_NOT_FOUND' | 'SENTINEL_TX_FAILED' | 'SENTINEL_TEE_ERROR' | 'SAFEGUARD_BLOCKED' | 'INSUFFICIENT_INVESTMENT' | 'INVESTMENT_LOCKED' | 'MARKET_NOT_SUPPORTED' | 'LEVERAGE_EXCEEDED' | 'POSITION_SIZE_EXCEEDED' | 'BLUEFIN_AUTH_FAILED' | 'BLUEFIN_API_ERROR' | 'POSITION_NOT_FOUND' | 'UNKNOWN';
8
+ type T2000ErrorCode = 'INSUFFICIENT_BALANCE' | 'INSUFFICIENT_GAS' | 'INVALID_ADDRESS' | 'INVALID_AMOUNT' | 'WALLET_NOT_FOUND' | 'WALLET_LOCKED' | 'WALLET_EXISTS' | 'SPONSOR_FAILED' | 'SPONSOR_RATE_LIMITED' | 'GAS_STATION_UNAVAILABLE' | 'GAS_FEE_EXCEEDED' | 'SIMULATION_FAILED' | 'TRANSACTION_FAILED' | 'ASSET_NOT_SUPPORTED' | 'SWAP_FAILED' | 'SLIPPAGE_EXCEEDED' | 'HEALTH_FACTOR_TOO_LOW' | 'WITHDRAW_WOULD_LIQUIDATE' | 'NO_COLLATERAL' | 'PROTOCOL_PAUSED' | 'PROTOCOL_UNAVAILABLE' | 'RPC_ERROR' | 'RPC_UNREACHABLE' | 'SPONSOR_UNAVAILABLE' | 'AUTO_TOPUP_FAILED' | 'PRICE_EXCEEDS_LIMIT' | 'UNSUPPORTED_NETWORK' | 'PAYMENT_EXPIRED' | 'DUPLICATE_PAYMENT' | 'FACILITATOR_REJECTION' | 'CONTACT_NOT_FOUND' | 'INVALID_CONTACT_NAME' | 'FACILITATOR_TIMEOUT' | 'SENTINEL_API_ERROR' | 'SENTINEL_NOT_FOUND' | 'SENTINEL_TX_FAILED' | 'SENTINEL_TEE_ERROR' | 'SAFEGUARD_BLOCKED' | 'INSUFFICIENT_INVESTMENT' | 'INVESTMENT_LOCKED' | 'INVEST_ALREADY_EARNING' | 'INVEST_NOT_EARNING' | 'BORROW_GUARD_INVESTMENT' | 'MARKET_NOT_SUPPORTED' | 'LEVERAGE_EXCEEDED' | 'POSITION_SIZE_EXCEEDED' | 'BLUEFIN_AUTH_FAILED' | 'BLUEFIN_API_ERROR' | 'POSITION_NOT_FOUND' | 'UNKNOWN';
9
9
  interface T2000ErrorData {
10
10
  reason?: string;
11
11
  [key: string]: unknown;
@@ -62,7 +62,7 @@ declare const SUPPORTED_ASSETS: {
62
62
  readonly displayName: "SUI";
63
63
  };
64
64
  readonly BTC: {
65
- readonly type: "0xaafb102dd0902f5055cadecd687fb5b71ca82ef0e0285d90afde828ec58ca96b::btc::BTC";
65
+ readonly type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC";
66
66
  readonly decimals: 8;
67
67
  readonly symbol: "BTC";
68
68
  readonly displayName: "Bitcoin";
@@ -84,7 +84,7 @@ declare const INVESTMENT_ASSETS: {
84
84
  readonly displayName: "SUI";
85
85
  };
86
86
  readonly BTC: {
87
- readonly type: "0xaafb102dd0902f5055cadecd687fb5b71ca82ef0e0285d90afde828ec58ca96b::btc::BTC";
87
+ readonly type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC";
88
88
  readonly decimals: 8;
89
89
  readonly symbol: "BTC";
90
90
  readonly displayName: "Bitcoin";
@@ -177,6 +177,9 @@ interface StoredPosition {
177
177
  costBasis: number;
178
178
  avgPrice: number;
179
179
  trades: InvestmentTrade[];
180
+ earning?: boolean;
181
+ earningProtocol?: string;
182
+ earningApy?: number;
180
183
  }
181
184
  declare class PortfolioManager {
182
185
  private data;
@@ -191,6 +194,9 @@ declare class PortfolioManager {
191
194
  getPositions(): Array<{
192
195
  asset: string;
193
196
  } & StoredPosition>;
197
+ recordEarn(asset: string, protocol: string, apy: number): void;
198
+ recordUnearn(asset: string): void;
199
+ isEarning(asset: string): boolean;
194
200
  getRealizedPnL(): number;
195
201
  }
196
202
 
@@ -287,6 +293,7 @@ declare class T2000 extends EventEmitter<T2000Events> {
287
293
  private _swapFromUsdc;
288
294
  private _convertWalletStablesToUsdc;
289
295
  maxWithdraw(): Promise<MaxWithdrawResult>;
296
+ private adjustMaxBorrowForInvestments;
290
297
  borrow(params: {
291
298
  amount: number;
292
299
  protocol?: string;
@@ -328,6 +335,12 @@ declare class T2000 extends EventEmitter<T2000Events> {
328
335
  usdAmount: number | 'all';
329
336
  maxSlippage?: number;
330
337
  }): Promise<InvestResult>;
338
+ investEarn(params: {
339
+ asset: InvestmentAsset;
340
+ }): Promise<InvestEarnResult>;
341
+ investUnearn(params: {
342
+ asset: InvestmentAsset;
343
+ }): Promise<InvestEarnResult>;
331
344
  getPortfolio(): Promise<PortfolioResult>;
332
345
  positions(): Promise<PositionsResult>;
333
346
  rates(): Promise<RatesResult>;
@@ -478,4 +491,4 @@ interface GasStatusResponse {
478
491
  }
479
492
  declare function getGasStatus(address?: string): Promise<GasStatusResponse>;
480
493
 
481
- export { type AutoTopUpResult, BPS_DENOMINATOR, BalanceResponse, BorrowResult, CLOCK_ID, type Contact, ContactManager, type ContactMap, DEFAULT_MAX_LEVERAGE, DEFAULT_MAX_POSITION_SIZE, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, DepositInfo, EarningsResult, type FeeOperation, FundStatusResult, GAS_RESERVE_MIN, type GasExecutionResult, GasMethod, type GasRequestType, type GasSponsorResponse, type GasStatusResponse, HealthFactorResult, INVESTMENT_ASSETS, InvestResult, type InvestmentAsset, InvestmentTrade, LendingAdapter, LendingRates, MIST_PER_SUI, MaxBorrowResult, MaxWithdrawResult, OUTBOUND_OPS, PERPS_MARKETS, type PerpsMarket, PortfolioManager, PortfolioResult, PositionsResult, type ProtocolFeeInfo, RatesResult, RebalanceResult, RepayResult, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, type SafeguardConfig, SafeguardEnforcer, SafeguardError, type SafeguardErrorDetails, type SafeguardRule, SaveResult, SendResult, SentinelAgent, SentinelAttackResult, type SimulationResult, type SupportedAsset, SwapAdapter, SwapResult, T2000, T2000Error, type T2000ErrorCode, type T2000ErrorData, T2000Options, TransactionRecord, type TxMetadata, USDC_DECIMALS, WithdrawResult, addCollectFeeToTx, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToStable, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
494
+ export { type AutoTopUpResult, BPS_DENOMINATOR, BalanceResponse, BorrowResult, CLOCK_ID, type Contact, ContactManager, type ContactMap, DEFAULT_MAX_LEVERAGE, DEFAULT_MAX_POSITION_SIZE, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, DepositInfo, EarningsResult, type FeeOperation, FundStatusResult, GAS_RESERVE_MIN, type GasExecutionResult, GasMethod, type GasRequestType, type GasSponsorResponse, type GasStatusResponse, HealthFactorResult, INVESTMENT_ASSETS, InvestEarnResult, InvestResult, type InvestmentAsset, InvestmentTrade, LendingAdapter, LendingRates, MIST_PER_SUI, MaxBorrowResult, MaxWithdrawResult, OUTBOUND_OPS, PERPS_MARKETS, type PerpsMarket, PortfolioManager, PortfolioResult, PositionsResult, type ProtocolFeeInfo, RatesResult, RebalanceResult, RepayResult, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, type SafeguardConfig, SafeguardEnforcer, SafeguardError, type SafeguardErrorDetails, type SafeguardRule, SaveResult, SendResult, SentinelAgent, SentinelAttackResult, type SimulationResult, type SupportedAsset, SwapAdapter, SwapResult, T2000, T2000Error, type T2000ErrorCode, type T2000ErrorData, T2000Options, TransactionRecord, type TxMetadata, USDC_DECIMALS, WithdrawResult, addCollectFeeToTx, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToStable, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
package/dist/index.d.ts CHANGED
@@ -1,11 +1,11 @@
1
+ import { I as InvestmentTrade, T as T2000Options, S as SendResult, B as BalanceResponse, a as TransactionRecord, D as DepositInfo, L as LendingAdapter, b as SwapAdapter, c as SaveResult, W as WithdrawResult, M as MaxWithdrawResult, d as BorrowResult, R as RepayResult, e as MaxBorrowResult, H as HealthFactorResult, f as SwapResult, g as InvestResult, h as InvestEarnResult, P as PortfolioResult, i as PositionsResult, j as RatesResult, k as LendingRates, l as RebalanceResult, E as EarningsResult, F as FundStatusResult, m as SentinelAgent, n as SentinelAttackResult, G as GasMethod } from './index-BykavuDO.js';
2
+ export { A as AdapterCapability, o as AdapterPositions, p as AdapterTxResult, q as AssetRates, C as CetusAdapter, r as GasReserve, s as HealthInfo, t as InvestmentPosition, N as NaviAdapter, u as PerpsAdapter, v as PerpsPosition, w as PositionEntry, x as PositionSide, y as ProtocolDescriptor, z as ProtocolRegistry, J as RebalanceStep, K as SentinelVerdict, O as SuilendAdapter, Q as SwapQuote, U as TradePositionsResult, V as TradeResult, X as allDescriptors, Y as cetusDescriptor, Z as getSentinelInfo, _ as listSentinels, $ as naviDescriptor, a0 as requestAttack, a1 as sentinelAttack, a2 as sentinelDescriptor, a3 as settleAttack, a4 as submitPrompt, a5 as suilendDescriptor } from './index-BykavuDO.js';
1
3
  import { EventEmitter } from 'eventemitter3';
2
4
  import { SuiJsonRpcClient } from '@mysten/sui/jsonRpc';
3
5
  import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
4
- import { I as InvestmentTrade, T as T2000Options, S as SendResult, B as BalanceResponse, a as TransactionRecord, D as DepositInfo, L as LendingAdapter, b as SwapAdapter, c as SaveResult, W as WithdrawResult, M as MaxWithdrawResult, d as BorrowResult, R as RepayResult, e as MaxBorrowResult, H as HealthFactorResult, f as SwapResult, g as InvestResult, P as PortfolioResult, h as PositionsResult, i as RatesResult, j as LendingRates, k as RebalanceResult, E as EarningsResult, F as FundStatusResult, l as SentinelAgent, m as SentinelAttackResult, G as GasMethod } from './index-B14ZyQZt.js';
5
- export { A as AdapterCapability, n as AdapterPositions, o as AdapterTxResult, p as AssetRates, C as CetusAdapter, q as GasReserve, r as HealthInfo, s as InvestmentPosition, N as NaviAdapter, t as PerpsAdapter, u as PerpsPosition, v as PositionEntry, w as PositionSide, x as ProtocolDescriptor, y as ProtocolRegistry, z as RebalanceStep, J as SentinelVerdict, K as SuilendAdapter, O as SwapQuote, Q as TradePositionsResult, U as TradeResult, V as allDescriptors, X as cetusDescriptor, Y as getSentinelInfo, Z as listSentinels, _ as naviDescriptor, $ as requestAttack, a0 as sentinelAttack, a1 as sentinelDescriptor, a2 as settleAttack, a3 as submitPrompt, a4 as suilendDescriptor } from './index-B14ZyQZt.js';
6
6
  import { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';
7
7
 
8
- type T2000ErrorCode = 'INSUFFICIENT_BALANCE' | 'INSUFFICIENT_GAS' | 'INVALID_ADDRESS' | 'INVALID_AMOUNT' | 'WALLET_NOT_FOUND' | 'WALLET_LOCKED' | 'WALLET_EXISTS' | 'SPONSOR_FAILED' | 'SPONSOR_RATE_LIMITED' | 'GAS_STATION_UNAVAILABLE' | 'GAS_FEE_EXCEEDED' | 'SIMULATION_FAILED' | 'TRANSACTION_FAILED' | 'ASSET_NOT_SUPPORTED' | 'SWAP_FAILED' | 'SLIPPAGE_EXCEEDED' | 'HEALTH_FACTOR_TOO_LOW' | 'WITHDRAW_WOULD_LIQUIDATE' | 'NO_COLLATERAL' | 'PROTOCOL_PAUSED' | 'PROTOCOL_UNAVAILABLE' | 'RPC_ERROR' | 'RPC_UNREACHABLE' | 'SPONSOR_UNAVAILABLE' | 'AUTO_TOPUP_FAILED' | 'PRICE_EXCEEDS_LIMIT' | 'UNSUPPORTED_NETWORK' | 'PAYMENT_EXPIRED' | 'DUPLICATE_PAYMENT' | 'FACILITATOR_REJECTION' | 'CONTACT_NOT_FOUND' | 'INVALID_CONTACT_NAME' | 'FACILITATOR_TIMEOUT' | 'SENTINEL_API_ERROR' | 'SENTINEL_NOT_FOUND' | 'SENTINEL_TX_FAILED' | 'SENTINEL_TEE_ERROR' | 'SAFEGUARD_BLOCKED' | 'INSUFFICIENT_INVESTMENT' | 'INVESTMENT_LOCKED' | 'MARKET_NOT_SUPPORTED' | 'LEVERAGE_EXCEEDED' | 'POSITION_SIZE_EXCEEDED' | 'BLUEFIN_AUTH_FAILED' | 'BLUEFIN_API_ERROR' | 'POSITION_NOT_FOUND' | 'UNKNOWN';
8
+ type T2000ErrorCode = 'INSUFFICIENT_BALANCE' | 'INSUFFICIENT_GAS' | 'INVALID_ADDRESS' | 'INVALID_AMOUNT' | 'WALLET_NOT_FOUND' | 'WALLET_LOCKED' | 'WALLET_EXISTS' | 'SPONSOR_FAILED' | 'SPONSOR_RATE_LIMITED' | 'GAS_STATION_UNAVAILABLE' | 'GAS_FEE_EXCEEDED' | 'SIMULATION_FAILED' | 'TRANSACTION_FAILED' | 'ASSET_NOT_SUPPORTED' | 'SWAP_FAILED' | 'SLIPPAGE_EXCEEDED' | 'HEALTH_FACTOR_TOO_LOW' | 'WITHDRAW_WOULD_LIQUIDATE' | 'NO_COLLATERAL' | 'PROTOCOL_PAUSED' | 'PROTOCOL_UNAVAILABLE' | 'RPC_ERROR' | 'RPC_UNREACHABLE' | 'SPONSOR_UNAVAILABLE' | 'AUTO_TOPUP_FAILED' | 'PRICE_EXCEEDS_LIMIT' | 'UNSUPPORTED_NETWORK' | 'PAYMENT_EXPIRED' | 'DUPLICATE_PAYMENT' | 'FACILITATOR_REJECTION' | 'CONTACT_NOT_FOUND' | 'INVALID_CONTACT_NAME' | 'FACILITATOR_TIMEOUT' | 'SENTINEL_API_ERROR' | 'SENTINEL_NOT_FOUND' | 'SENTINEL_TX_FAILED' | 'SENTINEL_TEE_ERROR' | 'SAFEGUARD_BLOCKED' | 'INSUFFICIENT_INVESTMENT' | 'INVESTMENT_LOCKED' | 'INVEST_ALREADY_EARNING' | 'INVEST_NOT_EARNING' | 'BORROW_GUARD_INVESTMENT' | 'MARKET_NOT_SUPPORTED' | 'LEVERAGE_EXCEEDED' | 'POSITION_SIZE_EXCEEDED' | 'BLUEFIN_AUTH_FAILED' | 'BLUEFIN_API_ERROR' | 'POSITION_NOT_FOUND' | 'UNKNOWN';
9
9
  interface T2000ErrorData {
10
10
  reason?: string;
11
11
  [key: string]: unknown;
@@ -62,7 +62,7 @@ declare const SUPPORTED_ASSETS: {
62
62
  readonly displayName: "SUI";
63
63
  };
64
64
  readonly BTC: {
65
- readonly type: "0xaafb102dd0902f5055cadecd687fb5b71ca82ef0e0285d90afde828ec58ca96b::btc::BTC";
65
+ readonly type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC";
66
66
  readonly decimals: 8;
67
67
  readonly symbol: "BTC";
68
68
  readonly displayName: "Bitcoin";
@@ -84,7 +84,7 @@ declare const INVESTMENT_ASSETS: {
84
84
  readonly displayName: "SUI";
85
85
  };
86
86
  readonly BTC: {
87
- readonly type: "0xaafb102dd0902f5055cadecd687fb5b71ca82ef0e0285d90afde828ec58ca96b::btc::BTC";
87
+ readonly type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC";
88
88
  readonly decimals: 8;
89
89
  readonly symbol: "BTC";
90
90
  readonly displayName: "Bitcoin";
@@ -177,6 +177,9 @@ interface StoredPosition {
177
177
  costBasis: number;
178
178
  avgPrice: number;
179
179
  trades: InvestmentTrade[];
180
+ earning?: boolean;
181
+ earningProtocol?: string;
182
+ earningApy?: number;
180
183
  }
181
184
  declare class PortfolioManager {
182
185
  private data;
@@ -191,6 +194,9 @@ declare class PortfolioManager {
191
194
  getPositions(): Array<{
192
195
  asset: string;
193
196
  } & StoredPosition>;
197
+ recordEarn(asset: string, protocol: string, apy: number): void;
198
+ recordUnearn(asset: string): void;
199
+ isEarning(asset: string): boolean;
194
200
  getRealizedPnL(): number;
195
201
  }
196
202
 
@@ -287,6 +293,7 @@ declare class T2000 extends EventEmitter<T2000Events> {
287
293
  private _swapFromUsdc;
288
294
  private _convertWalletStablesToUsdc;
289
295
  maxWithdraw(): Promise<MaxWithdrawResult>;
296
+ private adjustMaxBorrowForInvestments;
290
297
  borrow(params: {
291
298
  amount: number;
292
299
  protocol?: string;
@@ -328,6 +335,12 @@ declare class T2000 extends EventEmitter<T2000Events> {
328
335
  usdAmount: number | 'all';
329
336
  maxSlippage?: number;
330
337
  }): Promise<InvestResult>;
338
+ investEarn(params: {
339
+ asset: InvestmentAsset;
340
+ }): Promise<InvestEarnResult>;
341
+ investUnearn(params: {
342
+ asset: InvestmentAsset;
343
+ }): Promise<InvestEarnResult>;
331
344
  getPortfolio(): Promise<PortfolioResult>;
332
345
  positions(): Promise<PositionsResult>;
333
346
  rates(): Promise<RatesResult>;
@@ -478,4 +491,4 @@ interface GasStatusResponse {
478
491
  }
479
492
  declare function getGasStatus(address?: string): Promise<GasStatusResponse>;
480
493
 
481
- export { type AutoTopUpResult, BPS_DENOMINATOR, BalanceResponse, BorrowResult, CLOCK_ID, type Contact, ContactManager, type ContactMap, DEFAULT_MAX_LEVERAGE, DEFAULT_MAX_POSITION_SIZE, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, DepositInfo, EarningsResult, type FeeOperation, FundStatusResult, GAS_RESERVE_MIN, type GasExecutionResult, GasMethod, type GasRequestType, type GasSponsorResponse, type GasStatusResponse, HealthFactorResult, INVESTMENT_ASSETS, InvestResult, type InvestmentAsset, InvestmentTrade, LendingAdapter, LendingRates, MIST_PER_SUI, MaxBorrowResult, MaxWithdrawResult, OUTBOUND_OPS, PERPS_MARKETS, type PerpsMarket, PortfolioManager, PortfolioResult, PositionsResult, type ProtocolFeeInfo, RatesResult, RebalanceResult, RepayResult, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, type SafeguardConfig, SafeguardEnforcer, SafeguardError, type SafeguardErrorDetails, type SafeguardRule, SaveResult, SendResult, SentinelAgent, SentinelAttackResult, type SimulationResult, type SupportedAsset, SwapAdapter, SwapResult, T2000, T2000Error, type T2000ErrorCode, type T2000ErrorData, T2000Options, TransactionRecord, type TxMetadata, USDC_DECIMALS, WithdrawResult, addCollectFeeToTx, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToStable, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
494
+ export { type AutoTopUpResult, BPS_DENOMINATOR, BalanceResponse, BorrowResult, CLOCK_ID, type Contact, ContactManager, type ContactMap, DEFAULT_MAX_LEVERAGE, DEFAULT_MAX_POSITION_SIZE, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, DepositInfo, EarningsResult, type FeeOperation, FundStatusResult, GAS_RESERVE_MIN, type GasExecutionResult, GasMethod, type GasRequestType, type GasSponsorResponse, type GasStatusResponse, HealthFactorResult, INVESTMENT_ASSETS, InvestEarnResult, InvestResult, type InvestmentAsset, InvestmentTrade, LendingAdapter, LendingRates, MIST_PER_SUI, MaxBorrowResult, MaxWithdrawResult, OUTBOUND_OPS, PERPS_MARKETS, type PerpsMarket, PortfolioManager, PortfolioResult, PositionsResult, type ProtocolFeeInfo, RatesResult, RebalanceResult, RepayResult, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, type SafeguardConfig, SafeguardEnforcer, SafeguardError, type SafeguardErrorDetails, type SafeguardRule, SaveResult, SendResult, SentinelAgent, SentinelAttackResult, type SimulationResult, type SupportedAsset, SwapAdapter, SwapResult, T2000, T2000Error, type T2000ErrorCode, type T2000ErrorData, T2000Options, TransactionRecord, type TxMetadata, USDC_DECIMALS, WithdrawResult, addCollectFeeToTx, calculateFee, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, keypairFromPrivateKey, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, rawToStable, rawToUsdc, saveKey, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, suiToMist, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
package/dist/index.js CHANGED
@@ -58,7 +58,7 @@ var SUPPORTED_ASSETS = {
58
58
  displayName: "SUI"
59
59
  },
60
60
  BTC: {
61
- type: "0xaafb102dd0902f5055cadecd687fb5b71ca82ef0e0285d90afde828ec58ca96b::btc::BTC",
61
+ type: "0x0041f9f9344cac094454cd574e333c4fdb132d7bcc9379bcd4aab485b2a63942::wbtc::WBTC",
62
62
  decimals: 8,
63
63
  symbol: "BTC",
64
64
  displayName: "Bitcoin"
@@ -600,16 +600,23 @@ function resolvePoolSymbol(pool) {
600
600
  }
601
601
  return pool.token?.symbol ?? "UNKNOWN";
602
602
  }
603
+ function resolveAssetInfo(asset) {
604
+ if (asset in SUPPORTED_ASSETS) {
605
+ const info = SUPPORTED_ASSETS[asset];
606
+ return { type: info.type, decimals: info.decimals, displayName: info.displayName };
607
+ }
608
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `Unknown asset: ${asset}`);
609
+ }
603
610
  async function getPool(asset = "USDC") {
604
611
  const pools = await getPools();
605
- const targetType = SUPPORTED_ASSETS[asset].type;
612
+ const { type: targetType, displayName } = resolveAssetInfo(asset);
606
613
  const pool = pools.find(
607
614
  (p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType)
608
615
  );
609
616
  if (!pool) {
610
617
  throw new T2000Error(
611
618
  "ASSET_NOT_SUPPORTED",
612
- `${SUPPORTED_ASSETS[asset].displayName} pool not found on NAVI. Try: ${STABLE_ASSETS.filter((a) => a !== asset).join(", ")}`
619
+ `${displayName} pool not found on NAVI`
613
620
  );
614
621
  }
615
622
  return pool;
@@ -632,13 +639,14 @@ function addOracleUpdate(tx, config, pool) {
632
639
  ]
633
640
  });
634
641
  }
635
- function refreshStableOracles(tx, config, pools) {
636
- const stableTypes = STABLE_ASSETS.map((a) => SUPPORTED_ASSETS[a].type);
637
- const stablePools = pools.filter((p) => {
642
+ function refreshOracles(tx, config, pools, opts) {
643
+ const assetsToRefresh = NAVI_SUPPORTED_ASSETS;
644
+ const targetTypes = assetsToRefresh.map((a) => SUPPORTED_ASSETS[a].type);
645
+ const matchedPools = pools.filter((p) => {
638
646
  const ct = p.suiCoinType || p.coinType || "";
639
- return stableTypes.some((t) => matchesCoinType(ct, t));
647
+ return targetTypes.some((t) => matchesCoinType(ct, t));
640
648
  });
641
- for (const pool of stablePools) {
649
+ for (const pool of matchedPools) {
642
650
  addOracleUpdate(tx, config, pool);
643
651
  }
644
652
  }
@@ -712,7 +720,7 @@ async function buildSaveTx(client, address, amount, options = {}) {
712
720
  throw new T2000Error("INVALID_AMOUNT", "Save amount must be a positive number");
713
721
  }
714
722
  const asset = options.asset ?? "USDC";
715
- const assetInfo = SUPPORTED_ASSETS[asset];
723
+ const assetInfo = resolveAssetInfo(asset);
716
724
  const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
717
725
  const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
718
726
  const coins = await fetchCoins(client, address, assetInfo.type);
@@ -741,7 +749,7 @@ async function buildSaveTx(client, address, amount, options = {}) {
741
749
  }
742
750
  async function buildWithdrawTx(client, address, amount, options = {}) {
743
751
  const asset = options.asset ?? "USDC";
744
- const assetInfo = SUPPORTED_ASSETS[asset];
752
+ const assetInfo = resolveAssetInfo(asset);
745
753
  const [config, pool, pools, states] = await Promise.all([
746
754
  getConfig(),
747
755
  getPool(asset),
@@ -758,7 +766,7 @@ async function buildWithdrawTx(client, address, amount, options = {}) {
758
766
  }
759
767
  const tx = new Transaction();
760
768
  tx.setSender(address);
761
- refreshStableOracles(tx, config, pools);
769
+ refreshOracles(tx, config, pools);
762
770
  const [balance] = tx.moveCall({
763
771
  target: `${config.package}::incentive_v3::withdraw_v2`,
764
772
  arguments: [
@@ -784,7 +792,7 @@ async function buildWithdrawTx(client, address, amount, options = {}) {
784
792
  }
785
793
  async function addWithdrawToTx(tx, client, address, amount, options = {}) {
786
794
  const asset = options.asset ?? "USDC";
787
- const assetInfo = SUPPORTED_ASSETS[asset];
795
+ const assetInfo = resolveAssetInfo(asset);
788
796
  const [config, pool, pools, states] = await Promise.all([
789
797
  getConfig(),
790
798
  getPool(asset),
@@ -803,7 +811,7 @@ async function addWithdrawToTx(tx, client, address, amount, options = {}) {
803
811
  });
804
812
  return { coin: coin2, effectiveAmount: 0 };
805
813
  }
806
- refreshStableOracles(tx, config, pools);
814
+ refreshOracles(tx, config, pools);
807
815
  const [balance] = tx.moveCall({
808
816
  target: `${config.package}::incentive_v3::withdraw_v2`,
809
817
  arguments: [
@@ -852,7 +860,7 @@ async function addSaveToTx(tx, _client, _address, coin, options = {}) {
852
860
  typeArguments: [pool.suiCoinType]
853
861
  });
854
862
  }
855
- async function addRepayToTx(tx, client, _address, coin, options = {}) {
863
+ async function addRepayToTx(tx, _client, _address, coin, options = {}) {
856
864
  const asset = options.asset ?? "USDC";
857
865
  const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
858
866
  addOracleUpdate(tx, config, pool);
@@ -882,7 +890,7 @@ async function buildBorrowTx(client, address, amount, options = {}) {
882
890
  throw new T2000Error("INVALID_AMOUNT", "Borrow amount must be a positive number");
883
891
  }
884
892
  const asset = options.asset ?? "USDC";
885
- const assetInfo = SUPPORTED_ASSETS[asset];
893
+ const assetInfo = resolveAssetInfo(asset);
886
894
  const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
887
895
  const [config, pool, pools] = await Promise.all([
888
896
  getConfig(),
@@ -891,7 +899,7 @@ async function buildBorrowTx(client, address, amount, options = {}) {
891
899
  ]);
892
900
  const tx = new Transaction();
893
901
  tx.setSender(address);
894
- refreshStableOracles(tx, config, pools);
902
+ refreshOracles(tx, config, pools);
895
903
  const [balance] = tx.moveCall({
896
904
  target: `${config.package}::incentive_v3::borrow_v2`,
897
905
  arguments: [
@@ -923,7 +931,7 @@ async function buildRepayTx(client, address, amount, options = {}) {
923
931
  throw new T2000Error("INVALID_AMOUNT", "Repay amount must be a positive number");
924
932
  }
925
933
  const asset = options.asset ?? "USDC";
926
- const assetInfo = SUPPORTED_ASSETS[asset];
934
+ const assetInfo = resolveAssetInfo(asset);
927
935
  const rawAmount = Number(stableToRaw(amount, assetInfo.decimals));
928
936
  const [config, pool] = await Promise.all([getConfig(), getPool(asset)]);
929
937
  const coins = await fetchCoins(client, address, assetInfo.type);
@@ -1022,11 +1030,12 @@ async function getHealthFactor(client, addressOrKeypair) {
1022
1030
  liquidationThreshold: liqThreshold
1023
1031
  };
1024
1032
  }
1033
+ var NAVI_SUPPORTED_ASSETS = [...STABLE_ASSETS, "SUI", "ETH"];
1025
1034
  async function getRates(client) {
1026
1035
  try {
1027
1036
  const pools = await getPools();
1028
1037
  const result = {};
1029
- for (const asset of STABLE_ASSETS) {
1038
+ for (const asset of NAVI_SUPPORTED_ASSETS) {
1030
1039
  const targetType = SUPPORTED_ASSETS[asset].type;
1031
1040
  const pool = pools.find((p) => matchesCoinType(p.suiCoinType || p.coinType || "", targetType));
1032
1041
  if (!pool) continue;
@@ -1396,7 +1405,11 @@ var ProtocolRegistry = class {
1396
1405
  }
1397
1406
  async allRatesAcrossAssets() {
1398
1407
  const results = [];
1399
- for (const asset of STABLE_ASSETS) {
1408
+ const allAssets = [...STABLE_ASSETS, ...Object.keys(INVESTMENT_ASSETS)];
1409
+ const seen = /* @__PURE__ */ new Set();
1410
+ for (const asset of allAssets) {
1411
+ if (seen.has(asset)) continue;
1412
+ seen.add(asset);
1400
1413
  for (const adapter of this.lending.values()) {
1401
1414
  if (!adapter.supportedAssets.includes(asset)) continue;
1402
1415
  try {
@@ -1471,7 +1484,7 @@ var NaviAdapter = class {
1471
1484
  name = "NAVI Protocol";
1472
1485
  version = "1.0.0";
1473
1486
  capabilities = ["save", "withdraw", "borrow", "repay"];
1474
- supportedAssets = [...STABLE_ASSETS];
1487
+ supportedAssets = [...STABLE_ASSETS, "SUI", "ETH"];
1475
1488
  supportsSameAssetBorrow = true;
1476
1489
  client;
1477
1490
  async init(client) {
@@ -1498,23 +1511,23 @@ var NaviAdapter = class {
1498
1511
  return getHealthFactor(this.client, address);
1499
1512
  }
1500
1513
  async buildSaveTx(address, amount, asset, options) {
1501
- const stableAsset = normalizeAsset(asset);
1502
- const tx = await buildSaveTx(this.client, address, amount, { ...options, asset: stableAsset });
1514
+ const normalized = normalizeAsset(asset);
1515
+ const tx = await buildSaveTx(this.client, address, amount, { ...options, asset: normalized });
1503
1516
  return { tx };
1504
1517
  }
1505
1518
  async buildWithdrawTx(address, amount, asset) {
1506
- const stableAsset = normalizeAsset(asset);
1507
- const result = await buildWithdrawTx(this.client, address, amount, { asset: stableAsset });
1519
+ const normalized = normalizeAsset(asset);
1520
+ const result = await buildWithdrawTx(this.client, address, amount, { asset: normalized });
1508
1521
  return { tx: result.tx, effectiveAmount: result.effectiveAmount };
1509
1522
  }
1510
1523
  async buildBorrowTx(address, amount, asset, options) {
1511
- const stableAsset = normalizeAsset(asset);
1512
- const tx = await buildBorrowTx(this.client, address, amount, { ...options, asset: stableAsset });
1524
+ const normalized = normalizeAsset(asset);
1525
+ const tx = await buildBorrowTx(this.client, address, amount, { ...options, asset: normalized });
1513
1526
  return { tx };
1514
1527
  }
1515
1528
  async buildRepayTx(address, amount, asset) {
1516
- const stableAsset = normalizeAsset(asset);
1517
- const tx = await buildRepayTx(this.client, address, amount, { asset: stableAsset });
1529
+ const normalized = normalizeAsset(asset);
1530
+ const tx = await buildRepayTx(this.client, address, amount, { asset: normalized });
1518
1531
  return { tx };
1519
1532
  }
1520
1533
  async maxWithdraw(address, _asset) {
@@ -1524,16 +1537,16 @@ var NaviAdapter = class {
1524
1537
  return maxBorrowAmount(this.client, address);
1525
1538
  }
1526
1539
  async addWithdrawToTx(tx, address, amount, asset) {
1527
- const stableAsset = normalizeAsset(asset);
1528
- return addWithdrawToTx(tx, this.client, address, amount, { asset: stableAsset });
1540
+ const normalized = normalizeAsset(asset);
1541
+ return addWithdrawToTx(tx, this.client, address, amount, { asset: normalized });
1529
1542
  }
1530
1543
  async addSaveToTx(tx, address, coin, asset, options) {
1531
- const stableAsset = normalizeAsset(asset);
1532
- return addSaveToTx(tx, this.client, address, coin, { ...options, asset: stableAsset });
1544
+ const normalized = normalizeAsset(asset);
1545
+ return addSaveToTx(tx, this.client, address, coin, { ...options, asset: normalized });
1533
1546
  }
1534
1547
  async addRepayToTx(tx, address, coin, asset) {
1535
- const stableAsset = normalizeAsset(asset);
1536
- return addRepayToTx(tx, this.client, address, coin, { asset: stableAsset });
1548
+ const normalized = normalizeAsset(asset);
1549
+ return addRepayToTx(tx, this.client, address, coin, { asset: normalized });
1537
1550
  }
1538
1551
  };
1539
1552
  var DEFAULT_SLIPPAGE_BPS = 300;
@@ -1845,7 +1858,7 @@ var SuilendAdapter = class {
1845
1858
  name = "Suilend";
1846
1859
  version = "2.0.0";
1847
1860
  capabilities = ["save", "withdraw", "borrow", "repay"];
1848
- supportedAssets = [...STABLE_ASSETS];
1861
+ supportedAssets = [...STABLE_ASSETS, "SUI", "ETH", "BTC"];
1849
1862
  supportsSameAssetBorrow = false;
1850
1863
  client;
1851
1864
  publishedAt = null;
@@ -2867,6 +2880,38 @@ var PortfolioManager = class {
2867
2880
  this.load();
2868
2881
  return Object.entries(this.data.positions).filter(([, pos]) => pos.totalAmount > 0).map(([asset, pos]) => ({ asset, ...pos }));
2869
2882
  }
2883
+ recordEarn(asset, protocol, apy) {
2884
+ this.load();
2885
+ const pos = this.data.positions[asset];
2886
+ if (!pos || pos.totalAmount <= 0) {
2887
+ throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${asset} position to earn on`);
2888
+ }
2889
+ if (pos.earning) {
2890
+ throw new T2000Error("INVEST_ALREADY_EARNING", `${asset} is already earning via ${pos.earningProtocol}`);
2891
+ }
2892
+ pos.earning = true;
2893
+ pos.earningProtocol = protocol;
2894
+ pos.earningApy = apy;
2895
+ this.data.positions[asset] = pos;
2896
+ this.save();
2897
+ }
2898
+ recordUnearn(asset) {
2899
+ this.load();
2900
+ const pos = this.data.positions[asset];
2901
+ if (!pos || !pos.earning) {
2902
+ throw new T2000Error("INVEST_NOT_EARNING", `${asset} is not currently earning`);
2903
+ }
2904
+ pos.earning = false;
2905
+ pos.earningProtocol = void 0;
2906
+ pos.earningApy = void 0;
2907
+ this.data.positions[asset] = pos;
2908
+ this.save();
2909
+ }
2910
+ isEarning(asset) {
2911
+ this.load();
2912
+ const pos = this.data.positions[asset];
2913
+ return pos?.earning === true;
2914
+ }
2870
2915
  getRealizedPnL() {
2871
2916
  this.load();
2872
2917
  return this.data.realizedPnL;
@@ -3426,16 +3471,49 @@ To access invested funds: t2000 invest sell ${params.amount} ${asset}`,
3426
3471
  return adapter.maxWithdraw(this._address, "USDC");
3427
3472
  }
3428
3473
  // -- Borrowing --
3474
+ async adjustMaxBorrowForInvestments(adapter, maxResult) {
3475
+ const earningPositions = this.portfolio.getPositions().filter((p) => p.earning);
3476
+ if (earningPositions.length === 0) return maxResult;
3477
+ let investmentCollateralUsd = 0;
3478
+ const swapAdapter = this.registry.listSwap()[0];
3479
+ for (const pos of earningPositions) {
3480
+ if (pos.earningProtocol !== adapter.id) continue;
3481
+ try {
3482
+ let price = 0;
3483
+ if (pos.asset === "SUI" && swapAdapter) {
3484
+ price = await swapAdapter.getPoolPrice();
3485
+ } else if (swapAdapter) {
3486
+ const quote = await swapAdapter.getQuote("USDC", pos.asset, 1);
3487
+ price = quote.expectedOutput > 0 ? 1 / quote.expectedOutput : 0;
3488
+ }
3489
+ investmentCollateralUsd += pos.totalAmount * price;
3490
+ } catch {
3491
+ }
3492
+ }
3493
+ if (investmentCollateralUsd <= 0) return maxResult;
3494
+ const CONSERVATIVE_LTV = 0.6;
3495
+ const investmentBorrowCapacity = investmentCollateralUsd * CONSERVATIVE_LTV;
3496
+ const adjustedMax = Math.max(0, maxResult.maxAmount - investmentBorrowCapacity);
3497
+ return { ...maxResult, maxAmount: adjustedMax };
3498
+ }
3429
3499
  async borrow(params) {
3430
3500
  this.enforcer.assertNotLocked();
3431
3501
  const asset = "USDC";
3432
3502
  const adapter = await this.resolveLending(params.protocol, asset, "borrow");
3433
- const maxResult = await adapter.maxBorrow(this._address, asset);
3503
+ const rawMax = await adapter.maxBorrow(this._address, asset);
3504
+ const maxResult = await this.adjustMaxBorrowForInvestments(adapter, rawMax);
3434
3505
  if (maxResult.maxAmount <= 0) {
3506
+ const hasInvestmentEarning = this.portfolio.getPositions().some((p) => p.earning && p.earningProtocol === adapter.id);
3507
+ if (hasInvestmentEarning) {
3508
+ throw new T2000Error(
3509
+ "BORROW_GUARD_INVESTMENT",
3510
+ "Max safe borrow: $0.00. Only savings deposits (stablecoins) count as borrowable collateral. Investment collateral (SUI, ETH, BTC) is excluded."
3511
+ );
3512
+ }
3435
3513
  throw new T2000Error("NO_COLLATERAL", "No collateral deposited. Save first with `t2000 save <amount>`.");
3436
3514
  }
3437
3515
  if (params.amount > maxResult.maxAmount) {
3438
- throw new T2000Error("HEALTH_FACTOR_TOO_LOW", `Max safe borrow: $${maxResult.maxAmount.toFixed(2)}`, {
3516
+ throw new T2000Error("HEALTH_FACTOR_TOO_LOW", `Max safe borrow: $${maxResult.maxAmount.toFixed(2)}. Only savings deposits count as borrowable collateral.`, {
3439
3517
  maxBorrow: maxResult.maxAmount,
3440
3518
  currentHF: maxResult.currentHF
3441
3519
  });
@@ -3600,7 +3678,8 @@ To access invested funds: t2000 invest sell ${params.amount} ${asset}`,
3600
3678
  }
3601
3679
  async maxBorrow() {
3602
3680
  const adapter = await this.resolveLending(void 0, "USDC", "borrow");
3603
- return adapter.maxBorrow(this._address, "USDC");
3681
+ const rawMax = await adapter.maxBorrow(this._address, "USDC");
3682
+ return this.adjustMaxBorrowForInvestments(adapter, rawMax);
3604
3683
  }
3605
3684
  async healthFactor() {
3606
3685
  const adapter = await this.resolveLending(void 0, "USDC", "save");
@@ -3764,6 +3843,9 @@ To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
3764
3843
  if (!pos || pos.totalAmount <= 0) {
3765
3844
  throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${params.asset} position to sell`);
3766
3845
  }
3846
+ if (pos.earning && pos.earningProtocol) {
3847
+ await this.investUnearn({ asset: params.asset });
3848
+ }
3767
3849
  const assetInfo = SUPPORTED_ASSETS[params.asset];
3768
3850
  const assetBalance = await this.client.getBalance({
3769
3851
  owner: this._address,
@@ -3838,6 +3920,91 @@ To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
3838
3920
  position
3839
3921
  };
3840
3922
  }
3923
+ async investEarn(params) {
3924
+ this.enforcer.assertNotLocked();
3925
+ if (!(params.asset in INVESTMENT_ASSETS)) {
3926
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
3927
+ }
3928
+ const pos = this.portfolio.getPosition(params.asset);
3929
+ if (!pos || pos.totalAmount <= 0) {
3930
+ throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${params.asset} position to earn on`);
3931
+ }
3932
+ if (pos.earning) {
3933
+ throw new T2000Error("INVEST_ALREADY_EARNING", `${params.asset} is already earning via ${pos.earningProtocol}`);
3934
+ }
3935
+ const { adapter, rate } = await this.registry.bestSaveRate(params.asset);
3936
+ const assetInfo = SUPPORTED_ASSETS[params.asset];
3937
+ const assetBalance = await this.client.getBalance({
3938
+ owner: this._address,
3939
+ coinType: assetInfo.type
3940
+ });
3941
+ const walletAmount = Number(assetBalance.totalBalance) / 10 ** assetInfo.decimals;
3942
+ const gasReserve = params.asset === "SUI" ? GAS_RESERVE_MIN : 0;
3943
+ const depositAmount = Math.max(0, walletAmount - gasReserve);
3944
+ if (depositAmount <= 0) {
3945
+ throw new T2000Error("INSUFFICIENT_BALANCE", `No ${params.asset} available to deposit (wallet: ${walletAmount}, gas reserve: ${gasReserve})`);
3946
+ }
3947
+ const { tx } = await adapter.buildSaveTx(this._address, depositAmount, params.asset);
3948
+ const result = await this.client.signAndExecuteTransaction({
3949
+ signer: this.keypair,
3950
+ transaction: tx,
3951
+ options: { showEffects: true }
3952
+ });
3953
+ await this.client.waitForTransaction({ digest: result.digest });
3954
+ const gasCost = result.effects?.gasUsed ? Math.abs(
3955
+ (Number(result.effects.gasUsed.computationCost) + Number(result.effects.gasUsed.storageCost) - Number(result.effects.gasUsed.storageRebate)) / 1e9
3956
+ ) : 0;
3957
+ this.portfolio.recordEarn(params.asset, adapter.id, rate.saveApy);
3958
+ return {
3959
+ success: true,
3960
+ tx: result.digest,
3961
+ asset: params.asset,
3962
+ amount: depositAmount,
3963
+ protocol: adapter.name,
3964
+ apy: rate.saveApy,
3965
+ gasCost,
3966
+ gasMethod: "self-funded"
3967
+ };
3968
+ }
3969
+ async investUnearn(params) {
3970
+ this.enforcer.assertNotLocked();
3971
+ if (!(params.asset in INVESTMENT_ASSETS)) {
3972
+ throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
3973
+ }
3974
+ const pos = this.portfolio.getPosition(params.asset);
3975
+ if (!pos || !pos.earning || !pos.earningProtocol) {
3976
+ throw new T2000Error("INVEST_NOT_EARNING", `${params.asset} is not currently earning`);
3977
+ }
3978
+ const adapter = this.registry.getLending(pos.earningProtocol);
3979
+ if (!adapter) {
3980
+ throw new T2000Error("PROTOCOL_UNAVAILABLE", `Lending protocol ${pos.earningProtocol} not found`);
3981
+ }
3982
+ const positions = await adapter.getPositions(this._address);
3983
+ const supply = positions.supplies.find((s) => s.asset === params.asset);
3984
+ const withdrawAmount = supply?.amount ?? pos.totalAmount;
3985
+ const { tx, effectiveAmount } = await adapter.buildWithdrawTx(this._address, withdrawAmount, params.asset);
3986
+ const result = await this.client.signAndExecuteTransaction({
3987
+ signer: this.keypair,
3988
+ transaction: tx,
3989
+ options: { showEffects: true }
3990
+ });
3991
+ await this.client.waitForTransaction({ digest: result.digest });
3992
+ const gasCost = result.effects?.gasUsed ? Math.abs(
3993
+ (Number(result.effects.gasUsed.computationCost) + Number(result.effects.gasUsed.storageCost) - Number(result.effects.gasUsed.storageRebate)) / 1e9
3994
+ ) : 0;
3995
+ const protocolName = adapter.name;
3996
+ this.portfolio.recordUnearn(params.asset);
3997
+ return {
3998
+ success: true,
3999
+ tx: result.digest,
4000
+ asset: params.asset,
4001
+ amount: effectiveAmount,
4002
+ protocol: protocolName,
4003
+ apy: 0,
4004
+ gasCost,
4005
+ gasMethod: "self-funded"
4006
+ };
4007
+ }
3841
4008
  async getPortfolio() {
3842
4009
  const positions = this.portfolio.getPositions();
3843
4010
  const realizedPnL = this.portfolio.getRealizedPnL();
@@ -3887,7 +4054,10 @@ To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
3887
4054
  currentValue,
3888
4055
  unrealizedPnL,
3889
4056
  unrealizedPnLPct,
3890
- trades: pos.trades
4057
+ trades: pos.trades,
4058
+ earning: pos.earning,
4059
+ earningProtocol: pos.earningProtocol,
4060
+ earningApy: pos.earningApy
3891
4061
  });
3892
4062
  }
3893
4063
  const totalInvested = enriched.reduce((sum, p) => sum + p.costBasis, 0);
@@ -3952,8 +4122,11 @@ To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
3952
4122
  this.registry.allPositions(this._address),
3953
4123
  this.registry.allRatesAcrossAssets()
3954
4124
  ]);
4125
+ const earningAssets = new Set(
4126
+ this.portfolio.getPositions().filter((p) => p.earning).map((p) => p.asset)
4127
+ );
3955
4128
  const savePositions = allPositions.flatMap(
3956
- (p) => p.positions.supplies.filter((s) => s.amount > 0.01).map((s) => ({
4129
+ (p) => p.positions.supplies.filter((s) => s.amount > 0.01).filter((s) => !earningAssets.has(s.asset)).map((s) => ({
3957
4130
  protocolId: p.protocolId,
3958
4131
  protocol: p.protocol,
3959
4132
  asset: s.asset,