@t2000/sdk 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -9
- package/dist/adapters/index.cjs +21 -4
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.d.cts +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +21 -4
- package/dist/adapters/index.js.map +1 -1
- package/dist/{index-Dd4EvUGr.d.cts → index-B14ZyQZt.d.cts} +226 -129
- package/dist/{index-Dd4EvUGr.d.ts → index-B14ZyQZt.d.ts} +226 -129
- package/dist/index.cjs +408 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +134 -57
- package/dist/index.d.ts +134 -57
- package/dist/index.js +402 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -56,6 +56,18 @@ var SUPPORTED_ASSETS = {
|
|
|
56
56
|
decimals: 9,
|
|
57
57
|
symbol: "SUI",
|
|
58
58
|
displayName: "SUI"
|
|
59
|
+
},
|
|
60
|
+
BTC: {
|
|
61
|
+
type: "0xaafb102dd0902f5055cadecd687fb5b71ca82ef0e0285d90afde828ec58ca96b::btc::BTC",
|
|
62
|
+
decimals: 8,
|
|
63
|
+
symbol: "BTC",
|
|
64
|
+
displayName: "Bitcoin"
|
|
65
|
+
},
|
|
66
|
+
ETH: {
|
|
67
|
+
type: "0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH",
|
|
68
|
+
decimals: 8,
|
|
69
|
+
symbol: "ETH",
|
|
70
|
+
displayName: "Ethereum"
|
|
59
71
|
}
|
|
60
72
|
};
|
|
61
73
|
var STABLE_ASSETS = ["USDC", "USDT", "USDe", "USDsui"];
|
|
@@ -68,6 +80,15 @@ var DEFAULT_KEY_PATH = "~/.t2000/wallet.key";
|
|
|
68
80
|
var API_BASE_URL = process.env.T2000_API_URL ?? "https://api.t2000.ai";
|
|
69
81
|
var CETUS_USDC_SUI_POOL = "0x51e883ba7c0b566a26cbc8a94cd33eb0abd418a77cc1e60ad22fd9b1f29cd2ab";
|
|
70
82
|
var CETUS_PACKAGE = "0x1eabed72c53feb3805120a081dc15963c204dc8d091542592abaf7a35689b2fb";
|
|
83
|
+
var INVESTMENT_ASSETS = {
|
|
84
|
+
SUI: SUPPORTED_ASSETS.SUI,
|
|
85
|
+
BTC: SUPPORTED_ASSETS.BTC,
|
|
86
|
+
ETH: SUPPORTED_ASSETS.ETH
|
|
87
|
+
};
|
|
88
|
+
var PERPS_MARKETS = ["SUI-PERP"];
|
|
89
|
+
var DEFAULT_MAX_LEVERAGE = 5;
|
|
90
|
+
var DEFAULT_MAX_POSITION_SIZE = 1e3;
|
|
91
|
+
var GAS_RESERVE_MIN = 0.05;
|
|
71
92
|
var SENTINEL = {
|
|
72
93
|
PACKAGE: "0x88b83f36dafcd5f6dcdcf1d2cb5889b03f61264ab3cee9cae35db7aa940a21b7",
|
|
73
94
|
AGENT_REGISTRY: "0xc47564f5f14c12b31e0dfa1a3dc99a6380a1edf8929c28cb0eaa3359c8db36ac",
|
|
@@ -300,6 +321,11 @@ function formatSui(amount) {
|
|
|
300
321
|
if (amount < 1e-3) return `${amount.toFixed(6)} SUI`;
|
|
301
322
|
return `${amount.toFixed(3)} SUI`;
|
|
302
323
|
}
|
|
324
|
+
function formatAssetAmount(amount, asset) {
|
|
325
|
+
if (asset === "BTC") return amount.toFixed(8);
|
|
326
|
+
if (asset === "ETH") return amount.toFixed(6);
|
|
327
|
+
return amount.toFixed(4);
|
|
328
|
+
}
|
|
303
329
|
var ASSET_LOOKUP = /* @__PURE__ */ new Map();
|
|
304
330
|
for (const [key, info] of Object.entries(SUPPORTED_ASSETS)) {
|
|
305
331
|
ASSET_LOOKUP.set(key.toUpperCase(), key);
|
|
@@ -404,6 +430,8 @@ async function queryBalance(client, address) {
|
|
|
404
430
|
available: totalStables,
|
|
405
431
|
savings,
|
|
406
432
|
debt: 0,
|
|
433
|
+
investment: 0,
|
|
434
|
+
investmentPnL: 0,
|
|
407
435
|
gasReserve: {
|
|
408
436
|
sui: suiAmount,
|
|
409
437
|
usdEquiv
|
|
@@ -1682,10 +1710,10 @@ var CetusAdapter = class {
|
|
|
1682
1710
|
};
|
|
1683
1711
|
}
|
|
1684
1712
|
getSupportedPairs() {
|
|
1685
|
-
const pairs = [
|
|
1686
|
-
|
|
1687
|
-
{ from: "
|
|
1688
|
-
|
|
1713
|
+
const pairs = [];
|
|
1714
|
+
for (const asset of Object.keys(INVESTMENT_ASSETS)) {
|
|
1715
|
+
pairs.push({ from: "USDC", to: asset }, { from: asset, to: "USDC" });
|
|
1716
|
+
}
|
|
1689
1717
|
for (const a of STABLE_ASSETS) {
|
|
1690
1718
|
for (const b of STABLE_ASSETS) {
|
|
1691
1719
|
if (a !== b) pairs.push({ from: a, to: b });
|
|
@@ -2774,6 +2802,76 @@ var ContactManager = class {
|
|
|
2774
2802
|
}
|
|
2775
2803
|
}
|
|
2776
2804
|
};
|
|
2805
|
+
function emptyData() {
|
|
2806
|
+
return { positions: {}, realizedPnL: 0 };
|
|
2807
|
+
}
|
|
2808
|
+
var PortfolioManager = class {
|
|
2809
|
+
data = emptyData();
|
|
2810
|
+
filePath;
|
|
2811
|
+
dir;
|
|
2812
|
+
constructor(configDir) {
|
|
2813
|
+
this.dir = configDir ?? join(homedir(), ".t2000");
|
|
2814
|
+
this.filePath = join(this.dir, "portfolio.json");
|
|
2815
|
+
this.load();
|
|
2816
|
+
}
|
|
2817
|
+
load() {
|
|
2818
|
+
try {
|
|
2819
|
+
if (existsSync(this.filePath)) {
|
|
2820
|
+
this.data = JSON.parse(readFileSync(this.filePath, "utf-8"));
|
|
2821
|
+
}
|
|
2822
|
+
} catch {
|
|
2823
|
+
this.data = emptyData();
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
save() {
|
|
2827
|
+
if (!existsSync(this.dir)) mkdirSync(this.dir, { recursive: true });
|
|
2828
|
+
writeFileSync(this.filePath, JSON.stringify(this.data, null, 2));
|
|
2829
|
+
}
|
|
2830
|
+
recordBuy(trade) {
|
|
2831
|
+
this.load();
|
|
2832
|
+
const pos = this.data.positions[trade.asset] ?? { totalAmount: 0, costBasis: 0, avgPrice: 0, trades: [] };
|
|
2833
|
+
pos.totalAmount += trade.amount;
|
|
2834
|
+
pos.costBasis += trade.usdValue;
|
|
2835
|
+
pos.avgPrice = pos.costBasis / pos.totalAmount;
|
|
2836
|
+
pos.trades.push(trade);
|
|
2837
|
+
this.data.positions[trade.asset] = pos;
|
|
2838
|
+
this.save();
|
|
2839
|
+
}
|
|
2840
|
+
recordSell(trade) {
|
|
2841
|
+
this.load();
|
|
2842
|
+
const pos = this.data.positions[trade.asset];
|
|
2843
|
+
if (!pos || pos.totalAmount <= 0) {
|
|
2844
|
+
throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${trade.asset} position to sell`);
|
|
2845
|
+
}
|
|
2846
|
+
const sellAmount = Math.min(trade.amount, pos.totalAmount);
|
|
2847
|
+
const costOfSold = pos.avgPrice * sellAmount;
|
|
2848
|
+
const realizedPnL = trade.usdValue - costOfSold;
|
|
2849
|
+
pos.totalAmount -= sellAmount;
|
|
2850
|
+
pos.costBasis -= costOfSold;
|
|
2851
|
+
if (pos.totalAmount < 1e-6) {
|
|
2852
|
+
pos.totalAmount = 0;
|
|
2853
|
+
pos.costBasis = 0;
|
|
2854
|
+
pos.avgPrice = 0;
|
|
2855
|
+
}
|
|
2856
|
+
pos.trades.push(trade);
|
|
2857
|
+
this.data.realizedPnL += realizedPnL;
|
|
2858
|
+
this.data.positions[trade.asset] = pos;
|
|
2859
|
+
this.save();
|
|
2860
|
+
return realizedPnL;
|
|
2861
|
+
}
|
|
2862
|
+
getPosition(asset) {
|
|
2863
|
+
this.load();
|
|
2864
|
+
return this.data.positions[asset];
|
|
2865
|
+
}
|
|
2866
|
+
getPositions() {
|
|
2867
|
+
this.load();
|
|
2868
|
+
return Object.entries(this.data.positions).filter(([, pos]) => pos.totalAmount > 0).map(([asset, pos]) => ({ asset, ...pos }));
|
|
2869
|
+
}
|
|
2870
|
+
getRealizedPnL() {
|
|
2871
|
+
this.load();
|
|
2872
|
+
return this.data.realizedPnL;
|
|
2873
|
+
}
|
|
2874
|
+
};
|
|
2777
2875
|
var DEFAULT_CONFIG_DIR = join(homedir(), ".t2000");
|
|
2778
2876
|
var T2000 = class _T2000 extends EventEmitter {
|
|
2779
2877
|
keypair;
|
|
@@ -2782,6 +2880,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2782
2880
|
registry;
|
|
2783
2881
|
enforcer;
|
|
2784
2882
|
contacts;
|
|
2883
|
+
portfolio;
|
|
2785
2884
|
constructor(keypair, client, registry, configDir) {
|
|
2786
2885
|
super();
|
|
2787
2886
|
this.keypair = keypair;
|
|
@@ -2791,6 +2890,7 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2791
2890
|
this.enforcer = new SafeguardEnforcer(configDir);
|
|
2792
2891
|
this.enforcer.load();
|
|
2793
2892
|
this.contacts = new ContactManager(configDir);
|
|
2893
|
+
this.portfolio = new PortfolioManager(configDir);
|
|
2794
2894
|
}
|
|
2795
2895
|
static createDefaultRegistry(client) {
|
|
2796
2896
|
const registry = new ProtocolRegistry();
|
|
@@ -2870,6 +2970,19 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2870
2970
|
if (!(asset in SUPPORTED_ASSETS)) {
|
|
2871
2971
|
throw new T2000Error("ASSET_NOT_SUPPORTED", `Asset ${asset} is not supported`);
|
|
2872
2972
|
}
|
|
2973
|
+
if (asset in INVESTMENT_ASSETS) {
|
|
2974
|
+
const free = await this.getFreeBalance(asset);
|
|
2975
|
+
if (params.amount > free) {
|
|
2976
|
+
const pos = this.portfolio.getPosition(asset);
|
|
2977
|
+
const invested = pos?.totalAmount ?? 0;
|
|
2978
|
+
throw new T2000Error(
|
|
2979
|
+
"INVESTMENT_LOCKED",
|
|
2980
|
+
`Cannot send ${params.amount} ${asset} \u2014 ${invested.toFixed(4)} ${asset} is invested. Free ${asset}: ${free.toFixed(4)}
|
|
2981
|
+
To access invested funds: t2000 invest sell ${params.amount} ${asset}`,
|
|
2982
|
+
{ free, invested, requested: params.amount }
|
|
2983
|
+
);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2873
2986
|
const resolved = this.contacts.resolve(params.to);
|
|
2874
2987
|
const sendAmount = params.amount;
|
|
2875
2988
|
const sendTo = resolved.address;
|
|
@@ -2902,9 +3015,52 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
2902
3015
|
const debt = positions.positions.filter((p) => p.type === "borrow").reduce((sum, p) => sum + p.amount, 0);
|
|
2903
3016
|
bal.savings = savings;
|
|
2904
3017
|
bal.debt = debt;
|
|
2905
|
-
bal.total = bal.available + savings - debt + bal.gasReserve.usdEquiv;
|
|
2906
3018
|
} catch {
|
|
2907
3019
|
}
|
|
3020
|
+
try {
|
|
3021
|
+
const portfolioPositions = this.portfolio.getPositions();
|
|
3022
|
+
const suiPrice = bal.gasReserve.sui > 0 ? bal.gasReserve.usdEquiv / bal.gasReserve.sui : 0;
|
|
3023
|
+
const assetPrices = { SUI: suiPrice };
|
|
3024
|
+
const swapAdapter = this.registry.listSwap()[0];
|
|
3025
|
+
for (const pos of portfolioPositions) {
|
|
3026
|
+
if (pos.asset !== "SUI" && pos.asset in INVESTMENT_ASSETS && !(pos.asset in assetPrices)) {
|
|
3027
|
+
try {
|
|
3028
|
+
if (swapAdapter) {
|
|
3029
|
+
const quote = await swapAdapter.getQuote("USDC", pos.asset, 1);
|
|
3030
|
+
assetPrices[pos.asset] = quote.expectedOutput > 0 ? 1 / quote.expectedOutput : 0;
|
|
3031
|
+
}
|
|
3032
|
+
} catch {
|
|
3033
|
+
assetPrices[pos.asset] = 0;
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
}
|
|
3037
|
+
let investmentValue = 0;
|
|
3038
|
+
let investmentCostBasis = 0;
|
|
3039
|
+
for (const pos of portfolioPositions) {
|
|
3040
|
+
if (!(pos.asset in INVESTMENT_ASSETS)) continue;
|
|
3041
|
+
const price = assetPrices[pos.asset] ?? 0;
|
|
3042
|
+
if (pos.asset === "SUI") {
|
|
3043
|
+
const actualHeld = Math.min(pos.totalAmount, bal.gasReserve.sui);
|
|
3044
|
+
investmentValue += actualHeld * price;
|
|
3045
|
+
if (actualHeld < pos.totalAmount && pos.totalAmount > 0) {
|
|
3046
|
+
investmentCostBasis += pos.costBasis * (actualHeld / pos.totalAmount);
|
|
3047
|
+
} else {
|
|
3048
|
+
investmentCostBasis += pos.costBasis;
|
|
3049
|
+
}
|
|
3050
|
+
const gasSui = Math.max(0, bal.gasReserve.sui - pos.totalAmount);
|
|
3051
|
+
bal.gasReserve = { sui: gasSui, usdEquiv: gasSui * price };
|
|
3052
|
+
} else {
|
|
3053
|
+
investmentValue += pos.totalAmount * price;
|
|
3054
|
+
investmentCostBasis += pos.costBasis;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
bal.investment = investmentValue;
|
|
3058
|
+
bal.investmentPnL = investmentValue - investmentCostBasis;
|
|
3059
|
+
} catch {
|
|
3060
|
+
bal.investment = 0;
|
|
3061
|
+
bal.investmentPnL = 0;
|
|
3062
|
+
}
|
|
3063
|
+
bal.total = bal.available + bal.savings - bal.debt + bal.investment + bal.gasReserve.usdEquiv;
|
|
2908
3064
|
return bal;
|
|
2909
3065
|
}
|
|
2910
3066
|
async history(params) {
|
|
@@ -3467,6 +3623,19 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
3467
3623
|
if (fromAsset === toAsset) {
|
|
3468
3624
|
throw new T2000Error("INVALID_AMOUNT", "Cannot swap same asset");
|
|
3469
3625
|
}
|
|
3626
|
+
if (!params._bypassInvestmentGuard && fromAsset in INVESTMENT_ASSETS) {
|
|
3627
|
+
const free = await this.getFreeBalance(fromAsset);
|
|
3628
|
+
if (params.amount > free) {
|
|
3629
|
+
const pos = this.portfolio.getPosition(fromAsset);
|
|
3630
|
+
const invested = pos?.totalAmount ?? 0;
|
|
3631
|
+
throw new T2000Error(
|
|
3632
|
+
"INVESTMENT_LOCKED",
|
|
3633
|
+
`Cannot exchange ${params.amount} ${fromAsset} \u2014 ${invested.toFixed(4)} ${fromAsset} is invested. Free ${fromAsset}: ${free.toFixed(4)}
|
|
3634
|
+
To sell investment: t2000 invest sell ${params.amount} ${fromAsset}`,
|
|
3635
|
+
{ free, invested, requested: params.amount }
|
|
3636
|
+
);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3470
3639
|
const best = await this.registry.bestSwapQuote(fromAsset, toAsset, params.amount);
|
|
3471
3640
|
const adapter = best.adapter;
|
|
3472
3641
|
const fee = calculateFee("swap", params.amount);
|
|
@@ -3518,6 +3687,222 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
3518
3687
|
const fee = calculateFee("swap", params.amount);
|
|
3519
3688
|
return { ...best.quote, fee: { amount: fee.amount, rate: fee.rate } };
|
|
3520
3689
|
}
|
|
3690
|
+
// -- Investment --
|
|
3691
|
+
async investBuy(params) {
|
|
3692
|
+
this.enforcer.assertNotLocked();
|
|
3693
|
+
if (!params.usdAmount || params.usdAmount <= 0 || !isFinite(params.usdAmount)) {
|
|
3694
|
+
throw new T2000Error("INVALID_AMOUNT", "Investment amount must be greater than $0");
|
|
3695
|
+
}
|
|
3696
|
+
this.enforcer.check({ operation: "invest", amount: params.usdAmount });
|
|
3697
|
+
if (!(params.asset in INVESTMENT_ASSETS)) {
|
|
3698
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
|
|
3699
|
+
}
|
|
3700
|
+
const bal = await queryBalance(this.client, this._address);
|
|
3701
|
+
if (bal.available < params.usdAmount) {
|
|
3702
|
+
throw new T2000Error("INSUFFICIENT_BALANCE", `Insufficient checking balance. Available: $${bal.available.toFixed(2)}, requested: $${params.usdAmount.toFixed(2)}`);
|
|
3703
|
+
}
|
|
3704
|
+
const swapResult = await this.exchange({
|
|
3705
|
+
from: "USDC",
|
|
3706
|
+
to: params.asset,
|
|
3707
|
+
amount: params.usdAmount,
|
|
3708
|
+
maxSlippage: params.maxSlippage ?? 0.03,
|
|
3709
|
+
_bypassInvestmentGuard: true
|
|
3710
|
+
});
|
|
3711
|
+
if (swapResult.toAmount === 0) {
|
|
3712
|
+
throw new T2000Error("SWAP_FAILED", "Swap returned zero tokens \u2014 try a different amount or check liquidity");
|
|
3713
|
+
}
|
|
3714
|
+
const price = params.usdAmount / swapResult.toAmount;
|
|
3715
|
+
this.portfolio.recordBuy({
|
|
3716
|
+
id: `inv_${Date.now()}`,
|
|
3717
|
+
type: "buy",
|
|
3718
|
+
asset: params.asset,
|
|
3719
|
+
amount: swapResult.toAmount,
|
|
3720
|
+
price,
|
|
3721
|
+
usdValue: params.usdAmount,
|
|
3722
|
+
fee: swapResult.fee,
|
|
3723
|
+
tx: swapResult.tx,
|
|
3724
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3725
|
+
});
|
|
3726
|
+
const pos = this.portfolio.getPosition(params.asset);
|
|
3727
|
+
const currentPrice = price;
|
|
3728
|
+
const position = {
|
|
3729
|
+
asset: params.asset,
|
|
3730
|
+
totalAmount: pos?.totalAmount ?? swapResult.toAmount,
|
|
3731
|
+
costBasis: pos?.costBasis ?? params.usdAmount,
|
|
3732
|
+
avgPrice: pos?.avgPrice ?? price,
|
|
3733
|
+
currentPrice,
|
|
3734
|
+
currentValue: (pos?.totalAmount ?? swapResult.toAmount) * currentPrice,
|
|
3735
|
+
unrealizedPnL: 0,
|
|
3736
|
+
unrealizedPnLPct: 0,
|
|
3737
|
+
trades: pos?.trades ?? []
|
|
3738
|
+
};
|
|
3739
|
+
return {
|
|
3740
|
+
success: true,
|
|
3741
|
+
tx: swapResult.tx,
|
|
3742
|
+
type: "buy",
|
|
3743
|
+
asset: params.asset,
|
|
3744
|
+
amount: swapResult.toAmount,
|
|
3745
|
+
price,
|
|
3746
|
+
usdValue: params.usdAmount,
|
|
3747
|
+
fee: swapResult.fee,
|
|
3748
|
+
gasCost: swapResult.gasCost,
|
|
3749
|
+
gasMethod: swapResult.gasMethod,
|
|
3750
|
+
position
|
|
3751
|
+
};
|
|
3752
|
+
}
|
|
3753
|
+
async investSell(params) {
|
|
3754
|
+
this.enforcer.assertNotLocked();
|
|
3755
|
+
if (params.usdAmount !== "all") {
|
|
3756
|
+
if (!params.usdAmount || params.usdAmount <= 0 || !isFinite(params.usdAmount)) {
|
|
3757
|
+
throw new T2000Error("INVALID_AMOUNT", "Sell amount must be greater than $0");
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
if (!(params.asset in INVESTMENT_ASSETS)) {
|
|
3761
|
+
throw new T2000Error("ASSET_NOT_SUPPORTED", `${params.asset} is not available for investment`);
|
|
3762
|
+
}
|
|
3763
|
+
const pos = this.portfolio.getPosition(params.asset);
|
|
3764
|
+
if (!pos || pos.totalAmount <= 0) {
|
|
3765
|
+
throw new T2000Error("INSUFFICIENT_INVESTMENT", `No ${params.asset} position to sell`);
|
|
3766
|
+
}
|
|
3767
|
+
const assetInfo = SUPPORTED_ASSETS[params.asset];
|
|
3768
|
+
const assetBalance = await this.client.getBalance({
|
|
3769
|
+
owner: this._address,
|
|
3770
|
+
coinType: assetInfo.type
|
|
3771
|
+
});
|
|
3772
|
+
const walletAmount = Number(assetBalance.totalBalance) / 10 ** assetInfo.decimals;
|
|
3773
|
+
const gasReserve = params.asset === "SUI" ? GAS_RESERVE_MIN : 0;
|
|
3774
|
+
const maxSellable = Math.max(0, walletAmount - gasReserve);
|
|
3775
|
+
let sellAmountAsset;
|
|
3776
|
+
if (params.usdAmount === "all") {
|
|
3777
|
+
sellAmountAsset = Math.min(pos.totalAmount, maxSellable);
|
|
3778
|
+
} else {
|
|
3779
|
+
const swapAdapter = this.registry.listSwap()[0];
|
|
3780
|
+
if (!swapAdapter) throw new T2000Error("PROTOCOL_UNAVAILABLE", "No swap adapter available");
|
|
3781
|
+
const quote = await swapAdapter.getQuote("USDC", params.asset, 1);
|
|
3782
|
+
const assetPrice = 1 / quote.expectedOutput;
|
|
3783
|
+
sellAmountAsset = params.usdAmount / assetPrice;
|
|
3784
|
+
sellAmountAsset = Math.min(sellAmountAsset, pos.totalAmount);
|
|
3785
|
+
if (sellAmountAsset > maxSellable) {
|
|
3786
|
+
throw new T2000Error(
|
|
3787
|
+
"INSUFFICIENT_INVESTMENT",
|
|
3788
|
+
`Cannot sell $${params.usdAmount.toFixed(2)} \u2014 max sellable: $${(maxSellable * assetPrice).toFixed(2)} (gas reserve: ${gasReserve} ${params.asset})`
|
|
3789
|
+
);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
if (sellAmountAsset <= 0) {
|
|
3793
|
+
throw new T2000Error("INSUFFICIENT_INVESTMENT", "Nothing to sell after gas reserve");
|
|
3794
|
+
}
|
|
3795
|
+
const swapResult = await this.exchange({
|
|
3796
|
+
from: params.asset,
|
|
3797
|
+
to: "USDC",
|
|
3798
|
+
amount: sellAmountAsset,
|
|
3799
|
+
maxSlippage: params.maxSlippage ?? 0.03,
|
|
3800
|
+
_bypassInvestmentGuard: true
|
|
3801
|
+
});
|
|
3802
|
+
const price = swapResult.toAmount / sellAmountAsset;
|
|
3803
|
+
const realizedPnL = this.portfolio.recordSell({
|
|
3804
|
+
id: `inv_${Date.now()}`,
|
|
3805
|
+
type: "sell",
|
|
3806
|
+
asset: params.asset,
|
|
3807
|
+
amount: sellAmountAsset,
|
|
3808
|
+
price,
|
|
3809
|
+
usdValue: swapResult.toAmount,
|
|
3810
|
+
fee: swapResult.fee,
|
|
3811
|
+
tx: swapResult.tx,
|
|
3812
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3813
|
+
});
|
|
3814
|
+
const updatedPos = this.portfolio.getPosition(params.asset);
|
|
3815
|
+
const position = {
|
|
3816
|
+
asset: params.asset,
|
|
3817
|
+
totalAmount: updatedPos?.totalAmount ?? 0,
|
|
3818
|
+
costBasis: updatedPos?.costBasis ?? 0,
|
|
3819
|
+
avgPrice: updatedPos?.avgPrice ?? 0,
|
|
3820
|
+
currentPrice: price,
|
|
3821
|
+
currentValue: (updatedPos?.totalAmount ?? 0) * price,
|
|
3822
|
+
unrealizedPnL: 0,
|
|
3823
|
+
unrealizedPnLPct: 0,
|
|
3824
|
+
trades: updatedPos?.trades ?? []
|
|
3825
|
+
};
|
|
3826
|
+
return {
|
|
3827
|
+
success: true,
|
|
3828
|
+
tx: swapResult.tx,
|
|
3829
|
+
type: "sell",
|
|
3830
|
+
asset: params.asset,
|
|
3831
|
+
amount: sellAmountAsset,
|
|
3832
|
+
price,
|
|
3833
|
+
usdValue: swapResult.toAmount,
|
|
3834
|
+
fee: swapResult.fee,
|
|
3835
|
+
gasCost: swapResult.gasCost,
|
|
3836
|
+
gasMethod: swapResult.gasMethod,
|
|
3837
|
+
realizedPnL,
|
|
3838
|
+
position
|
|
3839
|
+
};
|
|
3840
|
+
}
|
|
3841
|
+
async getPortfolio() {
|
|
3842
|
+
const positions = this.portfolio.getPositions();
|
|
3843
|
+
const realizedPnL = this.portfolio.getRealizedPnL();
|
|
3844
|
+
const prices = {};
|
|
3845
|
+
const swapAdapter = this.registry.listSwap()[0];
|
|
3846
|
+
for (const asset of Object.keys(INVESTMENT_ASSETS)) {
|
|
3847
|
+
try {
|
|
3848
|
+
if (asset === "SUI" && swapAdapter) {
|
|
3849
|
+
prices[asset] = await swapAdapter.getPoolPrice();
|
|
3850
|
+
} else if (swapAdapter) {
|
|
3851
|
+
const quote = await swapAdapter.getQuote("USDC", asset, 1);
|
|
3852
|
+
prices[asset] = quote.expectedOutput > 0 ? 1 / quote.expectedOutput : 0;
|
|
3853
|
+
}
|
|
3854
|
+
} catch {
|
|
3855
|
+
prices[asset] = 0;
|
|
3856
|
+
}
|
|
3857
|
+
}
|
|
3858
|
+
const enriched = [];
|
|
3859
|
+
for (const pos of positions) {
|
|
3860
|
+
const currentPrice = prices[pos.asset] ?? 0;
|
|
3861
|
+
let totalAmount = pos.totalAmount;
|
|
3862
|
+
let costBasis = pos.costBasis;
|
|
3863
|
+
if (pos.asset in INVESTMENT_ASSETS) {
|
|
3864
|
+
try {
|
|
3865
|
+
const assetInfo = SUPPORTED_ASSETS[pos.asset];
|
|
3866
|
+
const bal = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
|
|
3867
|
+
const walletAmount = Number(bal.totalBalance) / 10 ** assetInfo.decimals;
|
|
3868
|
+
const gasReserve = pos.asset === "SUI" ? GAS_RESERVE_MIN : 0;
|
|
3869
|
+
const actualHeld = Math.max(0, walletAmount - gasReserve);
|
|
3870
|
+
if (actualHeld < totalAmount) {
|
|
3871
|
+
const ratio = totalAmount > 0 ? actualHeld / totalAmount : 0;
|
|
3872
|
+
costBasis *= ratio;
|
|
3873
|
+
totalAmount = actualHeld;
|
|
3874
|
+
}
|
|
3875
|
+
} catch {
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
const currentValue = totalAmount * currentPrice;
|
|
3879
|
+
const unrealizedPnL = currentPrice > 0 ? currentValue - costBasis : 0;
|
|
3880
|
+
const unrealizedPnLPct = currentPrice > 0 && costBasis > 0 ? unrealizedPnL / costBasis * 100 : 0;
|
|
3881
|
+
enriched.push({
|
|
3882
|
+
asset: pos.asset,
|
|
3883
|
+
totalAmount,
|
|
3884
|
+
costBasis,
|
|
3885
|
+
avgPrice: pos.avgPrice,
|
|
3886
|
+
currentPrice,
|
|
3887
|
+
currentValue,
|
|
3888
|
+
unrealizedPnL,
|
|
3889
|
+
unrealizedPnLPct,
|
|
3890
|
+
trades: pos.trades
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
const totalInvested = enriched.reduce((sum, p) => sum + p.costBasis, 0);
|
|
3894
|
+
const totalValue = enriched.reduce((sum, p) => sum + p.currentValue, 0);
|
|
3895
|
+
const totalUnrealizedPnL = totalValue - totalInvested;
|
|
3896
|
+
const totalUnrealizedPnLPct = totalInvested > 0 ? totalUnrealizedPnL / totalInvested * 100 : 0;
|
|
3897
|
+
return {
|
|
3898
|
+
positions: enriched,
|
|
3899
|
+
totalInvested,
|
|
3900
|
+
totalValue,
|
|
3901
|
+
unrealizedPnL: totalUnrealizedPnL,
|
|
3902
|
+
unrealizedPnLPct: totalUnrealizedPnLPct,
|
|
3903
|
+
realizedPnL
|
|
3904
|
+
};
|
|
3905
|
+
}
|
|
3521
3906
|
// -- Info --
|
|
3522
3907
|
async positions() {
|
|
3523
3908
|
const allPositions = await this.registry.allPositions(this._address);
|
|
@@ -3845,6 +4230,17 @@ var T2000 = class _T2000 extends EventEmitter {
|
|
|
3845
4230
|
return attack(this.client, this.keypair, id, prompt, fee);
|
|
3846
4231
|
}
|
|
3847
4232
|
// -- Helpers --
|
|
4233
|
+
async getFreeBalance(asset) {
|
|
4234
|
+
if (!(asset in INVESTMENT_ASSETS)) return Infinity;
|
|
4235
|
+
const pos = this.portfolio.getPosition(asset);
|
|
4236
|
+
const invested = pos?.totalAmount ?? 0;
|
|
4237
|
+
if (invested <= 0) return Infinity;
|
|
4238
|
+
const assetInfo = SUPPORTED_ASSETS[asset];
|
|
4239
|
+
const balance = await this.client.getBalance({ owner: this._address, coinType: assetInfo.type });
|
|
4240
|
+
const walletAmount = Number(balance.totalBalance) / 10 ** assetInfo.decimals;
|
|
4241
|
+
const gasReserve = asset === "SUI" ? GAS_RESERVE_MIN : 0;
|
|
4242
|
+
return Math.max(0, walletAmount - invested - gasReserve);
|
|
4243
|
+
}
|
|
3848
4244
|
async resolveLending(protocol, asset, capability) {
|
|
3849
4245
|
if (protocol) {
|
|
3850
4246
|
const adapter = this.registry.getLending(protocol);
|
|
@@ -3993,6 +4389,6 @@ var allDescriptors = [
|
|
|
3993
4389
|
descriptor
|
|
3994
4390
|
];
|
|
3995
4391
|
|
|
3996
|
-
export { BPS_DENOMINATOR, CLOCK_ID, CetusAdapter, ContactManager, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, MIST_PER_SUI, NaviAdapter, OUTBOUND_OPS, ProtocolRegistry, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, SafeguardEnforcer, SafeguardError, SuilendAdapter, T2000, T2000Error, USDC_DECIMALS, addCollectFeeToTx, allDescriptors, calculateFee, descriptor3 as cetusDescriptor, executeAutoTopUp, executeWithGas, exportPrivateKey, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, getSentinelInfo, keypairFromPrivateKey, listSentinels, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, descriptor2 as naviDescriptor, rawToStable, rawToUsdc, requestAttack, saveKey, attack as sentinelAttack, descriptor as sentinelDescriptor, settleAttack, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, submitPrompt, suiToMist, descriptor4 as suilendDescriptor, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
|
|
4392
|
+
export { BPS_DENOMINATOR, CLOCK_ID, CetusAdapter, ContactManager, DEFAULT_MAX_LEVERAGE, DEFAULT_MAX_POSITION_SIZE, DEFAULT_NETWORK, DEFAULT_SAFEGUARD_CONFIG, GAS_RESERVE_MIN, INVESTMENT_ASSETS, MIST_PER_SUI, NaviAdapter, OUTBOUND_OPS, PERPS_MARKETS, PortfolioManager, ProtocolRegistry, SENTINEL, SUI_DECIMALS, SUPPORTED_ASSETS, SafeguardEnforcer, SafeguardError, SuilendAdapter, T2000, T2000Error, USDC_DECIMALS, addCollectFeeToTx, allDescriptors, calculateFee, descriptor3 as cetusDescriptor, executeAutoTopUp, executeWithGas, exportPrivateKey, formatAssetAmount, formatSui, formatUsd, generateKeypair, getAddress, getDecimals, getGasStatus, getPoolPrice, getRates, getSentinelInfo, keypairFromPrivateKey, listSentinels, loadKey, mapMoveAbortCode, mapWalletError, mistToSui, descriptor2 as naviDescriptor, rawToStable, rawToUsdc, requestAttack, saveKey, attack as sentinelAttack, descriptor as sentinelDescriptor, settleAttack, shouldAutoTopUp, simulateTransaction, solveHashcash, stableToRaw, submitPrompt, suiToMist, descriptor4 as suilendDescriptor, throwIfSimulationFailed, truncateAddress, usdcToRaw, validateAddress, walletExists };
|
|
3997
4393
|
//# sourceMappingURL=index.js.map
|
|
3998
4394
|
//# sourceMappingURL=index.js.map
|