txflow-sdk 0.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/LICENSE +21 -0
- package/README.md +312 -0
- package/dist/client.d.ts +64 -0
- package/dist/client.js +283 -0
- package/dist/constants.d.ts +47 -0
- package/dist/constants.js +64 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +123 -0
- package/dist/signing.d.ts +10 -0
- package/dist/signing.js +81 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.js +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 andy-vibecoding
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
# txflow-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [TxFlow](https://txflow-testnet.xyz) perpetual DEX with EIP-712 signing and MCP (Model Context Protocol) server.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install txflow-sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
TxFlow uses an **agent wallet pattern**: a user wallet authorizes a temporary agent wallet via EIP-712 signature, and all subsequent trading actions are signed by the agent wallet.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { TxFlowClient } from "txflow-sdk";
|
|
17
|
+
import { ethers } from "ethers";
|
|
18
|
+
|
|
19
|
+
const userWallet = new ethers.Wallet("0xUSER_PRIVATE_KEY");
|
|
20
|
+
const agentWallet = new ethers.Wallet("0xAGENT_PRIVATE_KEY");
|
|
21
|
+
|
|
22
|
+
const client = new TxFlowClient({
|
|
23
|
+
privateKey: agentWallet.privateKey,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Step 1: Authorize the agent wallet (one-time)
|
|
27
|
+
await client.approveAgent(userWallet);
|
|
28
|
+
|
|
29
|
+
// Step 2: Place orders using the agent wallet
|
|
30
|
+
const result = await client.placeOrder({
|
|
31
|
+
asset: 1, // Asset index (use getPerpMeta to look up)
|
|
32
|
+
isBuy: true,
|
|
33
|
+
price: "50000",
|
|
34
|
+
size: "0.01",
|
|
35
|
+
reduceOnly: false,
|
|
36
|
+
timeInForce: "gtc",
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
const client = new TxFlowClient({
|
|
44
|
+
privateKey: "0x...", // Agent wallet private key (required)
|
|
45
|
+
baseUrl: "https://testnet-api.txflow.net", // API base URL (optional)
|
|
46
|
+
isMainnet: true, // Source identifier for signing (optional, default: true)
|
|
47
|
+
vaultAddress: null, // Vault address if applicable (optional)
|
|
48
|
+
chainId: 1337, // L1 signing domain chain ID (optional)
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Mainnet Migration
|
|
53
|
+
|
|
54
|
+
When TxFlow launches mainnet, update the config:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { TxFlowClient, MAINNET_API_URL } from "txflow-sdk";
|
|
58
|
+
|
|
59
|
+
const client = new TxFlowClient({
|
|
60
|
+
privateKey: "0x...",
|
|
61
|
+
baseUrl: MAINNET_API_URL, // or the actual mainnet URL
|
|
62
|
+
chainId: 1337, // update if mainnet uses a different chain ID
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
All signing logic, EIP-712 domains, and API calls adapt automatically based on the config.
|
|
67
|
+
|
|
68
|
+
## API Reference
|
|
69
|
+
|
|
70
|
+
### Trading
|
|
71
|
+
|
|
72
|
+
#### `placeOrder(params)`
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
await client.placeOrder({
|
|
76
|
+
asset: 1,
|
|
77
|
+
isBuy: true,
|
|
78
|
+
price: "50000",
|
|
79
|
+
size: "0.01",
|
|
80
|
+
reduceOnly: false,
|
|
81
|
+
timeInForce: "gtc", // "gtc" | "ioc" | "alo"
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
#### `placeBatchOrders(params[])`
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
await client.placeBatchOrders([
|
|
89
|
+
{ asset: 1, isBuy: true, price: "50000", size: "0.01" },
|
|
90
|
+
{ asset: 1, isBuy: false, price: "60000", size: "0.01" },
|
|
91
|
+
]);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
#### `cancelOrder(params)`
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
await client.cancelOrder({ asset: 1, orderId: "123456" });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### `cancelOrders(params[])`
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
await client.cancelOrders([
|
|
104
|
+
{ asset: 1, orderId: "123456" },
|
|
105
|
+
{ asset: 1, orderId: "789012" },
|
|
106
|
+
]);
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### `modifyOrder(params)`
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
await client.modifyOrder({
|
|
113
|
+
orderId: "123456",
|
|
114
|
+
asset: 1,
|
|
115
|
+
isBuy: true,
|
|
116
|
+
price: "51000",
|
|
117
|
+
size: "0.02",
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Account Settings
|
|
122
|
+
|
|
123
|
+
#### `updateLeverage(params)`
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
await client.updateLeverage({ asset: 1, leverage: 10 });
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
#### `updateMarginMode(params)`
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
await client.updateMarginMode({ asset: 1, isCross: true });
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### User Actions
|
|
136
|
+
|
|
137
|
+
These methods require the **user wallet** (not the agent wallet).
|
|
138
|
+
|
|
139
|
+
#### `approveAgent(userWallet, params?)`
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
await client.approveAgent(userWallet, {
|
|
143
|
+
agentAddress: "0x...", // defaults to SDK agent address
|
|
144
|
+
agentName: "MyBot", // defaults to "TradeAgent"
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `withdraw(userWallet, params)`
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
await client.withdraw(userWallet, {
|
|
152
|
+
destination: "0x...",
|
|
153
|
+
amount: "100",
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### `transferToken(userWallet, params)`
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
await client.transferToken(userWallet, {
|
|
161
|
+
amount: "100",
|
|
162
|
+
fromAccountType: "perp",
|
|
163
|
+
toAccountType: "spot",
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### `sendToken(userWallet, params)`
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
await client.sendToken(userWallet, {
|
|
171
|
+
toAddress: "0x...",
|
|
172
|
+
amount: "50",
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Queries
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const meta = await client.getPerpMeta();
|
|
180
|
+
const state = await client.getUserState("0xUSER_ADDRESS");
|
|
181
|
+
const orders = await client.getOpenOrders("0xUSER_ADDRESS");
|
|
182
|
+
const book = await client.getL2Book(1);
|
|
183
|
+
const assetData = await client.getActiveAssetData("0xUSER_ADDRESS", "1");
|
|
184
|
+
const candles = await client.getCandleSnapshot({
|
|
185
|
+
coin: "BTC",
|
|
186
|
+
interval: "1h",
|
|
187
|
+
startTime: Date.now() - 86400000,
|
|
188
|
+
endTime: Date.now(),
|
|
189
|
+
});
|
|
190
|
+
const fees = await client.getUserFees("0xUSER_ADDRESS");
|
|
191
|
+
const funding = await client.getFundingHistory({
|
|
192
|
+
user: "0xUSER_ADDRESS",
|
|
193
|
+
startTime: Date.now() - 86400000,
|
|
194
|
+
});
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Testnet Faucet
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
await client.requestFund("0xUSER_ADDRESS");
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Advanced: Low-Level Signing
|
|
204
|
+
|
|
205
|
+
For custom integrations, the signing primitives are exported directly:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import {
|
|
209
|
+
actionHash,
|
|
210
|
+
signL1Action,
|
|
211
|
+
createSign,
|
|
212
|
+
signUserAction,
|
|
213
|
+
getL1ActionDomain,
|
|
214
|
+
L1_ACTION_TYPES,
|
|
215
|
+
USER_ACTION_DOMAIN,
|
|
216
|
+
APPROVE_AGENT_TYPES,
|
|
217
|
+
} from "txflow-sdk";
|
|
218
|
+
|
|
219
|
+
// Compute action hash (MessagePack + nonce + vault encoding + keccak256)
|
|
220
|
+
const hash = actionHash(action, vaultAddress, nonce);
|
|
221
|
+
|
|
222
|
+
// Sign an L1 action (order, cancel, modify, leverage, margin mode)
|
|
223
|
+
const sig = await signL1Action(wallet, action, vaultAddress, nonce, isMainnet, chainId);
|
|
224
|
+
|
|
225
|
+
// Sign a user action (approve, withdraw, transfer)
|
|
226
|
+
const sig = await signUserAction(wallet, domain, types, message);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Signing Algorithm
|
|
230
|
+
|
|
231
|
+
1. Remove trailing zeros from `p` and `s` string fields recursively
|
|
232
|
+
2. MessagePack encode the action with BigInt64 support
|
|
233
|
+
3. Append nonce as 8-byte big-endian uint64
|
|
234
|
+
4. Append vault flag: `0x00` if null (9 extra bytes), or `0x01` + 20-byte address (29 extra bytes)
|
|
235
|
+
5. Keccak256 hash the buffer
|
|
236
|
+
6. Sign via EIP-712 typed data with the L1 action domain
|
|
237
|
+
|
|
238
|
+
## MCP Server
|
|
239
|
+
|
|
240
|
+
The SDK includes an MCP server for AI agent integration (e.g., Claude Desktop, Cursor, OpenClaw).
|
|
241
|
+
|
|
242
|
+
### Setup
|
|
243
|
+
|
|
244
|
+
Build the project first:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npm install && npm run build
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Configuration
|
|
251
|
+
|
|
252
|
+
Add to your MCP client config (e.g., `claude_desktop_config.json`):
|
|
253
|
+
|
|
254
|
+
```json
|
|
255
|
+
{
|
|
256
|
+
"mcpServers": {
|
|
257
|
+
"txflow": {
|
|
258
|
+
"command": "node",
|
|
259
|
+
"args": ["/path/to/txflow-sdk/dist/mcp.js"],
|
|
260
|
+
"env": {
|
|
261
|
+
"TXFLOW_AGENT_PRIVATE_KEY": "0xAGENT_PRIVATE_KEY",
|
|
262
|
+
"TXFLOW_USER_PRIVATE_KEY": "0xUSER_PRIVATE_KEY",
|
|
263
|
+
"TXFLOW_BASE_URL": "https://testnet-api.txflow.net"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Or if installed globally via npm:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"mcpServers": {
|
|
275
|
+
"txflow": {
|
|
276
|
+
"command": "txflow-mcp",
|
|
277
|
+
"env": {
|
|
278
|
+
"TXFLOW_AGENT_PRIVATE_KEY": "0xAGENT_PRIVATE_KEY",
|
|
279
|
+
"TXFLOW_USER_PRIVATE_KEY": "0xUSER_PRIVATE_KEY"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Environment Variables
|
|
287
|
+
|
|
288
|
+
| Variable | Required | Description |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| `TXFLOW_AGENT_PRIVATE_KEY` | Yes | Agent wallet private key for signing trades |
|
|
291
|
+
| `TXFLOW_USER_PRIVATE_KEY` | No | User wallet private key (only for `approve_agent`) |
|
|
292
|
+
| `TXFLOW_BASE_URL` | No | API URL (default: `https://testnet-api.txflow.net`) |
|
|
293
|
+
|
|
294
|
+
### Available Tools
|
|
295
|
+
|
|
296
|
+
| Tool | Description |
|
|
297
|
+
|---|---|
|
|
298
|
+
| `place_order` | Place a perpetual futures order |
|
|
299
|
+
| `cancel_order` | Cancel an open order |
|
|
300
|
+
| `cancel_orders` | Cancel multiple orders |
|
|
301
|
+
| `modify_order` | Modify an existing order |
|
|
302
|
+
| `update_leverage` | Update leverage for an asset |
|
|
303
|
+
| `update_margin_mode` | Switch cross/isolated margin |
|
|
304
|
+
| `approve_agent` | Authorize agent wallet (requires user key) |
|
|
305
|
+
| `get_open_orders` | List open orders for a user |
|
|
306
|
+
| `get_user_state` | Get account state, positions, balances |
|
|
307
|
+
| `get_perp_meta` | Get available assets and their indices |
|
|
308
|
+
| `get_l2_book` | Get order book depth for a coin |
|
|
309
|
+
|
|
310
|
+
## License
|
|
311
|
+
|
|
312
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import type { TxFlowConfig, PlaceOrderParams, CancelOrderParams, ModifyOrderParams, UpdateLeverageParams, UpdateMarginModeParams, ApproveAgentParams, WithdrawParams, TransferTokenParams, SendTokenParams, ApiResponse, InfoResponse } from "./types.js";
|
|
3
|
+
export declare class TxFlowClient {
|
|
4
|
+
private wallet;
|
|
5
|
+
private baseUrl;
|
|
6
|
+
private isMainnet;
|
|
7
|
+
private vaultAddress;
|
|
8
|
+
private chainId?;
|
|
9
|
+
constructor(config: TxFlowConfig);
|
|
10
|
+
get address(): string;
|
|
11
|
+
private postExchange;
|
|
12
|
+
private postInfo;
|
|
13
|
+
private buildOrderWire;
|
|
14
|
+
placeOrder(params: PlaceOrderParams): Promise<ApiResponse>;
|
|
15
|
+
placeBatchOrders(orderParams: PlaceOrderParams[]): Promise<ApiResponse>;
|
|
16
|
+
cancelOrder(params: CancelOrderParams): Promise<ApiResponse>;
|
|
17
|
+
cancelOrders(params: CancelOrderParams[]): Promise<ApiResponse>;
|
|
18
|
+
modifyOrder(params: ModifyOrderParams): Promise<ApiResponse>;
|
|
19
|
+
updateLeverage(params: UpdateLeverageParams): Promise<ApiResponse>;
|
|
20
|
+
updateMarginMode(params: UpdateMarginModeParams): Promise<ApiResponse>;
|
|
21
|
+
approveAgent(userWallet: ethers.Wallet, params?: Partial<ApproveAgentParams>): Promise<ApiResponse>;
|
|
22
|
+
withdraw(userWallet: ethers.Wallet, params: WithdrawParams): Promise<ApiResponse>;
|
|
23
|
+
transferToken(userWallet: ethers.Wallet, params: TransferTokenParams): Promise<ApiResponse>;
|
|
24
|
+
sendToken(userWallet: ethers.Wallet, params: SendTokenParams): Promise<ApiResponse>;
|
|
25
|
+
getOpenOrders(user: string): Promise<InfoResponse>;
|
|
26
|
+
getMultiUserOpenOrders(users: string[]): Promise<InfoResponse>;
|
|
27
|
+
getMaxPositions(params: {
|
|
28
|
+
user: string;
|
|
29
|
+
asset: string;
|
|
30
|
+
orderType: string;
|
|
31
|
+
buyPrice: string;
|
|
32
|
+
sellPrice: string;
|
|
33
|
+
reduceOnly: boolean;
|
|
34
|
+
}): Promise<InfoResponse>;
|
|
35
|
+
getLiquidationPrices(params: {
|
|
36
|
+
user: string;
|
|
37
|
+
asset: string;
|
|
38
|
+
orderType: string;
|
|
39
|
+
buyPrice: string;
|
|
40
|
+
sellPrice: string;
|
|
41
|
+
buyQuantity: string;
|
|
42
|
+
sellQuantity: string;
|
|
43
|
+
reduceOnly: boolean;
|
|
44
|
+
}): Promise<InfoResponse>;
|
|
45
|
+
getUserState(user: string): Promise<InfoResponse>;
|
|
46
|
+
getFundingHistory(params: {
|
|
47
|
+
user: string;
|
|
48
|
+
startTime: number;
|
|
49
|
+
endTime?: number;
|
|
50
|
+
}): Promise<InfoResponse>;
|
|
51
|
+
getUserFees(user: string): Promise<InfoResponse>;
|
|
52
|
+
getReferral(user: string): Promise<InfoResponse>;
|
|
53
|
+
getPerpMeta(dex?: string): Promise<InfoResponse>;
|
|
54
|
+
getSpotMeta(dex?: string): Promise<InfoResponse>;
|
|
55
|
+
getActiveAssetData(user: string, coin: string): Promise<InfoResponse>;
|
|
56
|
+
getL2Book(coin: string | number): Promise<InfoResponse>;
|
|
57
|
+
getCandleSnapshot(params: {
|
|
58
|
+
coin: string;
|
|
59
|
+
interval: string;
|
|
60
|
+
startTime: number;
|
|
61
|
+
endTime: number;
|
|
62
|
+
}): Promise<InfoResponse>;
|
|
63
|
+
requestFund(address?: string): Promise<ApiResponse>;
|
|
64
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { createSign, signUserAction } from "./signing.js";
|
|
3
|
+
import { TESTNET_API_URL, USDC_TOKEN_ADDRESS, USER_ACTION_DOMAIN, APPROVE_AGENT_TYPES, WITHDRAW_TYPES, TRANSFER_TOKEN_TYPES, SEND_TOKEN_TYPES, } from "./constants.js";
|
|
4
|
+
export class TxFlowClient {
|
|
5
|
+
wallet;
|
|
6
|
+
baseUrl;
|
|
7
|
+
isMainnet;
|
|
8
|
+
vaultAddress;
|
|
9
|
+
chainId;
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.wallet = new ethers.Wallet(config.privateKey);
|
|
12
|
+
this.baseUrl = config.baseUrl ?? TESTNET_API_URL;
|
|
13
|
+
this.isMainnet = config.isMainnet ?? true;
|
|
14
|
+
this.vaultAddress = config.vaultAddress ?? null;
|
|
15
|
+
this.chainId = config.chainId;
|
|
16
|
+
}
|
|
17
|
+
get address() {
|
|
18
|
+
return this.wallet.address;
|
|
19
|
+
}
|
|
20
|
+
async postExchange(body) {
|
|
21
|
+
const resp = await fetch(`${this.baseUrl}/exchange`, {
|
|
22
|
+
method: "POST",
|
|
23
|
+
headers: { "Content-Type": "application/json" },
|
|
24
|
+
body: JSON.stringify(body, (_key, value) => typeof value === "bigint" ? Number(value) : value),
|
|
25
|
+
});
|
|
26
|
+
return resp.json();
|
|
27
|
+
}
|
|
28
|
+
async postInfo(body) {
|
|
29
|
+
const resp = await fetch(`${this.baseUrl}/info`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify(body),
|
|
33
|
+
});
|
|
34
|
+
return resp.json();
|
|
35
|
+
}
|
|
36
|
+
buildOrderWire(params) {
|
|
37
|
+
return {
|
|
38
|
+
a: params.asset,
|
|
39
|
+
b: params.isBuy,
|
|
40
|
+
p: params.price,
|
|
41
|
+
s: params.size,
|
|
42
|
+
r: params.reduceOnly ?? false,
|
|
43
|
+
t: { limit: { tif: params.timeInForce ?? "gtc" } },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
async placeOrder(params) {
|
|
47
|
+
const order = this.buildOrderWire(params);
|
|
48
|
+
const action = { grouping: "na", orders: [order] };
|
|
49
|
+
const { signature, nonce } = await createSign(this.wallet, action, this.isMainnet, this.vaultAddress, this.chainId);
|
|
50
|
+
return this.postExchange({
|
|
51
|
+
action: { type: "order", grouping: "na", orders: [order], nonce },
|
|
52
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
53
|
+
nonce,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async placeBatchOrders(orderParams) {
|
|
57
|
+
const orders = orderParams.map((p) => this.buildOrderWire(p));
|
|
58
|
+
const action = { grouping: "na", orders };
|
|
59
|
+
const { signature, nonce } = await createSign(this.wallet, action, this.isMainnet, this.vaultAddress, this.chainId);
|
|
60
|
+
return this.postExchange({
|
|
61
|
+
action: { type: "order", grouping: "na", orders, nonce },
|
|
62
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
63
|
+
nonce,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async cancelOrder(params) {
|
|
67
|
+
const cancelAction = {
|
|
68
|
+
cancels: [{ a: params.asset, o: BigInt(params.orderId) }],
|
|
69
|
+
};
|
|
70
|
+
const { signature, nonce } = await createSign(this.wallet, cancelAction, this.isMainnet, this.vaultAddress, this.chainId);
|
|
71
|
+
return this.postExchange({
|
|
72
|
+
action: { type: "cancel", cancels: [{ a: params.asset, o: params.orderId }] },
|
|
73
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
74
|
+
nonce,
|
|
75
|
+
vaultAddress: this.vaultAddress,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
async cancelOrders(params) {
|
|
79
|
+
const cancelAction = {
|
|
80
|
+
cancels: params.map((p) => ({ a: p.asset, o: BigInt(p.orderId) })),
|
|
81
|
+
};
|
|
82
|
+
const { signature, nonce } = await createSign(this.wallet, cancelAction, this.isMainnet, this.vaultAddress, this.chainId);
|
|
83
|
+
return this.postExchange({
|
|
84
|
+
action: {
|
|
85
|
+
type: "cancel",
|
|
86
|
+
cancels: params.map((p) => ({ a: p.asset, o: p.orderId })),
|
|
87
|
+
},
|
|
88
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
89
|
+
nonce,
|
|
90
|
+
vaultAddress: this.vaultAddress,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async modifyOrder(params) {
|
|
94
|
+
const order = this.buildOrderWire({
|
|
95
|
+
asset: params.asset,
|
|
96
|
+
isBuy: params.isBuy,
|
|
97
|
+
price: params.price,
|
|
98
|
+
size: params.size,
|
|
99
|
+
reduceOnly: params.reduceOnly,
|
|
100
|
+
timeInForce: params.timeInForce,
|
|
101
|
+
});
|
|
102
|
+
const modifyAction = { oid: BigInt(params.orderId), order };
|
|
103
|
+
const { signature, nonce } = await createSign(this.wallet, modifyAction, this.isMainnet, this.vaultAddress, this.chainId);
|
|
104
|
+
return this.postExchange({
|
|
105
|
+
action: { type: "modify", oid: params.orderId, order },
|
|
106
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
107
|
+
nonce,
|
|
108
|
+
vaultAddress: this.vaultAddress,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
async updateLeverage(params) {
|
|
112
|
+
const action = { asset: params.asset, leverage: params.leverage };
|
|
113
|
+
const { signature, nonce } = await createSign(this.wallet, action, this.isMainnet, this.vaultAddress, this.chainId);
|
|
114
|
+
return this.postExchange({
|
|
115
|
+
action: { type: "updateLeverage", ...action },
|
|
116
|
+
isFrontend: true,
|
|
117
|
+
vaultAddress: this.vaultAddress,
|
|
118
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
119
|
+
nonce,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
async updateMarginMode(params) {
|
|
123
|
+
const action = { asset: params.asset, isCross: params.isCross };
|
|
124
|
+
const { signature, nonce } = await createSign(this.wallet, action, this.isMainnet, this.vaultAddress, this.chainId);
|
|
125
|
+
return this.postExchange({
|
|
126
|
+
action: { type: "updateMarginMode", ...action },
|
|
127
|
+
isFrontend: true,
|
|
128
|
+
vaultAddress: this.vaultAddress,
|
|
129
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
130
|
+
nonce,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
async approveAgent(userWallet, params) {
|
|
134
|
+
const agentAddress = params?.agentAddress ?? this.wallet.address;
|
|
135
|
+
const agentName = params?.agentName ?? "TradeAgent";
|
|
136
|
+
const nonce = params?.nonce ?? Date.now();
|
|
137
|
+
const message = {
|
|
138
|
+
ofinChain: "ofin",
|
|
139
|
+
agentAddress,
|
|
140
|
+
agentName,
|
|
141
|
+
nonce,
|
|
142
|
+
};
|
|
143
|
+
const signature = await signUserAction(userWallet, USER_ACTION_DOMAIN, APPROVE_AGENT_TYPES, message);
|
|
144
|
+
return this.postExchange({
|
|
145
|
+
action: {
|
|
146
|
+
type: "approveAgent",
|
|
147
|
+
agentAddress,
|
|
148
|
+
agentName,
|
|
149
|
+
nonce,
|
|
150
|
+
ofinChain: "ofin",
|
|
151
|
+
},
|
|
152
|
+
nonce,
|
|
153
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async withdraw(userWallet, params) {
|
|
157
|
+
const nonce = Date.now();
|
|
158
|
+
const signatureChainId = params.signatureChainId ?? "0x66eee";
|
|
159
|
+
const message = {
|
|
160
|
+
ofinChain: "ofin",
|
|
161
|
+
destination: params.destination,
|
|
162
|
+
signatureChainId,
|
|
163
|
+
tokenAddress: USDC_TOKEN_ADDRESS,
|
|
164
|
+
amount: params.amount,
|
|
165
|
+
nonce,
|
|
166
|
+
};
|
|
167
|
+
const signature = await signUserAction(userWallet, USER_ACTION_DOMAIN, WITHDRAW_TYPES, message);
|
|
168
|
+
return this.postExchange({
|
|
169
|
+
action: {
|
|
170
|
+
type: "withdraw3",
|
|
171
|
+
ofinChain: "ofin",
|
|
172
|
+
signatureChainId,
|
|
173
|
+
destination: params.destination,
|
|
174
|
+
tokenAddress: USDC_TOKEN_ADDRESS,
|
|
175
|
+
amount: params.amount,
|
|
176
|
+
nonce,
|
|
177
|
+
},
|
|
178
|
+
nonce,
|
|
179
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
async transferToken(userWallet, params) {
|
|
183
|
+
const nonce = Date.now();
|
|
184
|
+
const token = `USDC:${USDC_TOKEN_ADDRESS}`;
|
|
185
|
+
const message = {
|
|
186
|
+
ofinChain: "ofin",
|
|
187
|
+
amount: params.amount,
|
|
188
|
+
fromAccountType: params.fromAccountType,
|
|
189
|
+
toAccountType: params.toAccountType,
|
|
190
|
+
token,
|
|
191
|
+
nonce,
|
|
192
|
+
};
|
|
193
|
+
const signature = await signUserAction(userWallet, USER_ACTION_DOMAIN, TRANSFER_TOKEN_TYPES, message);
|
|
194
|
+
return this.postExchange({
|
|
195
|
+
action: {
|
|
196
|
+
type: "transferToken",
|
|
197
|
+
ofinChain: "ofin",
|
|
198
|
+
amount: params.amount,
|
|
199
|
+
fromAccountType: params.fromAccountType,
|
|
200
|
+
toAccountType: params.toAccountType,
|
|
201
|
+
token,
|
|
202
|
+
nonce,
|
|
203
|
+
},
|
|
204
|
+
nonce,
|
|
205
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
async sendToken(userWallet, params) {
|
|
209
|
+
const nonce = Date.now();
|
|
210
|
+
const token = `USDC:${USDC_TOKEN_ADDRESS}`;
|
|
211
|
+
const message = {
|
|
212
|
+
ofinChain: "ofin",
|
|
213
|
+
toAddress: params.toAddress,
|
|
214
|
+
amount: params.amount,
|
|
215
|
+
fromAccountType: params.fromAccountType ?? "perp",
|
|
216
|
+
toAccountType: params.toAccountType ?? "perp",
|
|
217
|
+
token,
|
|
218
|
+
nonce,
|
|
219
|
+
};
|
|
220
|
+
const signature = await signUserAction(userWallet, USER_ACTION_DOMAIN, SEND_TOKEN_TYPES, message);
|
|
221
|
+
return this.postExchange({
|
|
222
|
+
action: {
|
|
223
|
+
type: "sendToken",
|
|
224
|
+
ofinChain: "ofin",
|
|
225
|
+
toAddress: params.toAddress,
|
|
226
|
+
amount: params.amount,
|
|
227
|
+
fromAccountType: params.fromAccountType ?? "perp",
|
|
228
|
+
toAccountType: params.toAccountType ?? "perp",
|
|
229
|
+
token,
|
|
230
|
+
nonce,
|
|
231
|
+
},
|
|
232
|
+
nonce,
|
|
233
|
+
signature: { r: signature.r, s: signature.s, v: signature.v },
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async getOpenOrders(user) {
|
|
237
|
+
return this.postInfo({ type: "openorders", user });
|
|
238
|
+
}
|
|
239
|
+
async getMultiUserOpenOrders(users) {
|
|
240
|
+
return this.postInfo({ type: "multiuseropenorders", users, limit: "" });
|
|
241
|
+
}
|
|
242
|
+
async getMaxPositions(params) {
|
|
243
|
+
return this.postInfo({ type: "getmaxpositions", ...params });
|
|
244
|
+
}
|
|
245
|
+
async getLiquidationPrices(params) {
|
|
246
|
+
return this.postInfo({ type: "getliquidationprices", ...params });
|
|
247
|
+
}
|
|
248
|
+
async getUserState(user) {
|
|
249
|
+
return this.postInfo({ type: "clearinghouseState", user });
|
|
250
|
+
}
|
|
251
|
+
async getFundingHistory(params) {
|
|
252
|
+
return this.postInfo({ type: "userfunding", ...params });
|
|
253
|
+
}
|
|
254
|
+
async getUserFees(user) {
|
|
255
|
+
return this.postInfo({ type: "userfees", user });
|
|
256
|
+
}
|
|
257
|
+
async getReferral(user) {
|
|
258
|
+
return this.postInfo({ type: "referral", user });
|
|
259
|
+
}
|
|
260
|
+
async getPerpMeta(dex) {
|
|
261
|
+
return this.postInfo({ type: "perpmeta", dex: dex ?? "" });
|
|
262
|
+
}
|
|
263
|
+
async getSpotMeta(dex) {
|
|
264
|
+
return this.postInfo({ type: "spotmeta", dex: dex ?? "" });
|
|
265
|
+
}
|
|
266
|
+
async getActiveAssetData(user, coin) {
|
|
267
|
+
return this.postInfo({ type: "activeassetdata", user, coin });
|
|
268
|
+
}
|
|
269
|
+
async getL2Book(coin) {
|
|
270
|
+
return this.postInfo({ type: "l2book", coin: String(coin) });
|
|
271
|
+
}
|
|
272
|
+
async getCandleSnapshot(params) {
|
|
273
|
+
return this.postInfo({ type: "candleSnapshot", ...params });
|
|
274
|
+
}
|
|
275
|
+
async requestFund(address) {
|
|
276
|
+
const resp = await fetch(`${this.baseUrl}/request-fund`, {
|
|
277
|
+
method: "POST",
|
|
278
|
+
headers: { "Content-Type": "application/json" },
|
|
279
|
+
body: JSON.stringify({ address: address ?? this.wallet.address }),
|
|
280
|
+
});
|
|
281
|
+
return resp.json();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export declare const TESTNET_API_URL = "https://testnet-api.txflow.net";
|
|
2
|
+
export declare const MAINNET_API_URL = "https://api.txflow.net";
|
|
3
|
+
export declare const USDC_TOKEN_ADDRESS = "0xd04f0d19E8FbA49cfA541E272F9C60a2ad720464";
|
|
4
|
+
export declare const TESTNET_L1_CHAIN_ID = 1337;
|
|
5
|
+
export declare const MAINNET_L1_CHAIN_ID = 1337;
|
|
6
|
+
export declare function getL1ActionDomain(chainId?: number): {
|
|
7
|
+
name: "Exchange";
|
|
8
|
+
version: "1";
|
|
9
|
+
chainId: number;
|
|
10
|
+
verifyingContract: `0x${string}`;
|
|
11
|
+
};
|
|
12
|
+
export declare const L1_ACTION_TYPES: {
|
|
13
|
+
Agent: {
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
}[];
|
|
17
|
+
};
|
|
18
|
+
export declare const USER_ACTION_DOMAIN: {
|
|
19
|
+
name: string;
|
|
20
|
+
version: string;
|
|
21
|
+
chainId: number;
|
|
22
|
+
verifyingContract: `0x${string}`;
|
|
23
|
+
};
|
|
24
|
+
export declare const APPROVE_AGENT_TYPES: {
|
|
25
|
+
ApproveAgent: {
|
|
26
|
+
name: string;
|
|
27
|
+
type: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
export declare const WITHDRAW_TYPES: {
|
|
31
|
+
Withdraw3: {
|
|
32
|
+
name: string;
|
|
33
|
+
type: string;
|
|
34
|
+
}[];
|
|
35
|
+
};
|
|
36
|
+
export declare const TRANSFER_TOKEN_TYPES: {
|
|
37
|
+
TransferToken: {
|
|
38
|
+
name: string;
|
|
39
|
+
type: string;
|
|
40
|
+
}[];
|
|
41
|
+
};
|
|
42
|
+
export declare const SEND_TOKEN_TYPES: {
|
|
43
|
+
SendToken: {
|
|
44
|
+
name: string;
|
|
45
|
+
type: string;
|
|
46
|
+
}[];
|
|
47
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const TESTNET_API_URL = "https://testnet-api.txflow.net";
|
|
2
|
+
export const MAINNET_API_URL = "https://api.txflow.net";
|
|
3
|
+
export const USDC_TOKEN_ADDRESS = "0xd04f0d19E8FbA49cfA541E272F9C60a2ad720464";
|
|
4
|
+
export const TESTNET_L1_CHAIN_ID = 1337;
|
|
5
|
+
export const MAINNET_L1_CHAIN_ID = 1337;
|
|
6
|
+
export function getL1ActionDomain(chainId = TESTNET_L1_CHAIN_ID) {
|
|
7
|
+
return {
|
|
8
|
+
name: "Exchange",
|
|
9
|
+
version: "1",
|
|
10
|
+
chainId,
|
|
11
|
+
verifyingContract: "0x0000000000000000000000000000000000000000",
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export const L1_ACTION_TYPES = {
|
|
15
|
+
Agent: [
|
|
16
|
+
{ name: "source", type: "string" },
|
|
17
|
+
{ name: "connectionId", type: "bytes32" },
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
export const USER_ACTION_DOMAIN = {
|
|
21
|
+
name: "Ofin",
|
|
22
|
+
version: "1",
|
|
23
|
+
chainId: 1,
|
|
24
|
+
verifyingContract: "0x0000000000000000000000000000000000000000",
|
|
25
|
+
};
|
|
26
|
+
export const APPROVE_AGENT_TYPES = {
|
|
27
|
+
ApproveAgent: [
|
|
28
|
+
{ name: "ofinChain", type: "string" },
|
|
29
|
+
{ name: "agentAddress", type: "address" },
|
|
30
|
+
{ name: "agentName", type: "string" },
|
|
31
|
+
{ name: "nonce", type: "uint64" },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
export const WITHDRAW_TYPES = {
|
|
35
|
+
Withdraw3: [
|
|
36
|
+
{ name: "ofinChain", type: "string" },
|
|
37
|
+
{ name: "destination", type: "address" },
|
|
38
|
+
{ name: "signatureChainId", type: "string" },
|
|
39
|
+
{ name: "tokenAddress", type: "address" },
|
|
40
|
+
{ name: "amount", type: "string" },
|
|
41
|
+
{ name: "nonce", type: "uint64" },
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
export const TRANSFER_TOKEN_TYPES = {
|
|
45
|
+
TransferToken: [
|
|
46
|
+
{ name: "ofinChain", type: "string" },
|
|
47
|
+
{ name: "amount", type: "string" },
|
|
48
|
+
{ name: "fromAccountType", type: "string" },
|
|
49
|
+
{ name: "toAccountType", type: "string" },
|
|
50
|
+
{ name: "token", type: "string" },
|
|
51
|
+
{ name: "nonce", type: "uint64" },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
export const SEND_TOKEN_TYPES = {
|
|
55
|
+
SendToken: [
|
|
56
|
+
{ name: "ofinChain", type: "string" },
|
|
57
|
+
{ name: "toAddress", type: "address" },
|
|
58
|
+
{ name: "amount", type: "string" },
|
|
59
|
+
{ name: "fromAccountType", type: "string" },
|
|
60
|
+
{ name: "toAccountType", type: "string" },
|
|
61
|
+
{ name: "token", type: "string" },
|
|
62
|
+
{ name: "nonce", type: "uint64" },
|
|
63
|
+
],
|
|
64
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { TxFlowClient } from "./client.js";
|
|
2
|
+
export { actionHash, signL1Action, createSign, signUserAction, generateNonce } from "./signing.js";
|
|
3
|
+
export { TESTNET_API_URL, MAINNET_API_URL, USDC_TOKEN_ADDRESS, TESTNET_L1_CHAIN_ID, MAINNET_L1_CHAIN_ID, getL1ActionDomain, L1_ACTION_TYPES, USER_ACTION_DOMAIN, APPROVE_AGENT_TYPES, WITHDRAW_TYPES, TRANSFER_TOKEN_TYPES, SEND_TOKEN_TYPES, } from "./constants.js";
|
|
4
|
+
export type { TxFlowConfig, OrderWire, OrderGrouping, CancelWire, CancelAction, ModifyWire, Signature, SignedRequest, PlaceOrderParams, CancelOrderParams, ModifyOrderParams, UpdateLeverageParams, UpdateMarginModeParams, ApproveAgentParams, WithdrawParams, TransferTokenParams, SendTokenParams, ApiResponse, InfoResponse, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { TxFlowClient } from "./client.js";
|
|
2
|
+
export { actionHash, signL1Action, createSign, signUserAction, generateNonce } from "./signing.js";
|
|
3
|
+
export { TESTNET_API_URL, MAINNET_API_URL, USDC_TOKEN_ADDRESS, TESTNET_L1_CHAIN_ID, MAINNET_L1_CHAIN_ID, getL1ActionDomain, L1_ACTION_TYPES, USER_ACTION_DOMAIN, APPROVE_AGENT_TYPES, WITHDRAW_TYPES, TRANSFER_TOKEN_TYPES, SEND_TOKEN_TYPES, } from "./constants.js";
|
package/dist/mcp.d.ts
ADDED
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { TxFlowClient } from "./client.js";
|
|
6
|
+
import { ethers } from "ethers";
|
|
7
|
+
function getEnvOrThrow(name) {
|
|
8
|
+
const val = process.env[name];
|
|
9
|
+
if (!val)
|
|
10
|
+
throw new Error(`Missing required env var: ${name}`);
|
|
11
|
+
return val;
|
|
12
|
+
}
|
|
13
|
+
function fmt(result) {
|
|
14
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
15
|
+
}
|
|
16
|
+
async function main() {
|
|
17
|
+
const agentPrivateKey = getEnvOrThrow("TXFLOW_AGENT_PRIVATE_KEY");
|
|
18
|
+
const baseUrl = process.env.TXFLOW_BASE_URL || "https://testnet-api.txflow.net";
|
|
19
|
+
const userPrivateKey = process.env.TXFLOW_USER_PRIVATE_KEY;
|
|
20
|
+
const client = new TxFlowClient({
|
|
21
|
+
privateKey: agentPrivateKey,
|
|
22
|
+
baseUrl,
|
|
23
|
+
isMainnet: true,
|
|
24
|
+
});
|
|
25
|
+
const userWallet = userPrivateKey ? new ethers.Wallet(userPrivateKey) : null;
|
|
26
|
+
const server = new McpServer({
|
|
27
|
+
name: "txflow-mcp",
|
|
28
|
+
version: "0.1.0",
|
|
29
|
+
});
|
|
30
|
+
server.tool("place_order", "Place a perpetual futures order on TxFlow DEX.", {
|
|
31
|
+
asset: z.number().describe("Asset index (e.g. 0 for BTC)"),
|
|
32
|
+
isBuy: z.boolean().describe("True for buy/long, false for sell/short"),
|
|
33
|
+
price: z.string().describe("Order price"),
|
|
34
|
+
size: z.string().describe("Order size"),
|
|
35
|
+
reduceOnly: z.boolean().optional().describe("Reduce-only order"),
|
|
36
|
+
timeInForce: z.string().optional().describe("Time in force: gtc, ioc, alo"),
|
|
37
|
+
}, async (params) => {
|
|
38
|
+
const result = await client.placeOrder(params);
|
|
39
|
+
return fmt(result);
|
|
40
|
+
});
|
|
41
|
+
server.tool("cancel_order", "Cancel an open order on TxFlow DEX.", {
|
|
42
|
+
asset: z.number().describe("Asset index"),
|
|
43
|
+
orderId: z.string().describe("Order ID to cancel"),
|
|
44
|
+
}, async (params) => {
|
|
45
|
+
const result = await client.cancelOrder(params);
|
|
46
|
+
return fmt(result);
|
|
47
|
+
});
|
|
48
|
+
server.tool("cancel_orders", "Cancel multiple open orders on TxFlow DEX.", {
|
|
49
|
+
orders: z
|
|
50
|
+
.string()
|
|
51
|
+
.describe('JSON array of {asset: number, orderId: string} objects'),
|
|
52
|
+
}, async ({ orders }) => {
|
|
53
|
+
const parsed = JSON.parse(orders);
|
|
54
|
+
const result = await client.cancelOrders(parsed);
|
|
55
|
+
return fmt(result);
|
|
56
|
+
});
|
|
57
|
+
server.tool("modify_order", "Modify an existing order on TxFlow DEX.", {
|
|
58
|
+
orderId: z.string().describe("Order ID to modify"),
|
|
59
|
+
asset: z.number().describe("Asset index"),
|
|
60
|
+
isBuy: z.boolean().describe("True for buy, false for sell"),
|
|
61
|
+
price: z.string().describe("New price"),
|
|
62
|
+
size: z.string().describe("New size"),
|
|
63
|
+
reduceOnly: z.boolean().optional().describe("Reduce-only"),
|
|
64
|
+
timeInForce: z.string().optional().describe("Time in force"),
|
|
65
|
+
}, async (params) => {
|
|
66
|
+
const result = await client.modifyOrder(params);
|
|
67
|
+
return fmt(result);
|
|
68
|
+
});
|
|
69
|
+
server.tool("update_leverage", "Update leverage for an asset on TxFlow DEX.", {
|
|
70
|
+
asset: z.number().describe("Asset index"),
|
|
71
|
+
leverage: z.number().describe("New leverage value"),
|
|
72
|
+
}, async (params) => {
|
|
73
|
+
const result = await client.updateLeverage(params);
|
|
74
|
+
return fmt(result);
|
|
75
|
+
});
|
|
76
|
+
server.tool("update_margin_mode", "Update margin mode (cross/isolated) for an asset.", {
|
|
77
|
+
asset: z.number().describe("Asset index"),
|
|
78
|
+
isCross: z.boolean().describe("True for cross margin, false for isolated"),
|
|
79
|
+
}, async (params) => {
|
|
80
|
+
const result = await client.updateMarginMode(params);
|
|
81
|
+
return fmt(result);
|
|
82
|
+
});
|
|
83
|
+
server.tool("get_open_orders", "Get open orders for a user on TxFlow DEX.", {
|
|
84
|
+
user: z.string().describe("User address"),
|
|
85
|
+
}, async ({ user }) => {
|
|
86
|
+
const result = await client.getOpenOrders(user);
|
|
87
|
+
return fmt(result);
|
|
88
|
+
});
|
|
89
|
+
server.tool("get_user_state", "Get user account state including positions and balances.", {
|
|
90
|
+
user: z.string().describe("User address"),
|
|
91
|
+
}, async ({ user }) => {
|
|
92
|
+
const result = await client.getUserState(user);
|
|
93
|
+
return fmt(result);
|
|
94
|
+
});
|
|
95
|
+
server.tool("get_perp_meta", "Get perpetual futures metadata including available assets and their indices.", {
|
|
96
|
+
dex: z.string().optional().describe("DEX identifier (optional)"),
|
|
97
|
+
}, async ({ dex }) => {
|
|
98
|
+
const result = await client.getPerpMeta(dex);
|
|
99
|
+
return fmt(result);
|
|
100
|
+
});
|
|
101
|
+
server.tool("get_l2_book", "Get L2 order book for a coin.", {
|
|
102
|
+
coin: z.string().describe("Coin symbol, e.g. BTC"),
|
|
103
|
+
}, async ({ coin }) => {
|
|
104
|
+
const result = await client.getL2Book(coin);
|
|
105
|
+
return fmt(result);
|
|
106
|
+
});
|
|
107
|
+
server.tool("approve_agent", "Approve agent wallet for trading. Requires TXFLOW_USER_PRIVATE_KEY.", {
|
|
108
|
+
agentAddress: z.string().optional().describe("Agent address (defaults to SDK agent)"),
|
|
109
|
+
agentName: z.string().optional().describe("Agent name (defaults to TradeAgent)"),
|
|
110
|
+
}, async (params) => {
|
|
111
|
+
if (!userWallet) {
|
|
112
|
+
return fmt({ error: "TXFLOW_USER_PRIVATE_KEY env var is required for approve_agent" });
|
|
113
|
+
}
|
|
114
|
+
const result = await client.approveAgent(userWallet, params);
|
|
115
|
+
return fmt(result);
|
|
116
|
+
});
|
|
117
|
+
const transport = new StdioServerTransport();
|
|
118
|
+
await server.connect(transport);
|
|
119
|
+
}
|
|
120
|
+
main().catch((err) => {
|
|
121
|
+
console.error("Fatal error:", err);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import type { Signature } from "./types.js";
|
|
3
|
+
export declare function actionHash(action: unknown, vaultAddress: string | null, nonce: number): string;
|
|
4
|
+
export declare function signL1Action(wallet: ethers.Wallet, action: unknown, vaultAddress: string | null, nonce: number, isMainnet: boolean, chainId?: number): Promise<Signature>;
|
|
5
|
+
export declare function generateNonce(): number;
|
|
6
|
+
export declare function createSign(wallet: ethers.Wallet, action: unknown, isMainnet: boolean, vaultAddress?: string | null, chainId?: number): Promise<{
|
|
7
|
+
signature: Signature;
|
|
8
|
+
nonce: number;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function signUserAction(wallet: ethers.Wallet, domain: ethers.TypedDataDomain, types: Record<string, ethers.TypedDataField[]>, message: Record<string, unknown>): Promise<Signature>;
|
package/dist/signing.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { encode } from "@msgpack/msgpack";
|
|
3
|
+
import { getL1ActionDomain, L1_ACTION_TYPES } from "./constants.js";
|
|
4
|
+
function removeTrailingZeros(value) {
|
|
5
|
+
if (!value.includes("."))
|
|
6
|
+
return value;
|
|
7
|
+
const result = value.replace(/\.?0+$/, "");
|
|
8
|
+
return result === "-0" ? "0" : result;
|
|
9
|
+
}
|
|
10
|
+
function cleanAction(obj) {
|
|
11
|
+
if (!obj || typeof obj !== "object")
|
|
12
|
+
return obj;
|
|
13
|
+
if (Array.isArray(obj))
|
|
14
|
+
return obj.map((item) => cleanAction(item));
|
|
15
|
+
const cleaned = { ...obj };
|
|
16
|
+
for (const key in cleaned) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(cleaned, key)) {
|
|
18
|
+
const val = cleaned[key];
|
|
19
|
+
if (val && typeof val === "object") {
|
|
20
|
+
cleaned[key] = cleanAction(val);
|
|
21
|
+
}
|
|
22
|
+
else if ((key === "p" || key === "s") && typeof val === "string") {
|
|
23
|
+
cleaned[key] = removeTrailingZeros(val);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return cleaned;
|
|
28
|
+
}
|
|
29
|
+
function msgpackEncode(action) {
|
|
30
|
+
return encode(action, { useBigInt64: true });
|
|
31
|
+
}
|
|
32
|
+
export function actionHash(action, vaultAddress, nonce) {
|
|
33
|
+
const cleaned = cleanAction(action);
|
|
34
|
+
const encoded = msgpackEncode(cleaned);
|
|
35
|
+
const extraBytes = vaultAddress === null ? 9 : 29;
|
|
36
|
+
const buffer = new Uint8Array(encoded.length + extraBytes);
|
|
37
|
+
buffer.set(encoded);
|
|
38
|
+
const view = new DataView(buffer.buffer);
|
|
39
|
+
view.setBigUint64(encoded.length, BigInt(nonce), false);
|
|
40
|
+
if (vaultAddress === null) {
|
|
41
|
+
buffer[encoded.length + 8] = 0;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
buffer[encoded.length + 8] = 1;
|
|
45
|
+
const addrBytes = ethers.getBytes(vaultAddress);
|
|
46
|
+
buffer.set(addrBytes, encoded.length + 9);
|
|
47
|
+
}
|
|
48
|
+
return ethers.keccak256(buffer);
|
|
49
|
+
}
|
|
50
|
+
export async function signL1Action(wallet, action, vaultAddress, nonce, isMainnet, chainId) {
|
|
51
|
+
const hash = actionHash(action, vaultAddress, nonce);
|
|
52
|
+
const message = {
|
|
53
|
+
source: isMainnet ? "a" : "b",
|
|
54
|
+
connectionId: hash,
|
|
55
|
+
};
|
|
56
|
+
const domain = getL1ActionDomain(chainId);
|
|
57
|
+
const sig = await wallet.signTypedData(domain, L1_ACTION_TYPES, message);
|
|
58
|
+
const split = ethers.Signature.from(sig);
|
|
59
|
+
return {
|
|
60
|
+
r: split.r,
|
|
61
|
+
s: split.s,
|
|
62
|
+
v: split.v,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function generateNonce() {
|
|
66
|
+
return Date.now();
|
|
67
|
+
}
|
|
68
|
+
export async function createSign(wallet, action, isMainnet, vaultAddress = null, chainId) {
|
|
69
|
+
const nonce = generateNonce();
|
|
70
|
+
const signature = await signL1Action(wallet, action, vaultAddress, nonce, isMainnet, chainId);
|
|
71
|
+
return { signature, nonce };
|
|
72
|
+
}
|
|
73
|
+
export async function signUserAction(wallet, domain, types, message) {
|
|
74
|
+
const sig = await wallet.signTypedData(domain, types, message);
|
|
75
|
+
const split = ethers.Signature.from(sig);
|
|
76
|
+
return {
|
|
77
|
+
r: split.r,
|
|
78
|
+
s: split.s,
|
|
79
|
+
v: split.v,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
export interface TxFlowConfig {
|
|
2
|
+
privateKey: string;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
isMainnet?: boolean;
|
|
5
|
+
vaultAddress?: string | null;
|
|
6
|
+
chainId?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface OrderWire {
|
|
9
|
+
a: number;
|
|
10
|
+
b: boolean;
|
|
11
|
+
p: string;
|
|
12
|
+
s: string;
|
|
13
|
+
r: boolean;
|
|
14
|
+
t: {
|
|
15
|
+
limit: {
|
|
16
|
+
tif: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface OrderGrouping {
|
|
21
|
+
grouping: string;
|
|
22
|
+
orders: OrderWire[];
|
|
23
|
+
}
|
|
24
|
+
export interface CancelWire {
|
|
25
|
+
a: number;
|
|
26
|
+
o: bigint;
|
|
27
|
+
}
|
|
28
|
+
export interface CancelAction {
|
|
29
|
+
cancels: CancelWire[];
|
|
30
|
+
}
|
|
31
|
+
export interface ModifyWire {
|
|
32
|
+
oid: bigint;
|
|
33
|
+
order: OrderWire;
|
|
34
|
+
}
|
|
35
|
+
export interface Signature {
|
|
36
|
+
r: string;
|
|
37
|
+
s: string;
|
|
38
|
+
v: number;
|
|
39
|
+
}
|
|
40
|
+
export interface SignedRequest {
|
|
41
|
+
action: Record<string, unknown>;
|
|
42
|
+
signature: Signature;
|
|
43
|
+
nonce: number;
|
|
44
|
+
vaultAddress?: string | null;
|
|
45
|
+
}
|
|
46
|
+
export interface PlaceOrderParams {
|
|
47
|
+
asset: number;
|
|
48
|
+
isBuy: boolean;
|
|
49
|
+
price: string;
|
|
50
|
+
size: string;
|
|
51
|
+
reduceOnly?: boolean;
|
|
52
|
+
timeInForce?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface CancelOrderParams {
|
|
55
|
+
asset: number;
|
|
56
|
+
orderId: string;
|
|
57
|
+
}
|
|
58
|
+
export interface ModifyOrderParams {
|
|
59
|
+
orderId: string;
|
|
60
|
+
asset: number;
|
|
61
|
+
isBuy: boolean;
|
|
62
|
+
price: string;
|
|
63
|
+
size: string;
|
|
64
|
+
reduceOnly?: boolean;
|
|
65
|
+
timeInForce?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface UpdateLeverageParams {
|
|
68
|
+
asset: number;
|
|
69
|
+
leverage: number;
|
|
70
|
+
}
|
|
71
|
+
export interface UpdateMarginModeParams {
|
|
72
|
+
asset: number;
|
|
73
|
+
isCross: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface ApproveAgentParams {
|
|
76
|
+
agentAddress: string;
|
|
77
|
+
agentName?: string;
|
|
78
|
+
nonce?: number;
|
|
79
|
+
}
|
|
80
|
+
export interface WithdrawParams {
|
|
81
|
+
destination: string;
|
|
82
|
+
amount: string;
|
|
83
|
+
signatureChainId?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface TransferTokenParams {
|
|
86
|
+
amount: string;
|
|
87
|
+
fromAccountType: string;
|
|
88
|
+
toAccountType: string;
|
|
89
|
+
}
|
|
90
|
+
export interface SendTokenParams {
|
|
91
|
+
toAddress: string;
|
|
92
|
+
amount: string;
|
|
93
|
+
fromAccountType?: string;
|
|
94
|
+
toAccountType?: string;
|
|
95
|
+
}
|
|
96
|
+
export interface ApiResponse<T = unknown> {
|
|
97
|
+
status: string;
|
|
98
|
+
response?: string;
|
|
99
|
+
data?: T;
|
|
100
|
+
}
|
|
101
|
+
export interface InfoResponse<T = unknown> {
|
|
102
|
+
code: number;
|
|
103
|
+
success: boolean;
|
|
104
|
+
data?: T;
|
|
105
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "txflow-sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript SDK for TxFlow perpetual DEX with EIP-712 signing and MCP server",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"txflow-mcp": "dist/mcp.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"txflow",
|
|
29
|
+
"perp",
|
|
30
|
+
"dex",
|
|
31
|
+
"eip712",
|
|
32
|
+
"trading",
|
|
33
|
+
"sdk",
|
|
34
|
+
"mcp",
|
|
35
|
+
"perpetual",
|
|
36
|
+
"futures"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://gitlab.com/andy-vibecoding/txflow-sdk.git"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
45
|
+
"@msgpack/msgpack": "^3.0.0-beta2",
|
|
46
|
+
"ethers": "^6.13.0",
|
|
47
|
+
"zod": "^3.24.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/node": "^22.0.0",
|
|
51
|
+
"tsx": "^4.19.0",
|
|
52
|
+
"typescript": "^5.7.0"
|
|
53
|
+
}
|
|
54
|
+
}
|