@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,457 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for initializing and managing the Alpha Futures contracts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { localhost, mainnet, sepolia } from 'viem/chains';
|
|
8
|
+
|
|
9
|
+
import type { Chain } from 'viem';
|
|
10
|
+
import { AlphaFuturesClient, type AlphaClientConfig } from '../../src/AlphaFuturesClient';
|
|
11
|
+
import type { Environment } from '../../src/config/environments';
|
|
12
|
+
import { NETWORK_CONFIGS } from '../../src/constants/networks';
|
|
13
|
+
import { SUBNET_MARKETS, getAssetAddress, isSubnetMarket } from '../../src/constants/assets';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import type { PublicClient } from 'viem';
|
|
16
|
+
|
|
17
|
+
export interface ClientOptions {
|
|
18
|
+
network?: string;
|
|
19
|
+
rpc?: string;
|
|
20
|
+
key?: string;
|
|
21
|
+
privateKey?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Initialize and return Alpha Futures client
|
|
26
|
+
*/
|
|
27
|
+
export async function getClient(options: ClientOptions): Promise<AlphaFuturesClient> {
|
|
28
|
+
const privateKey = options.key || options.privateKey || process.env.PRIVATE_KEY;
|
|
29
|
+
const rpcUrl = options.rpc;
|
|
30
|
+
|
|
31
|
+
// Auto-detect network: if custom RPC URL points to localhost, override to use 'localhost' network
|
|
32
|
+
// This ensures contract addresses are read from process.env instead of hardcoded production addresses
|
|
33
|
+
let network = options.network;
|
|
34
|
+
if (
|
|
35
|
+
rpcUrl &&
|
|
36
|
+
(rpcUrl.includes('localhost') ||
|
|
37
|
+
rpcUrl.includes('127.0.0.1') ||
|
|
38
|
+
rpcUrl.includes('::1') ||
|
|
39
|
+
rpcUrl.includes(':8545'))
|
|
40
|
+
) {
|
|
41
|
+
network = 'localhost';
|
|
42
|
+
} else if (!network) {
|
|
43
|
+
network = 'localhost'; // Default to localhost for safety
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Get contract addresses for the network
|
|
47
|
+
const networkConfig = NETWORK_CONFIGS[network];
|
|
48
|
+
if (!networkConfig) {
|
|
49
|
+
throw new Error(`Network ${network} not found in configuration`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Get the chain based on network - use RPC from config
|
|
53
|
+
let chain: Chain;
|
|
54
|
+
switch (network) {
|
|
55
|
+
case 'mainnet':
|
|
56
|
+
chain = mainnet;
|
|
57
|
+
break;
|
|
58
|
+
case 'bittensor':
|
|
59
|
+
// Use RPC URL from network config
|
|
60
|
+
chain = {
|
|
61
|
+
id: networkConfig.chainId,
|
|
62
|
+
name: networkConfig.name,
|
|
63
|
+
nativeCurrency: {
|
|
64
|
+
decimals: 18,
|
|
65
|
+
name: 'TAO',
|
|
66
|
+
symbol: 'TAO',
|
|
67
|
+
},
|
|
68
|
+
rpcUrls: {
|
|
69
|
+
default: {
|
|
70
|
+
http: [
|
|
71
|
+
rpcUrl || networkConfig.rpcUrl || 'https://bittensor-finney.api.onfinality.io/public',
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
blockExplorers: {
|
|
76
|
+
default: {
|
|
77
|
+
name: 'Bittensor Explorer',
|
|
78
|
+
url: networkConfig.blockExplorer || 'https://evm.taostats.io',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
break;
|
|
83
|
+
case 'sepolia':
|
|
84
|
+
chain = sepolia;
|
|
85
|
+
break;
|
|
86
|
+
case 'localhost':
|
|
87
|
+
default:
|
|
88
|
+
// Use custom localhost/anvil chain configuration
|
|
89
|
+
chain = {
|
|
90
|
+
id: 31337,
|
|
91
|
+
name: 'Localhost',
|
|
92
|
+
nativeCurrency: {
|
|
93
|
+
decimals: 18,
|
|
94
|
+
name: 'Ether',
|
|
95
|
+
symbol: 'ETH',
|
|
96
|
+
},
|
|
97
|
+
rpcUrls: {
|
|
98
|
+
default: { http: [rpcUrl || networkConfig.rpcUrl || 'http://127.0.0.1:8545'] },
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const config: AlphaClientConfig = {
|
|
105
|
+
rpcUrl: rpcUrl || chain.rpcUrls.default.http[0],
|
|
106
|
+
privateKey: privateKey as `0x${string}` | undefined,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
// Map network string to Environment type
|
|
111
|
+
const environment = network as Environment;
|
|
112
|
+
return new AlphaFuturesClient(environment, config);
|
|
113
|
+
} catch (error: any) {
|
|
114
|
+
throw new Error(`Failed to initialize client: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Validates if a string is a valid Ethereum address
|
|
120
|
+
*/
|
|
121
|
+
function isValidAddress(address: string): boolean {
|
|
122
|
+
return /^0x[a-fA-F0-9]{40}$/.test(address);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get asset mapping for supported subnet markets
|
|
127
|
+
*/
|
|
128
|
+
export function getAssetMapping(): Record<string, `0x${string}`> {
|
|
129
|
+
const mapping: Record<string, `0x${string}`> = {};
|
|
130
|
+
|
|
131
|
+
// Map all subnet markets
|
|
132
|
+
Object.entries(SUBNET_MARKETS).forEach(([symbol, market]) => {
|
|
133
|
+
mapping[symbol] = market.marketAddress;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return mapping;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get market address for an asset symbol using subnet markets configuration
|
|
141
|
+
* Also supports BTC/ETH from environment variables for localhost testing
|
|
142
|
+
*/
|
|
143
|
+
export function getMarketAddress(asset: string): `0x${string}` {
|
|
144
|
+
const normalizedAsset = asset.toUpperCase();
|
|
145
|
+
|
|
146
|
+
// Check if it's a supported subnet market
|
|
147
|
+
if (isSubnetMarket(normalizedAsset)) {
|
|
148
|
+
return SUBNET_MARKETS[normalizedAsset].marketAddress;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Check for test assets from environment variables (localhost/anvil testing)
|
|
152
|
+
const testAssetEnvMap: Record<string, string> = {
|
|
153
|
+
BTC: 'ALPHA_BTC',
|
|
154
|
+
ETH: 'ALPHA_ETH',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (testAssetEnvMap[normalizedAsset]) {
|
|
158
|
+
const envVar = testAssetEnvMap[normalizedAsset];
|
|
159
|
+
const address = process.env[envVar];
|
|
160
|
+
if (address && /^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
161
|
+
return address as `0x${string}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check legacy assets
|
|
166
|
+
try {
|
|
167
|
+
return getAssetAddress(normalizedAsset);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
// Asset not found
|
|
170
|
+
const availableMarkets = Object.keys(SUBNET_MARKETS).join(', ');
|
|
171
|
+
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Market address not found for asset '${asset}'.\n` +
|
|
174
|
+
`Available subnet markets: ${availableMarkets}\n` +
|
|
175
|
+
`For localhost testing, BTC/ETH are available if ALPHA_BTC/ALPHA_ETH env vars are set.\n` +
|
|
176
|
+
`\n` +
|
|
177
|
+
`Supported assets are subnet markets only. Please use one of:\n` +
|
|
178
|
+
` BITMIND (BitMind - SN34)\n` +
|
|
179
|
+
` CHUTES (Chutes - SN64)\n` +
|
|
180
|
+
` AFFINE (Affine - SN120)\n` +
|
|
181
|
+
` RIDGES (Ridges - SN62)\n`,
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* List all available subnet market addresses
|
|
188
|
+
*/
|
|
189
|
+
export function listAvailableMarketAddresses(): Record<string, string> {
|
|
190
|
+
const markets: Record<string, string> = {};
|
|
191
|
+
|
|
192
|
+
// Get all subnet markets
|
|
193
|
+
Object.entries(SUBNET_MARKETS).forEach(([symbol, market]) => {
|
|
194
|
+
markets[symbol] = market.marketAddress;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return markets;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Validate that required subnet market addresses are available
|
|
202
|
+
*/
|
|
203
|
+
export function validateMarketAddresses(requiredAssets: string[] = ['BITMIND']): {
|
|
204
|
+
valid: boolean;
|
|
205
|
+
missing: string[];
|
|
206
|
+
available: Record<string, string>;
|
|
207
|
+
} {
|
|
208
|
+
const missing: string[] = [];
|
|
209
|
+
const available = listAvailableMarketAddresses();
|
|
210
|
+
|
|
211
|
+
for (const asset of requiredAssets) {
|
|
212
|
+
if (!available[asset.toUpperCase()]) {
|
|
213
|
+
missing.push(asset);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
valid: missing.length === 0,
|
|
219
|
+
missing,
|
|
220
|
+
available,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Resolve asset symbol to validated market address
|
|
226
|
+
*/
|
|
227
|
+
export function resolveAsset(asset?: string): string {
|
|
228
|
+
// Default to BitMind (first subnet market) if no asset specified
|
|
229
|
+
const targetAsset = asset || 'BITMIND';
|
|
230
|
+
const normalizedAsset = targetAsset.toUpperCase();
|
|
231
|
+
|
|
232
|
+
// Check if it's a supported subnet market
|
|
233
|
+
if (isSubnetMarket(normalizedAsset)) {
|
|
234
|
+
return normalizedAsset;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Check for test assets from environment variables (localhost/anvil testing)
|
|
238
|
+
const testAssetEnvMap: Record<string, string> = {
|
|
239
|
+
BTC: 'ALPHA_BTC',
|
|
240
|
+
ETH: 'ALPHA_ETH',
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
if (testAssetEnvMap[normalizedAsset]) {
|
|
244
|
+
const envVar = testAssetEnvMap[normalizedAsset];
|
|
245
|
+
const address = process.env[envVar];
|
|
246
|
+
if (address && /^0x[a-fA-F0-9]{40}$/.test(address)) {
|
|
247
|
+
return normalizedAsset;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const availableMarkets = Object.keys(SUBNET_MARKETS).join(', ');
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Unsupported asset '${targetAsset}'. Available subnet markets: ${availableMarkets}`,
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Chunk large block ranges for event queries to prevent RPC timeouts
|
|
259
|
+
*/
|
|
260
|
+
export async function getEventsInChunks<T>(
|
|
261
|
+
publicClient: PublicClient,
|
|
262
|
+
getEventsConfig: {
|
|
263
|
+
address: `0x${string}`;
|
|
264
|
+
abi: any;
|
|
265
|
+
eventName: string;
|
|
266
|
+
args?: any;
|
|
267
|
+
},
|
|
268
|
+
fromBlock: bigint | 'earliest' = 'earliest',
|
|
269
|
+
toBlock: bigint | 'latest' = 'latest',
|
|
270
|
+
chunkSize: number = 5000,
|
|
271
|
+
): Promise<T[]> {
|
|
272
|
+
// Get current block number if needed
|
|
273
|
+
const currentBlock = await publicClient.getBlockNumber();
|
|
274
|
+
|
|
275
|
+
// Convert 'earliest' and 'latest' to actual block numbers
|
|
276
|
+
let startBlock: bigint;
|
|
277
|
+
let endBlock: bigint;
|
|
278
|
+
|
|
279
|
+
if (fromBlock === 'earliest') {
|
|
280
|
+
startBlock = 0n;
|
|
281
|
+
} else {
|
|
282
|
+
startBlock = fromBlock;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (toBlock === 'latest') {
|
|
286
|
+
endBlock = currentBlock;
|
|
287
|
+
} else {
|
|
288
|
+
endBlock = toBlock;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// If the range is small enough, query directly
|
|
292
|
+
const totalBlocks = Number(endBlock - startBlock);
|
|
293
|
+
if (totalBlocks <= chunkSize) {
|
|
294
|
+
return (await publicClient.getContractEvents({
|
|
295
|
+
...getEventsConfig,
|
|
296
|
+
fromBlock: startBlock,
|
|
297
|
+
toBlock: endBlock,
|
|
298
|
+
})) as T[];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Query in chunks
|
|
302
|
+
const allEvents: T[] = [];
|
|
303
|
+
const chunkSizeBigInt = BigInt(chunkSize);
|
|
304
|
+
|
|
305
|
+
for (let currentStart = startBlock; currentStart <= endBlock; currentStart += chunkSizeBigInt) {
|
|
306
|
+
const currentEnd =
|
|
307
|
+
currentStart + chunkSizeBigInt - 1n > endBlock
|
|
308
|
+
? endBlock
|
|
309
|
+
: currentStart + chunkSizeBigInt - 1n;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const events = (await publicClient.getContractEvents({
|
|
313
|
+
...getEventsConfig,
|
|
314
|
+
fromBlock: currentStart,
|
|
315
|
+
toBlock: currentEnd,
|
|
316
|
+
})) as T[];
|
|
317
|
+
|
|
318
|
+
allEvents.push(...events);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.warn(
|
|
321
|
+
`Warning: Failed to fetch events for blocks ${currentStart}-${currentEnd}:`,
|
|
322
|
+
error,
|
|
323
|
+
);
|
|
324
|
+
// Continue with remaining chunks
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return allEvents;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get recent events with a reasonable limit to prevent timeouts
|
|
333
|
+
*/
|
|
334
|
+
export async function getRecentEvents<T>(
|
|
335
|
+
publicClient: PublicClient,
|
|
336
|
+
getEventsConfig: {
|
|
337
|
+
address: `0x${string}`;
|
|
338
|
+
abi: any;
|
|
339
|
+
eventName: string;
|
|
340
|
+
args?: any;
|
|
341
|
+
},
|
|
342
|
+
maxBlocks: number = 10000,
|
|
343
|
+
): Promise<T[]> {
|
|
344
|
+
const currentBlock = await publicClient.getBlockNumber();
|
|
345
|
+
const fromBlock = currentBlock > BigInt(maxBlocks) ? currentBlock - BigInt(maxBlocks) : 0n;
|
|
346
|
+
|
|
347
|
+
return getEventsInChunks<T>(
|
|
348
|
+
publicClient,
|
|
349
|
+
getEventsConfig,
|
|
350
|
+
fromBlock,
|
|
351
|
+
currentBlock,
|
|
352
|
+
5000, // 5k block chunks
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Get events with optimized chunking for better performance
|
|
358
|
+
* Uses larger chunks initially and reduces on failures
|
|
359
|
+
*/
|
|
360
|
+
export async function getEventsOptimized<T>(
|
|
361
|
+
publicClient: PublicClient,
|
|
362
|
+
getEventsConfig: {
|
|
363
|
+
address: `0x${string}`;
|
|
364
|
+
abi: any;
|
|
365
|
+
eventName: string;
|
|
366
|
+
args?: any;
|
|
367
|
+
},
|
|
368
|
+
fromBlock: bigint | 'earliest' = 'earliest',
|
|
369
|
+
toBlock: bigint | 'latest' = 'latest',
|
|
370
|
+
limit?: number,
|
|
371
|
+
): Promise<T[]> {
|
|
372
|
+
// For very large ranges, prefer recent events
|
|
373
|
+
if (fromBlock === 'earliest') {
|
|
374
|
+
const currentBlock = await publicClient.getBlockNumber();
|
|
375
|
+
const maxRecentBlocks = 50000; // Look back up to 50k blocks
|
|
376
|
+
fromBlock =
|
|
377
|
+
currentBlock > BigInt(maxRecentBlocks) ? currentBlock - BigInt(maxRecentBlocks) : 0n;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const events = await getEventsInChunks<T>(
|
|
381
|
+
publicClient,
|
|
382
|
+
getEventsConfig,
|
|
383
|
+
fromBlock,
|
|
384
|
+
toBlock,
|
|
385
|
+
10000, // 10k block chunks for better performance
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Apply limit if specified
|
|
389
|
+
if (limit && events.length > limit) {
|
|
390
|
+
return events.slice(-limit); // Get most recent
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return events;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Handle errors with appropriate formatting
|
|
398
|
+
*/
|
|
399
|
+
export function handleError(error: any, options: any) {
|
|
400
|
+
if (options.json) {
|
|
401
|
+
console.error(
|
|
402
|
+
JSON.stringify(
|
|
403
|
+
{
|
|
404
|
+
error: true,
|
|
405
|
+
message: error.message || error.toString(),
|
|
406
|
+
code: error.code,
|
|
407
|
+
stack: options.debug ? error.stack : undefined,
|
|
408
|
+
},
|
|
409
|
+
null,
|
|
410
|
+
2,
|
|
411
|
+
),
|
|
412
|
+
);
|
|
413
|
+
} else {
|
|
414
|
+
console.error(chalk.red('\n❌ Error:'), error.message || error);
|
|
415
|
+
|
|
416
|
+
// Show additional error details
|
|
417
|
+
if (error.reason) {
|
|
418
|
+
console.error(chalk.gray('Reason:'), error.reason);
|
|
419
|
+
}
|
|
420
|
+
if (error.code) {
|
|
421
|
+
console.error(chalk.gray('Code:'), error.code);
|
|
422
|
+
}
|
|
423
|
+
if (error.transaction) {
|
|
424
|
+
console.error(chalk.gray('Transaction:'), error.transaction.hash);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Show stack trace in debug mode
|
|
428
|
+
if (options.debug) {
|
|
429
|
+
console.error(chalk.gray('\nStack trace:'));
|
|
430
|
+
console.error(error.stack);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Provide helpful suggestions based on error type
|
|
434
|
+
if (error.message?.includes('insufficient funds')) {
|
|
435
|
+
console.error(
|
|
436
|
+
chalk.yellow('\n💡 Tip: Check your account balance with "alpha-futures account balance"'),
|
|
437
|
+
);
|
|
438
|
+
} else if (error.message?.includes('nonce')) {
|
|
439
|
+
console.error(
|
|
440
|
+
chalk.yellow('\n💡 Tip: This might be a network issue. Try again in a moment.'),
|
|
441
|
+
);
|
|
442
|
+
} else if (error.message?.includes('gas')) {
|
|
443
|
+
console.error(
|
|
444
|
+
chalk.yellow(
|
|
445
|
+
'\n💡 Tip: The transaction may require more gas. Try increasing the gas limit.',
|
|
446
|
+
),
|
|
447
|
+
);
|
|
448
|
+
} else if (error.message?.includes('revert')) {
|
|
449
|
+
console.error(
|
|
450
|
+
chalk.yellow('\n💡 Tip: The transaction was reverted. Check your inputs and try again.'),
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Exit with error code
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for managing CLI configuration
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
import { getSupportedNetworks, isValidNetwork } from '../../src/constants/networks';
|
|
12
|
+
|
|
13
|
+
export interface CliConfig {
|
|
14
|
+
network?: string;
|
|
15
|
+
rpcUrl?: string;
|
|
16
|
+
defaultAsset?: string;
|
|
17
|
+
defaultLeverage?: number;
|
|
18
|
+
confirmations?: boolean;
|
|
19
|
+
displayCurrency?: 'USD' | 'TAO';
|
|
20
|
+
theme?: 'default' | 'dark' | 'light';
|
|
21
|
+
gasPrice?: string;
|
|
22
|
+
gasLimit?: string;
|
|
23
|
+
slippage?: number;
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the config file path
|
|
29
|
+
*/
|
|
30
|
+
function getConfigPath(customPath?: string): string {
|
|
31
|
+
if (customPath) {
|
|
32
|
+
// Expand ~ to home directory
|
|
33
|
+
if (customPath.startsWith('~')) {
|
|
34
|
+
return path.join(os.homedir(), customPath.slice(1));
|
|
35
|
+
}
|
|
36
|
+
return customPath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Default path: ~/.alpha-futures/config.json
|
|
40
|
+
return path.join(os.homedir(), '.alpha-futures', 'config.json');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load configuration from file
|
|
45
|
+
*/
|
|
46
|
+
export async function loadConfig(configPath?: string): Promise<CliConfig> {
|
|
47
|
+
const filePath = getConfigPath(configPath);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
51
|
+
return JSON.parse(data);
|
|
52
|
+
} catch (error: any) {
|
|
53
|
+
// Return empty config if file doesn't exist
|
|
54
|
+
if (error.code === 'ENOENT') {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.warn(chalk.yellow('Warning: Failed to load config:'), error.message);
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Save configuration to file
|
|
65
|
+
*/
|
|
66
|
+
export async function saveConfig(configPath: string | undefined, config: CliConfig): Promise<void> {
|
|
67
|
+
const filePath = getConfigPath(configPath);
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Ensure directory exists
|
|
71
|
+
const dir = path.dirname(filePath);
|
|
72
|
+
await fs.mkdir(dir, { recursive: true });
|
|
73
|
+
|
|
74
|
+
// Save config
|
|
75
|
+
await fs.writeFile(filePath, JSON.stringify(config, null, 2));
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
throw new Error(`Failed to save config: ${error.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get a config value with fallback
|
|
83
|
+
*/
|
|
84
|
+
export function getConfigValue<T>(config: CliConfig, key: string, defaultValue: T): T {
|
|
85
|
+
return config[key] !== undefined ? config[key] : defaultValue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Merge configurations with priority
|
|
90
|
+
*/
|
|
91
|
+
export function mergeConfigs(...configs: Partial<CliConfig>[]): CliConfig {
|
|
92
|
+
return configs.reduce((merged, config) => {
|
|
93
|
+
return { ...merged, ...config };
|
|
94
|
+
}, {} as CliConfig);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Validate configuration
|
|
99
|
+
*/
|
|
100
|
+
export function validateConfig(config: CliConfig): string[] {
|
|
101
|
+
const errors: string[] = [];
|
|
102
|
+
|
|
103
|
+
// Validate network
|
|
104
|
+
if (config.network) {
|
|
105
|
+
if (!isValidNetwork(config.network)) {
|
|
106
|
+
const validNetworks = getSupportedNetworks();
|
|
107
|
+
errors.push(
|
|
108
|
+
`Invalid network: ${config.network}. Must be one of: ${validNetworks.join(', ')}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate RPC URL
|
|
114
|
+
if (config.rpcUrl) {
|
|
115
|
+
try {
|
|
116
|
+
new URL(config.rpcUrl);
|
|
117
|
+
} catch {
|
|
118
|
+
errors.push(`Invalid RPC URL: ${config.rpcUrl}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Validate leverage
|
|
123
|
+
if (config.defaultLeverage !== undefined) {
|
|
124
|
+
if (config.defaultLeverage < 1 || config.defaultLeverage > 10) {
|
|
125
|
+
errors.push('Default leverage must be between 1 and 10');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Validate slippage
|
|
130
|
+
if (config.slippage !== undefined) {
|
|
131
|
+
if (config.slippage < 0 || config.slippage > 50) {
|
|
132
|
+
errors.push('Slippage must be between 0 and 50 percent');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get default configuration
|
|
141
|
+
*/
|
|
142
|
+
export function getDefaultConfig(): CliConfig {
|
|
143
|
+
return {
|
|
144
|
+
network: 'localhost',
|
|
145
|
+
defaultAsset: 'ALPHA',
|
|
146
|
+
defaultLeverage: 3,
|
|
147
|
+
confirmations: true,
|
|
148
|
+
displayCurrency: 'USD',
|
|
149
|
+
theme: 'default',
|
|
150
|
+
slippage: 0.5,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Export configuration
|
|
156
|
+
*/
|
|
157
|
+
export async function exportConfig(config: CliConfig, outputPath: string): Promise<void> {
|
|
158
|
+
try {
|
|
159
|
+
await fs.writeFile(outputPath, JSON.stringify(config, null, 2));
|
|
160
|
+
console.log(chalk.green(`✓ Configuration exported to ${outputPath}`));
|
|
161
|
+
} catch (error: any) {
|
|
162
|
+
throw new Error(`Failed to export config: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Import configuration
|
|
168
|
+
*/
|
|
169
|
+
export async function importConfig(inputPath: string): Promise<CliConfig> {
|
|
170
|
+
try {
|
|
171
|
+
const data = await fs.readFile(inputPath, 'utf-8');
|
|
172
|
+
const config = JSON.parse(data);
|
|
173
|
+
|
|
174
|
+
// Validate imported config
|
|
175
|
+
const errors = validateConfig(config);
|
|
176
|
+
if (errors.length > 0) {
|
|
177
|
+
throw new Error(`Invalid configuration:\n${errors.join('\n')}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return config;
|
|
181
|
+
} catch (error: any) {
|
|
182
|
+
throw new Error(`Failed to import config: ${error.message}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Create a config preset
|
|
188
|
+
*/
|
|
189
|
+
export function createPreset(name: string): CliConfig {
|
|
190
|
+
const presets: Record<string, CliConfig> = {
|
|
191
|
+
conservative: {
|
|
192
|
+
defaultLeverage: 1,
|
|
193
|
+
confirmations: true,
|
|
194
|
+
slippage: 0.1,
|
|
195
|
+
},
|
|
196
|
+
moderate: {
|
|
197
|
+
defaultLeverage: 3,
|
|
198
|
+
confirmations: true,
|
|
199
|
+
slippage: 0.5,
|
|
200
|
+
},
|
|
201
|
+
aggressive: {
|
|
202
|
+
defaultLeverage: 5,
|
|
203
|
+
confirmations: false,
|
|
204
|
+
slippage: 1.0,
|
|
205
|
+
},
|
|
206
|
+
testnet: {
|
|
207
|
+
network: 'sepolia',
|
|
208
|
+
confirmations: true,
|
|
209
|
+
defaultLeverage: 3,
|
|
210
|
+
},
|
|
211
|
+
mainnet: {
|
|
212
|
+
network: 'bittensor',
|
|
213
|
+
confirmations: true,
|
|
214
|
+
defaultLeverage: 2,
|
|
215
|
+
slippage: 0.3,
|
|
216
|
+
},
|
|
217
|
+
bittensor: {
|
|
218
|
+
network: 'bittensor',
|
|
219
|
+
confirmations: true,
|
|
220
|
+
defaultLeverage: 3,
|
|
221
|
+
slippage: 0.5,
|
|
222
|
+
},
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
return presets[name] || getDefaultConfig();
|
|
226
|
+
}
|