@megatao/sdk 1.1.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/.env.example +37 -0
- package/CHANGELOG.md +19 -0
- package/README.md +199 -0
- package/bin/alf +4 -0
- package/cli/README.md +198 -0
- package/cli/TEST_MANUAL.md +577 -0
- package/cli/commands/account.ts +545 -0
- package/cli/commands/funding.ts +481 -0
- package/cli/commands/liquidation.ts +523 -0
- package/cli/commands/market.ts +590 -0
- package/cli/commands/orders.ts +395 -0
- package/cli/commands/position.ts +1085 -0
- package/cli/commands/shared/positionUtils.ts +239 -0
- package/cli/commands/trading.ts +483 -0
- package/cli/commands/utils.ts +281 -0
- package/cli/commands/vault.ts +522 -0
- package/cli/index.ts +169 -0
- package/cli/interactive.ts +530 -0
- package/cli/utils/client.ts +457 -0
- package/cli/utils/config.ts +226 -0
- package/cli/utils/display.ts +258 -0
- package/cli/utils/index.ts +10 -0
- package/cli/utils/prompts.ts +364 -0
- package/config.example.json +23 -0
- package/dist/AlphaFuturesClient.d.ts +36 -0
- package/dist/AlphaFuturesClient.d.ts.map +1 -0
- package/dist/AlphaFuturesClient.js +116 -0
- package/dist/AlphaFuturesClient.js.map +1 -0
- package/dist/abi/Alpha.json +5987 -0
- package/dist/abi/abis.d.ts +319 -0
- package/dist/abi/abis.d.ts.map +1 -0
- package/dist/abi/abis.js +128 -0
- package/dist/abi/abis.js.map +1 -0
- package/dist/abi/index.d.ts +11 -0
- package/dist/abi/index.d.ts.map +1 -0
- package/dist/abi/index.js +15 -0
- package/dist/abi/index.js.map +1 -0
- package/dist/config/contracts.config.d.ts +70 -0
- package/dist/config/contracts.config.d.ts.map +1 -0
- package/dist/config/contracts.config.js +137 -0
- package/dist/config/contracts.config.js.map +1 -0
- package/dist/config/environments/alpha.config.d.ts +17 -0
- package/dist/config/environments/alpha.config.d.ts.map +1 -0
- package/dist/config/environments/alpha.config.js +140 -0
- package/dist/config/environments/alpha.config.js.map +1 -0
- package/dist/config/environments/beta.config.d.ts +16 -0
- package/dist/config/environments/beta.config.d.ts.map +1 -0
- package/dist/config/environments/beta.config.js +131 -0
- package/dist/config/environments/beta.config.js.map +1 -0
- package/dist/config/environments/dev.config.d.ts +13 -0
- package/dist/config/environments/dev.config.d.ts.map +1 -0
- package/dist/config/environments/dev.config.js +123 -0
- package/dist/config/environments/dev.config.js.map +1 -0
- package/dist/config/environments/index.d.ts +48 -0
- package/dist/config/environments/index.d.ts.map +1 -0
- package/dist/config/environments/index.js +81 -0
- package/dist/config/environments/index.js.map +1 -0
- package/dist/config/environments/localhost.config.d.ts +16 -0
- package/dist/config/environments/localhost.config.d.ts.map +1 -0
- package/dist/config/environments/localhost.config.js +152 -0
- package/dist/config/environments/localhost.config.js.map +1 -0
- package/dist/config/environments/prod.config.d.ts +20 -0
- package/dist/config/environments/prod.config.d.ts.map +1 -0
- package/dist/config/environments/prod.config.js +143 -0
- package/dist/config/environments/prod.config.js.map +1 -0
- package/dist/config/index.d.ts +7 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +41 -0
- package/dist/config/index.js.map +1 -0
- package/dist/constants/assets.d.ts +76 -0
- package/dist/constants/assets.d.ts.map +1 -0
- package/dist/constants/assets.js +277 -0
- package/dist/constants/assets.js.map +1 -0
- package/dist/constants/contracts.d.ts +41 -0
- package/dist/constants/contracts.d.ts.map +1 -0
- package/dist/constants/contracts.js +57 -0
- package/dist/constants/contracts.js.map +1 -0
- package/dist/constants/index.d.ts +36 -0
- package/dist/constants/index.d.ts.map +1 -0
- package/dist/constants/index.js +75 -0
- package/dist/constants/index.js.map +1 -0
- package/dist/constants/networks.d.ts +32 -0
- package/dist/constants/networks.d.ts.map +1 -0
- package/dist/constants/networks.js +174 -0
- package/dist/constants/networks.js.map +1 -0
- package/dist/contracts/index.d.ts +5 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +21 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/viem/AlphaViem.d.ts +518 -0
- package/dist/contracts/viem/AlphaViem.d.ts.map +1 -0
- package/dist/contracts/viem/AlphaViem.js +1287 -0
- package/dist/contracts/viem/AlphaViem.js.map +1 -0
- package/dist/contracts/viem/PriceOracleViem.d.ts +71 -0
- package/dist/contracts/viem/PriceOracleViem.d.ts.map +1 -0
- package/dist/contracts/viem/PriceOracleViem.js +212 -0
- package/dist/contracts/viem/PriceOracleViem.js.map +1 -0
- package/dist/contracts/viem/index.d.ts +9 -0
- package/dist/contracts/viem/index.d.ts.map +1 -0
- package/dist/contracts/viem/index.js +17 -0
- package/dist/contracts/viem/index.js.map +1 -0
- package/dist/errors/index.d.ts +44 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +83 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/types/alpha.d.ts +299 -0
- package/dist/types/alpha.d.ts.map +1 -0
- package/dist/types/alpha.js +6 -0
- package/dist/types/alpha.js.map +1 -0
- package/dist/types/client.d.ts +24 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +13 -0
- package/dist/types/client.js.map +1 -0
- package/dist/types/contracts.d.ts +48 -0
- package/dist/types/contracts.d.ts.map +1 -0
- package/dist/types/contracts.js +6 -0
- package/dist/types/contracts.js.map +1 -0
- package/dist/types/funding.d.ts +27 -0
- package/dist/types/funding.d.ts.map +1 -0
- package/dist/types/funding.js +6 -0
- package/dist/types/funding.js.map +1 -0
- package/dist/types/index.d.ts +92 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +47 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/liquidation.d.ts +20 -0
- package/dist/types/liquidation.d.ts.map +1 -0
- package/dist/types/liquidation.js +6 -0
- package/dist/types/liquidation.js.map +1 -0
- package/dist/types/margin.d.ts +29 -0
- package/dist/types/margin.d.ts.map +1 -0
- package/dist/types/margin.js +6 -0
- package/dist/types/margin.js.map +1 -0
- package/dist/types/oracle.d.ts +21 -0
- package/dist/types/oracle.d.ts.map +1 -0
- package/dist/types/oracle.js +6 -0
- package/dist/types/oracle.js.map +1 -0
- package/dist/types/positions.d.ts +43 -0
- package/dist/types/positions.d.ts.map +1 -0
- package/dist/types/positions.js +13 -0
- package/dist/types/positions.js.map +1 -0
- package/dist/utils/calculations.d.ts +84 -0
- package/dist/utils/calculations.d.ts.map +1 -0
- package/dist/utils/calculations.js +155 -0
- package/dist/utils/calculations.js.map +1 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +129 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/events.d.ts +40 -0
- package/dist/utils/events.d.ts.map +1 -0
- package/dist/utils/events.js +73 -0
- package/dist/utils/events.js.map +1 -0
- package/dist/utils/format.d.ts +40 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +86 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/network.d.ts +52 -0
- package/dist/utils/network.d.ts.map +1 -0
- package/dist/utils/network.js +192 -0
- package/dist/utils/network.js.map +1 -0
- package/dist/utils/positionCalculations.d.ts +145 -0
- package/dist/utils/positionCalculations.d.ts.map +1 -0
- package/dist/utils/positionCalculations.js +278 -0
- package/dist/utils/positionCalculations.js.map +1 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +68 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/README.md +40 -0
- package/docs/api/API.md +831 -0
- package/docs/guides/GETTING_STARTED.md +316 -0
- package/docs/guides/TRADING_GUIDE.md +677 -0
- package/docs/integration/INTEGRATION_GUIDE.md +1679 -0
- package/docs/integration/VIEM_INTEGRATION.md +294 -0
- package/docs/reference/CLI_QUICK_REFERENCE.md +197 -0
- package/docs/reference/TROUBLESHOOTING.md +922 -0
- package/package.json +113 -0
- package/src/AlphaFuturesClient.ts +158 -0
- package/src/abi/.gitkeep +1 -0
- package/src/abi/Alpha.json +5987 -0
- package/src/abi/README.md +99 -0
- package/src/abi/abis.ts +131 -0
- package/src/abi/index.ts +13 -0
- package/src/config/contracts.config.ts +186 -0
- package/src/config/environments/alpha.config.ts +139 -0
- package/src/config/environments/beta.config.ts +130 -0
- package/src/config/environments/dev.config.ts +122 -0
- package/src/config/environments/index.ts +87 -0
- package/src/config/environments/localhost.config.ts +153 -0
- package/src/config/environments/prod.config.ts +142 -0
- package/src/config/index.ts +29 -0
- package/src/constants/assets.ts +299 -0
- package/src/constants/contracts.ts +64 -0
- package/src/constants/index.ts +69 -0
- package/src/constants/networks.ts +182 -0
- package/src/contracts/index.ts +5 -0
- package/src/contracts/viem/AlphaViem.ts +1615 -0
- package/src/contracts/viem/PriceOracleViem.ts +272 -0
- package/src/contracts/viem/index.ts +11 -0
- package/src/errors/index.ts +87 -0
- package/src/index.ts +59 -0
- package/src/types/VIEM_TYPES_README.md +70 -0
- package/src/types/alpha.ts +358 -0
- package/src/types/client.ts +27 -0
- package/src/types/contracts.ts +74 -0
- package/src/types/funding.ts +31 -0
- package/src/types/index.ts +108 -0
- package/src/types/liquidation.ts +23 -0
- package/src/types/margin.ts +34 -0
- package/src/types/oracle.ts +24 -0
- package/src/types/positions.ts +48 -0
- package/src/utils/calculations.ts +175 -0
- package/src/utils/errors.ts +147 -0
- package/src/utils/events.ts +98 -0
- package/src/utils/format.ts +84 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/network.ts +212 -0
- package/src/utils/positionCalculations.ts +317 -0
- package/src/utils/validation.ts +76 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network utilities for Alpha Futures SDK
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Chain, PublicClient, createPublicClient, http } from 'viem';
|
|
6
|
+
import { mainnet, sepolia } from 'viem/chains';
|
|
7
|
+
import { NetworkConfig } from '../types';
|
|
8
|
+
import {
|
|
9
|
+
NETWORK_CONFIGS,
|
|
10
|
+
getCurrentNetwork,
|
|
11
|
+
getNetworkByChainId,
|
|
12
|
+
isValidNetwork,
|
|
13
|
+
} from '../constants/networks';
|
|
14
|
+
|
|
15
|
+
export interface NetworkInfo {
|
|
16
|
+
chainId: number;
|
|
17
|
+
name: string;
|
|
18
|
+
isSupported: boolean;
|
|
19
|
+
config?: NetworkConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a custom chain configuration
|
|
24
|
+
*/
|
|
25
|
+
export function createCustomChain(config: NetworkConfig): Chain {
|
|
26
|
+
return {
|
|
27
|
+
id: config.chainId,
|
|
28
|
+
name: config.name,
|
|
29
|
+
nativeCurrency: {
|
|
30
|
+
decimals: 18,
|
|
31
|
+
name: 'TAO',
|
|
32
|
+
symbol: 'TAO',
|
|
33
|
+
},
|
|
34
|
+
rpcUrls: {
|
|
35
|
+
default: {
|
|
36
|
+
http: config.rpcUrl ? [config.rpcUrl] : [],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
blockExplorers: config.blockExplorer
|
|
40
|
+
? {
|
|
41
|
+
default: {
|
|
42
|
+
name: `${config.name} Explorer`,
|
|
43
|
+
url: config.blockExplorer,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get chain configuration by ID
|
|
52
|
+
*/
|
|
53
|
+
export function getChainById(chainId: number): Chain {
|
|
54
|
+
switch (chainId) {
|
|
55
|
+
case 1:
|
|
56
|
+
return mainnet;
|
|
57
|
+
case 11155111:
|
|
58
|
+
return sepolia;
|
|
59
|
+
default: {
|
|
60
|
+
// Try to find in our configs
|
|
61
|
+
const config = getNetworkByChainId(chainId);
|
|
62
|
+
if (config) {
|
|
63
|
+
return createCustomChain(config);
|
|
64
|
+
}
|
|
65
|
+
// Return a basic chain config
|
|
66
|
+
return {
|
|
67
|
+
id: chainId,
|
|
68
|
+
name: `Chain ${chainId}`,
|
|
69
|
+
nativeCurrency: {
|
|
70
|
+
decimals: 18,
|
|
71
|
+
name: 'ETH',
|
|
72
|
+
symbol: 'ETH',
|
|
73
|
+
},
|
|
74
|
+
rpcUrls: {
|
|
75
|
+
default: { http: [] },
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detects the current network from a public client
|
|
84
|
+
*/
|
|
85
|
+
export async function detectNetwork(client: PublicClient): Promise<NetworkInfo> {
|
|
86
|
+
try {
|
|
87
|
+
const chainId = await client.getChainId();
|
|
88
|
+
const config = getNetworkByChainId(chainId);
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
chainId,
|
|
92
|
+
name: config?.name || `Unknown (${chainId})`,
|
|
93
|
+
isSupported: !!config,
|
|
94
|
+
config,
|
|
95
|
+
};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw new Error(`Failed to detect network: ${error}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Switches to a different network (for wallet providers like MetaMask)
|
|
103
|
+
*/
|
|
104
|
+
export async function switchNetwork(
|
|
105
|
+
provider: Record<string, unknown>, // EIP-1193 provider
|
|
106
|
+
targetNetwork: string,
|
|
107
|
+
): Promise<void> {
|
|
108
|
+
if (!isValidNetwork(targetNetwork)) {
|
|
109
|
+
throw new Error(`Invalid network: ${targetNetwork}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const config = NETWORK_CONFIGS[targetNetwork];
|
|
113
|
+
const chainIdHex = `0x${config.chainId.toString(16)}`;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Try to switch to the network
|
|
117
|
+
await (provider as any).request({
|
|
118
|
+
method: 'wallet_switchEthereumChain',
|
|
119
|
+
params: [{ chainId: chainIdHex }],
|
|
120
|
+
});
|
|
121
|
+
} catch (error: unknown) {
|
|
122
|
+
// Network not added to wallet
|
|
123
|
+
if ((error as { code?: number }).code === 4902) {
|
|
124
|
+
await addNetwork(provider, targetNetwork);
|
|
125
|
+
} else {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Adds a network to the wallet
|
|
133
|
+
*/
|
|
134
|
+
export async function addNetwork(
|
|
135
|
+
provider: Record<string, unknown>, // EIP-1193 provider
|
|
136
|
+
networkName: string,
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
const config = NETWORK_CONFIGS[networkName];
|
|
139
|
+
if (!config) {
|
|
140
|
+
throw new Error(`Network configuration not found for: ${networkName}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const params = {
|
|
144
|
+
chainId: `0x${config.chainId.toString(16)}`,
|
|
145
|
+
chainName: config.name,
|
|
146
|
+
nativeCurrency: {
|
|
147
|
+
name: 'TAO',
|
|
148
|
+
symbol: 'TAO',
|
|
149
|
+
decimals: 18,
|
|
150
|
+
},
|
|
151
|
+
rpcUrls: config.rpcUrl ? [config.rpcUrl] : [],
|
|
152
|
+
blockExplorerUrls: config.blockExplorer ? [config.blockExplorer] : undefined,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
await (provider as any).request({
|
|
156
|
+
method: 'wallet_addEthereumChain',
|
|
157
|
+
params: [params],
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Creates a public client for a specific network
|
|
163
|
+
*/
|
|
164
|
+
export function createNetworkClient(networkName: string): PublicClient {
|
|
165
|
+
const config = NETWORK_CONFIGS[networkName];
|
|
166
|
+
if (!config || !config.rpcUrl) {
|
|
167
|
+
throw new Error(`Invalid network or missing RPC URL: ${networkName}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const chain = getChainById(config.chainId);
|
|
171
|
+
|
|
172
|
+
return createPublicClient({
|
|
173
|
+
chain,
|
|
174
|
+
transport: http(config.rpcUrl),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Validates chain ID matches expected network
|
|
180
|
+
*/
|
|
181
|
+
export async function validateChainId(
|
|
182
|
+
client: PublicClient,
|
|
183
|
+
expectedNetwork: string,
|
|
184
|
+
): Promise<boolean> {
|
|
185
|
+
const config = NETWORK_CONFIGS[expectedNetwork];
|
|
186
|
+
if (!config) return false;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const chainId = await client.getChainId();
|
|
190
|
+
return chainId === config.chainId;
|
|
191
|
+
} catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Get all supported networks
|
|
198
|
+
*/
|
|
199
|
+
export function getSupportedNetworks(): string[] {
|
|
200
|
+
return Object.keys(NETWORK_CONFIGS);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Format network name for display
|
|
205
|
+
*/
|
|
206
|
+
export function formatNetworkName(network: string): string {
|
|
207
|
+
const config = NETWORK_CONFIGS[network];
|
|
208
|
+
return config?.name || network;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Re-export from constants for backward compatibility
|
|
212
|
+
export { getCurrentNetwork, isValidNetwork, NETWORK_CONFIGS };
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { parseEther, formatEther } from 'viem';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Position calculation utilities for Alpha Futures protocol
|
|
5
|
+
* Supports both collateral-first and size-first paradigms
|
|
6
|
+
* All calculations use 18 decimal precision (wei format in contracts)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// COLLATERAL-FIRST PARADIGM (NEW - Primary UX)
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Calculate position size in tokens from collateral and leverage
|
|
15
|
+
* @param collateralUSD - Collateral amount in USD (as number, e.g., 100 for $100)
|
|
16
|
+
* @param leverage - Leverage multiplier (as number, e.g., 3.5 for 3.5x)
|
|
17
|
+
* @param tokenPriceUSD - Token price in USD (as number, e.g., 0.019)
|
|
18
|
+
* @returns Position size as bigint in wei (18 decimals)
|
|
19
|
+
* @example
|
|
20
|
+
* // $100 collateral, 3.5x leverage, $0.019 token price
|
|
21
|
+
* // = (100 * 3.5) / 0.019 = 18,421 tokens
|
|
22
|
+
* calculatePositionSizeFromCollateral(100, 3.5, 0.019)
|
|
23
|
+
* // Returns: 18421052631578947368421n (18421.05... * 1e18)
|
|
24
|
+
*/
|
|
25
|
+
export function calculatePositionSizeFromCollateral(
|
|
26
|
+
collateralUSD: number,
|
|
27
|
+
leverage: number,
|
|
28
|
+
tokenPriceUSD: number,
|
|
29
|
+
): bigint {
|
|
30
|
+
if (tokenPriceUSD <= 0) {
|
|
31
|
+
throw new Error('Token price must be greater than 0');
|
|
32
|
+
}
|
|
33
|
+
if (leverage <= 0) {
|
|
34
|
+
throw new Error('Leverage must be greater than 0');
|
|
35
|
+
}
|
|
36
|
+
if (collateralUSD < 0) {
|
|
37
|
+
throw new Error('Collateral must be non-negative');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const positionSizeInTokens = (collateralUSD * leverage) / tokenPriceUSD;
|
|
41
|
+
return parseEther(positionSizeInTokens.toString());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calculate position value in USD from collateral and leverage
|
|
46
|
+
* @param collateralUSD - Collateral amount in USD
|
|
47
|
+
* @param leverage - Leverage multiplier
|
|
48
|
+
* @returns Position value in USD
|
|
49
|
+
* @example
|
|
50
|
+
* // $100 collateral, 3.5x leverage
|
|
51
|
+
* calculatePositionValue(100, 3.5) // Returns: 350
|
|
52
|
+
*/
|
|
53
|
+
export function calculatePositionValue(collateralUSD: number, leverage: number): number {
|
|
54
|
+
if (leverage <= 0) {
|
|
55
|
+
throw new Error('Leverage must be greater than 0');
|
|
56
|
+
}
|
|
57
|
+
if (collateralUSD < 0) {
|
|
58
|
+
throw new Error('Collateral must be non-negative');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return collateralUSD * leverage;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// SIZE-FIRST PARADIGM (OLD - Backward Compatibility)
|
|
66
|
+
// ============================================================================
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Calculate required collateral from position size and leverage
|
|
70
|
+
* @param positionSizeUSD - Position size in USD
|
|
71
|
+
* @param leverage - Leverage multiplier
|
|
72
|
+
* @returns Required collateral in USD
|
|
73
|
+
* @example
|
|
74
|
+
* // $30 position size, 3x leverage
|
|
75
|
+
* // = 30 / 3 = $10 collateral required
|
|
76
|
+
* calculateCollateralFromSize(30, 3) // Returns: 10
|
|
77
|
+
*/
|
|
78
|
+
export function calculateCollateralFromSize(positionSizeUSD: number, leverage: number): number {
|
|
79
|
+
if (leverage <= 0) {
|
|
80
|
+
throw new Error('Leverage must be greater than 0');
|
|
81
|
+
}
|
|
82
|
+
if (positionSizeUSD < 0) {
|
|
83
|
+
throw new Error('Position size must be non-negative');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return positionSizeUSD / leverage;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Calculate leverage from position size and collateral
|
|
91
|
+
* @param positionSizeUSD - Position size in USD
|
|
92
|
+
* @param collateralUSD - Collateral amount in USD
|
|
93
|
+
* @returns Calculated leverage
|
|
94
|
+
* @example
|
|
95
|
+
* calculateLeverageFromSizeAndCollateral(30, 10) // Returns: 3
|
|
96
|
+
*/
|
|
97
|
+
export function calculateLeverageFromSizeAndCollateral(
|
|
98
|
+
positionSizeUSD: number,
|
|
99
|
+
collateralUSD: number,
|
|
100
|
+
): number {
|
|
101
|
+
if (collateralUSD <= 0) {
|
|
102
|
+
throw new Error('Collateral must be greater than 0');
|
|
103
|
+
}
|
|
104
|
+
if (positionSizeUSD < 0) {
|
|
105
|
+
throw new Error('Position size must be non-negative');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return positionSizeUSD / collateralUSD;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// CONVERSION UTILITIES
|
|
113
|
+
// ============================================================================
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Convert USD amount to token amount using price
|
|
117
|
+
* @param amountUSD - Amount in USD
|
|
118
|
+
* @param tokenPriceUSD - Token price in USD
|
|
119
|
+
* @returns Token amount as bigint in wei
|
|
120
|
+
* @example
|
|
121
|
+
* // $100 USD at $0.019 per token
|
|
122
|
+
* usdToTokens(100, 0.019) // Returns: ~5263157894736842105263n (5263.16 * 1e18)
|
|
123
|
+
*/
|
|
124
|
+
export function usdToTokens(amountUSD: number, tokenPriceUSD: number): bigint {
|
|
125
|
+
if (tokenPriceUSD <= 0) {
|
|
126
|
+
throw new Error('Token price must be greater than 0');
|
|
127
|
+
}
|
|
128
|
+
if (amountUSD < 0) {
|
|
129
|
+
throw new Error('Amount must be non-negative');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const tokens = amountUSD / tokenPriceUSD;
|
|
133
|
+
return parseEther(tokens.toString());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Convert token amount to USD using price
|
|
138
|
+
* @param tokenAmount - Token amount as bigint in wei
|
|
139
|
+
* @param tokenPriceUSD - Token price in USD
|
|
140
|
+
* @returns Amount in USD
|
|
141
|
+
* @example
|
|
142
|
+
* // 1000 tokens at $0.019 per token
|
|
143
|
+
* tokensToUSD(parseEther('1000'), 0.019) // Returns: 19
|
|
144
|
+
*/
|
|
145
|
+
export function tokensToUSD(tokenAmount: bigint, tokenPriceUSD: number): number {
|
|
146
|
+
if (tokenPriceUSD < 0) {
|
|
147
|
+
throw new Error('Token price must be non-negative');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const tokens = parseFloat(formatEther(tokenAmount));
|
|
151
|
+
return tokens * tokenPriceUSD;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Convert leverage number to wei format (1e18 precision)
|
|
156
|
+
* @param leverage - Leverage as number (e.g., 3.5)
|
|
157
|
+
* @returns Leverage as bigint in wei (e.g., 3.5e18)
|
|
158
|
+
* @example
|
|
159
|
+
* leverageToWei(3.5) // Returns: 3500000000000000000n
|
|
160
|
+
*/
|
|
161
|
+
export function leverageToWei(leverage: number): bigint {
|
|
162
|
+
if (leverage < 0) {
|
|
163
|
+
throw new Error('Leverage must be non-negative');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return parseEther(leverage.toString());
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Convert leverage wei format to number
|
|
171
|
+
* @param leverageWei - Leverage as bigint in wei
|
|
172
|
+
* @returns Leverage as number
|
|
173
|
+
* @example
|
|
174
|
+
* leverageFromWei(3500000000000000000n) // Returns: 3.5
|
|
175
|
+
*/
|
|
176
|
+
export function leverageFromWei(leverageWei: bigint): number {
|
|
177
|
+
return parseFloat(formatEther(leverageWei));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================================
|
|
181
|
+
// PROFIT/LOSS CALCULATIONS
|
|
182
|
+
// ============================================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Calculate P&L percentage from price movement and leverage
|
|
186
|
+
* @param priceChangePercent - Price change in percentage (e.g., 10 for +10%)
|
|
187
|
+
* @param leverage - Leverage multiplier
|
|
188
|
+
* @param isLong - True for long position, false for short
|
|
189
|
+
* @returns P&L percentage
|
|
190
|
+
* @example
|
|
191
|
+
* // Long position, 10x leverage, +10% price move
|
|
192
|
+
* calculatePnLPercent(10, 10, true) // Returns: 100 (100% gain)
|
|
193
|
+
*
|
|
194
|
+
* // Short position, 5x leverage, +10% price move
|
|
195
|
+
* calculatePnLPercent(10, 5, false) // Returns: -50 (50% loss)
|
|
196
|
+
*/
|
|
197
|
+
export function calculatePnLPercent(
|
|
198
|
+
priceChangePercent: number,
|
|
199
|
+
leverage: number,
|
|
200
|
+
isLong: boolean,
|
|
201
|
+
): number {
|
|
202
|
+
if (leverage < 0) {
|
|
203
|
+
throw new Error('Leverage must be non-negative');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const direction = isLong ? 1 : -1;
|
|
207
|
+
return priceChangePercent * leverage * direction;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Calculate absolute P&L amount in USD from price movement
|
|
212
|
+
* @param collateralUSD - Collateral amount in USD
|
|
213
|
+
* @param leverage - Leverage multiplier
|
|
214
|
+
* @param priceChangePercent - Price change in percentage
|
|
215
|
+
* @param isLong - True for long position, false for short
|
|
216
|
+
* @returns P&L amount in USD
|
|
217
|
+
* @example
|
|
218
|
+
* // $100 collateral, 3x leverage, +10% price move, long
|
|
219
|
+
* calculatePnLAmount(100, 3, 10, true) // Returns: 30 (30% of $100)
|
|
220
|
+
*/
|
|
221
|
+
export function calculatePnLAmount(
|
|
222
|
+
collateralUSD: number,
|
|
223
|
+
leverage: number,
|
|
224
|
+
priceChangePercent: number,
|
|
225
|
+
isLong: boolean,
|
|
226
|
+
): number {
|
|
227
|
+
if (leverage <= 0) {
|
|
228
|
+
throw new Error('Leverage must be greater than 0');
|
|
229
|
+
}
|
|
230
|
+
if (collateralUSD < 0) {
|
|
231
|
+
throw new Error('Collateral must be non-negative');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const pnlPercent = calculatePnLPercent(priceChangePercent, leverage, isLong);
|
|
235
|
+
return (collateralUSD * pnlPercent) / 100;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Calculate liquidation price from entry price and leverage
|
|
240
|
+
* @param entryPrice - Entry price of the position
|
|
241
|
+
* @param leverage - Leverage multiplier
|
|
242
|
+
* @param isLong - True for long position, false for short
|
|
243
|
+
* @param maintenanceMarginPercent - Maintenance margin percentage (default 20%)
|
|
244
|
+
* @returns Liquidation price
|
|
245
|
+
* @example
|
|
246
|
+
* // Long position at $100, 3x leverage, 20% maintenance margin
|
|
247
|
+
* // Liquidation at ~26.67% loss from entry (80% / 3)
|
|
248
|
+
* calculateLiquidationPriceFromLeverage(100, 3, true) // Returns: ~73.33
|
|
249
|
+
*/
|
|
250
|
+
export function calculateLiquidationPriceFromLeverage(
|
|
251
|
+
entryPrice: number,
|
|
252
|
+
leverage: number,
|
|
253
|
+
isLong: boolean,
|
|
254
|
+
maintenanceMarginPercent: number = 20,
|
|
255
|
+
): number {
|
|
256
|
+
if (entryPrice <= 0) {
|
|
257
|
+
throw new Error('Entry price must be greater than 0');
|
|
258
|
+
}
|
|
259
|
+
if (leverage <= 0) {
|
|
260
|
+
throw new Error('Leverage must be greater than 0');
|
|
261
|
+
}
|
|
262
|
+
if (maintenanceMarginPercent < 0 || maintenanceMarginPercent >= 100) {
|
|
263
|
+
throw new Error('Maintenance margin must be between 0 and 100');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Initial margin % = 100 / leverage
|
|
267
|
+
// Max loss before liquidation = (initial margin % - maintenance margin %)
|
|
268
|
+
// For long: liquidation = entry * (1 - maxLossPercent / 100)
|
|
269
|
+
// For short: liquidation = entry * (1 + maxLossPercent / 100)
|
|
270
|
+
|
|
271
|
+
const initialMarginPercent = 100 / leverage;
|
|
272
|
+
const maxLossPercent = initialMarginPercent - maintenanceMarginPercent;
|
|
273
|
+
|
|
274
|
+
if (isLong) {
|
|
275
|
+
return entryPrice * (1 - maxLossPercent / 100);
|
|
276
|
+
} else {
|
|
277
|
+
return entryPrice * (1 + maxLossPercent / 100);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Calculate required margin for a position size and leverage
|
|
283
|
+
* @param positionValueUSD - Position value in USD
|
|
284
|
+
* @param leverage - Leverage multiplier
|
|
285
|
+
* @returns Required margin in USD
|
|
286
|
+
* @example
|
|
287
|
+
* calculateRequiredMargin(350, 3.5) // Returns: 100
|
|
288
|
+
*/
|
|
289
|
+
export function calculateRequiredMargin(positionValueUSD: number, leverage: number): number {
|
|
290
|
+
if (leverage <= 0) {
|
|
291
|
+
throw new Error('Leverage must be greater than 0');
|
|
292
|
+
}
|
|
293
|
+
if (positionValueUSD < 0) {
|
|
294
|
+
throw new Error('Position value must be non-negative');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return positionValueUSD / leverage;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Calculate maximum position size from available collateral
|
|
302
|
+
* @param collateralUSD - Available collateral in USD
|
|
303
|
+
* @param leverage - Desired leverage
|
|
304
|
+
* @param tokenPriceUSD - Token price in USD
|
|
305
|
+
* @returns Maximum position size in tokens (as bigint in wei)
|
|
306
|
+
* @example
|
|
307
|
+
* // $100 collateral, 3x leverage, $0.019 token price
|
|
308
|
+
* calculateMaxPositionSize(100, 3, 0.019)
|
|
309
|
+
* // Returns: 15789473684210526315789n (~15789.47 tokens)
|
|
310
|
+
*/
|
|
311
|
+
export function calculateMaxPositionSize(
|
|
312
|
+
collateralUSD: number,
|
|
313
|
+
leverage: number,
|
|
314
|
+
tokenPriceUSD: number,
|
|
315
|
+
): bigint {
|
|
316
|
+
return calculatePositionSizeFromCollateral(collateralUSD, leverage, tokenPriceUSD);
|
|
317
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isAddress } from 'viem';
|
|
6
|
+
import { ValidationError } from '../errors';
|
|
7
|
+
import { MIN_POSITION_SIZE, MAX_LEVERAGE, PRECISION } from '../constants';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Validate Ethereum address
|
|
11
|
+
*/
|
|
12
|
+
export function validateAddress(address: string, fieldName: string = 'address'): void {
|
|
13
|
+
if (!isAddress(address)) {
|
|
14
|
+
throw new ValidationError(`Invalid Ethereum address: ${address}`, fieldName);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate position size
|
|
20
|
+
*/
|
|
21
|
+
export function validatePositionSize(size: bigint): void {
|
|
22
|
+
if (size < MIN_POSITION_SIZE) {
|
|
23
|
+
throw new ValidationError(
|
|
24
|
+
`Position size must be at least ${MIN_POSITION_SIZE / PRECISION} TAO`,
|
|
25
|
+
'size',
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Validate leverage
|
|
32
|
+
*/
|
|
33
|
+
export function validateLeverage(leverage: bigint): void {
|
|
34
|
+
if (leverage < 1n || leverage > MAX_LEVERAGE) {
|
|
35
|
+
throw new ValidationError(`Leverage must be between 1 and ${MAX_LEVERAGE}`, 'leverage');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate positive amount
|
|
41
|
+
*/
|
|
42
|
+
export function validatePositiveAmount(amount: bigint, fieldName: string = 'amount'): void {
|
|
43
|
+
if (amount <= 0n) {
|
|
44
|
+
throw new ValidationError(`${fieldName} must be positive`, fieldName);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate asset symbol
|
|
50
|
+
*/
|
|
51
|
+
export function validateAsset(asset: string): void {
|
|
52
|
+
if (!asset || asset.trim().length === 0) {
|
|
53
|
+
throw new ValidationError('Asset symbol cannot be empty', 'asset');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Basic validation for asset symbol format
|
|
57
|
+
const assetRegex = /^[A-Z0-9]{2,10}$/;
|
|
58
|
+
if (!assetRegex.test(asset)) {
|
|
59
|
+
throw new ValidationError(
|
|
60
|
+
'Asset symbol must be 2-10 uppercase alphanumeric characters',
|
|
61
|
+
'asset',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validate margin amount
|
|
68
|
+
*/
|
|
69
|
+
export function validateMargin(margin: bigint, requiredMargin: bigint): void {
|
|
70
|
+
if (margin < requiredMargin) {
|
|
71
|
+
throw new ValidationError(
|
|
72
|
+
`Insufficient margin. Required: ${requiredMargin}, Provided: ${margin}`,
|
|
73
|
+
'margin',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|