@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,1679 @@
|
|
|
1
|
+
# Alpha Futures SDK Integration Guide
|
|
2
|
+
|
|
3
|
+
## Table of Contents
|
|
4
|
+
|
|
5
|
+
- [Overview](#overview)
|
|
6
|
+
- [Installation](#installation)
|
|
7
|
+
- [Basic Integration](#basic-integration)
|
|
8
|
+
- [Authentication & Security](#authentication--security)
|
|
9
|
+
- [Trading Bot Integration](#trading-bot-integration)
|
|
10
|
+
- [Web Application Integration](#web-application-integration)
|
|
11
|
+
- [API Server Integration](#api-server-integration)
|
|
12
|
+
- [Mobile Integration](#mobile-integration)
|
|
13
|
+
- [WebSocket & Real-time Data](#websocket--real-time-data)
|
|
14
|
+
- [Error Handling & Recovery](#error-handling--recovery)
|
|
15
|
+
- [Testing Your Integration](#testing-your-integration)
|
|
16
|
+
- [Performance Optimization](#performance-optimization)
|
|
17
|
+
- [Security Best Practices](#security-best-practices)
|
|
18
|
+
|
|
19
|
+
## Overview
|
|
20
|
+
|
|
21
|
+
This guide covers how to integrate the Alpha Futures SDK into various types of applications, from simple scripts to complex trading systems.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
### NPM/Yarn
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Using npm
|
|
29
|
+
npm install @alpha-futures/sdk ethers
|
|
30
|
+
|
|
31
|
+
# Using yarn
|
|
32
|
+
yarn add @alpha-futures/sdk ethers
|
|
33
|
+
|
|
34
|
+
# Using pnpm
|
|
35
|
+
pnpm add @alpha-futures/sdk ethers
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### TypeScript Configuration
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"compilerOptions": {
|
|
43
|
+
"target": "ES2020",
|
|
44
|
+
"module": "commonjs",
|
|
45
|
+
"lib": ["ES2020"],
|
|
46
|
+
"strict": true,
|
|
47
|
+
"esModuleInterop": true,
|
|
48
|
+
"skipLibCheck": true,
|
|
49
|
+
"forceConsistentCasingInFileNames": true,
|
|
50
|
+
"resolveJsonModule": true,
|
|
51
|
+
"moduleResolution": "node"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Basic Integration
|
|
57
|
+
|
|
58
|
+
### Simple Script
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
62
|
+
import { ethers } from 'ethers';
|
|
63
|
+
import dotenv from 'dotenv';
|
|
64
|
+
|
|
65
|
+
dotenv.config();
|
|
66
|
+
|
|
67
|
+
async function main() {
|
|
68
|
+
// Initialize client
|
|
69
|
+
const client = new AlphaFuturesClient({
|
|
70
|
+
rpcUrl: process.env.RPC_URL!,
|
|
71
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
72
|
+
network: 'mainnet'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Get current price
|
|
76
|
+
const price = await client.priceOracle.getPrice('ALPHA');
|
|
77
|
+
console.log('ALPHA Price:', ethers.formatEther(price));
|
|
78
|
+
|
|
79
|
+
// Check account
|
|
80
|
+
const account = await client.marginAccount.getMarginAccount(
|
|
81
|
+
await client.signer.getAddress()
|
|
82
|
+
);
|
|
83
|
+
console.log('Available Margin:', ethers.formatEther(account.availableMargin));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main().catch(console.error);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Environment Configuration
|
|
90
|
+
|
|
91
|
+
`.env` file:
|
|
92
|
+
```bash
|
|
93
|
+
# Network Configuration
|
|
94
|
+
RPC_URL=https://bittensor-evm.alchemyapi.io/v2/your-key
|
|
95
|
+
NETWORK=mainnet
|
|
96
|
+
|
|
97
|
+
# Account Configuration
|
|
98
|
+
PRIVATE_KEY=0x...
|
|
99
|
+
|
|
100
|
+
# Optional: Custom Contract Addresses
|
|
101
|
+
MARGIN_ACCOUNT_ADDRESS=0x...
|
|
102
|
+
POSITION_MANAGER_ADDRESS=0x...
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Configuration Management
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { ClientConfig, NetworkConfig } from '@alpha-futures/sdk';
|
|
109
|
+
|
|
110
|
+
class ConfigManager {
|
|
111
|
+
static getConfig(): ClientConfig {
|
|
112
|
+
const network = process.env.NETWORK || 'mainnet';
|
|
113
|
+
|
|
114
|
+
// Custom network configuration
|
|
115
|
+
if (process.env.CUSTOM_NETWORK) {
|
|
116
|
+
const customNetwork: NetworkConfig = {
|
|
117
|
+
chainId: parseInt(process.env.CHAIN_ID!),
|
|
118
|
+
name: process.env.NETWORK_NAME!,
|
|
119
|
+
rpcUrl: process.env.RPC_URL!,
|
|
120
|
+
contracts: {
|
|
121
|
+
marginAccount: process.env.MARGIN_ACCOUNT_ADDRESS!,
|
|
122
|
+
positionManager: process.env.POSITION_MANAGER_ADDRESS!,
|
|
123
|
+
fundingRate: process.env.FUNDING_RATE_ADDRESS!,
|
|
124
|
+
liquidationEngine: process.env.LIQUIDATION_ENGINE_ADDRESS!,
|
|
125
|
+
protocolVault: process.env.PROTOCOL_VAULT_ADDRESS!,
|
|
126
|
+
priceOracle: process.env.PRICE_ORACLE_ADDRESS!,
|
|
127
|
+
taoToken: process.env.TAO_TOKEN_ADDRESS!
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
rpcUrl: process.env.RPC_URL!,
|
|
133
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
134
|
+
networkConfig: customNetwork
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Standard network
|
|
139
|
+
return {
|
|
140
|
+
rpcUrl: process.env.RPC_URL!,
|
|
141
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
142
|
+
network
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Authentication & Security
|
|
149
|
+
|
|
150
|
+
### Secure Key Management
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { Wallet } from 'ethers';
|
|
154
|
+
import * as crypto from 'crypto';
|
|
155
|
+
|
|
156
|
+
class SecureWalletManager {
|
|
157
|
+
private encryptedKey: string;
|
|
158
|
+
private salt: Buffer;
|
|
159
|
+
|
|
160
|
+
constructor() {
|
|
161
|
+
this.salt = crypto.randomBytes(32);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Encrypt private key
|
|
165
|
+
encryptPrivateKey(privateKey: string, password: string): void {
|
|
166
|
+
const key = crypto.pbkdf2Sync(password, this.salt, 100000, 32, 'sha256');
|
|
167
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, this.salt.slice(0, 16));
|
|
168
|
+
|
|
169
|
+
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
|
|
170
|
+
encrypted += cipher.final('hex');
|
|
171
|
+
|
|
172
|
+
const authTag = cipher.getAuthTag();
|
|
173
|
+
this.encryptedKey = encrypted + authTag.toString('hex');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Decrypt and create wallet
|
|
177
|
+
createWallet(password: string, provider: any): Wallet {
|
|
178
|
+
const key = crypto.pbkdf2Sync(password, this.salt, 100000, 32, 'sha256');
|
|
179
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, this.salt.slice(0, 16));
|
|
180
|
+
|
|
181
|
+
const encrypted = this.encryptedKey.slice(0, -32);
|
|
182
|
+
const authTag = Buffer.from(this.encryptedKey.slice(-32), 'hex');
|
|
183
|
+
decipher.setAuthTag(authTag);
|
|
184
|
+
|
|
185
|
+
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
186
|
+
decrypted += decipher.final('utf8');
|
|
187
|
+
|
|
188
|
+
return new Wallet(decrypted, provider);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Multi-Signature Integration
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
197
|
+
|
|
198
|
+
class MultiSigIntegration {
|
|
199
|
+
private client: AlphaFuturesClient;
|
|
200
|
+
private signers: string[];
|
|
201
|
+
private threshold: number;
|
|
202
|
+
|
|
203
|
+
constructor(client: AlphaFuturesClient, signers: string[], threshold: number) {
|
|
204
|
+
this.client = client;
|
|
205
|
+
this.signers = signers;
|
|
206
|
+
this.threshold = threshold;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async proposeTransaction(
|
|
210
|
+
target: string,
|
|
211
|
+
data: string,
|
|
212
|
+
value: bigint
|
|
213
|
+
): Promise<number> {
|
|
214
|
+
// Implementation depends on your multisig contract
|
|
215
|
+
// This is a conceptual example
|
|
216
|
+
const proposalId = await this.submitToMultisig(target, data, value);
|
|
217
|
+
return proposalId;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async executeAfterApproval(proposalId: number): Promise<void> {
|
|
221
|
+
const approvals = await this.getApprovals(proposalId);
|
|
222
|
+
if (approvals >= this.threshold) {
|
|
223
|
+
await this.executeMultisigTransaction(proposalId);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Trading Bot Integration
|
|
230
|
+
|
|
231
|
+
### Basic Trading Bot Structure
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
235
|
+
import { EventEmitter } from 'events';
|
|
236
|
+
|
|
237
|
+
abstract class TradingBot extends EventEmitter {
|
|
238
|
+
protected client: AlphaFuturesClient;
|
|
239
|
+
protected isRunning: boolean = false;
|
|
240
|
+
protected config: BotConfig;
|
|
241
|
+
|
|
242
|
+
constructor(client: AlphaFuturesClient, config: BotConfig) {
|
|
243
|
+
super();
|
|
244
|
+
this.client = client;
|
|
245
|
+
this.config = config;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async start(): Promise<void> {
|
|
249
|
+
this.isRunning = true;
|
|
250
|
+
this.emit('started');
|
|
251
|
+
|
|
252
|
+
while (this.isRunning) {
|
|
253
|
+
try {
|
|
254
|
+
await this.executeStrategy();
|
|
255
|
+
await this.sleep(this.config.interval);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
this.emit('error', error);
|
|
258
|
+
await this.handleError(error);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
stop(): void {
|
|
264
|
+
this.isRunning = false;
|
|
265
|
+
this.emit('stopped');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
abstract executeStrategy(): Promise<void>;
|
|
269
|
+
abstract handleError(error: any): Promise<void>;
|
|
270
|
+
|
|
271
|
+
protected sleep(ms: number): Promise<void> {
|
|
272
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
interface BotConfig {
|
|
277
|
+
interval: number;
|
|
278
|
+
maxPositions: number;
|
|
279
|
+
riskPerTrade: number;
|
|
280
|
+
stopLoss: number;
|
|
281
|
+
takeProfit: number;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Grid Trading Bot Example
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
class GridTradingBot extends TradingBot {
|
|
289
|
+
private gridLevels: bigint[];
|
|
290
|
+
private positions: Map<string, GridPosition> = new Map();
|
|
291
|
+
|
|
292
|
+
constructor(
|
|
293
|
+
client: AlphaFuturesClient,
|
|
294
|
+
config: GridBotConfig
|
|
295
|
+
) {
|
|
296
|
+
super(client, config);
|
|
297
|
+
this.gridLevels = this.calculateGridLevels();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private calculateGridLevels(): bigint[] {
|
|
301
|
+
const config = this.config as GridBotConfig;
|
|
302
|
+
const levels: bigint[] = [];
|
|
303
|
+
|
|
304
|
+
for (let i = 0; i <= config.gridCount; i++) {
|
|
305
|
+
const price = config.lowerBound +
|
|
306
|
+
(config.upperBound - config.lowerBound) * BigInt(i) / BigInt(config.gridCount);
|
|
307
|
+
levels.push(price);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return levels;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async executeStrategy(): Promise<void> {
|
|
314
|
+
const currentPrice = await this.client.priceOracle.getPrice('ALPHA');
|
|
315
|
+
|
|
316
|
+
// Check each grid level
|
|
317
|
+
for (let i = 0; i < this.gridLevels.length - 1; i++) {
|
|
318
|
+
const lowerLevel = this.gridLevels[i];
|
|
319
|
+
const upperLevel = this.gridLevels[i + 1];
|
|
320
|
+
const levelKey = `${i}`;
|
|
321
|
+
|
|
322
|
+
if (currentPrice >= lowerLevel && currentPrice <= upperLevel) {
|
|
323
|
+
if (!this.positions.has(levelKey)) {
|
|
324
|
+
// Open position at this level
|
|
325
|
+
await this.openGridPosition(levelKey, lowerLevel, upperLevel);
|
|
326
|
+
}
|
|
327
|
+
} else if (this.positions.has(levelKey)) {
|
|
328
|
+
// Close position if price moved out of range
|
|
329
|
+
await this.closeGridPosition(levelKey);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
private async openGridPosition(
|
|
335
|
+
key: string,
|
|
336
|
+
buyPrice: bigint,
|
|
337
|
+
sellPrice: bigint
|
|
338
|
+
): Promise<void> {
|
|
339
|
+
const size = this.calculatePositionSize();
|
|
340
|
+
|
|
341
|
+
const tx = await this.client.positionManager.openPosition({
|
|
342
|
+
asset: 'ALPHA',
|
|
343
|
+
size,
|
|
344
|
+
leverage: 2,
|
|
345
|
+
isLong: true
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
await tx.wait();
|
|
349
|
+
|
|
350
|
+
this.positions.set(key, {
|
|
351
|
+
positionId: await this.getPositionIdFromTx(tx),
|
|
352
|
+
buyPrice,
|
|
353
|
+
sellPrice,
|
|
354
|
+
size
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
this.emit('positionOpened', key);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private async closeGridPosition(key: string): Promise<void> {
|
|
361
|
+
const position = this.positions.get(key);
|
|
362
|
+
if (!position) return;
|
|
363
|
+
|
|
364
|
+
await this.client.positionManager.closePosition(position.positionId);
|
|
365
|
+
this.positions.delete(key);
|
|
366
|
+
|
|
367
|
+
this.emit('positionClosed', key);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async handleError(error: any): Promise<void> {
|
|
371
|
+
console.error('Grid bot error:', error);
|
|
372
|
+
|
|
373
|
+
// Retry logic
|
|
374
|
+
if (error.code === 'NETWORK_ERROR') {
|
|
375
|
+
await this.sleep(5000);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Critical error - stop bot
|
|
380
|
+
if (error.code === 'INSUFFICIENT_FUNDS') {
|
|
381
|
+
this.stop();
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
interface GridBotConfig extends BotConfig {
|
|
387
|
+
gridCount: number;
|
|
388
|
+
lowerBound: bigint;
|
|
389
|
+
upperBound: bigint;
|
|
390
|
+
amountPerGrid: bigint;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
interface GridPosition {
|
|
394
|
+
positionId: bigint;
|
|
395
|
+
buyPrice: bigint;
|
|
396
|
+
sellPrice: bigint;
|
|
397
|
+
size: bigint;
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## Web Application Integration
|
|
402
|
+
|
|
403
|
+
### React Integration
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// hooks/useAlphaFutures.ts
|
|
407
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
408
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
409
|
+
import { ethers } from 'ethers';
|
|
410
|
+
|
|
411
|
+
export function useAlphaFutures() {
|
|
412
|
+
const [client, setClient] = useState<AlphaFuturesClient | null>(null);
|
|
413
|
+
const [account, setAccount] = useState<string | null>(null);
|
|
414
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
415
|
+
|
|
416
|
+
const connect = useCallback(async () => {
|
|
417
|
+
if (typeof window.ethereum === 'undefined') {
|
|
418
|
+
throw new Error('MetaMask not installed');
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Request account access
|
|
422
|
+
const accounts = await window.ethereum.request({
|
|
423
|
+
method: 'eth_requestAccounts'
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Create provider and signer
|
|
427
|
+
const provider = new ethers.BrowserProvider(window.ethereum);
|
|
428
|
+
const signer = await provider.getSigner();
|
|
429
|
+
|
|
430
|
+
// Initialize client
|
|
431
|
+
const alphaClient = new AlphaFuturesClient({
|
|
432
|
+
provider,
|
|
433
|
+
signer,
|
|
434
|
+
network: 'mainnet'
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
setClient(alphaClient);
|
|
438
|
+
setAccount(accounts[0]);
|
|
439
|
+
setIsConnected(true);
|
|
440
|
+
}, []);
|
|
441
|
+
|
|
442
|
+
const disconnect = useCallback(() => {
|
|
443
|
+
setClient(null);
|
|
444
|
+
setAccount(null);
|
|
445
|
+
setIsConnected(false);
|
|
446
|
+
}, []);
|
|
447
|
+
|
|
448
|
+
// Auto-connect if previously connected
|
|
449
|
+
useEffect(() => {
|
|
450
|
+
const checkConnection = async () => {
|
|
451
|
+
if (typeof window.ethereum !== 'undefined') {
|
|
452
|
+
const accounts = await window.ethereum.request({
|
|
453
|
+
method: 'eth_accounts'
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
if (accounts.length > 0) {
|
|
457
|
+
await connect();
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
checkConnection();
|
|
463
|
+
}, [connect]);
|
|
464
|
+
|
|
465
|
+
// Listen for account changes
|
|
466
|
+
useEffect(() => {
|
|
467
|
+
if (typeof window.ethereum !== 'undefined') {
|
|
468
|
+
window.ethereum.on('accountsChanged', (accounts: string[]) => {
|
|
469
|
+
if (accounts.length === 0) {
|
|
470
|
+
disconnect();
|
|
471
|
+
} else {
|
|
472
|
+
setAccount(accounts[0]);
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
window.ethereum.on('chainChanged', () => {
|
|
477
|
+
window.location.reload();
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
}, [disconnect]);
|
|
481
|
+
|
|
482
|
+
return {
|
|
483
|
+
client,
|
|
484
|
+
account,
|
|
485
|
+
isConnected,
|
|
486
|
+
connect,
|
|
487
|
+
disconnect
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### React Trading Component
|
|
493
|
+
|
|
494
|
+
```tsx
|
|
495
|
+
// components/TradingPanel.tsx
|
|
496
|
+
import React, { useState, useEffect } from 'react';
|
|
497
|
+
import { useAlphaFutures } from '../hooks/useAlphaFutures';
|
|
498
|
+
import { ethers } from 'ethers';
|
|
499
|
+
|
|
500
|
+
export function TradingPanel() {
|
|
501
|
+
const { client, account, isConnected } = useAlphaFutures();
|
|
502
|
+
const [loading, setLoading] = useState(false);
|
|
503
|
+
const [position, setPosition] = useState({
|
|
504
|
+
asset: 'ALPHA',
|
|
505
|
+
size: '1000',
|
|
506
|
+
leverage: 3,
|
|
507
|
+
isLong: true
|
|
508
|
+
});
|
|
509
|
+
const [accountInfo, setAccountInfo] = useState(null);
|
|
510
|
+
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
if (client && account) {
|
|
513
|
+
loadAccountInfo();
|
|
514
|
+
}
|
|
515
|
+
}, [client, account]);
|
|
516
|
+
|
|
517
|
+
const loadAccountInfo = async () => {
|
|
518
|
+
try {
|
|
519
|
+
const info = await client.marginAccount.getMarginAccount(account);
|
|
520
|
+
setAccountInfo(info);
|
|
521
|
+
} catch (error) {
|
|
522
|
+
console.error('Failed to load account info:', error);
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const handleTrade = async () => {
|
|
527
|
+
if (!client) return;
|
|
528
|
+
|
|
529
|
+
setLoading(true);
|
|
530
|
+
try {
|
|
531
|
+
const tx = await client.positionManager.openPosition({
|
|
532
|
+
asset: position.asset,
|
|
533
|
+
size: ethers.parseEther(position.size),
|
|
534
|
+
leverage: position.leverage,
|
|
535
|
+
isLong: position.isLong
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
await tx.wait();
|
|
539
|
+
alert('Position opened successfully!');
|
|
540
|
+
await loadAccountInfo();
|
|
541
|
+
} catch (error) {
|
|
542
|
+
alert(`Error: ${error.message}`);
|
|
543
|
+
} finally {
|
|
544
|
+
setLoading(false);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
if (!isConnected) {
|
|
549
|
+
return <div>Please connect your wallet</div>;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
<div className="trading-panel">
|
|
554
|
+
<h2>Open Position</h2>
|
|
555
|
+
|
|
556
|
+
{accountInfo && (
|
|
557
|
+
<div className="account-info">
|
|
558
|
+
<p>Available Margin: {ethers.formatEther(accountInfo.availableMargin)} TAO</p>
|
|
559
|
+
<p>Used Margin: {ethers.formatEther(accountInfo.usedMargin)} TAO</p>
|
|
560
|
+
</div>
|
|
561
|
+
)}
|
|
562
|
+
|
|
563
|
+
<div className="form-group">
|
|
564
|
+
<label>Asset</label>
|
|
565
|
+
<select
|
|
566
|
+
value={position.asset}
|
|
567
|
+
onChange={(e) => setPosition({...position, asset: e.target.value})}
|
|
568
|
+
>
|
|
569
|
+
<option value="ALPHA">ALPHA</option>
|
|
570
|
+
<option value="BETA">BETA</option>
|
|
571
|
+
</select>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
<div className="form-group">
|
|
575
|
+
<label>Size (TAO)</label>
|
|
576
|
+
<input
|
|
577
|
+
type="number"
|
|
578
|
+
value={position.size}
|
|
579
|
+
onChange={(e) => setPosition({...position, size: e.target.value})}
|
|
580
|
+
/>
|
|
581
|
+
</div>
|
|
582
|
+
|
|
583
|
+
<div className="form-group">
|
|
584
|
+
<label>Leverage</label>
|
|
585
|
+
<input
|
|
586
|
+
type="range"
|
|
587
|
+
min="1"
|
|
588
|
+
max="10"
|
|
589
|
+
value={position.leverage}
|
|
590
|
+
onChange={(e) => setPosition({...position, leverage: parseInt(e.target.value)})}
|
|
591
|
+
/>
|
|
592
|
+
<span>{position.leverage}x</span>
|
|
593
|
+
</div>
|
|
594
|
+
|
|
595
|
+
<div className="form-group">
|
|
596
|
+
<label>Direction</label>
|
|
597
|
+
<button
|
|
598
|
+
className={position.isLong ? 'active' : ''}
|
|
599
|
+
onClick={() => setPosition({...position, isLong: true})}
|
|
600
|
+
>
|
|
601
|
+
Long
|
|
602
|
+
</button>
|
|
603
|
+
<button
|
|
604
|
+
className={!position.isLong ? 'active' : ''}
|
|
605
|
+
onClick={() => setPosition({...position, isLong: false})}
|
|
606
|
+
>
|
|
607
|
+
Short
|
|
608
|
+
</button>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
<button
|
|
612
|
+
onClick={handleTrade}
|
|
613
|
+
disabled={loading}
|
|
614
|
+
>
|
|
615
|
+
{loading ? 'Opening Position...' : 'Open Position'}
|
|
616
|
+
</button>
|
|
617
|
+
</div>
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
### Vue.js Integration
|
|
623
|
+
|
|
624
|
+
```typescript
|
|
625
|
+
// composables/useAlphaFutures.ts
|
|
626
|
+
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
|
627
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
628
|
+
import { ethers } from 'ethers';
|
|
629
|
+
|
|
630
|
+
export function useAlphaFutures() {
|
|
631
|
+
const client = ref<AlphaFuturesClient | null>(null);
|
|
632
|
+
const account = ref<string | null>(null);
|
|
633
|
+
const isConnected = computed(() => !!client.value && !!account.value);
|
|
634
|
+
|
|
635
|
+
const connect = async () => {
|
|
636
|
+
if (!window.ethereum) {
|
|
637
|
+
throw new Error('MetaMask not installed');
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const accounts = await window.ethereum.request({
|
|
641
|
+
method: 'eth_requestAccounts'
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
const provider = new ethers.BrowserProvider(window.ethereum);
|
|
645
|
+
const signer = await provider.getSigner();
|
|
646
|
+
|
|
647
|
+
client.value = new AlphaFuturesClient({
|
|
648
|
+
provider,
|
|
649
|
+
signer,
|
|
650
|
+
network: 'mainnet'
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
account.value = accounts[0];
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
const disconnect = () => {
|
|
657
|
+
client.value = null;
|
|
658
|
+
account.value = null;
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
onMounted(() => {
|
|
662
|
+
// Auto-connect logic
|
|
663
|
+
if (window.ethereum) {
|
|
664
|
+
window.ethereum.on('accountsChanged', handleAccountsChanged);
|
|
665
|
+
window.ethereum.on('chainChanged', handleChainChanged);
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
onUnmounted(() => {
|
|
670
|
+
if (window.ethereum) {
|
|
671
|
+
window.ethereum.removeListener('accountsChanged', handleAccountsChanged);
|
|
672
|
+
window.ethereum.removeListener('chainChanged', handleChainChanged);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
return {
|
|
677
|
+
client: computed(() => client.value),
|
|
678
|
+
account: computed(() => account.value),
|
|
679
|
+
isConnected,
|
|
680
|
+
connect,
|
|
681
|
+
disconnect
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
## API Server Integration
|
|
687
|
+
|
|
688
|
+
### Express.js API Server
|
|
689
|
+
|
|
690
|
+
```typescript
|
|
691
|
+
// server.ts
|
|
692
|
+
import express from 'express';
|
|
693
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
694
|
+
import { ethers } from 'ethers';
|
|
695
|
+
import rateLimit from 'express-rate-limit';
|
|
696
|
+
import helmet from 'helmet';
|
|
697
|
+
|
|
698
|
+
const app = express();
|
|
699
|
+
app.use(helmet());
|
|
700
|
+
app.use(express.json());
|
|
701
|
+
|
|
702
|
+
// Rate limiting
|
|
703
|
+
const limiter = rateLimit({
|
|
704
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
705
|
+
max: 100 // limit each IP to 100 requests per windowMs
|
|
706
|
+
});
|
|
707
|
+
app.use('/api/', limiter);
|
|
708
|
+
|
|
709
|
+
// Initialize client
|
|
710
|
+
const client = new AlphaFuturesClient({
|
|
711
|
+
rpcUrl: process.env.RPC_URL!,
|
|
712
|
+
privateKey: process.env.PRIVATE_KEY!,
|
|
713
|
+
network: 'mainnet'
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// Middleware for API authentication
|
|
717
|
+
const authenticateAPI = (req: Request, res: Response, next: NextFunction) => {
|
|
718
|
+
const apiKey = req.headers['x-api-key'];
|
|
719
|
+
|
|
720
|
+
if (!apiKey || !isValidApiKey(apiKey)) {
|
|
721
|
+
return res.status(401).json({ error: 'Invalid API key' });
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
next();
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
// Price endpoint
|
|
728
|
+
app.get('/api/prices/:asset', authenticateAPI, async (req, res) => {
|
|
729
|
+
try {
|
|
730
|
+
const { asset } = req.params;
|
|
731
|
+
const price = await client.priceOracle.getPrice(asset);
|
|
732
|
+
|
|
733
|
+
res.json({
|
|
734
|
+
asset,
|
|
735
|
+
price: ethers.formatEther(price),
|
|
736
|
+
timestamp: Date.now()
|
|
737
|
+
});
|
|
738
|
+
} catch (error) {
|
|
739
|
+
res.status(500).json({ error: error.message });
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Position endpoints
|
|
744
|
+
app.post('/api/positions', authenticateAPI, async (req, res) => {
|
|
745
|
+
try {
|
|
746
|
+
const { asset, size, leverage, isLong, userAddress } = req.body;
|
|
747
|
+
|
|
748
|
+
// Validate inputs
|
|
749
|
+
if (!asset || !size || !leverage || isLong === undefined || !userAddress) {
|
|
750
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Create position on behalf of user
|
|
754
|
+
// Note: This requires proper authorization setup
|
|
755
|
+
const tx = await client.positionManager.openPosition({
|
|
756
|
+
asset,
|
|
757
|
+
size: ethers.parseEther(size),
|
|
758
|
+
leverage,
|
|
759
|
+
isLong
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
res.json({
|
|
763
|
+
txHash: tx.hash,
|
|
764
|
+
status: 'pending'
|
|
765
|
+
});
|
|
766
|
+
} catch (error) {
|
|
767
|
+
res.status(500).json({ error: error.message });
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
// WebSocket for real-time updates
|
|
772
|
+
import { WebSocketServer } from 'ws';
|
|
773
|
+
|
|
774
|
+
const wss = new WebSocketServer({ port: 8080 });
|
|
775
|
+
|
|
776
|
+
wss.on('connection', (ws) => {
|
|
777
|
+
console.log('New WebSocket connection');
|
|
778
|
+
|
|
779
|
+
// Subscribe to position events
|
|
780
|
+
client.positionManager.on('PositionOpened', (positionId, trader, asset) => {
|
|
781
|
+
ws.send(JSON.stringify({
|
|
782
|
+
type: 'positionOpened',
|
|
783
|
+
data: { positionId: positionId.toString(), trader, asset }
|
|
784
|
+
}));
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
// Price updates
|
|
788
|
+
const priceInterval = setInterval(async () => {
|
|
789
|
+
try {
|
|
790
|
+
const prices = await client.priceOracle.getAllPrices();
|
|
791
|
+
ws.send(JSON.stringify({
|
|
792
|
+
type: 'priceUpdate',
|
|
793
|
+
data: prices.map(p => ({
|
|
794
|
+
asset: p.asset,
|
|
795
|
+
price: ethers.formatEther(p.price),
|
|
796
|
+
timestamp: p.timestamp
|
|
797
|
+
}))
|
|
798
|
+
}));
|
|
799
|
+
} catch (error) {
|
|
800
|
+
console.error('Price update error:', error);
|
|
801
|
+
}
|
|
802
|
+
}, 5000); // Every 5 seconds
|
|
803
|
+
|
|
804
|
+
ws.on('close', () => {
|
|
805
|
+
clearInterval(priceInterval);
|
|
806
|
+
});
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
app.listen(3000, () => {
|
|
810
|
+
console.log('API server running on port 3000');
|
|
811
|
+
});
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
### GraphQL Integration
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
// graphql/schema.ts
|
|
818
|
+
import { gql } from 'apollo-server-express';
|
|
819
|
+
|
|
820
|
+
export const typeDefs = gql`
|
|
821
|
+
type Position {
|
|
822
|
+
id: String!
|
|
823
|
+
trader: String!
|
|
824
|
+
asset: String!
|
|
825
|
+
size: String!
|
|
826
|
+
isLong: Boolean!
|
|
827
|
+
leverage: Int!
|
|
828
|
+
entryPrice: String!
|
|
829
|
+
margin: String!
|
|
830
|
+
unrealizedPnL: String
|
|
831
|
+
marginRatio: String
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
type MarginAccount {
|
|
835
|
+
user: String!
|
|
836
|
+
totalDeposited: String!
|
|
837
|
+
totalWithdrawn: String!
|
|
838
|
+
availableMargin: String!
|
|
839
|
+
usedMargin: String!
|
|
840
|
+
unrealizedPnL: String!
|
|
841
|
+
marginRatio: String!
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
type Price {
|
|
845
|
+
asset: String!
|
|
846
|
+
price: String!
|
|
847
|
+
timestamp: Int!
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
type FundingRate {
|
|
851
|
+
asset: String!
|
|
852
|
+
rate: String!
|
|
853
|
+
isPositive: Boolean!
|
|
854
|
+
nextUpdateTime: String!
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
type Query {
|
|
858
|
+
position(id: String!): Position
|
|
859
|
+
positions(trader: String!): [Position!]!
|
|
860
|
+
marginAccount(address: String!): MarginAccount
|
|
861
|
+
price(asset: String!): Price
|
|
862
|
+
prices: [Price!]!
|
|
863
|
+
fundingRate(asset: String!): FundingRate
|
|
864
|
+
fundingRates: [FundingRate!]!
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
type Mutation {
|
|
868
|
+
openPosition(
|
|
869
|
+
asset: String!
|
|
870
|
+
size: String!
|
|
871
|
+
leverage: Int!
|
|
872
|
+
isLong: Boolean!
|
|
873
|
+
): TransactionResponse!
|
|
874
|
+
|
|
875
|
+
closePosition(id: String!): TransactionResponse!
|
|
876
|
+
|
|
877
|
+
deposit(amount: String!): TransactionResponse!
|
|
878
|
+
|
|
879
|
+
withdraw(amount: String!): TransactionResponse!
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
type TransactionResponse {
|
|
883
|
+
hash: String!
|
|
884
|
+
status: String!
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
type Subscription {
|
|
888
|
+
priceUpdates(asset: String): Price!
|
|
889
|
+
positionUpdates(trader: String!): Position!
|
|
890
|
+
fundingRateUpdates(asset: String): FundingRate!
|
|
891
|
+
}
|
|
892
|
+
`;
|
|
893
|
+
|
|
894
|
+
// graphql/resolvers.ts
|
|
895
|
+
export const resolvers = {
|
|
896
|
+
Query: {
|
|
897
|
+
position: async (_, { id }, { client }) => {
|
|
898
|
+
const position = await client.positionManager.getPosition(BigInt(id));
|
|
899
|
+
return formatPosition(position);
|
|
900
|
+
},
|
|
901
|
+
|
|
902
|
+
positions: async (_, { trader }, { client }) => {
|
|
903
|
+
const positions = await client.positionManager.getAllPositions(trader);
|
|
904
|
+
return positions.map(formatPosition);
|
|
905
|
+
},
|
|
906
|
+
|
|
907
|
+
marginAccount: async (_, { address }, { client }) => {
|
|
908
|
+
const account = await client.marginAccount.getMarginAccount(address);
|
|
909
|
+
return formatMarginAccount(account);
|
|
910
|
+
},
|
|
911
|
+
|
|
912
|
+
price: async (_, { asset }, { client }) => {
|
|
913
|
+
const price = await client.priceOracle.getPrice(asset);
|
|
914
|
+
return {
|
|
915
|
+
asset,
|
|
916
|
+
price: ethers.formatEther(price),
|
|
917
|
+
timestamp: Date.now()
|
|
918
|
+
};
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
prices: async (_, __, { client }) => {
|
|
922
|
+
const prices = await client.priceOracle.getAllPrices();
|
|
923
|
+
return prices.map(p => ({
|
|
924
|
+
asset: p.asset,
|
|
925
|
+
price: ethers.formatEther(p.price),
|
|
926
|
+
timestamp: p.timestamp
|
|
927
|
+
}));
|
|
928
|
+
}
|
|
929
|
+
},
|
|
930
|
+
|
|
931
|
+
Mutation: {
|
|
932
|
+
openPosition: async (_, args, { client }) => {
|
|
933
|
+
const tx = await client.positionManager.openPosition({
|
|
934
|
+
asset: args.asset,
|
|
935
|
+
size: ethers.parseEther(args.size),
|
|
936
|
+
leverage: args.leverage,
|
|
937
|
+
isLong: args.isLong
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
return {
|
|
941
|
+
hash: tx.hash,
|
|
942
|
+
status: 'pending'
|
|
943
|
+
};
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
closePosition: async (_, { id }, { client }) => {
|
|
947
|
+
const tx = await client.positionManager.closePosition(BigInt(id));
|
|
948
|
+
return {
|
|
949
|
+
hash: tx.hash,
|
|
950
|
+
status: 'pending'
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
|
|
955
|
+
Subscription: {
|
|
956
|
+
priceUpdates: {
|
|
957
|
+
subscribe: async function* (_, { asset }, { client }) {
|
|
958
|
+
while (true) {
|
|
959
|
+
const price = await client.priceOracle.getPrice(asset || 'ALPHA');
|
|
960
|
+
yield {
|
|
961
|
+
priceUpdates: {
|
|
962
|
+
asset: asset || 'ALPHA',
|
|
963
|
+
price: ethers.formatEther(price),
|
|
964
|
+
timestamp: Date.now()
|
|
965
|
+
}
|
|
966
|
+
};
|
|
967
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
## Mobile Integration
|
|
976
|
+
|
|
977
|
+
### React Native Integration
|
|
978
|
+
|
|
979
|
+
```typescript
|
|
980
|
+
// services/AlphaFuturesService.ts
|
|
981
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
982
|
+
import { ethers } from 'ethers';
|
|
983
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
984
|
+
import { WalletConnectProvider } from '@walletconnect/react-native';
|
|
985
|
+
|
|
986
|
+
export class AlphaFuturesService {
|
|
987
|
+
private client: AlphaFuturesClient | null = null;
|
|
988
|
+
private walletConnector: WalletConnectProvider;
|
|
989
|
+
|
|
990
|
+
constructor() {
|
|
991
|
+
this.walletConnector = new WalletConnectProvider({
|
|
992
|
+
infuraId: process.env.INFURA_ID,
|
|
993
|
+
bridge: 'https://bridge.walletconnect.org',
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async connect(): Promise<void> {
|
|
998
|
+
// Connect via WalletConnect
|
|
999
|
+
if (!this.walletConnector.connected) {
|
|
1000
|
+
await this.walletConnector.createSession();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Create provider
|
|
1004
|
+
const provider = new ethers.providers.Web3Provider(
|
|
1005
|
+
this.walletConnector as any
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
const signer = provider.getSigner();
|
|
1009
|
+
|
|
1010
|
+
// Initialize client
|
|
1011
|
+
this.client = new AlphaFuturesClient({
|
|
1012
|
+
provider,
|
|
1013
|
+
signer,
|
|
1014
|
+
network: 'mainnet'
|
|
1015
|
+
});
|
|
1016
|
+
|
|
1017
|
+
// Save connection state
|
|
1018
|
+
await AsyncStorage.setItem('walletConnected', 'true');
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
async disconnect(): Promise<void> {
|
|
1022
|
+
if (this.walletConnector.connected) {
|
|
1023
|
+
await this.walletConnector.killSession();
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
this.client = null;
|
|
1027
|
+
await AsyncStorage.removeItem('walletConnected');
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
getClient(): AlphaFuturesClient {
|
|
1031
|
+
if (!this.client) {
|
|
1032
|
+
throw new Error('Not connected');
|
|
1033
|
+
}
|
|
1034
|
+
return this.client;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// hooks/useAlphaFutures.ts (React Native)
|
|
1039
|
+
import { useState, useEffect } from 'react';
|
|
1040
|
+
import { AlphaFuturesService } from '../services/AlphaFuturesService';
|
|
1041
|
+
|
|
1042
|
+
const service = new AlphaFuturesService();
|
|
1043
|
+
|
|
1044
|
+
export function useAlphaFutures() {
|
|
1045
|
+
const [isConnected, setIsConnected] = useState(false);
|
|
1046
|
+
const [loading, setLoading] = useState(true);
|
|
1047
|
+
|
|
1048
|
+
useEffect(() => {
|
|
1049
|
+
checkConnection();
|
|
1050
|
+
}, []);
|
|
1051
|
+
|
|
1052
|
+
const checkConnection = async () => {
|
|
1053
|
+
try {
|
|
1054
|
+
const connected = await AsyncStorage.getItem('walletConnected');
|
|
1055
|
+
if (connected === 'true') {
|
|
1056
|
+
await service.connect();
|
|
1057
|
+
setIsConnected(true);
|
|
1058
|
+
}
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
console.error('Connection check failed:', error);
|
|
1061
|
+
} finally {
|
|
1062
|
+
setLoading(false);
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
const connect = async () => {
|
|
1067
|
+
try {
|
|
1068
|
+
setLoading(true);
|
|
1069
|
+
await service.connect();
|
|
1070
|
+
setIsConnected(true);
|
|
1071
|
+
} catch (error) {
|
|
1072
|
+
throw error;
|
|
1073
|
+
} finally {
|
|
1074
|
+
setLoading(false);
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
const disconnect = async () => {
|
|
1079
|
+
try {
|
|
1080
|
+
await service.disconnect();
|
|
1081
|
+
setIsConnected(false);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
console.error('Disconnect failed:', error);
|
|
1084
|
+
}
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
return {
|
|
1088
|
+
client: isConnected ? service.getClient() : null,
|
|
1089
|
+
isConnected,
|
|
1090
|
+
loading,
|
|
1091
|
+
connect,
|
|
1092
|
+
disconnect
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
## WebSocket & Real-time Data
|
|
1098
|
+
|
|
1099
|
+
### Real-time Price Feed
|
|
1100
|
+
|
|
1101
|
+
```typescript
|
|
1102
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
1103
|
+
import WebSocket from 'ws';
|
|
1104
|
+
|
|
1105
|
+
class RealTimePriceFeed {
|
|
1106
|
+
private client: AlphaFuturesClient;
|
|
1107
|
+
private ws: WebSocket | null = null;
|
|
1108
|
+
private subscribers: Map<string, Set<(price: bigint) => void>> = new Map();
|
|
1109
|
+
|
|
1110
|
+
constructor(client: AlphaFuturesClient) {
|
|
1111
|
+
this.client = client;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
connect(wsUrl: string): void {
|
|
1115
|
+
this.ws = new WebSocket(wsUrl);
|
|
1116
|
+
|
|
1117
|
+
this.ws.on('open', () => {
|
|
1118
|
+
console.log('WebSocket connected');
|
|
1119
|
+
this.subscribeToAllAssets();
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
this.ws.on('message', (data: string) => {
|
|
1123
|
+
const message = JSON.parse(data);
|
|
1124
|
+
if (message.type === 'price') {
|
|
1125
|
+
this.notifySubscribers(message.asset, BigInt(message.price));
|
|
1126
|
+
}
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
this.ws.on('error', (error) => {
|
|
1130
|
+
console.error('WebSocket error:', error);
|
|
1131
|
+
this.reconnect(wsUrl);
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
this.ws.on('close', () => {
|
|
1135
|
+
console.log('WebSocket closed');
|
|
1136
|
+
this.reconnect(wsUrl);
|
|
1137
|
+
});
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
private reconnect(wsUrl: string): void {
|
|
1141
|
+
setTimeout(() => {
|
|
1142
|
+
console.log('Reconnecting...');
|
|
1143
|
+
this.connect(wsUrl);
|
|
1144
|
+
}, 5000);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
subscribeToPriceUpdates(
|
|
1148
|
+
asset: string,
|
|
1149
|
+
callback: (price: bigint) => void
|
|
1150
|
+
): () => void {
|
|
1151
|
+
if (!this.subscribers.has(asset)) {
|
|
1152
|
+
this.subscribers.set(asset, new Set());
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
this.subscribers.get(asset)!.add(callback);
|
|
1156
|
+
|
|
1157
|
+
// Return unsubscribe function
|
|
1158
|
+
return () => {
|
|
1159
|
+
this.subscribers.get(asset)?.delete(callback);
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
private notifySubscribers(asset: string, price: bigint): void {
|
|
1164
|
+
this.subscribers.get(asset)?.forEach(callback => {
|
|
1165
|
+
callback(price);
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
private subscribeToAllAssets(): void {
|
|
1170
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1171
|
+
this.ws.send(JSON.stringify({
|
|
1172
|
+
type: 'subscribe',
|
|
1173
|
+
assets: ['ALPHA', 'BETA', 'GAMMA']
|
|
1174
|
+
}));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
### Event Streaming
|
|
1181
|
+
|
|
1182
|
+
```typescript
|
|
1183
|
+
class EventStreamer {
|
|
1184
|
+
private client: AlphaFuturesClient;
|
|
1185
|
+
private eventHandlers: Map<string, Function[]> = new Map();
|
|
1186
|
+
|
|
1187
|
+
constructor(client: AlphaFuturesClient) {
|
|
1188
|
+
this.client = client;
|
|
1189
|
+
this.setupEventListeners();
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
private setupEventListeners(): void {
|
|
1193
|
+
// Position events
|
|
1194
|
+
this.client.positionManager.on('PositionOpened', (...args) => {
|
|
1195
|
+
this.emit('positionOpened', ...args);
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
this.client.positionManager.on('PositionClosed', (...args) => {
|
|
1199
|
+
this.emit('positionClosed', ...args);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
// Liquidation events
|
|
1203
|
+
this.client.liquidationEngine.on('PositionLiquidated', (...args) => {
|
|
1204
|
+
this.emit('positionLiquidated', ...args);
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// Margin events
|
|
1208
|
+
this.client.marginAccount.on('MarginDeposited', (...args) => {
|
|
1209
|
+
this.emit('marginDeposited', ...args);
|
|
1210
|
+
});
|
|
1211
|
+
|
|
1212
|
+
this.client.marginAccount.on('MarginWithdrawn', (...args) => {
|
|
1213
|
+
this.emit('marginWithdrawn', ...args);
|
|
1214
|
+
});
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
on(event: string, handler: Function): void {
|
|
1218
|
+
if (!this.eventHandlers.has(event)) {
|
|
1219
|
+
this.eventHandlers.set(event, []);
|
|
1220
|
+
}
|
|
1221
|
+
this.eventHandlers.get(event)!.push(handler);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
off(event: string, handler: Function): void {
|
|
1225
|
+
const handlers = this.eventHandlers.get(event);
|
|
1226
|
+
if (handlers) {
|
|
1227
|
+
const index = handlers.indexOf(handler);
|
|
1228
|
+
if (index > -1) {
|
|
1229
|
+
handlers.splice(index, 1);
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
private emit(event: string, ...args: any[]): void {
|
|
1235
|
+
const handlers = this.eventHandlers.get(event);
|
|
1236
|
+
if (handlers) {
|
|
1237
|
+
handlers.forEach(handler => handler(...args));
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
```
|
|
1242
|
+
|
|
1243
|
+
## Error Handling & Recovery
|
|
1244
|
+
|
|
1245
|
+
### Comprehensive Error Handler
|
|
1246
|
+
|
|
1247
|
+
```typescript
|
|
1248
|
+
import { ContractError, NetworkError, ValidationError } from '@alpha-futures/sdk';
|
|
1249
|
+
|
|
1250
|
+
class ErrorHandler {
|
|
1251
|
+
private retryAttempts: Map<string, number> = new Map();
|
|
1252
|
+
private maxRetries: number = 3;
|
|
1253
|
+
private retryDelay: number = 1000;
|
|
1254
|
+
|
|
1255
|
+
async handleWithRetry<T>(
|
|
1256
|
+
operation: () => Promise<T>,
|
|
1257
|
+
context: string
|
|
1258
|
+
): Promise<T> {
|
|
1259
|
+
const attempts = this.retryAttempts.get(context) || 0;
|
|
1260
|
+
|
|
1261
|
+
try {
|
|
1262
|
+
const result = await operation();
|
|
1263
|
+
this.retryAttempts.delete(context);
|
|
1264
|
+
return result;
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
return this.handleError(error, context, operation);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
private async handleError<T>(
|
|
1271
|
+
error: any,
|
|
1272
|
+
context: string,
|
|
1273
|
+
operation: () => Promise<T>
|
|
1274
|
+
): Promise<T> {
|
|
1275
|
+
const attempts = this.retryAttempts.get(context) || 0;
|
|
1276
|
+
|
|
1277
|
+
// Categorize error
|
|
1278
|
+
if (this.isRetryableError(error) && attempts < this.maxRetries) {
|
|
1279
|
+
this.retryAttempts.set(context, attempts + 1);
|
|
1280
|
+
|
|
1281
|
+
// Exponential backoff
|
|
1282
|
+
const delay = this.retryDelay * Math.pow(2, attempts);
|
|
1283
|
+
await this.sleep(delay);
|
|
1284
|
+
|
|
1285
|
+
console.log(`Retrying ${context} (attempt ${attempts + 1}/${this.maxRetries})`);
|
|
1286
|
+
return this.handleWithRetry(operation, context);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Non-retryable or max retries exceeded
|
|
1290
|
+
this.retryAttempts.delete(context);
|
|
1291
|
+
throw this.enhanceError(error, context);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
private isRetryableError(error: any): boolean {
|
|
1295
|
+
// Network errors
|
|
1296
|
+
if (error instanceof NetworkError) {
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
// Specific error codes
|
|
1301
|
+
const retryableCodes = [
|
|
1302
|
+
'NETWORK_ERROR',
|
|
1303
|
+
'TIMEOUT',
|
|
1304
|
+
'SERVER_ERROR',
|
|
1305
|
+
'ECONNRESET',
|
|
1306
|
+
'ETIMEDOUT',
|
|
1307
|
+
'ENOTFOUND'
|
|
1308
|
+
];
|
|
1309
|
+
|
|
1310
|
+
return retryableCodes.includes(error.code);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
private enhanceError(error: any, context: string): Error {
|
|
1314
|
+
if (error instanceof ValidationError) {
|
|
1315
|
+
return new Error(`Validation failed in ${context}: ${error.message}`);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
if (error instanceof ContractError) {
|
|
1319
|
+
return new Error(`Contract error in ${context}: ${error.reason || error.message}`);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
if (error instanceof NetworkError) {
|
|
1323
|
+
return new Error(`Network error in ${context}: ${error.message}`);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
return new Error(`Unknown error in ${context}: ${error.message || error}`);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
private sleep(ms: number): Promise<void> {
|
|
1330
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
// Usage
|
|
1335
|
+
const errorHandler = new ErrorHandler();
|
|
1336
|
+
|
|
1337
|
+
async function safeOpenPosition(client: AlphaFuturesClient, params: any) {
|
|
1338
|
+
return errorHandler.handleWithRetry(
|
|
1339
|
+
() => client.positionManager.openPosition(params),
|
|
1340
|
+
'openPosition'
|
|
1341
|
+
);
|
|
1342
|
+
}
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
## Testing Your Integration
|
|
1346
|
+
|
|
1347
|
+
### Unit Tests
|
|
1348
|
+
|
|
1349
|
+
```typescript
|
|
1350
|
+
// __tests__/integration.test.ts
|
|
1351
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
1352
|
+
import { ethers } from 'ethers';
|
|
1353
|
+
|
|
1354
|
+
jest.mock('@alpha-futures/sdk');
|
|
1355
|
+
|
|
1356
|
+
describe('Alpha Futures Integration', () => {
|
|
1357
|
+
let client: AlphaFuturesClient;
|
|
1358
|
+
let mockProvider: any;
|
|
1359
|
+
let mockSigner: any;
|
|
1360
|
+
|
|
1361
|
+
beforeEach(() => {
|
|
1362
|
+
mockProvider = {
|
|
1363
|
+
getNetwork: jest.fn().mockResolvedValue({ chainId: 1 }),
|
|
1364
|
+
};
|
|
1365
|
+
|
|
1366
|
+
mockSigner = {
|
|
1367
|
+
getAddress: jest.fn().mockResolvedValue('0x123...'),
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
client = new AlphaFuturesClient({
|
|
1371
|
+
provider: mockProvider,
|
|
1372
|
+
signer: mockSigner,
|
|
1373
|
+
network: 'mainnet'
|
|
1374
|
+
});
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
describe('Position Management', () => {
|
|
1378
|
+
it('should open a position', async () => {
|
|
1379
|
+
const mockTx = { hash: '0xabc...', wait: jest.fn() };
|
|
1380
|
+
client.positionManager.openPosition = jest.fn().mockResolvedValue(mockTx);
|
|
1381
|
+
|
|
1382
|
+
const params = {
|
|
1383
|
+
asset: 'ALPHA',
|
|
1384
|
+
size: ethers.parseEther('1000'),
|
|
1385
|
+
leverage: 3,
|
|
1386
|
+
isLong: true
|
|
1387
|
+
};
|
|
1388
|
+
|
|
1389
|
+
const tx = await client.positionManager.openPosition(params);
|
|
1390
|
+
|
|
1391
|
+
expect(tx.hash).toBe('0xabc...');
|
|
1392
|
+
expect(client.positionManager.openPosition).toHaveBeenCalledWith(params);
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
### Integration Tests
|
|
1399
|
+
|
|
1400
|
+
```typescript
|
|
1401
|
+
// __tests__/e2e.test.ts
|
|
1402
|
+
import { AlphaFuturesClient } from '@alpha-futures/sdk';
|
|
1403
|
+
import { ethers } from 'ethers';
|
|
1404
|
+
|
|
1405
|
+
describe('E2E Integration Tests', () => {
|
|
1406
|
+
let client: AlphaFuturesClient;
|
|
1407
|
+
|
|
1408
|
+
beforeAll(async () => {
|
|
1409
|
+
// Use test network
|
|
1410
|
+
client = new AlphaFuturesClient({
|
|
1411
|
+
rpcUrl: process.env.TEST_RPC_URL,
|
|
1412
|
+
privateKey: process.env.TEST_PRIVATE_KEY,
|
|
1413
|
+
network: 'testnet'
|
|
1414
|
+
});
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
it('should complete full trading flow', async () => {
|
|
1418
|
+
// 1. Check initial balance
|
|
1419
|
+
const initialAccount = await client.marginAccount.getMarginAccount(
|
|
1420
|
+
await client.signer.getAddress()
|
|
1421
|
+
);
|
|
1422
|
+
|
|
1423
|
+
// 2. Deposit margin
|
|
1424
|
+
const depositTx = await client.marginAccount.deposit(
|
|
1425
|
+
ethers.parseEther('100')
|
|
1426
|
+
);
|
|
1427
|
+
await depositTx.wait();
|
|
1428
|
+
|
|
1429
|
+
// 3. Open position
|
|
1430
|
+
const openTx = await client.positionManager.openPosition({
|
|
1431
|
+
asset: 'ALPHA',
|
|
1432
|
+
size: ethers.parseEther('300'),
|
|
1433
|
+
leverage: 3,
|
|
1434
|
+
isLong: true
|
|
1435
|
+
});
|
|
1436
|
+
const receipt = await openTx.wait();
|
|
1437
|
+
|
|
1438
|
+
// Extract position ID from events
|
|
1439
|
+
const positionId = extractPositionId(receipt);
|
|
1440
|
+
|
|
1441
|
+
// 4. Check position
|
|
1442
|
+
const position = await client.positionManager.getPosition(positionId);
|
|
1443
|
+
expect(position.size).toBe(ethers.parseEther('300'));
|
|
1444
|
+
|
|
1445
|
+
// 5. Close position
|
|
1446
|
+
const closeTx = await client.positionManager.closePosition(positionId);
|
|
1447
|
+
await closeTx.wait();
|
|
1448
|
+
|
|
1449
|
+
// 6. Verify final state
|
|
1450
|
+
const finalAccount = await client.marginAccount.getMarginAccount(
|
|
1451
|
+
await client.signer.getAddress()
|
|
1452
|
+
);
|
|
1453
|
+
|
|
1454
|
+
expect(finalAccount.usedMargin).toBe(0n);
|
|
1455
|
+
}, 60000); // 60 second timeout
|
|
1456
|
+
});
|
|
1457
|
+
```
|
|
1458
|
+
|
|
1459
|
+
## Performance Optimization
|
|
1460
|
+
|
|
1461
|
+
### Caching Layer
|
|
1462
|
+
|
|
1463
|
+
```typescript
|
|
1464
|
+
class CachedAlphaFuturesClient extends AlphaFuturesClient {
|
|
1465
|
+
private cache: Map<string, { value: any; timestamp: number }> = new Map();
|
|
1466
|
+
private cacheTTL: number = 5000; // 5 seconds
|
|
1467
|
+
|
|
1468
|
+
async getPrice(asset: string): Promise<bigint> {
|
|
1469
|
+
const cacheKey = `price:${asset}`;
|
|
1470
|
+
const cached = this.getFromCache(cacheKey);
|
|
1471
|
+
|
|
1472
|
+
if (cached) {
|
|
1473
|
+
return cached;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const price = await super.priceOracle.getPrice(asset);
|
|
1477
|
+
this.setCache(cacheKey, price);
|
|
1478
|
+
|
|
1479
|
+
return price;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
private getFromCache(key: string): any {
|
|
1483
|
+
const item = this.cache.get(key);
|
|
1484
|
+
if (!item) return null;
|
|
1485
|
+
|
|
1486
|
+
if (Date.now() - item.timestamp > this.cacheTTL) {
|
|
1487
|
+
this.cache.delete(key);
|
|
1488
|
+
return null;
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
return item.value;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
private setCache(key: string, value: any): void {
|
|
1495
|
+
this.cache.set(key, {
|
|
1496
|
+
value,
|
|
1497
|
+
timestamp: Date.now()
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
clearCache(): void {
|
|
1502
|
+
this.cache.clear();
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
```
|
|
1506
|
+
|
|
1507
|
+
### Batch Operations
|
|
1508
|
+
|
|
1509
|
+
```typescript
|
|
1510
|
+
class BatchOperations {
|
|
1511
|
+
private client: AlphaFuturesClient;
|
|
1512
|
+
private queue: Array<{
|
|
1513
|
+
operation: () => Promise<any>;
|
|
1514
|
+
resolve: (value: any) => void;
|
|
1515
|
+
reject: (error: any) => void;
|
|
1516
|
+
}> = [];
|
|
1517
|
+
private processing: boolean = false;
|
|
1518
|
+
|
|
1519
|
+
constructor(client: AlphaFuturesClient) {
|
|
1520
|
+
this.client = client;
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
async addToQueue<T>(operation: () => Promise<T>): Promise<T> {
|
|
1524
|
+
return new Promise((resolve, reject) => {
|
|
1525
|
+
this.queue.push({ operation, resolve, reject });
|
|
1526
|
+
this.processQueue();
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
private async processQueue(): Promise<void> {
|
|
1531
|
+
if (this.processing || this.queue.length === 0) {
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
this.processing = true;
|
|
1536
|
+
|
|
1537
|
+
while (this.queue.length > 0) {
|
|
1538
|
+
const batch = this.queue.splice(0, 10); // Process 10 at a time
|
|
1539
|
+
|
|
1540
|
+
await Promise.all(
|
|
1541
|
+
batch.map(async ({ operation, resolve, reject }) => {
|
|
1542
|
+
try {
|
|
1543
|
+
const result = await operation();
|
|
1544
|
+
resolve(result);
|
|
1545
|
+
} catch (error) {
|
|
1546
|
+
reject(error);
|
|
1547
|
+
}
|
|
1548
|
+
})
|
|
1549
|
+
);
|
|
1550
|
+
|
|
1551
|
+
// Rate limiting
|
|
1552
|
+
await this.sleep(100);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
this.processing = false;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
private sleep(ms: number): Promise<void> {
|
|
1559
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
```
|
|
1563
|
+
|
|
1564
|
+
## Security Best Practices
|
|
1565
|
+
|
|
1566
|
+
### 1. Private Key Security
|
|
1567
|
+
|
|
1568
|
+
- Never expose private keys in code
|
|
1569
|
+
- Use environment variables or secure key management systems
|
|
1570
|
+
- Implement key rotation policies
|
|
1571
|
+
|
|
1572
|
+
### 2. Input Validation
|
|
1573
|
+
|
|
1574
|
+
```typescript
|
|
1575
|
+
class SecureIntegration {
|
|
1576
|
+
validatePositionParams(params: any): void {
|
|
1577
|
+
// Asset validation
|
|
1578
|
+
if (!this.isValidAsset(params.asset)) {
|
|
1579
|
+
throw new ValidationError('Invalid asset', 'asset');
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
// Size validation
|
|
1583
|
+
const size = BigInt(params.size);
|
|
1584
|
+
if (size < MIN_POSITION_SIZE) {
|
|
1585
|
+
throw new ValidationError('Position size too small', 'size');
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
// Leverage validation
|
|
1589
|
+
if (params.leverage < 1 || params.leverage > 10) {
|
|
1590
|
+
throw new ValidationError('Invalid leverage', 'leverage');
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
private isValidAsset(asset: string): boolean {
|
|
1595
|
+
const validAssets = ['ALPHA', 'BETA', 'GAMMA'];
|
|
1596
|
+
return validAssets.includes(asset);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
### 3. Rate Limiting
|
|
1602
|
+
|
|
1603
|
+
```typescript
|
|
1604
|
+
class RateLimiter {
|
|
1605
|
+
private requests: Map<string, number[]> = new Map();
|
|
1606
|
+
private limit: number;
|
|
1607
|
+
private window: number;
|
|
1608
|
+
|
|
1609
|
+
constructor(limit: number = 100, windowMs: number = 60000) {
|
|
1610
|
+
this.limit = limit;
|
|
1611
|
+
this.window = windowMs;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
isAllowed(identifier: string): boolean {
|
|
1615
|
+
const now = Date.now();
|
|
1616
|
+
const requests = this.requests.get(identifier) || [];
|
|
1617
|
+
|
|
1618
|
+
// Remove old requests
|
|
1619
|
+
const validRequests = requests.filter(time => now - time < this.window);
|
|
1620
|
+
|
|
1621
|
+
if (validRequests.length >= this.limit) {
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
validRequests.push(now);
|
|
1626
|
+
this.requests.set(identifier, validRequests);
|
|
1627
|
+
|
|
1628
|
+
return true;
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
```
|
|
1632
|
+
|
|
1633
|
+
### 4. Transaction Security
|
|
1634
|
+
|
|
1635
|
+
```typescript
|
|
1636
|
+
class SecureTransactionManager {
|
|
1637
|
+
async sendTransaction(
|
|
1638
|
+
client: AlphaFuturesClient,
|
|
1639
|
+
operation: () => Promise<any>,
|
|
1640
|
+
options: {
|
|
1641
|
+
maxGasPrice?: bigint;
|
|
1642
|
+
requireConfirmations?: number;
|
|
1643
|
+
} = {}
|
|
1644
|
+
): Promise<any> {
|
|
1645
|
+
// Check gas price
|
|
1646
|
+
if (options.maxGasPrice) {
|
|
1647
|
+
const gasPrice = await client.provider.getGasPrice();
|
|
1648
|
+
if (gasPrice > options.maxGasPrice) {
|
|
1649
|
+
throw new Error('Gas price too high');
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
// Execute transaction
|
|
1654
|
+
const tx = await operation();
|
|
1655
|
+
|
|
1656
|
+
// Wait for confirmations
|
|
1657
|
+
const receipt = await tx.wait(options.requireConfirmations || 1);
|
|
1658
|
+
|
|
1659
|
+
// Verify success
|
|
1660
|
+
if (receipt.status !== 1) {
|
|
1661
|
+
throw new Error('Transaction failed');
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
return receipt;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
```
|
|
1668
|
+
|
|
1669
|
+
## Conclusion
|
|
1670
|
+
|
|
1671
|
+
Integrating the Alpha Futures SDK provides powerful capabilities for building trading applications. Key considerations:
|
|
1672
|
+
|
|
1673
|
+
1. **Security First**: Always prioritize security in your implementation
|
|
1674
|
+
2. **Error Handling**: Implement robust error handling and recovery
|
|
1675
|
+
3. **Performance**: Use caching and batching for optimal performance
|
|
1676
|
+
4. **Testing**: Thoroughly test all integration points
|
|
1677
|
+
5. **Monitoring**: Implement logging and monitoring for production
|
|
1678
|
+
|
|
1679
|
+
For additional support and examples, visit the [GitHub repository](https://github.com/alpha-futures/sdk) or join our [Discord community](https://discord.gg/alphafutures).
|