logiqical 0.2.0 → 0.3.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/README.md +402 -81
- package/dist/cli.mjs +3278 -0
- package/dist/index.d.mts +749 -792
- package/dist/index.d.ts +749 -792
- package/dist/index.js +2283 -774
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2268 -773
- package/dist/index.mjs.map +1 -0
- package/package.json +20 -10
package/dist/index.mjs
CHANGED
|
@@ -1,990 +1,2485 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// src/client.ts
|
|
2
|
+
import { ethers as ethers8, JsonRpcProvider as JsonRpcProvider2, Contract } from "ethers";
|
|
3
|
+
|
|
4
|
+
// src/wallet.ts
|
|
5
|
+
import {
|
|
6
|
+
Wallet,
|
|
7
|
+
JsonRpcProvider
|
|
8
|
+
} from "ethers";
|
|
9
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
var CHAINS = {
|
|
13
|
+
avalanche: {
|
|
14
|
+
chainId: 43114,
|
|
15
|
+
name: "Avalanche C-Chain",
|
|
16
|
+
rpcUrl: "https://api.avax.network/ext/bc/C/rpc",
|
|
17
|
+
nativeCurrency: { name: "Avalanche", symbol: "AVAX", decimals: 18 },
|
|
18
|
+
blockExplorer: "https://snowtrace.io"
|
|
19
|
+
},
|
|
20
|
+
fuji: {
|
|
21
|
+
chainId: 43113,
|
|
22
|
+
name: "Avalanche Fuji Testnet",
|
|
23
|
+
rpcUrl: "https://api.avax-test.network/ext/bc/C/rpc",
|
|
24
|
+
nativeCurrency: { name: "Avalanche", symbol: "AVAX", decimals: 18 },
|
|
25
|
+
blockExplorer: "https://testnet.snowtrace.io"
|
|
26
|
+
},
|
|
27
|
+
ethereum: {
|
|
28
|
+
chainId: 1,
|
|
29
|
+
name: "Ethereum",
|
|
30
|
+
rpcUrl: "https://eth.llamarpc.com",
|
|
31
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
32
|
+
blockExplorer: "https://etherscan.io"
|
|
33
|
+
},
|
|
34
|
+
base: {
|
|
35
|
+
chainId: 8453,
|
|
36
|
+
name: "Base",
|
|
37
|
+
rpcUrl: "https://mainnet.base.org",
|
|
38
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
39
|
+
blockExplorer: "https://basescan.org"
|
|
40
|
+
},
|
|
41
|
+
arbitrum: {
|
|
42
|
+
chainId: 42161,
|
|
43
|
+
name: "Arbitrum One",
|
|
44
|
+
rpcUrl: "https://arb1.arbitrum.io/rpc",
|
|
45
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
46
|
+
blockExplorer: "https://arbiscan.io"
|
|
47
|
+
},
|
|
48
|
+
optimism: {
|
|
49
|
+
chainId: 10,
|
|
50
|
+
name: "Optimism",
|
|
51
|
+
rpcUrl: "https://mainnet.optimism.io",
|
|
52
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
53
|
+
blockExplorer: "https://optimistic.etherscan.io"
|
|
54
|
+
},
|
|
55
|
+
polygon: {
|
|
56
|
+
chainId: 137,
|
|
57
|
+
name: "Polygon",
|
|
58
|
+
rpcUrl: "https://polygon-rpc.com",
|
|
59
|
+
nativeCurrency: { name: "MATIC", symbol: "MATIC", decimals: 18 },
|
|
60
|
+
blockExplorer: "https://polygonscan.com"
|
|
61
|
+
},
|
|
62
|
+
bsc: {
|
|
63
|
+
chainId: 56,
|
|
64
|
+
name: "BNB Smart Chain",
|
|
65
|
+
rpcUrl: "https://bsc-dataseed.binance.org",
|
|
66
|
+
nativeCurrency: { name: "BNB", symbol: "BNB", decimals: 18 },
|
|
67
|
+
blockExplorer: "https://bscscan.com"
|
|
68
|
+
},
|
|
69
|
+
fantom: {
|
|
70
|
+
chainId: 250,
|
|
71
|
+
name: "Fantom Opera",
|
|
72
|
+
rpcUrl: "https://rpc.ftm.tools",
|
|
73
|
+
nativeCurrency: { name: "Fantom", symbol: "FTM", decimals: 18 },
|
|
74
|
+
blockExplorer: "https://ftmscan.com"
|
|
75
|
+
},
|
|
76
|
+
gnosis: {
|
|
77
|
+
chainId: 100,
|
|
78
|
+
name: "Gnosis Chain",
|
|
79
|
+
rpcUrl: "https://rpc.gnosischain.com",
|
|
80
|
+
nativeCurrency: { name: "xDAI", symbol: "xDAI", decimals: 18 },
|
|
81
|
+
blockExplorer: "https://gnosisscan.io"
|
|
82
|
+
},
|
|
83
|
+
zksync: {
|
|
84
|
+
chainId: 324,
|
|
85
|
+
name: "zkSync Era",
|
|
86
|
+
rpcUrl: "https://mainnet.era.zksync.io",
|
|
87
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
88
|
+
blockExplorer: "https://explorer.zksync.io"
|
|
89
|
+
},
|
|
90
|
+
linea: {
|
|
91
|
+
chainId: 59144,
|
|
92
|
+
name: "Linea",
|
|
93
|
+
rpcUrl: "https://rpc.linea.build",
|
|
94
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
95
|
+
blockExplorer: "https://lineascan.build"
|
|
96
|
+
},
|
|
97
|
+
scroll: {
|
|
98
|
+
chainId: 534352,
|
|
99
|
+
name: "Scroll",
|
|
100
|
+
rpcUrl: "https://rpc.scroll.io",
|
|
101
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
102
|
+
blockExplorer: "https://scrollscan.com"
|
|
103
|
+
},
|
|
104
|
+
blast: {
|
|
105
|
+
chainId: 81457,
|
|
106
|
+
name: "Blast",
|
|
107
|
+
rpcUrl: "https://rpc.blast.io",
|
|
108
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
109
|
+
blockExplorer: "https://blastscan.io"
|
|
110
|
+
},
|
|
111
|
+
mantle: {
|
|
112
|
+
chainId: 5e3,
|
|
113
|
+
name: "Mantle",
|
|
114
|
+
rpcUrl: "https://rpc.mantle.xyz",
|
|
115
|
+
nativeCurrency: { name: "MNT", symbol: "MNT", decimals: 18 },
|
|
116
|
+
blockExplorer: "https://mantlescan.xyz"
|
|
117
|
+
},
|
|
118
|
+
celo: {
|
|
119
|
+
chainId: 42220,
|
|
120
|
+
name: "Celo",
|
|
121
|
+
rpcUrl: "https://forno.celo.org",
|
|
122
|
+
nativeCurrency: { name: "CELO", symbol: "CELO", decimals: 18 },
|
|
123
|
+
blockExplorer: "https://celoscan.io"
|
|
124
|
+
},
|
|
125
|
+
moonbeam: {
|
|
126
|
+
chainId: 1284,
|
|
127
|
+
name: "Moonbeam",
|
|
128
|
+
rpcUrl: "https://rpc.api.moonbeam.network",
|
|
129
|
+
nativeCurrency: { name: "GLMR", symbol: "GLMR", decimals: 18 },
|
|
130
|
+
blockExplorer: "https://moonscan.io"
|
|
131
|
+
},
|
|
132
|
+
sei: {
|
|
133
|
+
chainId: 1329,
|
|
134
|
+
name: "Sei",
|
|
135
|
+
rpcUrl: "https://evm-rpc.sei-apis.com",
|
|
136
|
+
nativeCurrency: { name: "SEI", symbol: "SEI", decimals: 18 },
|
|
137
|
+
blockExplorer: "https://seitrace.com"
|
|
138
|
+
},
|
|
139
|
+
mode: {
|
|
140
|
+
chainId: 34443,
|
|
141
|
+
name: "Mode",
|
|
142
|
+
rpcUrl: "https://mainnet.mode.network",
|
|
143
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
144
|
+
blockExplorer: "https://explorer.mode.network"
|
|
145
|
+
},
|
|
146
|
+
aurora: {
|
|
147
|
+
chainId: 1313161554,
|
|
148
|
+
name: "Aurora",
|
|
149
|
+
rpcUrl: "https://mainnet.aurora.dev",
|
|
150
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
151
|
+
blockExplorer: "https://explorer.aurora.dev"
|
|
8
152
|
}
|
|
9
153
|
};
|
|
10
|
-
var
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
154
|
+
var KEYSTORE_DIR = join(homedir(), ".logiqical", "keys");
|
|
155
|
+
function ensureKeystoreDir() {
|
|
156
|
+
if (!existsSync(KEYSTORE_DIR)) {
|
|
157
|
+
mkdirSync(KEYSTORE_DIR, { recursive: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
var AgentWallet = class _AgentWallet {
|
|
161
|
+
wallet;
|
|
162
|
+
provider;
|
|
163
|
+
chain;
|
|
164
|
+
address;
|
|
165
|
+
constructor(wallet, provider, chain) {
|
|
166
|
+
this.wallet = wallet;
|
|
167
|
+
this.provider = provider;
|
|
168
|
+
this.chain = chain;
|
|
169
|
+
this.address = wallet.address;
|
|
170
|
+
}
|
|
171
|
+
/** The private key (use carefully) */
|
|
172
|
+
get privateKey() {
|
|
173
|
+
return this.wallet.privateKey;
|
|
174
|
+
}
|
|
175
|
+
// ── Factory Methods ──
|
|
176
|
+
/** Generate a brand new wallet with a random private key */
|
|
177
|
+
static generate(config = {}) {
|
|
178
|
+
const { provider, chain } = _AgentWallet.resolveProvider(config);
|
|
179
|
+
const hdWallet = Wallet.createRandom();
|
|
180
|
+
const wallet = new Wallet(hdWallet.privateKey, provider);
|
|
181
|
+
return new _AgentWallet(wallet, provider, chain);
|
|
182
|
+
}
|
|
183
|
+
/** Import an existing wallet from a private key */
|
|
184
|
+
static fromPrivateKey(privateKey, config = {}) {
|
|
185
|
+
const { provider, chain } = _AgentWallet.resolveProvider(config);
|
|
186
|
+
const wallet = new Wallet(privateKey, provider);
|
|
187
|
+
return new _AgentWallet(wallet, provider, chain);
|
|
188
|
+
}
|
|
189
|
+
/** Boot from encrypted keystore, or generate + save if none exists */
|
|
190
|
+
static async boot(config = {}) {
|
|
191
|
+
const password = config.password ?? "logiqical-agent";
|
|
192
|
+
const name = config.keystoreName ?? "agent";
|
|
193
|
+
const keystorePath = join(KEYSTORE_DIR, `${name}.json`);
|
|
194
|
+
const { provider, chain } = _AgentWallet.resolveProvider(config);
|
|
195
|
+
if (existsSync(keystorePath)) {
|
|
196
|
+
const data = JSON.parse(readFileSync(keystorePath, "utf-8"));
|
|
197
|
+
const decrypted = await Wallet.fromEncryptedJson(data.encryptedJson, password);
|
|
198
|
+
const connected = new Wallet(decrypted.privateKey, provider);
|
|
199
|
+
return new _AgentWallet(connected, provider, chain);
|
|
200
|
+
}
|
|
201
|
+
const hdWallet = Wallet.createRandom();
|
|
202
|
+
const wallet = new Wallet(hdWallet.privateKey, provider);
|
|
203
|
+
const agentWallet = new _AgentWallet(wallet, provider, chain);
|
|
204
|
+
await agentWallet.saveKeystore(password, name);
|
|
205
|
+
return agentWallet;
|
|
206
|
+
}
|
|
207
|
+
// ── Keystore Operations ──
|
|
208
|
+
/** Encrypt and save the wallet to the keystore directory */
|
|
209
|
+
async saveKeystore(password, name) {
|
|
210
|
+
const pw = password ?? "logiqical-agent";
|
|
211
|
+
const n = name ?? "agent";
|
|
212
|
+
ensureKeystoreDir();
|
|
213
|
+
const encryptedJson = await this.wallet.encrypt(pw);
|
|
214
|
+
const data = {
|
|
215
|
+
address: this.address,
|
|
216
|
+
encryptedJson,
|
|
217
|
+
network: this.chain.name,
|
|
218
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
219
|
+
};
|
|
220
|
+
const keystorePath = join(KEYSTORE_DIR, `${n}.json`);
|
|
221
|
+
writeFileSync(keystorePath, JSON.stringify(data, null, 2));
|
|
222
|
+
return keystorePath;
|
|
223
|
+
}
|
|
224
|
+
// ── Core Operations ──
|
|
225
|
+
/** Get native token balance (formatted) */
|
|
226
|
+
async getBalance() {
|
|
227
|
+
const balance = await this.provider.getBalance(this.address);
|
|
228
|
+
const { formatEther } = await import("ethers");
|
|
229
|
+
return formatEther(balance);
|
|
230
|
+
}
|
|
231
|
+
/** Get balance of any address */
|
|
232
|
+
async getBalanceOf(address) {
|
|
233
|
+
const balance = await this.provider.getBalance(address);
|
|
234
|
+
const { formatEther } = await import("ethers");
|
|
235
|
+
return formatEther(balance);
|
|
236
|
+
}
|
|
237
|
+
/** Sign a message */
|
|
238
|
+
async signMessage(message) {
|
|
239
|
+
return this.wallet.signMessage(message);
|
|
240
|
+
}
|
|
241
|
+
/** Sign typed data (EIP-712) */
|
|
242
|
+
async signTypedData(domain, types, value) {
|
|
243
|
+
return this.wallet.signTypedData(domain, types, value);
|
|
244
|
+
}
|
|
245
|
+
/** Sign a transaction without broadcasting */
|
|
246
|
+
async signTransaction(tx) {
|
|
247
|
+
return this.wallet.signTransaction(tx);
|
|
248
|
+
}
|
|
249
|
+
/** Sign and broadcast a transaction */
|
|
250
|
+
async sendTransaction(tx) {
|
|
251
|
+
return this.wallet.sendTransaction(tx);
|
|
252
|
+
}
|
|
253
|
+
/** Send native token to an address */
|
|
254
|
+
async send(to, amount) {
|
|
255
|
+
const { parseEther } = await import("ethers");
|
|
256
|
+
return this.wallet.sendTransaction({
|
|
257
|
+
to,
|
|
258
|
+
value: parseEther(amount)
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Sign and broadcast an unsigned tx object returned by the Logiqical API.
|
|
263
|
+
* Handles the { to, data, value, chainId, gas/gasLimit } format.
|
|
264
|
+
*/
|
|
265
|
+
async signAndBroadcast(unsignedTx) {
|
|
266
|
+
const tx = {
|
|
267
|
+
to: unsignedTx.to,
|
|
268
|
+
data: unsignedTx.data,
|
|
269
|
+
value: unsignedTx.value
|
|
270
|
+
};
|
|
271
|
+
if (unsignedTx.gasLimit || unsignedTx.gas) {
|
|
272
|
+
tx.gasLimit = unsignedTx.gasLimit || unsignedTx.gas;
|
|
273
|
+
}
|
|
274
|
+
return this.wallet.sendTransaction(tx);
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Sign and broadcast multiple unsigned txs in sequence.
|
|
278
|
+
* Waits for each to confirm before sending the next.
|
|
279
|
+
*/
|
|
280
|
+
async signAndBroadcastAll(unsignedTxs, confirmations = 1) {
|
|
281
|
+
const results = [];
|
|
282
|
+
for (const utx of unsignedTxs) {
|
|
283
|
+
const txResponse = await this.signAndBroadcast(utx);
|
|
284
|
+
await txResponse.wait(confirmations);
|
|
285
|
+
results.push(txResponse);
|
|
286
|
+
}
|
|
287
|
+
return results;
|
|
288
|
+
}
|
|
289
|
+
/** Call a read-only contract method (no gas, no signing) */
|
|
290
|
+
async call(tx) {
|
|
291
|
+
return this.provider.call(tx);
|
|
292
|
+
}
|
|
293
|
+
/** Switch to a different network (returns new AgentWallet instance) */
|
|
294
|
+
switchNetwork(network) {
|
|
295
|
+
const { provider, chain } = _AgentWallet.resolveProvider({ network });
|
|
296
|
+
const wallet = new Wallet(this.privateKey, provider);
|
|
297
|
+
return new _AgentWallet(wallet, provider, chain);
|
|
298
|
+
}
|
|
299
|
+
// ── Internal ──
|
|
300
|
+
static resolveProvider(config) {
|
|
301
|
+
const networkKey = config.network ?? "avalanche";
|
|
302
|
+
const chain = CHAINS[networkKey];
|
|
303
|
+
if (!chain && !config.rpcUrl) {
|
|
304
|
+
throw new Error(`Unknown network "${networkKey}". Use a known chain (${Object.keys(CHAINS).join(", ")}) or provide rpcUrl.`);
|
|
305
|
+
}
|
|
306
|
+
const rpcUrl = config.rpcUrl ?? chain.rpcUrl;
|
|
307
|
+
const resolvedChain = chain ?? {
|
|
308
|
+
chainId: 0,
|
|
309
|
+
name: networkKey,
|
|
310
|
+
rpcUrl,
|
|
311
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }
|
|
312
|
+
};
|
|
313
|
+
const provider = new JsonRpcProvider(rpcUrl, resolvedChain.chainId || void 0);
|
|
314
|
+
return { provider, chain: resolvedChain };
|
|
14
315
|
}
|
|
15
316
|
};
|
|
16
317
|
|
|
17
|
-
// src/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
318
|
+
// src/policy.ts
|
|
319
|
+
import { ethers } from "ethers";
|
|
320
|
+
var PolicyEngine = class {
|
|
321
|
+
policy;
|
|
322
|
+
spendLog = [];
|
|
323
|
+
constructor(policy = {}) {
|
|
324
|
+
this.policy = policy;
|
|
325
|
+
}
|
|
326
|
+
getPolicy() {
|
|
327
|
+
return { ...this.policy };
|
|
328
|
+
}
|
|
329
|
+
setPolicy(policy) {
|
|
330
|
+
this.policy = policy;
|
|
331
|
+
}
|
|
332
|
+
updatePolicy(updates) {
|
|
333
|
+
this.policy = { ...this.policy, ...updates };
|
|
334
|
+
}
|
|
335
|
+
/** Check a transaction against the policy. Throws if rejected. */
|
|
336
|
+
check(tx) {
|
|
337
|
+
const value = tx.value ? BigInt(tx.value) : 0n;
|
|
338
|
+
if (this.policy.maxPerTx) {
|
|
339
|
+
const max = ethers.parseEther(this.policy.maxPerTx);
|
|
340
|
+
if (value > max) {
|
|
341
|
+
throw new PolicyError(
|
|
342
|
+
`Transaction value ${ethers.formatEther(value)} exceeds per-tx limit of ${this.policy.maxPerTx}`,
|
|
343
|
+
"MAX_PER_TX_EXCEEDED"
|
|
344
|
+
);
|
|
37
345
|
}
|
|
38
346
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
347
|
+
if (this.policy.allowedContracts && this.policy.allowedContracts.length > 0) {
|
|
348
|
+
const to = tx.to.toLowerCase();
|
|
349
|
+
const allowed = this.policy.allowedContracts.map((a) => a.toLowerCase());
|
|
350
|
+
if (!allowed.includes(to)) {
|
|
351
|
+
throw new PolicyError(
|
|
352
|
+
`Contract ${tx.to} not in allowlist`,
|
|
353
|
+
"CONTRACT_NOT_ALLOWED"
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (this.policy.blockedContracts && this.policy.blockedContracts.length > 0) {
|
|
358
|
+
const to = tx.to.toLowerCase();
|
|
359
|
+
const blocked = this.policy.blockedContracts.map((b) => b.toLowerCase());
|
|
360
|
+
if (blocked.includes(to)) {
|
|
361
|
+
throw new PolicyError(
|
|
362
|
+
`Contract ${tx.to} is blocked`,
|
|
363
|
+
"CONTRACT_BLOCKED"
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (this.policy.maxPerHour) {
|
|
368
|
+
const max = ethers.parseEther(this.policy.maxPerHour);
|
|
369
|
+
const hourSpend = this.getSpendSince(Date.now() - 36e5);
|
|
370
|
+
if (hourSpend + value > max) {
|
|
371
|
+
throw new PolicyError(
|
|
372
|
+
`Would exceed hourly budget of ${this.policy.maxPerHour} AVAX (spent ${ethers.formatEther(hourSpend)} this hour)`,
|
|
373
|
+
"HOURLY_BUDGET_EXCEEDED"
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (this.policy.maxPerDay) {
|
|
378
|
+
const max = ethers.parseEther(this.policy.maxPerDay);
|
|
379
|
+
const daySpend = this.getSpendSince(Date.now() - 864e5);
|
|
380
|
+
if (daySpend + value > max) {
|
|
381
|
+
throw new PolicyError(
|
|
382
|
+
`Would exceed daily budget of ${this.policy.maxPerDay} AVAX (spent ${ethers.formatEther(daySpend)} today)`,
|
|
383
|
+
"DAILY_BUDGET_EXCEEDED"
|
|
384
|
+
);
|
|
385
|
+
}
|
|
64
386
|
}
|
|
65
|
-
return data;
|
|
66
387
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
this.http = http;
|
|
73
|
-
this.auth = auth;
|
|
388
|
+
/** Record a spend after successful broadcast */
|
|
389
|
+
recordSpend(value) {
|
|
390
|
+
this.spendLog.push({ amount: value, timestamp: Date.now() });
|
|
391
|
+
const cutoff = Date.now() - 864e5;
|
|
392
|
+
this.spendLog = this.spendLog.filter((r) => r.timestamp > cutoff);
|
|
74
393
|
}
|
|
75
|
-
/**
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
394
|
+
/** Get budget status */
|
|
395
|
+
getBudgetStatus() {
|
|
396
|
+
const hourSpend = this.getSpendSince(Date.now() - 36e5);
|
|
397
|
+
const daySpend = this.getSpendSince(Date.now() - 864e5);
|
|
398
|
+
return {
|
|
399
|
+
spentLastHour: ethers.formatEther(hourSpend),
|
|
400
|
+
spentLast24h: ethers.formatEther(daySpend),
|
|
401
|
+
remainingHour: this.policy.maxPerHour ? ethers.formatEther(ethers.parseEther(this.policy.maxPerHour) - hourSpend) : null,
|
|
402
|
+
remainingDay: this.policy.maxPerDay ? ethers.formatEther(ethers.parseEther(this.policy.maxPerDay) - daySpend) : null
|
|
403
|
+
};
|
|
82
404
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
* @param avax - Amount of AVAX to spend
|
|
86
|
-
*/
|
|
87
|
-
async quote(avax) {
|
|
88
|
-
await this.auth();
|
|
89
|
-
return this.http.get("/quote", { avax });
|
|
405
|
+
get shouldSimulate() {
|
|
406
|
+
return this.policy.simulateBeforeSend ?? false;
|
|
90
407
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
* @param arena - Amount of ARENA to sell
|
|
94
|
-
*/
|
|
95
|
-
async sellQuote(arena) {
|
|
96
|
-
await this.auth();
|
|
97
|
-
return this.http.get("/quote/sell", { arena });
|
|
408
|
+
get isDryRun() {
|
|
409
|
+
return this.policy.dryRun ?? false;
|
|
98
410
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
*
|
|
102
|
-
* Sign the transaction, then broadcast via `client.broadcast()`.
|
|
103
|
-
*
|
|
104
|
-
* @param wallet - Your wallet address
|
|
105
|
-
* @param avax - Amount of AVAX to spend
|
|
106
|
-
* @param slippage - Slippage tolerance in basis points (default: 500 = 5%)
|
|
107
|
-
*/
|
|
108
|
-
async buildBuy(wallet, avax, slippage) {
|
|
109
|
-
await this.auth();
|
|
110
|
-
return this.http.get("/build/buy", { wallet, avax, slippage });
|
|
411
|
+
getSpendSince(since) {
|
|
412
|
+
return this.spendLog.filter((r) => r.timestamp > since).reduce((sum, r) => sum + r.amount, 0n);
|
|
111
413
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
414
|
+
};
|
|
415
|
+
var PolicyError = class extends Error {
|
|
416
|
+
constructor(message, code) {
|
|
417
|
+
super(message);
|
|
418
|
+
this.code = code;
|
|
419
|
+
this.name = "PolicyError";
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/modules/swap.ts
|
|
424
|
+
import { ethers as ethers2 } from "ethers";
|
|
425
|
+
|
|
426
|
+
// src/constants.ts
|
|
427
|
+
var CHAIN_ID = 43114;
|
|
428
|
+
var ARENA_TOKEN = "0xB8d7710f7d8349A506b75dD184F05777c82dAd0C";
|
|
429
|
+
var WAVAX = "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7";
|
|
430
|
+
var ARENA_STAKING = "0xeffb809d99142ce3b51c1796c096f5b01b4aaec4";
|
|
431
|
+
var LB_ROUTER = "0x18556DA13313f3532c54711497A8FedAC273220E";
|
|
432
|
+
var LB_QUOTER = "0x9A550a522BBaDFB69019b0432800Ed17855A51C3";
|
|
433
|
+
var LAUNCH_CONTRACT = "0x8315f1eb449Dd4B779495C3A0b05e5d194446c6e";
|
|
434
|
+
var TOKEN_MANAGER = "0x2196e106af476f57618373ec028924767c758464";
|
|
435
|
+
var AVAX_HELPER = "0x03f1a18519abedbef210fa44e13b71fec01b8dfa";
|
|
436
|
+
var ARENA_PAIRED_THRESHOLD = 100000000000n;
|
|
437
|
+
var GRANULARITY_SCALER = 10n ** 18n;
|
|
438
|
+
var ARENA_SHARES_CONTRACT = "0xc605c2cf66ee98ea925b1bb4fea584b71c00cc4c";
|
|
439
|
+
var FRACTION_SCALER = 100;
|
|
440
|
+
var ARENA_SOCIAL_API = "https://api.starsarena.com";
|
|
441
|
+
var LIFI_API = "https://li.quest/v1";
|
|
442
|
+
var HL_INFO = "https://api.hyperliquid.xyz/info";
|
|
443
|
+
var DEFAULT_SLIPPAGE_BPS = 500;
|
|
444
|
+
var ERC20_ABI = [
|
|
445
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
446
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
447
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
448
|
+
"function decimals() view returns (uint8)",
|
|
449
|
+
"function symbol() view returns (string)",
|
|
450
|
+
"function name() view returns (string)",
|
|
451
|
+
"function totalSupply() view returns (uint256)"
|
|
452
|
+
];
|
|
453
|
+
var ARENA_STAKING_ABI = [
|
|
454
|
+
"function deposit(uint256 _amount)",
|
|
455
|
+
"function withdraw(uint256 _amount)",
|
|
456
|
+
"function emergencyWithdraw()",
|
|
457
|
+
"function pendingReward(address _user, address _token) view returns (uint256)",
|
|
458
|
+
"function getUserInfo(address _user, address _rewardToken) view returns (uint256, uint256)",
|
|
459
|
+
"function rewardTokensLength() view returns (uint256)"
|
|
460
|
+
];
|
|
461
|
+
var LB_ROUTER_ABI = [
|
|
462
|
+
"function swapExactNATIVEForTokens(uint256 amountOutMin, tuple(uint256[] pairBinSteps, uint8[] versions, address[] tokenPath) path, address to, uint256 deadline) payable returns (uint256 amountOut)",
|
|
463
|
+
"function swapExactTokensForNATIVE(uint256 amountIn, uint256 amountOutMin, tuple(uint256[] pairBinSteps, uint8[] versions, address[] tokenPath) path, address payable to, uint256 deadline) returns (uint256 amountOut)"
|
|
464
|
+
];
|
|
465
|
+
var LB_QUOTER_ABI = [
|
|
466
|
+
"function findBestPathFromAmountIn(address[] calldata route, uint128 amountIn) view returns (tuple(address[] route, address[] pairs, uint256[] binSteps, uint256[] versions, uint128[] amounts, uint128[] virtualAmountsWithoutSlippage, uint128[] fees) quote)"
|
|
467
|
+
];
|
|
468
|
+
var LAUNCH_CONTRACT_ABI = [
|
|
469
|
+
"function createToken(uint16 a, uint8 b, uint128 curveScaler, uint8 creatorFeeBasisPoints, address tokenCreatorAddress, uint256 tokenSplit, string name, string symbol, uint256 amount) payable",
|
|
470
|
+
"function buyAndCreateLpIfPossible(uint256 amount, uint256 tokenId) payable",
|
|
471
|
+
"function sell(uint256 amount, uint256 tokenId)",
|
|
472
|
+
"function calculateCostWithFees(uint256 amountInToken, uint256 tokenId) view returns (uint256)",
|
|
473
|
+
"function calculateRewardWithFees(uint256 amount, uint256 tokenId) view returns (uint256)",
|
|
474
|
+
"function getMaxTokensForSale(uint256 tokenId) view returns (uint256)",
|
|
475
|
+
"function tokenSupply(uint256 tokenId) view returns (uint256)",
|
|
476
|
+
"function getTokenParameters(uint256 tokenId) view returns (uint128 curveScaler, uint16 a, uint8 b, bool lpDeployed, uint8 lpPercentage, uint8 salePercentage, uint8 creatorFeeBasisPoints, address creatorAddress, address pairAddress, address tokenContractAddress)",
|
|
477
|
+
"function tokenIdentifier() view returns (uint256)",
|
|
478
|
+
"function protocolFeeBasisPoint() view returns (uint256)",
|
|
479
|
+
"event Buy(address indexed user, uint256 indexed tokenId, uint256 tokenAmount, uint256 cost, uint256 tokenSupply, address referrerAddress, uint256 referralFee, uint256 creatorFee, uint256 protocolFee)",
|
|
480
|
+
"event Sell(address indexed user, uint256 indexed tokenId, uint256 tokenAmount, uint256 reward, uint256 tokenSupply, address referrerAddress, uint256 referralFee, uint256 creatorFee, uint256 protocolFee)"
|
|
481
|
+
];
|
|
482
|
+
var TOKEN_MANAGER_ABI = [
|
|
483
|
+
"function createToken(uint32 a, uint8 b, uint128 curveScaler, uint8 creatorFeeBasisPoints, address tokenCreatorAddress, uint256 tokenSplit, string name, string symbol, uint256 amount)",
|
|
484
|
+
"function calculateCostWithFees(uint256 amountInToken, uint256 tokenId) view returns (uint256)",
|
|
485
|
+
"function calculateRewardWithFees(uint256 amount, uint256 tokenId) view returns (uint256)",
|
|
486
|
+
"function getMaxTokensForSale(uint256 tokenId) view returns (uint256)",
|
|
487
|
+
"function tokenSupply(uint256 tokenId) view returns (uint256)",
|
|
488
|
+
"function getTokenParameters(uint256 tokenId) view returns (uint128 curveScaler, uint32 a, uint8 b, bool lpDeployed, uint8 lpPercentage, uint8 salePercentage, uint8 creatorFeeBasisPoints, address creatorAddress, address pairAddress, address tokenContractAddress)",
|
|
489
|
+
"function tokenIdentifier() view returns (uint256)",
|
|
490
|
+
"function protocolFeeBasisPoint() view returns (uint256)",
|
|
491
|
+
"event Buy(address indexed user, uint256 indexed tokenId, uint256 tokenAmount, uint256 cost, uint256 tokenSupply, address referrerAddress, uint256 referralFee, uint256 creatorFee, uint256 protocolFee)",
|
|
492
|
+
"event Sell(address indexed user, uint256 indexed tokenId, uint256 tokenAmount, uint256 reward, uint256 tokenSupply, address referrerAddress, uint256 referralFee, uint256 creatorFee, uint256 protocolFee)"
|
|
493
|
+
];
|
|
494
|
+
var AVAX_HELPER_ABI = [
|
|
495
|
+
"function buyAndCreateLpIfPossibleWithAvax(uint256 tokenId, uint256 amountOutMin) payable returns (uint256)",
|
|
496
|
+
"function sellToAvax(uint256 tokenId, uint256 amount, uint256 amountOutAvaxMin) returns (uint256)"
|
|
497
|
+
];
|
|
498
|
+
var SHARES_ABI = [
|
|
499
|
+
"function buyFractionalShares(address sharesSubject, address user, uint256 amount) payable",
|
|
500
|
+
"function sellFractionalShares(address sharesSubject, address user, uint256 amount) payable",
|
|
501
|
+
"function getBuyPriceForFractionalSharesAfterFee(address sharesSubject, uint256 amount) view returns (uint256)",
|
|
502
|
+
"function getSellPriceForFractionalSharesAfterFee(address sharesSubject, uint256 amount) view returns (uint256)",
|
|
503
|
+
"function getBuyPriceForFractionalShares(address sharesSubject, uint256 amount) view returns (uint256)",
|
|
504
|
+
"function getSellPriceForFractionalShares(address sharesSubject, uint256 amount) view returns (uint256)",
|
|
505
|
+
"function getMyFractionalShares(address sharesSubject, address user) view returns (uint256)",
|
|
506
|
+
"function getSharesSupply(address sharesSubject) view returns (uint256)",
|
|
507
|
+
"function getTotalFractionalSupply(address sharesSubject) view returns (uint256)",
|
|
508
|
+
"function fractionalSharesBalance(address, address) view returns (uint256)",
|
|
509
|
+
"function protocolFeePercent() view returns (uint256)",
|
|
510
|
+
"function subjectFeePercent() view returns (uint256)",
|
|
511
|
+
"function referralFeePercent() view returns (uint256)"
|
|
512
|
+
];
|
|
513
|
+
|
|
514
|
+
// src/modules/swap.ts
|
|
515
|
+
var SwapModule = class {
|
|
516
|
+
constructor(provider) {
|
|
517
|
+
this.provider = provider;
|
|
518
|
+
this.arenaToken = new ethers2.Contract(ethers2.getAddress(ARENA_TOKEN), ERC20_ABI, provider);
|
|
519
|
+
this.lbQuoter = new ethers2.Contract(ethers2.getAddress(LB_QUOTER), LB_QUOTER_ABI, provider);
|
|
520
|
+
}
|
|
521
|
+
arenaToken;
|
|
522
|
+
lbQuoter;
|
|
523
|
+
/** Get AVAX and ARENA balances for a wallet */
|
|
524
|
+
async getBalances(wallet) {
|
|
525
|
+
const [avax, arena] = await Promise.all([
|
|
526
|
+
this.provider.getBalance(wallet),
|
|
527
|
+
this.arenaToken.balanceOf(wallet)
|
|
528
|
+
]);
|
|
529
|
+
const decimals = await this.arenaToken.decimals();
|
|
530
|
+
return {
|
|
531
|
+
avax: avax.toString(),
|
|
532
|
+
arena: arena.toString(),
|
|
533
|
+
avaxFormatted: ethers2.formatEther(avax),
|
|
534
|
+
arenaFormatted: ethers2.formatUnits(arena, decimals)
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
/** Quote how much ARENA for a given AVAX amount (includes 0.3% fee) */
|
|
538
|
+
async quote(avaxAmount) {
|
|
539
|
+
const amountIn = ethers2.parseEther(avaxAmount);
|
|
540
|
+
const fee = amountIn * 30n / 10000n;
|
|
541
|
+
const netAmount = amountIn - fee;
|
|
542
|
+
const route = [WAVAX, ARENA_TOKEN];
|
|
543
|
+
const quote = await this.lbQuoter.findBestPathFromAmountIn(route, netAmount);
|
|
544
|
+
const arenaOut = quote.amounts[quote.amounts.length - 1];
|
|
545
|
+
const decimals = await this.arenaToken.decimals();
|
|
546
|
+
const arenaFormatted = ethers2.formatUnits(arenaOut, decimals);
|
|
547
|
+
return {
|
|
548
|
+
arenaOut: arenaFormatted,
|
|
549
|
+
fee: ethers2.formatEther(fee),
|
|
550
|
+
netAvax: ethers2.formatEther(netAmount),
|
|
551
|
+
rate: (parseFloat(arenaFormatted) / parseFloat(ethers2.formatEther(netAmount))).toFixed(2)
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
/** Quote how much AVAX for selling ARENA */
|
|
555
|
+
async sellQuote(arenaAmount) {
|
|
556
|
+
const decimals = await this.arenaToken.decimals();
|
|
557
|
+
const amountIn = ethers2.parseUnits(arenaAmount, decimals);
|
|
558
|
+
const route = [ARENA_TOKEN, WAVAX];
|
|
559
|
+
const quote = await this.lbQuoter.findBestPathFromAmountIn(route, amountIn);
|
|
560
|
+
const avaxOut = quote.amounts[quote.amounts.length - 1];
|
|
561
|
+
const avaxFormatted = ethers2.formatEther(avaxOut);
|
|
562
|
+
return {
|
|
563
|
+
avaxOut: avaxFormatted,
|
|
564
|
+
arenaIn: arenaAmount,
|
|
565
|
+
rate: (parseFloat(arenaAmount) / parseFloat(avaxFormatted)).toFixed(2)
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/** Build unsigned tx to buy ARENA with AVAX via LFJ DEX */
|
|
569
|
+
async buildBuy(wallet, avaxAmount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
|
|
570
|
+
const amountIn = ethers2.parseEther(avaxAmount);
|
|
571
|
+
const route = [WAVAX, ARENA_TOKEN];
|
|
572
|
+
const quote = await this.lbQuoter.findBestPathFromAmountIn(route, amountIn);
|
|
573
|
+
const expectedOut = quote.amounts[quote.amounts.length - 1];
|
|
574
|
+
if (expectedOut === 0n) throw new Error("Quote returned zero \u2014 pool may have no liquidity");
|
|
575
|
+
const clampedSlippage = BigInt(Math.max(0, Math.min(1e4, Number(slippageBps))));
|
|
576
|
+
const amountOutMin = expectedOut - expectedOut * clampedSlippage / 10000n;
|
|
577
|
+
const deadline = Math.floor(Date.now() / 1e3) + 3600;
|
|
578
|
+
const path = {
|
|
579
|
+
pairBinSteps: [...quote.binSteps].map((b) => b.toString()),
|
|
580
|
+
versions: [...quote.versions].map((v) => Number(v)),
|
|
581
|
+
tokenPath: [...route]
|
|
582
|
+
};
|
|
583
|
+
const iface = new ethers2.Interface(LB_ROUTER_ABI);
|
|
584
|
+
const data = iface.encodeFunctionData("swapExactNATIVEForTokens", [amountOutMin, path, wallet, deadline]);
|
|
585
|
+
const decimals = await this.arenaToken.decimals();
|
|
586
|
+
return {
|
|
587
|
+
transactions: [{
|
|
588
|
+
to: ethers2.getAddress(LB_ROUTER),
|
|
589
|
+
data,
|
|
590
|
+
value: ethers2.toBeHex(amountIn, 32),
|
|
591
|
+
chainId: CHAIN_ID,
|
|
592
|
+
gasLimit: "500000",
|
|
593
|
+
description: `Buy ARENA with ${avaxAmount} AVAX (~${ethers2.formatUnits(expectedOut, decimals)} ARENA)`
|
|
594
|
+
}],
|
|
595
|
+
summary: `Swap ${avaxAmount} AVAX \u2192 ~${ethers2.formatUnits(expectedOut, decimals)} ARENA`
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
/** Build unsigned txs to sell ARENA for AVAX: [approve, swap] */
|
|
599
|
+
async buildSell(wallet, arenaAmount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
|
|
600
|
+
const decimals = await this.arenaToken.decimals();
|
|
601
|
+
let sellAmount;
|
|
602
|
+
if (arenaAmount === "max") {
|
|
603
|
+
sellAmount = await this.arenaToken.balanceOf(wallet);
|
|
604
|
+
if (sellAmount === 0n) throw new Error("No ARENA balance to sell");
|
|
605
|
+
} else {
|
|
606
|
+
sellAmount = ethers2.parseUnits(arenaAmount, decimals);
|
|
607
|
+
}
|
|
608
|
+
const route = [ARENA_TOKEN, WAVAX];
|
|
609
|
+
const quote = await this.lbQuoter.findBestPathFromAmountIn(route, sellAmount);
|
|
610
|
+
const expectedOut = quote.amounts[quote.amounts.length - 1];
|
|
611
|
+
if (expectedOut === 0n) throw new Error("Quote returned zero \u2014 pool may have no liquidity");
|
|
612
|
+
const clampedSlippage = BigInt(Math.max(0, Math.min(1e4, Number(slippageBps))));
|
|
613
|
+
const amountOutMin = expectedOut - expectedOut * clampedSlippage / 10000n;
|
|
614
|
+
const deadline = Math.floor(Date.now() / 1e3) + 3600;
|
|
615
|
+
const path = {
|
|
616
|
+
pairBinSteps: [...quote.binSteps].map((b) => b.toString()),
|
|
617
|
+
versions: [...quote.versions].map((v) => Number(v)),
|
|
618
|
+
tokenPath: [...route]
|
|
619
|
+
};
|
|
620
|
+
const approveIface = new ethers2.Interface(ERC20_ABI);
|
|
621
|
+
const approveData = approveIface.encodeFunctionData("approve", [ethers2.getAddress(LB_ROUTER), sellAmount]);
|
|
622
|
+
const swapIface = new ethers2.Interface(LB_ROUTER_ABI);
|
|
623
|
+
const swapData = swapIface.encodeFunctionData("swapExactTokensForNATIVE", [sellAmount, amountOutMin, path, wallet, deadline]);
|
|
624
|
+
return {
|
|
625
|
+
transactions: [
|
|
626
|
+
{
|
|
627
|
+
to: ethers2.getAddress(ARENA_TOKEN),
|
|
628
|
+
data: approveData,
|
|
629
|
+
value: "0",
|
|
630
|
+
chainId: CHAIN_ID,
|
|
631
|
+
gasLimit: "60000",
|
|
632
|
+
description: `Approve ${ethers2.formatUnits(sellAmount, decimals)} ARENA for swap`
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
to: ethers2.getAddress(LB_ROUTER),
|
|
636
|
+
data: swapData,
|
|
637
|
+
value: "0",
|
|
638
|
+
chainId: CHAIN_ID,
|
|
639
|
+
gasLimit: "500000",
|
|
640
|
+
description: `Sell ${ethers2.formatUnits(sellAmount, decimals)} ARENA for ~${ethers2.formatEther(expectedOut)} AVAX`
|
|
641
|
+
}
|
|
642
|
+
]
|
|
643
|
+
};
|
|
128
644
|
}
|
|
129
645
|
};
|
|
130
646
|
|
|
131
647
|
// src/modules/staking.ts
|
|
648
|
+
import { ethers as ethers3 } from "ethers";
|
|
132
649
|
var StakingModule = class {
|
|
133
|
-
constructor(
|
|
134
|
-
this.
|
|
135
|
-
this.
|
|
650
|
+
constructor(provider) {
|
|
651
|
+
this.provider = provider;
|
|
652
|
+
this.arenaToken = new ethers3.Contract(ethers3.getAddress(ARENA_TOKEN), ERC20_ABI, provider);
|
|
653
|
+
this.staking = new ethers3.Contract(ethers3.getAddress(ARENA_STAKING), ARENA_STAKING_ABI, provider);
|
|
136
654
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
*/
|
|
655
|
+
arenaToken;
|
|
656
|
+
staking;
|
|
657
|
+
/** Get staking info — staked amount, pending rewards */
|
|
141
658
|
async getInfo(wallet) {
|
|
142
|
-
await this.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* @param amount - Amount of ARENA to stake
|
|
154
|
-
*/
|
|
659
|
+
const decimals = await this.arenaToken.decimals();
|
|
660
|
+
const [stakedRaw] = await this.staking.getUserInfo(wallet, ethers3.getAddress(ARENA_TOKEN));
|
|
661
|
+
const pendingRaw = await this.staking.pendingReward(wallet, ethers3.getAddress(ARENA_TOKEN));
|
|
662
|
+
return {
|
|
663
|
+
staked: stakedRaw.toString(),
|
|
664
|
+
stakedFormatted: ethers3.formatUnits(stakedRaw, decimals),
|
|
665
|
+
rewards: pendingRaw.toString(),
|
|
666
|
+
rewardsFormatted: ethers3.formatUnits(pendingRaw, decimals)
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
/** Build txs to stake ARENA: [approve, deposit] */
|
|
155
670
|
async buildStake(wallet, amount) {
|
|
156
|
-
await this.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
671
|
+
const decimals = await this.arenaToken.decimals();
|
|
672
|
+
let stakeAmount;
|
|
673
|
+
if (amount === "max") {
|
|
674
|
+
stakeAmount = await this.arenaToken.balanceOf(wallet);
|
|
675
|
+
} else {
|
|
676
|
+
stakeAmount = ethers3.parseUnits(amount, decimals);
|
|
677
|
+
}
|
|
678
|
+
const approveIface = new ethers3.Interface(ERC20_ABI);
|
|
679
|
+
const approveData = approveIface.encodeFunctionData("approve", [ethers3.getAddress(ARENA_STAKING), stakeAmount]);
|
|
680
|
+
const stakingIface = new ethers3.Interface(ARENA_STAKING_ABI);
|
|
681
|
+
const stakeData = stakingIface.encodeFunctionData("deposit", [stakeAmount]);
|
|
682
|
+
return {
|
|
683
|
+
transactions: [
|
|
684
|
+
{
|
|
685
|
+
to: ethers3.getAddress(ARENA_TOKEN),
|
|
686
|
+
data: approveData,
|
|
687
|
+
value: "0",
|
|
688
|
+
chainId: CHAIN_ID,
|
|
689
|
+
gasLimit: "60000",
|
|
690
|
+
description: `Approve ${ethers3.formatUnits(stakeAmount, decimals)} ARENA for staking`
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
to: ethers3.getAddress(ARENA_STAKING),
|
|
694
|
+
data: stakeData,
|
|
695
|
+
value: "0",
|
|
696
|
+
chainId: CHAIN_ID,
|
|
697
|
+
gasLimit: "300000",
|
|
698
|
+
description: `Stake ${ethers3.formatUnits(stakeAmount, decimals)} ARENA`
|
|
699
|
+
}
|
|
700
|
+
]
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
/** Build tx to unstake ARENA + claim rewards */
|
|
177
704
|
async buildUnstake(wallet, amount) {
|
|
178
|
-
await this.
|
|
179
|
-
|
|
705
|
+
const decimals = await this.arenaToken.decimals();
|
|
706
|
+
let withdrawAmount;
|
|
707
|
+
if (amount === "max") {
|
|
708
|
+
const [stakedRaw] = await this.staking.getUserInfo(wallet, ethers3.getAddress(ARENA_TOKEN));
|
|
709
|
+
withdrawAmount = stakedRaw;
|
|
710
|
+
} else {
|
|
711
|
+
withdrawAmount = ethers3.parseUnits(amount, decimals);
|
|
712
|
+
}
|
|
713
|
+
const iface = new ethers3.Interface(ARENA_STAKING_ABI);
|
|
714
|
+
const data = iface.encodeFunctionData("withdraw", [withdrawAmount]);
|
|
715
|
+
return {
|
|
716
|
+
transactions: [{
|
|
717
|
+
to: ethers3.getAddress(ARENA_STAKING),
|
|
718
|
+
data,
|
|
719
|
+
value: "0",
|
|
720
|
+
chainId: CHAIN_ID,
|
|
721
|
+
gasLimit: "300000",
|
|
722
|
+
description: `Unstake ${ethers3.formatUnits(withdrawAmount, decimals)} ARENA + claim rewards`
|
|
723
|
+
}]
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
/** Build buy-and-stake flow (3 txs): buy ARENA via LFJ, approve, stake */
|
|
727
|
+
async buildBuyAndStake(wallet, avaxAmount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
|
|
728
|
+
const lbQuoter = new ethers3.Contract(ethers3.getAddress(LB_QUOTER), LB_QUOTER_ABI, this.provider);
|
|
729
|
+
const amountIn = ethers3.parseEther(avaxAmount);
|
|
730
|
+
const route = [WAVAX, ARENA_TOKEN];
|
|
731
|
+
const quote = await lbQuoter.findBestPathFromAmountIn(route, amountIn);
|
|
732
|
+
const expectedOut = quote.amounts[quote.amounts.length - 1];
|
|
733
|
+
const clampedSlippage = BigInt(Math.max(0, Math.min(1e4, Number(slippageBps))));
|
|
734
|
+
const amountOutMin = expectedOut - expectedOut * clampedSlippage / 10000n;
|
|
735
|
+
const deadline = Math.floor(Date.now() / 1e3) + 3600;
|
|
736
|
+
const decimals = await this.arenaToken.decimals();
|
|
737
|
+
const path = {
|
|
738
|
+
pairBinSteps: [...quote.binSteps].map((b) => b.toString()),
|
|
739
|
+
versions: [...quote.versions].map((v) => Number(v)),
|
|
740
|
+
tokenPath: [...route]
|
|
741
|
+
};
|
|
742
|
+
const routerIface = new ethers3.Interface(LB_ROUTER_ABI);
|
|
743
|
+
const buyData = routerIface.encodeFunctionData("swapExactNATIVEForTokens", [amountOutMin, path, wallet, deadline]);
|
|
744
|
+
const erc20Iface = new ethers3.Interface(ERC20_ABI);
|
|
745
|
+
const approveData = erc20Iface.encodeFunctionData("approve", [ethers3.getAddress(ARENA_STAKING), ethers3.MaxUint256]);
|
|
746
|
+
const stakingIface = new ethers3.Interface(ARENA_STAKING_ABI);
|
|
747
|
+
const stakeData = stakingIface.encodeFunctionData("deposit", [expectedOut]);
|
|
748
|
+
return {
|
|
749
|
+
transactions: [
|
|
750
|
+
{
|
|
751
|
+
to: ethers3.getAddress(LB_ROUTER),
|
|
752
|
+
data: buyData,
|
|
753
|
+
value: ethers3.toBeHex(amountIn, 32),
|
|
754
|
+
chainId: CHAIN_ID,
|
|
755
|
+
gasLimit: "500000",
|
|
756
|
+
description: `Step 1/3: Buy ~${ethers3.formatUnits(expectedOut, decimals)} ARENA with ${avaxAmount} AVAX`
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
to: ethers3.getAddress(ARENA_TOKEN),
|
|
760
|
+
data: approveData,
|
|
761
|
+
value: "0",
|
|
762
|
+
chainId: CHAIN_ID,
|
|
763
|
+
gasLimit: "60000",
|
|
764
|
+
description: `Step 2/3: Approve ARENA for staking`
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
to: ethers3.getAddress(ARENA_STAKING),
|
|
768
|
+
data: stakeData,
|
|
769
|
+
value: "0",
|
|
770
|
+
chainId: CHAIN_ID,
|
|
771
|
+
gasLimit: "300000",
|
|
772
|
+
description: `Step 3/3: Stake ~${ethers3.formatUnits(expectedOut, decimals)} ARENA`
|
|
773
|
+
}
|
|
774
|
+
]
|
|
775
|
+
};
|
|
180
776
|
}
|
|
181
777
|
};
|
|
182
778
|
|
|
183
779
|
// src/modules/launchpad.ts
|
|
780
|
+
import { ethers as ethers4 } from "ethers";
|
|
184
781
|
var LaunchpadModule = class {
|
|
185
|
-
constructor(
|
|
186
|
-
this.
|
|
187
|
-
this.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
* @param q - Search query — name, symbol, or 0x contract address
|
|
202
|
-
*/
|
|
203
|
-
async search(q) {
|
|
204
|
-
await this.auth();
|
|
205
|
-
return this.http.get("/launchpad/search", { q });
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Get tokens that are closest to graduating from the bonding curve to DEX.
|
|
209
|
-
* @param count - Number of tokens (max 20, default 5)
|
|
210
|
-
*/
|
|
211
|
-
async getGraduating(count) {
|
|
212
|
-
await this.auth();
|
|
213
|
-
return this.http.get("/launchpad/graduating", { count });
|
|
214
|
-
}
|
|
215
|
-
/**
|
|
216
|
-
* Get tokens that have already graduated from the bonding curve to DEX.
|
|
217
|
-
* @param count - Number of tokens (max 50, default 10)
|
|
218
|
-
*/
|
|
219
|
-
async getGraduated(count) {
|
|
220
|
-
await this.auth();
|
|
221
|
-
return this.http.get("/launchpad/graduated", { count });
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Get top tokens by trading volume.
|
|
225
|
-
* @param timeframe - "5m", "1h", "4h", "24h", or "all_time"
|
|
226
|
-
* @param count - Number of tokens (max 50, default 10)
|
|
227
|
-
*/
|
|
228
|
-
async getTopVolume(timeframe, count) {
|
|
229
|
-
await this.auth();
|
|
230
|
-
return this.http.get("/launchpad/top-volume", { timeframe, count });
|
|
231
|
-
}
|
|
232
|
-
// ── Intelligence ──
|
|
233
|
-
/**
|
|
234
|
-
* Get full token profile with stats — price, market cap, graduation progress, buy/sell activity.
|
|
235
|
-
* @param tokenId - Arena token ID
|
|
236
|
-
* @param address - Or token contract address (0x...)
|
|
237
|
-
*/
|
|
238
|
-
async getToken(tokenId, address) {
|
|
239
|
-
await this.auth();
|
|
240
|
-
return this.http.get("/launchpad/token", { tokenId, address });
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Get a buy or sell quote for a bonding curve token.
|
|
244
|
-
* @param tokenId - Arena token ID
|
|
245
|
-
* @param side - "buy" or "sell"
|
|
246
|
-
* @param amount - For buy: AVAX amount. For sell: token amount.
|
|
247
|
-
*/
|
|
248
|
-
async quote(tokenId, side, amount) {
|
|
249
|
-
await this.auth();
|
|
250
|
-
const params = { tokenId, side };
|
|
251
|
-
if (side === "buy") params.avax = amount;
|
|
252
|
-
else params.tokenAmount = amount;
|
|
253
|
-
return this.http.get("/launchpad/quote", params);
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Get agent's tracked portfolio — all launchpad tokens the agent has bought.
|
|
257
|
-
* @param wallet - Agent wallet address
|
|
258
|
-
*/
|
|
259
|
-
async getPortfolio(wallet) {
|
|
260
|
-
await this.auth();
|
|
261
|
-
return this.http.get("/launchpad/portfolio", { wallet });
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Get market cap data for a token.
|
|
265
|
-
* @param tokenId - Arena token ID
|
|
266
|
-
*/
|
|
267
|
-
async getMarketCap(tokenId) {
|
|
268
|
-
await this.auth();
|
|
269
|
-
return this.http.get("/launchpad/market-cap", { tokenId });
|
|
270
|
-
}
|
|
271
|
-
/**
|
|
272
|
-
* Get recent trade activity for a token.
|
|
273
|
-
* @param tokenId - Arena token ID
|
|
274
|
-
* @param address - Or token contract address
|
|
275
|
-
* @param count - Number of trades (max 50, default 20)
|
|
276
|
-
*/
|
|
277
|
-
async getActivity(tokenId, address, count) {
|
|
278
|
-
await this.auth();
|
|
279
|
-
return this.http.get("/launchpad/activity", { tokenId, address, count });
|
|
280
|
-
}
|
|
281
|
-
/**
|
|
282
|
-
* Get top holders for a token with PnL data.
|
|
283
|
-
* @param address - Token contract address
|
|
284
|
-
* @param tokenId - Or Arena token ID
|
|
285
|
-
* @param count - Number of holders (max 50, default 20)
|
|
286
|
-
*/
|
|
287
|
-
async getHolders(address, tokenId, count) {
|
|
288
|
-
await this.auth();
|
|
289
|
-
return this.http.get("/launchpad/holders", { address, tokenId, count });
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Get platform overview — total tokens launched, contract addresses, stats.
|
|
293
|
-
*/
|
|
782
|
+
constructor(provider) {
|
|
783
|
+
this.provider = provider;
|
|
784
|
+
this.launchContract = new ethers4.Contract(ethers4.getAddress(LAUNCH_CONTRACT), LAUNCH_CONTRACT_ABI, provider);
|
|
785
|
+
this.tokenManager = new ethers4.Contract(ethers4.getAddress(TOKEN_MANAGER), TOKEN_MANAGER_ABI, provider);
|
|
786
|
+
this.avaxHelper = new ethers4.Contract(ethers4.getAddress(AVAX_HELPER), AVAX_HELPER_ABI, provider);
|
|
787
|
+
}
|
|
788
|
+
launchContract;
|
|
789
|
+
tokenManager;
|
|
790
|
+
avaxHelper;
|
|
791
|
+
isArenaPaired(tokenId) {
|
|
792
|
+
return BigInt(tokenId) >= ARENA_PAIRED_THRESHOLD;
|
|
793
|
+
}
|
|
794
|
+
getContract(tokenId) {
|
|
795
|
+
return this.isArenaPaired(tokenId) ? this.tokenManager : this.launchContract;
|
|
796
|
+
}
|
|
797
|
+
/** Get platform overview — total tokens, fees, contract addresses */
|
|
294
798
|
async getOverview() {
|
|
295
|
-
await
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
799
|
+
const [avaxLatest, arenaLatest, protocolFeeAvax, protocolFeeArena] = await Promise.all([
|
|
800
|
+
this.launchContract.tokenIdentifier(),
|
|
801
|
+
this.tokenManager.tokenIdentifier(),
|
|
802
|
+
this.launchContract.protocolFeeBasisPoint(),
|
|
803
|
+
this.tokenManager.protocolFeeBasisPoint()
|
|
804
|
+
]);
|
|
805
|
+
return {
|
|
806
|
+
totalAvaxPairedTokens: (avaxLatest - 1n).toString(),
|
|
807
|
+
totalArenaPairedTokens: (arenaLatest - ARENA_PAIRED_THRESHOLD).toString(),
|
|
808
|
+
totalTokens: (avaxLatest - 1n + (arenaLatest - ARENA_PAIRED_THRESHOLD)).toString(),
|
|
809
|
+
protocolFeeBps: { avaxPaired: protocolFeeAvax.toString(), arenaPaired: protocolFeeArena.toString() },
|
|
810
|
+
contracts: { launchContract: LAUNCH_CONTRACT, tokenManager: TOKEN_MANAGER, avaxHelper: AVAX_HELPER }
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
/** Get full token info by ID */
|
|
814
|
+
async getToken(tokenId) {
|
|
815
|
+
const contract = this.getContract(tokenId);
|
|
816
|
+
const id = BigInt(tokenId);
|
|
817
|
+
const [params, supply, maxForSale] = await Promise.all([
|
|
818
|
+
contract.getTokenParameters(id),
|
|
819
|
+
contract.tokenSupply(id),
|
|
820
|
+
contract.getMaxTokensForSale(id)
|
|
821
|
+
]);
|
|
822
|
+
const tokenAddress = params.tokenContractAddress;
|
|
823
|
+
if (tokenAddress === ethers4.ZeroAddress) throw new Error(`Token ID ${tokenId} not found`);
|
|
824
|
+
const token = new ethers4.Contract(tokenAddress, ERC20_ABI, this.provider);
|
|
825
|
+
let name = "Unknown", symbol = "UNKNOWN";
|
|
826
|
+
try {
|
|
827
|
+
[name, symbol] = await Promise.all([token.name(), token.symbol()]);
|
|
828
|
+
} catch {
|
|
829
|
+
}
|
|
830
|
+
let priceAvax = "0";
|
|
831
|
+
if (!params.lpDeployed) {
|
|
832
|
+
try {
|
|
833
|
+
const cost = await contract.calculateCostWithFees(1n, id);
|
|
834
|
+
priceAvax = ethers4.formatEther(cost);
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
const salePerc = BigInt(params.salePercentage);
|
|
839
|
+
const saleAllocation = supply * salePerc / 100n;
|
|
840
|
+
const amountSold = saleAllocation > maxForSale ? saleAllocation - maxForSale : 0n;
|
|
841
|
+
const graduationProgress = saleAllocation > 0n ? Number(amountSold * 10000n / saleAllocation) / 100 : 0;
|
|
842
|
+
return {
|
|
843
|
+
tokenId,
|
|
844
|
+
type: this.isArenaPaired(tokenId) ? "ARENA-paired" : "AVAX-paired",
|
|
322
845
|
name,
|
|
323
846
|
symbol,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
847
|
+
tokenAddress,
|
|
848
|
+
creator: params.creatorAddress,
|
|
849
|
+
priceAvax,
|
|
850
|
+
graduationProgress: `${graduationProgress.toFixed(2)}%`,
|
|
851
|
+
graduated: params.lpDeployed,
|
|
852
|
+
amountSold: ethers4.formatUnits(amountSold, 18),
|
|
853
|
+
totalSupply: ethers4.formatUnits(supply, 18),
|
|
854
|
+
remainingForSale: ethers4.formatUnits(maxForSale, 18)
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
/** Get a buy or sell quote */
|
|
858
|
+
async quote(tokenId, side, amount) {
|
|
859
|
+
const contract = this.getContract(tokenId);
|
|
860
|
+
const id = BigInt(tokenId);
|
|
861
|
+
if (side === "buy") {
|
|
862
|
+
const avaxWei = ethers4.parseEther(amount);
|
|
863
|
+
if (!this.isArenaPaired(tokenId)) {
|
|
864
|
+
const tokenAmountWei2 = await this.binarySearchTokenAmount(avaxWei, id);
|
|
865
|
+
if (tokenAmountWei2 === 0n) return { tokenId, side, avaxIn: amount, tokensOut: "0", note: "Insufficient liquidity" };
|
|
866
|
+
const exactCost = await this.launchContract.calculateCostWithFees(tokenAmountWei2 / GRANULARITY_SCALER, id);
|
|
867
|
+
return { tokenId, side, avaxIn: ethers4.formatEther(exactCost), tokensOut: ethers4.formatUnits(tokenAmountWei2, 18) };
|
|
868
|
+
}
|
|
869
|
+
return { tokenId, side, avaxIn: amount, tokensOut: "determined at execution (ARENA-paired)" };
|
|
870
|
+
}
|
|
871
|
+
const tokenAmountWei = ethers4.parseUnits(amount, 18);
|
|
872
|
+
const reward = await contract.calculateRewardWithFees(tokenAmountWei, id);
|
|
873
|
+
return {
|
|
874
|
+
tokenId,
|
|
875
|
+
side,
|
|
876
|
+
tokenAmount: amount,
|
|
877
|
+
rewardAvax: this.isArenaPaired(tokenId) ? void 0 : ethers4.formatEther(reward),
|
|
878
|
+
rewardArena: this.isArenaPaired(tokenId) ? ethers4.formatUnits(reward, 18) : void 0
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
/** Get recent token launches */
|
|
882
|
+
async getRecent(count = 10) {
|
|
883
|
+
const results = [];
|
|
884
|
+
const fetchToken = async (id, contract, pairType) => {
|
|
885
|
+
try {
|
|
886
|
+
const params = await contract.getTokenParameters(id);
|
|
887
|
+
if (params.tokenContractAddress === ethers4.ZeroAddress) return null;
|
|
888
|
+
const token = new ethers4.Contract(params.tokenContractAddress, ERC20_ABI, this.provider);
|
|
889
|
+
let name = "Unknown", symbol = "UNKNOWN";
|
|
890
|
+
try {
|
|
891
|
+
[name, symbol] = await Promise.all([token.name(), token.symbol()]);
|
|
892
|
+
} catch {
|
|
893
|
+
}
|
|
894
|
+
return { tokenId: id.toString(), type: pairType, name, symbol, tokenAddress: params.tokenContractAddress, graduated: params.lpDeployed };
|
|
895
|
+
} catch {
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
const latestAvax = await this.launchContract.tokenIdentifier();
|
|
900
|
+
const avaxPromises = [];
|
|
901
|
+
for (let i = 0; i < Math.ceil(count / 2) && latestAvax - BigInt(i) > 0n; i++) {
|
|
902
|
+
avaxPromises.push(fetchToken(latestAvax - BigInt(i) - 1n, this.launchContract, "AVAX-paired"));
|
|
903
|
+
}
|
|
904
|
+
const latestArena = await this.tokenManager.tokenIdentifier();
|
|
905
|
+
const arenaPromises = [];
|
|
906
|
+
for (let i = 0; i < Math.ceil(count / 2) && latestArena - BigInt(i) >= ARENA_PAIRED_THRESHOLD; i++) {
|
|
907
|
+
arenaPromises.push(fetchToken(latestArena - BigInt(i) - 1n, this.tokenManager, "ARENA-paired"));
|
|
908
|
+
}
|
|
909
|
+
results.push(...(await Promise.all([...avaxPromises, ...arenaPromises])).filter(Boolean));
|
|
910
|
+
return { count: results.length, tokens: results.slice(0, count) };
|
|
911
|
+
}
|
|
912
|
+
/** Build unsigned tx to buy a launchpad token */
|
|
913
|
+
async buildBuy(wallet, tokenId, avax, slippageBps = DEFAULT_SLIPPAGE_BPS) {
|
|
914
|
+
const id = BigInt(tokenId);
|
|
915
|
+
const avaxWei = ethers4.parseEther(avax);
|
|
916
|
+
const contract = this.getContract(tokenId);
|
|
917
|
+
const params = await contract.getTokenParameters(id);
|
|
918
|
+
if (params.lpDeployed) throw new Error("Token graduated to DEX \u2014 use dex.buildSwap() instead");
|
|
919
|
+
if (!this.isArenaPaired(tokenId)) {
|
|
920
|
+
const tokenAmountWei = await this.binarySearchTokenAmount(avaxWei, id);
|
|
921
|
+
if (tokenAmountWei === 0n) throw new Error("Cannot calculate buy amount \u2014 token may be sold out");
|
|
922
|
+
const iface2 = new ethers4.Interface(LAUNCH_CONTRACT_ABI);
|
|
923
|
+
const data2 = iface2.encodeFunctionData("buyAndCreateLpIfPossible", [tokenAmountWei, id]);
|
|
924
|
+
return { transactions: [{ to: ethers4.getAddress(LAUNCH_CONTRACT), data: data2, value: ethers4.toBeHex(avaxWei, 32), chainId: CHAIN_ID, gasLimit: "500000", description: `Buy ~${ethers4.formatUnits(tokenAmountWei, 18)} tokens (ID ${tokenId})` }] };
|
|
925
|
+
}
|
|
926
|
+
const iface = new ethers4.Interface(AVAX_HELPER_ABI);
|
|
927
|
+
const data = iface.encodeFunctionData("buyAndCreateLpIfPossibleWithAvax", [id, 0n]);
|
|
928
|
+
return { transactions: [{ to: ethers4.getAddress(AVAX_HELPER), data, value: ethers4.toBeHex(avaxWei, 32), chainId: CHAIN_ID, gasLimit: "500000", description: `Buy tokens (ID ${tokenId}) with ${avax} AVAX` }] };
|
|
929
|
+
}
|
|
930
|
+
/** Build unsigned txs to sell a launchpad token: [approve, sell] */
|
|
931
|
+
async buildSell(wallet, tokenId, amount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
|
|
932
|
+
const id = BigInt(tokenId);
|
|
933
|
+
const contract = this.getContract(tokenId);
|
|
934
|
+
const params = await contract.getTokenParameters(id);
|
|
935
|
+
if (params.lpDeployed) throw new Error("Token graduated to DEX \u2014 use dex.buildSwap() instead");
|
|
936
|
+
const tokenAddress = params.tokenContractAddress;
|
|
937
|
+
if (tokenAddress === ethers4.ZeroAddress) throw new Error(`Token ID ${tokenId} not found`);
|
|
938
|
+
const token = new ethers4.Contract(tokenAddress, ERC20_ABI, this.provider);
|
|
939
|
+
let sellAmount;
|
|
940
|
+
if (amount === "max") {
|
|
941
|
+
sellAmount = await token.balanceOf(wallet);
|
|
942
|
+
} else {
|
|
943
|
+
sellAmount = ethers4.parseUnits(amount, 18);
|
|
944
|
+
}
|
|
945
|
+
if (sellAmount === 0n) throw new Error("Zero balance");
|
|
946
|
+
if (!this.isArenaPaired(tokenId)) {
|
|
947
|
+
sellAmount = sellAmount / GRANULARITY_SCALER * GRANULARITY_SCALER;
|
|
948
|
+
if (sellAmount === 0n) throw new Error("Balance too small");
|
|
949
|
+
}
|
|
950
|
+
const spender = this.isArenaPaired(tokenId) ? ethers4.getAddress(AVAX_HELPER) : ethers4.getAddress(LAUNCH_CONTRACT);
|
|
951
|
+
const erc20Iface = new ethers4.Interface(ERC20_ABI);
|
|
952
|
+
const approveTx = { to: tokenAddress, data: erc20Iface.encodeFunctionData("approve", [spender, ethers4.MaxUint256]), value: "0", chainId: CHAIN_ID, gasLimit: "60000", description: "Approve token for selling" };
|
|
953
|
+
let sellData;
|
|
954
|
+
let sellTo;
|
|
955
|
+
if (!this.isArenaPaired(tokenId)) {
|
|
956
|
+
sellData = new ethers4.Interface(LAUNCH_CONTRACT_ABI).encodeFunctionData("sell", [sellAmount, id]);
|
|
957
|
+
sellTo = ethers4.getAddress(LAUNCH_CONTRACT);
|
|
958
|
+
} else {
|
|
959
|
+
let minOut = 0n;
|
|
960
|
+
try {
|
|
961
|
+
const reward = await contract.calculateRewardWithFees(sellAmount, id);
|
|
962
|
+
minOut = reward - reward * BigInt(slippageBps) / 10000n;
|
|
963
|
+
} catch {
|
|
964
|
+
}
|
|
965
|
+
sellData = new ethers4.Interface(AVAX_HELPER_ABI).encodeFunctionData("sellToAvax", [id, sellAmount, minOut]);
|
|
966
|
+
sellTo = ethers4.getAddress(AVAX_HELPER);
|
|
967
|
+
}
|
|
968
|
+
return { transactions: [approveTx, { to: sellTo, data: sellData, value: "0", chainId: CHAIN_ID, gasLimit: "500000", description: `Sell ${ethers4.formatUnits(sellAmount, 18)} tokens (ID ${tokenId})` }] };
|
|
969
|
+
}
|
|
970
|
+
/** Binary search for max tokens purchasable with given AVAX */
|
|
971
|
+
async binarySearchTokenAmount(avaxBudgetWei, tokenId) {
|
|
972
|
+
let maxForSale;
|
|
973
|
+
try {
|
|
974
|
+
maxForSale = await this.launchContract.getMaxTokensForSale(tokenId);
|
|
975
|
+
} catch {
|
|
976
|
+
maxForSale = 100000000n * GRANULARITY_SCALER;
|
|
977
|
+
}
|
|
978
|
+
const maxWhole = maxForSale / GRANULARITY_SCALER;
|
|
979
|
+
if (maxWhole <= 0n) return 0n;
|
|
980
|
+
let lo = 1n;
|
|
981
|
+
let hi = maxWhole;
|
|
982
|
+
let best = 0n;
|
|
983
|
+
for (let i = 0; i < 30 && lo <= hi; i++) {
|
|
984
|
+
const mid = (lo + hi) / 2n;
|
|
985
|
+
try {
|
|
986
|
+
const cost = await this.launchContract.calculateCostWithFees(mid, tokenId);
|
|
987
|
+
if (cost <= avaxBudgetWei) {
|
|
988
|
+
best = mid;
|
|
989
|
+
lo = mid + 1n;
|
|
990
|
+
} else {
|
|
991
|
+
hi = mid - 1n;
|
|
992
|
+
}
|
|
993
|
+
} catch {
|
|
994
|
+
hi = mid - 1n;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
return best > 0n ? best * GRANULARITY_SCALER : 0n;
|
|
386
998
|
}
|
|
387
999
|
};
|
|
388
1000
|
|
|
389
1001
|
// src/modules/dex.ts
|
|
1002
|
+
import { ethers as ethers5 } from "ethers";
|
|
1003
|
+
var KNOWN_TOKENS = {
|
|
1004
|
+
AVAX: WAVAX,
|
|
1005
|
+
WAVAX,
|
|
1006
|
+
USDC: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
1007
|
+
"USDC.e": "0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664",
|
|
1008
|
+
USDT: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7",
|
|
1009
|
+
"USDT.e": "0xc7198437980c041c805A1EDcbA50c1Ce5db95118",
|
|
1010
|
+
JOE: "0x6e84a6216eA6dACC71eE8E6b0a5B7322EEbC0fDd",
|
|
1011
|
+
ARENA: "0xB8d7710f7d8349A506b75dD184F05777c82dAd0C",
|
|
1012
|
+
"BTC.b": "0x152b9d0FdC40C096DE20232Db5A0dF62B48dEeEB",
|
|
1013
|
+
"WETH.e": "0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB",
|
|
1014
|
+
GMX: "0x62edc0692BD897D2295872a9FFCac5425011c661",
|
|
1015
|
+
sAVAX: "0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE"
|
|
1016
|
+
};
|
|
390
1017
|
var DexModule = class {
|
|
391
|
-
constructor(
|
|
392
|
-
this.
|
|
393
|
-
this.
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
1018
|
+
constructor(provider) {
|
|
1019
|
+
this.provider = provider;
|
|
1020
|
+
this.lbQuoter = new ethers5.Contract(ethers5.getAddress(LB_QUOTER), LB_QUOTER_ABI, provider);
|
|
1021
|
+
}
|
|
1022
|
+
lbQuoter;
|
|
1023
|
+
/** Resolve token symbol or address to a checksummed address */
|
|
1024
|
+
resolveToken(tokenOrAddress) {
|
|
1025
|
+
const upper = tokenOrAddress.toUpperCase();
|
|
1026
|
+
if (KNOWN_TOKENS[upper]) return ethers5.getAddress(KNOWN_TOKENS[upper]);
|
|
1027
|
+
if (KNOWN_TOKENS[tokenOrAddress]) return ethers5.getAddress(KNOWN_TOKENS[tokenOrAddress]);
|
|
1028
|
+
if (tokenOrAddress.startsWith("0x")) return ethers5.getAddress(tokenOrAddress);
|
|
1029
|
+
throw new Error(`Unknown token: ${tokenOrAddress}. Use a symbol (AVAX, USDC, JOE, ARENA) or a 0x contract address.`);
|
|
1030
|
+
}
|
|
1031
|
+
/** List known tokens */
|
|
1032
|
+
getTokens() {
|
|
1033
|
+
const tokens = Object.entries(KNOWN_TOKENS).map(([symbol, address]) => ({ symbol, address }));
|
|
1034
|
+
return { tokens, note: "You can also pass any ERC-20 contract address directly." };
|
|
1035
|
+
}
|
|
1036
|
+
/** Look up any ERC-20 token by address */
|
|
407
1037
|
async getTokenInfo(address) {
|
|
408
|
-
|
|
409
|
-
|
|
1038
|
+
const addr = ethers5.getAddress(address);
|
|
1039
|
+
const token = new ethers5.Contract(addr, ERC20_ABI, this.provider);
|
|
1040
|
+
const [name, symbol, decimals] = await Promise.all([token.name(), token.symbol(), token.decimals()]);
|
|
1041
|
+
return { address: addr, symbol, name, decimals: Number(decimals) };
|
|
1042
|
+
}
|
|
1043
|
+
/** Get balance of any token */
|
|
1044
|
+
async getBalance(wallet, tokenOrAddress) {
|
|
1045
|
+
const address = this.resolveToken(tokenOrAddress);
|
|
1046
|
+
if (address.toLowerCase() === WAVAX.toLowerCase() && tokenOrAddress.toUpperCase() === "AVAX") {
|
|
1047
|
+
const balance2 = await this.provider.getBalance(wallet);
|
|
1048
|
+
return { wallet, token: "AVAX", balance: balance2.toString(), formatted: ethers5.formatEther(balance2), symbol: "AVAX" };
|
|
1049
|
+
}
|
|
1050
|
+
const token = new ethers5.Contract(address, ERC20_ABI, this.provider);
|
|
1051
|
+
const [balance, symbol, decimals] = await Promise.all([token.balanceOf(wallet), token.symbol(), token.decimals()]);
|
|
1052
|
+
return { wallet, token: address, balance: balance.toString(), formatted: ethers5.formatUnits(balance, decimals), symbol };
|
|
410
1053
|
}
|
|
411
|
-
/**
|
|
412
|
-
* Quote a swap between any two tokens on Avalanche.
|
|
413
|
-
* @param from - Source token symbol or contract address
|
|
414
|
-
* @param to - Destination token symbol or contract address
|
|
415
|
-
* @param amount - Amount of source token to swap
|
|
416
|
-
*/
|
|
1054
|
+
/** Quote a swap between any two tokens */
|
|
417
1055
|
async quote(from, to, amount) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
await
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
1056
|
+
const fromAddr = this.resolveToken(from);
|
|
1057
|
+
const toAddr = this.resolveToken(to);
|
|
1058
|
+
const fromToken = new ethers5.Contract(fromAddr, ERC20_ABI, this.provider);
|
|
1059
|
+
const decimals = fromAddr.toLowerCase() === WAVAX.toLowerCase() ? 18 : Number(await fromToken.decimals());
|
|
1060
|
+
const amountIn = ethers5.parseUnits(amount, decimals);
|
|
1061
|
+
const route = [fromAddr, toAddr];
|
|
1062
|
+
const quoteResult = await this.lbQuoter.findBestPathFromAmountIn(route, amountIn);
|
|
1063
|
+
const amountOut = quoteResult.amounts[quoteResult.amounts.length - 1];
|
|
1064
|
+
const toToken = new ethers5.Contract(toAddr, ERC20_ABI, this.provider);
|
|
1065
|
+
const toDecimals = toAddr.toLowerCase() === WAVAX.toLowerCase() ? 18 : Number(await toToken.decimals());
|
|
1066
|
+
const outFormatted = ethers5.formatUnits(amountOut, toDecimals);
|
|
1067
|
+
return {
|
|
1068
|
+
from,
|
|
1069
|
+
to,
|
|
1070
|
+
amountIn: amount,
|
|
1071
|
+
amountOut: outFormatted,
|
|
1072
|
+
rate: (parseFloat(outFormatted) / parseFloat(amount)).toFixed(6),
|
|
1073
|
+
path: route
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
/** Build swap transactions (1 tx for AVAX→token, 2 txs for token→token with approve) */
|
|
1077
|
+
async buildSwap(wallet, from, to, amount, slippageBps = DEFAULT_SLIPPAGE_BPS) {
|
|
1078
|
+
const fromAddr = this.resolveToken(from);
|
|
1079
|
+
const toAddr = this.resolveToken(to);
|
|
1080
|
+
const isFromNative = from.toUpperCase() === "AVAX";
|
|
1081
|
+
const isToNative = to.toUpperCase() === "AVAX";
|
|
1082
|
+
const fromToken = new ethers5.Contract(fromAddr, ERC20_ABI, this.provider);
|
|
1083
|
+
const fromDecimals = isFromNative ? 18 : Number(await fromToken.decimals());
|
|
1084
|
+
let amountIn;
|
|
1085
|
+
if (amount === "max" && !isFromNative) {
|
|
1086
|
+
amountIn = await fromToken.balanceOf(wallet);
|
|
1087
|
+
if (amountIn === 0n) throw new Error("No balance to swap");
|
|
1088
|
+
} else {
|
|
1089
|
+
amountIn = ethers5.parseUnits(amount, fromDecimals);
|
|
1090
|
+
}
|
|
1091
|
+
const route = [fromAddr, toAddr];
|
|
1092
|
+
const quoteResult = await this.lbQuoter.findBestPathFromAmountIn(route, amountIn);
|
|
1093
|
+
const expectedOut = quoteResult.amounts[quoteResult.amounts.length - 1];
|
|
1094
|
+
if (expectedOut === 0n) throw new Error("Quote returned zero \u2014 no liquidity for this pair");
|
|
1095
|
+
const clampedSlippage = BigInt(Math.max(0, Math.min(1e4, Number(slippageBps))));
|
|
1096
|
+
const amountOutMin = expectedOut - expectedOut * clampedSlippage / 10000n;
|
|
1097
|
+
const deadline = Math.floor(Date.now() / 1e3) + 3600;
|
|
1098
|
+
const path = {
|
|
1099
|
+
pairBinSteps: [...quoteResult.binSteps].map((b) => b.toString()),
|
|
1100
|
+
versions: [...quoteResult.versions].map((v) => Number(v)),
|
|
1101
|
+
tokenPath: [...route]
|
|
1102
|
+
};
|
|
1103
|
+
const txs = [];
|
|
1104
|
+
const routerIface = new ethers5.Interface(LB_ROUTER_ABI);
|
|
1105
|
+
if (isFromNative) {
|
|
1106
|
+
const data = routerIface.encodeFunctionData("swapExactNATIVEForTokens", [amountOutMin, path, wallet, deadline]);
|
|
1107
|
+
txs.push({ to: ethers5.getAddress(LB_ROUTER), data, value: ethers5.toBeHex(amountIn, 32), chainId: CHAIN_ID, gasLimit: "500000", description: `Swap ${amount} AVAX \u2192 ${to}` });
|
|
1108
|
+
} else if (isToNative) {
|
|
1109
|
+
const erc20Iface = new ethers5.Interface(ERC20_ABI);
|
|
1110
|
+
txs.push({
|
|
1111
|
+
to: fromAddr,
|
|
1112
|
+
data: erc20Iface.encodeFunctionData("approve", [ethers5.getAddress(LB_ROUTER), amountIn]),
|
|
1113
|
+
value: "0",
|
|
1114
|
+
chainId: CHAIN_ID,
|
|
1115
|
+
gasLimit: "60000",
|
|
1116
|
+
description: `Approve ${from} for swap`
|
|
1117
|
+
});
|
|
1118
|
+
const data = routerIface.encodeFunctionData("swapExactTokensForNATIVE", [amountIn, amountOutMin, path, wallet, deadline]);
|
|
1119
|
+
txs.push({ to: ethers5.getAddress(LB_ROUTER), data, value: "0", chainId: CHAIN_ID, gasLimit: "500000", description: `Swap ${from} \u2192 AVAX` });
|
|
1120
|
+
} else {
|
|
1121
|
+
const erc20Iface = new ethers5.Interface(ERC20_ABI);
|
|
1122
|
+
txs.push({
|
|
1123
|
+
to: fromAddr,
|
|
1124
|
+
data: erc20Iface.encodeFunctionData("approve", [ethers5.getAddress(LB_ROUTER), amountIn]),
|
|
1125
|
+
value: "0",
|
|
1126
|
+
chainId: CHAIN_ID,
|
|
1127
|
+
gasLimit: "60000",
|
|
1128
|
+
description: `Approve ${from} for swap`
|
|
1129
|
+
});
|
|
1130
|
+
throw new Error("Direct token\u2192token swaps not yet supported. Route through AVAX: swap TOKEN\u2192AVAX, then AVAX\u2192TOKEN.");
|
|
1131
|
+
}
|
|
1132
|
+
const toToken = new ethers5.Contract(toAddr, ERC20_ABI, this.provider);
|
|
1133
|
+
const toDecimals = isToNative ? 18 : Number(await toToken.decimals());
|
|
1134
|
+
return { transactions: txs, summary: `Swap ${amount} ${from} \u2192 ~${ethers5.formatUnits(expectedOut, toDecimals)} ${to}` };
|
|
444
1135
|
}
|
|
445
1136
|
};
|
|
446
1137
|
|
|
447
1138
|
// src/modules/perps.ts
|
|
448
1139
|
var PerpsModule = class {
|
|
449
|
-
constructor(
|
|
450
|
-
this.
|
|
451
|
-
this.auth = auth;
|
|
1140
|
+
constructor(arenaApiKey) {
|
|
1141
|
+
this.arenaApiKey = arenaApiKey;
|
|
452
1142
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
* Link your Arena API key to enable perps trading.
|
|
456
|
-
* One-time setup — after this, all /perp endpoints work automatically.
|
|
457
|
-
* @param arenaApiKey - Your Arena API key (from arena.social)
|
|
458
|
-
*/
|
|
459
|
-
async setup(arenaApiKey) {
|
|
460
|
-
await this.auth();
|
|
461
|
-
return this.http.post("/perp/setup", { arenaApiKey });
|
|
1143
|
+
setArenaApiKey(key) {
|
|
1144
|
+
this.arenaApiKey = key;
|
|
462
1145
|
}
|
|
463
|
-
|
|
464
|
-
|
|
1146
|
+
async arenaRequest(method, path, body, query) {
|
|
1147
|
+
if (!this.arenaApiKey) throw new Error("Arena API key required for perps. Pass arenaApiKey in config.");
|
|
1148
|
+
let url = `${ARENA_SOCIAL_API}${path}`;
|
|
1149
|
+
if (query) {
|
|
1150
|
+
const params = new URLSearchParams(query);
|
|
1151
|
+
url += `?${params.toString()}`;
|
|
1152
|
+
}
|
|
1153
|
+
const headers = { "x-api-key": this.arenaApiKey, "Content-Type": "application/json" };
|
|
1154
|
+
const res = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : void 0 });
|
|
1155
|
+
const data = await res.json();
|
|
1156
|
+
if (!res.ok) {
|
|
1157
|
+
throw new Error(data.message || data.error || `Arena perps error ${res.status}`);
|
|
1158
|
+
}
|
|
1159
|
+
return data;
|
|
1160
|
+
}
|
|
1161
|
+
async hlPost(body) {
|
|
1162
|
+
const res = await fetch(HL_INFO, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
1163
|
+
if (!res.ok) throw new Error(`Hyperliquid API error ${res.status}`);
|
|
1164
|
+
return res.json();
|
|
1165
|
+
}
|
|
1166
|
+
// ── Setup (via Arena API) ──
|
|
465
1167
|
async register() {
|
|
466
|
-
|
|
467
|
-
return this.http.post("/perp/register", {});
|
|
1168
|
+
return this.arenaRequest("POST", "/agents/perp/register", { provider: "HYPERLIQUID" });
|
|
468
1169
|
}
|
|
469
|
-
/** Check perps registration status */
|
|
470
1170
|
async getRegistrationStatus() {
|
|
471
|
-
|
|
472
|
-
return this.http.get("/perp/registration-status");
|
|
1171
|
+
return this.arenaRequest("GET", "/agents/perp/registration-status", void 0, { provider: "HYPERLIQUID" });
|
|
473
1172
|
}
|
|
474
|
-
/** Get your Hyperliquid API wallet address */
|
|
475
1173
|
async getWalletAddress() {
|
|
476
|
-
|
|
477
|
-
return this.http.get("/perp/wallet-address");
|
|
1174
|
+
return this.arenaRequest("GET", "/agents/perp/wallet-address", void 0, { provider: "HYPERLIQUID" });
|
|
478
1175
|
}
|
|
479
|
-
// ── Auth Flow ──
|
|
480
|
-
/** Check which auth steps are completed */
|
|
1176
|
+
// ── Auth Flow (via Arena API — EIP-712 signing) ──
|
|
481
1177
|
async getAuthStatus() {
|
|
482
|
-
|
|
483
|
-
return this.http.post("/perp/auth/status", {});
|
|
1178
|
+
return this.arenaRequest("POST", "/agents/perp/auth/status", { provider: "HYPERLIQUID" });
|
|
484
1179
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
*/
|
|
490
|
-
async getAuthPayload(step, body) {
|
|
491
|
-
await this.auth();
|
|
492
|
-
return this.http.post(`/perp/auth/${step}/payload`, body || {});
|
|
1180
|
+
async getAuthPayload(step, mainWalletAddress) {
|
|
1181
|
+
const body = { provider: "HYPERLIQUID" };
|
|
1182
|
+
if (mainWalletAddress) body.mainWalletAddress = mainWalletAddress;
|
|
1183
|
+
return this.arenaRequest("POST", `/agents/perp/auth/${step}/payload`, body);
|
|
493
1184
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
* @param step - Auth step
|
|
497
|
-
* @param body - Signed payload with signature and metadata
|
|
498
|
-
*/
|
|
499
|
-
async submitAuthSignature(step, body) {
|
|
500
|
-
await this.auth();
|
|
501
|
-
return this.http.post(`/perp/auth/${step}/submit`, body);
|
|
1185
|
+
async submitAuthSignature(step, signature, mainWalletAddress, metadata) {
|
|
1186
|
+
return this.arenaRequest("POST", `/agents/perp/auth/${step}/submit`, { provider: "HYPERLIQUID", mainWalletAddress, signature, metadata });
|
|
502
1187
|
}
|
|
503
|
-
/** Enable HIP-3 abstraction (automated, no signature needed) */
|
|
504
1188
|
async enableHip3() {
|
|
505
|
-
|
|
506
|
-
return this.http.post("/perp/auth/enable-hip3", {});
|
|
1189
|
+
return this.arenaRequest("POST", "/agents/perp/auth/enable-hip3", { provider: "HYPERLIQUID" });
|
|
507
1190
|
}
|
|
508
|
-
// ── Market Data ──
|
|
509
|
-
/** Get all available perpetual trading pairs (250+ markets) */
|
|
1191
|
+
// ── Market Data (direct Hyperliquid) ──
|
|
510
1192
|
async getTradingPairs() {
|
|
511
|
-
await this.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
1193
|
+
const raw = await this.hlPost({ type: "metaAndAssetCtxs" });
|
|
1194
|
+
const universe = raw[0].universe;
|
|
1195
|
+
const pairs = universe.map((m, i) => ({
|
|
1196
|
+
name: m.name,
|
|
1197
|
+
symbol: m.name,
|
|
1198
|
+
baseAssetId: i,
|
|
1199
|
+
sizePrecision: m.szDecimals,
|
|
1200
|
+
maxLeverage: m.maxLeverage,
|
|
1201
|
+
isDelisted: false,
|
|
1202
|
+
marginMode: m.onlyIsolated ? "isolated" : "cross"
|
|
1203
|
+
}));
|
|
1204
|
+
return { pairs, count: pairs.length };
|
|
1205
|
+
}
|
|
1206
|
+
// ── Trading (via Arena API — they hold the signing keys) ──
|
|
521
1207
|
async updateLeverage(symbol, leverage, leverageType = "cross") {
|
|
522
|
-
|
|
523
|
-
return this.http.post("/perp/leverage/update", { symbol, leverage, leverageType });
|
|
1208
|
+
return this.arenaRequest("POST", "/agents/perp/leverage/update", { provider: "HYPERLIQUID", symbol, leverage, leverageType });
|
|
524
1209
|
}
|
|
525
|
-
// ── Trading ──
|
|
526
|
-
/**
|
|
527
|
-
* Place one or more perpetual orders.
|
|
528
|
-
* @param orders - Array of order objects (see docs for BaseOrderParams)
|
|
529
|
-
*/
|
|
530
1210
|
async placeOrder(orders) {
|
|
531
|
-
|
|
532
|
-
return this.http.post("/perp/orders/place", { orders });
|
|
1211
|
+
return this.arenaRequest("POST", "/agents/perp/orders/place", { provider: "HYPERLIQUID", orders });
|
|
533
1212
|
}
|
|
534
|
-
/**
|
|
535
|
-
* Cancel one or more open orders.
|
|
536
|
-
* @param cancels - Array of { assetIndex, oid }
|
|
537
|
-
*/
|
|
538
1213
|
async cancelOrders(cancels) {
|
|
539
|
-
|
|
540
|
-
return this.http.post("/perp/orders/cancel", { cancels });
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Modify an existing order.
|
|
544
|
-
* @param oid - Order ID to modify
|
|
545
|
-
* @param order - New order parameters
|
|
546
|
-
*/
|
|
547
|
-
async modifyOrder(oid, order) {
|
|
548
|
-
await this.auth();
|
|
549
|
-
return this.http.post("/perp/orders/modify", { oid, order });
|
|
1214
|
+
return this.arenaRequest("POST", "/agents/perp/orders/cancel", { provider: "HYPERLIQUID", cancels });
|
|
550
1215
|
}
|
|
551
|
-
/**
|
|
552
|
-
* Close a position (convenience — creates reduce-only IOC with 10% slippage).
|
|
553
|
-
* @param symbol - Market symbol
|
|
554
|
-
* @param positionSide - "long" or "short"
|
|
555
|
-
* @param size - Position size to close
|
|
556
|
-
* @param currentPrice - Current market price
|
|
557
|
-
* @param closePercent - Percentage to close (default: 100)
|
|
558
|
-
*/
|
|
559
1216
|
async closePosition(symbol, positionSide, size, currentPrice, closePercent = 100) {
|
|
560
|
-
|
|
561
|
-
return this.http.post("/perp/orders/close-position", {
|
|
562
|
-
symbol,
|
|
563
|
-
positionSide,
|
|
564
|
-
size,
|
|
565
|
-
currentPrice,
|
|
566
|
-
closePercent
|
|
567
|
-
});
|
|
1217
|
+
return this.arenaRequest("POST", "/agents/perp/orders/close-position", { provider: "HYPERLIQUID", symbol, positionSide, size, currentPrice, closePercent });
|
|
568
1218
|
}
|
|
569
|
-
// ── Account Data ──
|
|
570
|
-
/** Get open orders */
|
|
571
1219
|
async getOrders() {
|
|
572
|
-
|
|
573
|
-
return this.http.get("/perp/orders");
|
|
1220
|
+
return this.arenaRequest("GET", "/agents/perp/orders", void 0, { provider: "HYPERLIQUID" });
|
|
574
1221
|
}
|
|
575
|
-
/** Get trade execution history */
|
|
576
1222
|
async getTradeExecutions() {
|
|
577
|
-
|
|
578
|
-
return this.http.get("/perp/trade-executions");
|
|
1223
|
+
return this.arenaRequest("GET", "/agents/perp/trade-executions", void 0, { provider: "HYPERLIQUID" });
|
|
579
1224
|
}
|
|
580
|
-
|
|
581
|
-
* Get positions and margin summary (queries Hyperliquid directly).
|
|
582
|
-
* @param wallet - Your Hyperliquid wallet address (from getWalletAddress)
|
|
583
|
-
*/
|
|
1225
|
+
// ── Positions (direct Hyperliquid — read-only, no auth needed) ──
|
|
584
1226
|
async getPositions(wallet) {
|
|
585
|
-
|
|
586
|
-
return this.http.get("/perp/positions", { wallet });
|
|
1227
|
+
return this.hlPost({ type: "clearinghouseState", user: wallet });
|
|
587
1228
|
}
|
|
588
|
-
/**
|
|
589
|
-
* Get open orders from Hyperliquid directly.
|
|
590
|
-
* @param wallet - Your Hyperliquid wallet address
|
|
591
|
-
*/
|
|
592
1229
|
async getOpenOrders(wallet) {
|
|
593
|
-
|
|
594
|
-
return this.http.get("/perp/open-orders", { wallet });
|
|
1230
|
+
return this.hlPost({ type: "openOrders", user: wallet });
|
|
595
1231
|
}
|
|
596
1232
|
};
|
|
597
1233
|
|
|
598
1234
|
// src/modules/bridge.ts
|
|
1235
|
+
var BRIDGE_CHAINS = {
|
|
1236
|
+
ethereum: 1,
|
|
1237
|
+
base: 8453,
|
|
1238
|
+
arbitrum: 42161,
|
|
1239
|
+
optimism: 10,
|
|
1240
|
+
polygon: 137,
|
|
1241
|
+
bsc: 56,
|
|
1242
|
+
avalanche: 43114,
|
|
1243
|
+
fantom: 250,
|
|
1244
|
+
gnosis: 100,
|
|
1245
|
+
zksync: 324,
|
|
1246
|
+
linea: 59144,
|
|
1247
|
+
scroll: 534352,
|
|
1248
|
+
blast: 81457,
|
|
1249
|
+
mantle: 5e3
|
|
1250
|
+
};
|
|
1251
|
+
var USDC = {
|
|
1252
|
+
"1": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
1253
|
+
"42161": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
|
|
1254
|
+
"43114": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
|
1255
|
+
"8453": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
1256
|
+
"10": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
|
1257
|
+
"137": "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"
|
|
1258
|
+
};
|
|
1259
|
+
var NATIVE_TOKEN = "0x0000000000000000000000000000000000000000";
|
|
599
1260
|
var BridgeModule = class {
|
|
600
|
-
constructor(http, auth) {
|
|
601
|
-
this.http = http;
|
|
602
|
-
this.auth = auth;
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* Get all supported chains for cross-chain bridging.
|
|
606
|
-
*/
|
|
607
1261
|
async getChains() {
|
|
608
|
-
await
|
|
609
|
-
|
|
1262
|
+
const res = await fetch(`${LIFI_API}/chains`);
|
|
1263
|
+
if (!res.ok) throw new Error(`Li.Fi chains failed (${res.status})`);
|
|
1264
|
+
const data = await res.json();
|
|
1265
|
+
return { chains: data.chains, count: data.chains.length };
|
|
610
1266
|
}
|
|
611
|
-
/**
|
|
612
|
-
* Get tokens available on specified chains.
|
|
613
|
-
* @param chains - Comma-separated chain IDs (e.g. "43114,42161")
|
|
614
|
-
*/
|
|
615
1267
|
async getTokens(chains) {
|
|
616
|
-
await
|
|
617
|
-
|
|
1268
|
+
const res = await fetch(`${LIFI_API}/tokens?chains=${chains}`);
|
|
1269
|
+
if (!res.ok) throw new Error(`Li.Fi tokens failed (${res.status})`);
|
|
1270
|
+
const data = await res.json();
|
|
1271
|
+
return { tokens: data.tokens };
|
|
618
1272
|
}
|
|
619
|
-
/**
|
|
620
|
-
* Get info for a specific token on a chain.
|
|
621
|
-
* @param chainId - Chain ID
|
|
622
|
-
* @param address - Token contract address
|
|
623
|
-
*/
|
|
624
1273
|
async getToken(chainId, address) {
|
|
625
|
-
await
|
|
626
|
-
|
|
1274
|
+
const res = await fetch(`${LIFI_API}/token?chain=${chainId}&token=${address}`);
|
|
1275
|
+
if (!res.ok) throw new Error(`Li.Fi token failed (${res.status})`);
|
|
1276
|
+
return await res.json();
|
|
627
1277
|
}
|
|
628
|
-
/**
|
|
629
|
-
* Get available bridge connections between two chains.
|
|
630
|
-
* @param fromChainId - Source chain ID
|
|
631
|
-
* @param toChainId - Destination chain ID
|
|
632
|
-
* @param fromToken - Optional source token address
|
|
633
|
-
* @param toToken - Optional destination token address
|
|
634
|
-
*/
|
|
635
1278
|
async getConnections(fromChainId, toChainId, fromToken, toToken) {
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (
|
|
639
|
-
|
|
640
|
-
|
|
1279
|
+
const params = new URLSearchParams({ fromChain: fromChainId.toString(), toChain: toChainId.toString() });
|
|
1280
|
+
if (fromToken) params.set("fromToken", fromToken);
|
|
1281
|
+
if (toToken) params.set("toToken", toToken);
|
|
1282
|
+
const res = await fetch(`${LIFI_API}/connections?${params}`);
|
|
1283
|
+
if (!res.ok) throw new Error(`Li.Fi connections failed (${res.status})`);
|
|
1284
|
+
const data = await res.json();
|
|
1285
|
+
return { connections: data.connections };
|
|
641
1286
|
}
|
|
642
|
-
/**
|
|
643
|
-
* Get a bridge quote with unsigned transaction data.
|
|
644
|
-
* The agent signs the returned tx on the source chain.
|
|
645
|
-
*
|
|
646
|
-
* @param fromChainId - Source chain ID
|
|
647
|
-
* @param toChainId - Destination chain ID
|
|
648
|
-
* @param fromToken - Source token address (use 0x0000...0000 for native tokens)
|
|
649
|
-
* @param toToken - Destination token address
|
|
650
|
-
* @param fromAmount - Human-readable amount (e.g. "0.1")
|
|
651
|
-
* @param fromAddress - Sender wallet address
|
|
652
|
-
* @param toAddress - Optional receiver address (defaults to fromAddress)
|
|
653
|
-
* @param slippage - Slippage tolerance as decimal (default 0.03 = 3%)
|
|
654
|
-
* @param fromDecimals - Token decimals (default 18)
|
|
655
|
-
*/
|
|
656
1287
|
async getQuote(fromChainId, toChainId, fromToken, toToken, fromAmount, fromAddress, toAddress, slippage, fromDecimals) {
|
|
657
|
-
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1288
|
+
const decimals = fromDecimals ?? 18;
|
|
1289
|
+
const amountWei = this.parseUnits(fromAmount, decimals);
|
|
1290
|
+
const params = new URLSearchParams({
|
|
1291
|
+
fromChain: fromChainId.toString(),
|
|
1292
|
+
toChain: toChainId.toString(),
|
|
1293
|
+
fromToken,
|
|
1294
|
+
toToken,
|
|
1295
|
+
fromAmount: amountWei,
|
|
1296
|
+
fromAddress,
|
|
1297
|
+
toAddress: toAddress ?? fromAddress,
|
|
1298
|
+
slippage: (slippage ?? 0.03).toString(),
|
|
1299
|
+
integrator: "logiqical"
|
|
1300
|
+
});
|
|
1301
|
+
const res = await fetch(`${LIFI_API}/quote?${params}`);
|
|
1302
|
+
if (!res.ok) {
|
|
1303
|
+
const body = await res.text().catch(() => "");
|
|
1304
|
+
throw new Error(`Li.Fi quote failed (${res.status}): ${body}`);
|
|
1305
|
+
}
|
|
1306
|
+
return this.parseQuote(await res.json());
|
|
663
1307
|
}
|
|
664
|
-
/**
|
|
665
|
-
* Get multiple bridge route options.
|
|
666
|
-
*/
|
|
667
1308
|
async getRoutes(fromChainId, toChainId, fromToken, toToken, fromAmount, fromAddress, toAddress, slippage, fromDecimals) {
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
1309
|
+
const decimals = fromDecimals ?? 18;
|
|
1310
|
+
const amountWei = this.parseUnits(fromAmount, decimals);
|
|
1311
|
+
const body = {
|
|
1312
|
+
fromChainId,
|
|
1313
|
+
toChainId,
|
|
1314
|
+
fromTokenAddress: fromToken,
|
|
1315
|
+
toTokenAddress: toToken,
|
|
1316
|
+
fromAmount: amountWei,
|
|
1317
|
+
fromAddress,
|
|
1318
|
+
toAddress: toAddress ?? fromAddress,
|
|
1319
|
+
options: { slippage: slippage ?? 0.03, integrator: "logiqical", order: "RECOMMENDED" }
|
|
1320
|
+
};
|
|
1321
|
+
const res = await fetch(`${LIFI_API}/advanced/routes`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
1322
|
+
if (!res.ok) {
|
|
1323
|
+
const b = await res.text().catch(() => "");
|
|
1324
|
+
throw new Error(`Li.Fi routes failed (${res.status}): ${b}`);
|
|
1325
|
+
}
|
|
1326
|
+
const data = await res.json();
|
|
1327
|
+
const routes = (data.routes ?? []).map((r) => this.parseRoute(r));
|
|
1328
|
+
return { routes, count: routes.length };
|
|
674
1329
|
}
|
|
675
|
-
/**
|
|
676
|
-
* Check bridge transfer status.
|
|
677
|
-
* @param txHash - Source chain transaction hash
|
|
678
|
-
* @param fromChainId - Source chain ID
|
|
679
|
-
* @param toChainId - Destination chain ID
|
|
680
|
-
* @param bridge - Optional bridge tool name
|
|
681
|
-
*/
|
|
682
1330
|
async getStatus(txHash, fromChainId, toChainId, bridge) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
1331
|
+
const params = new URLSearchParams({ txHash, fromChain: fromChainId.toString(), toChain: toChainId.toString() });
|
|
1332
|
+
if (bridge) params.set("bridge", bridge);
|
|
1333
|
+
const res = await fetch(`${LIFI_API}/status?${params}`);
|
|
1334
|
+
if (!res.ok) throw new Error(`Li.Fi status failed (${res.status})`);
|
|
1335
|
+
return await res.json();
|
|
1336
|
+
}
|
|
1337
|
+
getInfo() {
|
|
1338
|
+
return { chains: BRIDGE_CHAINS, usdc: USDC, nativeToken: NATIVE_TOKEN, tip: "Use 0x0000...0000 for native tokens" };
|
|
1339
|
+
}
|
|
1340
|
+
parseUnits(amount, decimals) {
|
|
1341
|
+
const parts = amount.split(".");
|
|
1342
|
+
const whole = parts[0] || "0";
|
|
1343
|
+
const frac = (parts[1] || "").slice(0, decimals).padEnd(decimals, "0");
|
|
1344
|
+
return (BigInt(whole) * BigInt(10) ** BigInt(decimals) + BigInt(frac)).toString();
|
|
1345
|
+
}
|
|
1346
|
+
parseQuote(data) {
|
|
1347
|
+
const action = data.action ?? {};
|
|
1348
|
+
const estimate = data.estimate ?? {};
|
|
1349
|
+
const txReq = data.transactionRequest;
|
|
1350
|
+
const quote = {
|
|
1351
|
+
id: data.id ?? data.tool ?? "unknown",
|
|
1352
|
+
fromChainId: action.fromChainId ?? 0,
|
|
1353
|
+
toChainId: action.toChainId ?? 0,
|
|
1354
|
+
fromToken: action.fromToken?.address ?? "",
|
|
1355
|
+
toToken: action.toToken?.address ?? "",
|
|
1356
|
+
fromAmount: action.fromAmount ?? "0",
|
|
1357
|
+
toAmount: estimate.toAmount ?? "0",
|
|
1358
|
+
estimatedGas: estimate.gasCosts?.[0]?.amountUSD ?? "0",
|
|
1359
|
+
estimatedTime: estimate.executionDuration ?? 0,
|
|
1360
|
+
tool: data.tool ?? "unknown"
|
|
1361
|
+
};
|
|
1362
|
+
if (txReq) {
|
|
1363
|
+
quote.transaction = { to: txReq.to, data: txReq.data, value: txReq.value ?? "0x0", gasLimit: txReq.gasLimit, chainId: txReq.chainId ?? action.fromChainId };
|
|
1364
|
+
}
|
|
1365
|
+
return quote;
|
|
1366
|
+
}
|
|
1367
|
+
parseRoute(route) {
|
|
1368
|
+
const steps = route.steps ?? [];
|
|
1369
|
+
const firstStep = steps[0] ?? {};
|
|
1370
|
+
const action = firstStep.action ?? {};
|
|
1371
|
+
const estimate = firstStep.estimate ?? {};
|
|
1372
|
+
return {
|
|
1373
|
+
id: route.id ?? "unknown",
|
|
1374
|
+
fromChainId: route.fromChainId ?? 0,
|
|
1375
|
+
toChainId: route.toChainId ?? 0,
|
|
1376
|
+
fromToken: route.fromToken?.address ?? "",
|
|
1377
|
+
toToken: route.toToken?.address ?? "",
|
|
1378
|
+
fromAmount: route.fromAmount ?? "0",
|
|
1379
|
+
toAmount: route.toAmount ?? "0",
|
|
1380
|
+
estimatedGas: route.gasCostUSD ?? "0",
|
|
1381
|
+
estimatedTime: steps.reduce((t, s) => t + (s.estimate?.executionDuration ?? 0), 0),
|
|
1382
|
+
tool: firstStep.tool ?? "unknown"
|
|
1383
|
+
};
|
|
694
1384
|
}
|
|
695
1385
|
};
|
|
696
1386
|
|
|
697
1387
|
// src/modules/tickets.ts
|
|
1388
|
+
import { ethers as ethers6 } from "ethers";
|
|
698
1389
|
var TicketsModule = class {
|
|
699
|
-
constructor(
|
|
700
|
-
this.
|
|
701
|
-
this.
|
|
1390
|
+
constructor(provider) {
|
|
1391
|
+
this.provider = provider;
|
|
1392
|
+
this.contract = new ethers6.Contract(ethers6.getAddress(ARENA_SHARES_CONTRACT), SHARES_ABI, provider);
|
|
702
1393
|
}
|
|
703
|
-
|
|
1394
|
+
contract;
|
|
704
1395
|
async getBuyPrice(subject, amount = "1") {
|
|
705
|
-
|
|
706
|
-
|
|
1396
|
+
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1397
|
+
const subjectAddr = ethers6.getAddress(subject);
|
|
1398
|
+
const [price, priceWithFee] = await Promise.all([
|
|
1399
|
+
this.contract.getBuyPriceForFractionalShares(subjectAddr, fractionalAmount),
|
|
1400
|
+
this.contract.getBuyPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount)
|
|
1401
|
+
]);
|
|
1402
|
+
return { price: ethers6.formatEther(price), priceWithFee: ethers6.formatEther(priceWithFee), tickets: amount, fractionalAmount };
|
|
707
1403
|
}
|
|
708
|
-
/** Get sell price for tickets */
|
|
709
1404
|
async getSellPrice(subject, amount = "1") {
|
|
710
|
-
|
|
711
|
-
|
|
1405
|
+
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1406
|
+
const subjectAddr = ethers6.getAddress(subject);
|
|
1407
|
+
const [price, priceAfterFee] = await Promise.all([
|
|
1408
|
+
this.contract.getSellPriceForFractionalShares(subjectAddr, fractionalAmount),
|
|
1409
|
+
this.contract.getSellPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount)
|
|
1410
|
+
]);
|
|
1411
|
+
return { price: ethers6.formatEther(price), priceAfterFee: ethers6.formatEther(priceAfterFee), tickets: amount, fractionalAmount };
|
|
712
1412
|
}
|
|
713
|
-
/** Get ticket balance for a user on a subject */
|
|
714
1413
|
async getBalance(subject, user) {
|
|
715
|
-
await this.
|
|
716
|
-
return
|
|
1414
|
+
const frac = await this.contract.getMyFractionalShares(ethers6.getAddress(subject), ethers6.getAddress(user));
|
|
1415
|
+
return { tickets: (Number(frac) / FRACTION_SCALER).toString(), fractionalAmount: frac.toString() };
|
|
717
1416
|
}
|
|
718
|
-
/** Get ticket supply for a subject */
|
|
719
1417
|
async getSupply(subject) {
|
|
720
|
-
|
|
721
|
-
|
|
1418
|
+
const subjectAddr = ethers6.getAddress(subject);
|
|
1419
|
+
const [wholeSupply, fracSupply] = await Promise.all([
|
|
1420
|
+
this.contract.getSharesSupply(subjectAddr),
|
|
1421
|
+
this.contract.getTotalFractionalSupply(subjectAddr)
|
|
1422
|
+
]);
|
|
1423
|
+
return { wholeSupply: wholeSupply.toString(), fractionalSupply: fracSupply.toString(), tickets: (Number(fracSupply) / FRACTION_SCALER).toString() };
|
|
722
1424
|
}
|
|
723
|
-
/** Get fee structure */
|
|
724
1425
|
async getFees() {
|
|
725
|
-
await
|
|
726
|
-
|
|
1426
|
+
const [protocol, subject, referral] = await Promise.all([
|
|
1427
|
+
this.contract.protocolFeePercent(),
|
|
1428
|
+
this.contract.subjectFeePercent(),
|
|
1429
|
+
this.contract.referralFeePercent()
|
|
1430
|
+
]);
|
|
1431
|
+
const toPercent = (v) => (Number(v) / 1e16).toFixed(1) + "%";
|
|
1432
|
+
return { protocolFee: toPercent(protocol), subjectFee: toPercent(subject), referralFee: toPercent(referral), totalFeePercent: toPercent(protocol + subject + referral) };
|
|
727
1433
|
}
|
|
728
|
-
/** Build unsigned tx to buy tickets */
|
|
729
1434
|
async buildBuyTx(wallet, subject, amount = "1") {
|
|
730
|
-
|
|
731
|
-
|
|
1435
|
+
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1436
|
+
const subjectAddr = ethers6.getAddress(subject);
|
|
1437
|
+
const userAddr = ethers6.getAddress(wallet);
|
|
1438
|
+
const cost = await this.contract.getBuyPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount);
|
|
1439
|
+
if (cost === 0n) throw new Error("Could not get buy price \u2014 subject may not exist");
|
|
1440
|
+
const iface = new ethers6.Interface(SHARES_ABI);
|
|
1441
|
+
const data = iface.encodeFunctionData("buyFractionalShares", [subjectAddr, userAddr, fractionalAmount]);
|
|
1442
|
+
return {
|
|
1443
|
+
transaction: {
|
|
1444
|
+
to: ethers6.getAddress(ARENA_SHARES_CONTRACT),
|
|
1445
|
+
data,
|
|
1446
|
+
value: ethers6.toBeHex(cost, 32),
|
|
1447
|
+
chainId: CHAIN_ID,
|
|
1448
|
+
gasLimit: "200000",
|
|
1449
|
+
description: `Buy ${amount} ticket(s) for ~${ethers6.formatEther(cost)} AVAX`
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
732
1452
|
}
|
|
733
|
-
/** Build unsigned tx to sell tickets */
|
|
734
1453
|
async buildSellTx(wallet, subject, amount = "1") {
|
|
735
|
-
|
|
736
|
-
|
|
1454
|
+
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1455
|
+
const subjectAddr = ethers6.getAddress(subject);
|
|
1456
|
+
const userAddr = ethers6.getAddress(wallet);
|
|
1457
|
+
const balance = await this.contract.getMyFractionalShares(subjectAddr, userAddr);
|
|
1458
|
+
if (balance < BigInt(fractionalAmount)) {
|
|
1459
|
+
throw new Error(`Insufficient tickets: have ${Number(balance) / FRACTION_SCALER}, trying to sell ${amount}`);
|
|
1460
|
+
}
|
|
1461
|
+
const proceeds = await this.contract.getSellPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount);
|
|
1462
|
+
const iface = new ethers6.Interface(SHARES_ABI);
|
|
1463
|
+
const data = iface.encodeFunctionData("sellFractionalShares", [subjectAddr, userAddr, fractionalAmount]);
|
|
1464
|
+
return {
|
|
1465
|
+
transaction: {
|
|
1466
|
+
to: ethers6.getAddress(ARENA_SHARES_CONTRACT),
|
|
1467
|
+
data,
|
|
1468
|
+
value: "0x0",
|
|
1469
|
+
chainId: CHAIN_ID,
|
|
1470
|
+
gasLimit: "200000",
|
|
1471
|
+
description: `Sell ${amount} ticket(s) for ~${ethers6.formatEther(proceeds)} AVAX`
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
ticketsToFractional(tickets) {
|
|
1476
|
+
const num = parseFloat(tickets);
|
|
1477
|
+
if (isNaN(num) || num <= 0) throw new Error(`Invalid ticket amount: ${tickets}`);
|
|
1478
|
+
return Math.round(num * FRACTION_SCALER);
|
|
737
1479
|
}
|
|
738
1480
|
};
|
|
739
1481
|
|
|
740
1482
|
// src/modules/social.ts
|
|
741
1483
|
var SocialModule = class {
|
|
742
|
-
constructor(
|
|
743
|
-
this.http = http;
|
|
744
|
-
this.auth = auth;
|
|
1484
|
+
constructor(arenaApiKey) {
|
|
745
1485
|
this.arenaApiKey = arenaApiKey;
|
|
746
1486
|
}
|
|
747
|
-
headers() {
|
|
748
|
-
const h = {};
|
|
749
|
-
if (this.arenaApiKey) h["X-Arena-Api-Key"] = this.arenaApiKey;
|
|
750
|
-
return h;
|
|
751
|
-
}
|
|
752
|
-
/** Set the Arena API key for social endpoints */
|
|
753
1487
|
setArenaApiKey(key) {
|
|
754
1488
|
this.arenaApiKey = key;
|
|
755
1489
|
}
|
|
756
|
-
|
|
1490
|
+
async request(method, path, body, query) {
|
|
1491
|
+
if (!this.arenaApiKey) throw new Error("Arena API key required for social endpoints. Pass arenaApiKey in config.");
|
|
1492
|
+
let url = `${ARENA_SOCIAL_API}${path}`;
|
|
1493
|
+
if (query) {
|
|
1494
|
+
const params = new URLSearchParams(query);
|
|
1495
|
+
url += `?${params.toString()}`;
|
|
1496
|
+
}
|
|
1497
|
+
const headers = { "x-api-key": this.arenaApiKey, "Content-Type": "application/json" };
|
|
1498
|
+
const res = await fetch(url, { method, headers, body: body ? JSON.stringify(body) : void 0 });
|
|
1499
|
+
const data = await res.json();
|
|
1500
|
+
if (!res.ok) {
|
|
1501
|
+
const msg = data.message || data.error || `Arena API error ${res.status}`;
|
|
1502
|
+
throw new Error(msg);
|
|
1503
|
+
}
|
|
1504
|
+
return data;
|
|
1505
|
+
}
|
|
1506
|
+
// ── User Discovery ──
|
|
757
1507
|
async searchUsers(q, page = 1, pageSize = 20) {
|
|
758
|
-
|
|
759
|
-
return this.http.get(`/social/users/search?q=${encodeURIComponent(q)}&page=${page}&pageSize=${pageSize}`, void 0, this.headers());
|
|
1508
|
+
return this.request("GET", "/agents/user/search", void 0, { searchString: q, page: String(page), pageSize: String(pageSize) });
|
|
760
1509
|
}
|
|
761
1510
|
async getUserByHandle(handle) {
|
|
762
|
-
|
|
763
|
-
return this.http.get(`/social/users/handle/${encodeURIComponent(handle)}`, void 0, this.headers());
|
|
1511
|
+
return this.request("GET", "/agents/user/handle", void 0, { handle });
|
|
764
1512
|
}
|
|
765
1513
|
async getUserById(userId) {
|
|
766
|
-
|
|
767
|
-
return this.http.get(`/social/users/id/${userId}`, void 0, this.headers());
|
|
1514
|
+
return this.request("GET", "/agents/user/id", void 0, { userId });
|
|
768
1515
|
}
|
|
769
1516
|
async getTopUsers(page = 1, pageSize = 20) {
|
|
770
|
-
|
|
771
|
-
return this.http.get(`/social/users/top?page=${page}&pageSize=${pageSize}`, void 0, this.headers());
|
|
1517
|
+
return this.request("GET", "/agents/user/top", void 0, { page: String(page), pageSize: String(pageSize) });
|
|
772
1518
|
}
|
|
773
1519
|
async getMe() {
|
|
774
|
-
|
|
775
|
-
return this.http.get("/social/me", void 0, this.headers());
|
|
1520
|
+
return this.request("GET", "/agents/user/me");
|
|
776
1521
|
}
|
|
777
|
-
//
|
|
1522
|
+
// ── Profile ──
|
|
778
1523
|
async updateProfile(updates) {
|
|
779
|
-
|
|
780
|
-
|
|
1524
|
+
return this.request("PATCH", "/agents/user/profile", updates);
|
|
1525
|
+
}
|
|
1526
|
+
async updateBanner(bannerUrl) {
|
|
1527
|
+
return this.request("POST", "/agents/profile/banner", { bannerUrl });
|
|
781
1528
|
}
|
|
782
|
-
//
|
|
1529
|
+
// ── Follow ──
|
|
783
1530
|
async follow(userId) {
|
|
784
|
-
|
|
785
|
-
return this.http.post("/social/follow", { userId }, this.headers());
|
|
1531
|
+
return this.request("POST", "/agents/follow/follow", { userId });
|
|
786
1532
|
}
|
|
787
1533
|
async unfollow(userId) {
|
|
788
|
-
|
|
789
|
-
return this.http.post("/social/unfollow", { userId }, this.headers());
|
|
1534
|
+
return this.request("POST", "/agents/follow/unfollow", { userId });
|
|
790
1535
|
}
|
|
791
|
-
async getFollowers(userId) {
|
|
792
|
-
|
|
793
|
-
return this.http.get(`/social/followers/${userId}`, void 0, this.headers());
|
|
1536
|
+
async getFollowers(userId, page = 1, pageSize = 20) {
|
|
1537
|
+
return this.request("GET", "/agents/follow/followers/list", void 0, { followersOfUserId: userId, pageNumber: String(page), pageSize: String(pageSize) });
|
|
794
1538
|
}
|
|
795
|
-
async getFollowing(userId) {
|
|
796
|
-
|
|
797
|
-
return this.http.get(`/social/following/${userId}`, void 0, this.headers());
|
|
1539
|
+
async getFollowing(userId, page = 1, pageSize = 20) {
|
|
1540
|
+
return this.request("GET", "/agents/follow/following/list", void 0, { followingUserId: userId, pageNumber: String(page), pageSize: String(pageSize) });
|
|
798
1541
|
}
|
|
799
|
-
//
|
|
1542
|
+
// ── Shares ──
|
|
800
1543
|
async getSharesStats(userId) {
|
|
801
|
-
|
|
802
|
-
return this.http.get(`/social/shares/stats/${userId}`, void 0, this.headers());
|
|
1544
|
+
return this.request("GET", "/agents/shares/stats", void 0, { userId });
|
|
803
1545
|
}
|
|
804
|
-
async getShareHolders(userId) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
return this.
|
|
1546
|
+
async getShareHolders(userId, page = 1, pageSize = 20) {
|
|
1547
|
+
const q = { page: String(page), pageSize: String(pageSize) };
|
|
1548
|
+
if (userId) q.userId = userId;
|
|
1549
|
+
return this.request("GET", "/agents/shares/holders", void 0, q);
|
|
808
1550
|
}
|
|
809
|
-
async getHoldings() {
|
|
810
|
-
|
|
811
|
-
return this.http.get("/social/shares/holdings", void 0, this.headers());
|
|
1551
|
+
async getHoldings(page = 1, pageSize = 20) {
|
|
1552
|
+
return this.request("GET", "/agents/shares/holdings", void 0, { page: String(page), pageSize: String(pageSize) });
|
|
812
1553
|
}
|
|
813
|
-
//
|
|
1554
|
+
// ── Chat: Conversations ──
|
|
814
1555
|
async getConversations(page = 1) {
|
|
815
|
-
|
|
816
|
-
return this.http.get(`/social/chat/conversations?page=${page}`, void 0, this.headers());
|
|
1556
|
+
return this.request("GET", "/agents/chat/conversations", void 0, { page: String(page), pageSize: "20" });
|
|
817
1557
|
}
|
|
818
1558
|
async getDirectMessages() {
|
|
819
|
-
|
|
820
|
-
return this.http.get("/social/chat/dms", void 0, this.headers());
|
|
1559
|
+
return this.request("GET", "/agents/chat/direct-messages", void 0, { page: "1", pageSize: "20" });
|
|
821
1560
|
}
|
|
822
1561
|
async getGroupChats() {
|
|
823
|
-
|
|
824
|
-
return this.http.get("/social/chat/groups", void 0, this.headers());
|
|
1562
|
+
return this.request("GET", "/agents/chat/project-chats", void 0, { page: "1", pageSize: "20" });
|
|
825
1563
|
}
|
|
826
1564
|
async getGroup(groupId) {
|
|
827
|
-
|
|
828
|
-
return this.http.get(`/social/chat/group/${groupId}`, void 0, this.headers());
|
|
1565
|
+
return this.request("GET", "/agents/chat/group", void 0, { groupId });
|
|
829
1566
|
}
|
|
830
1567
|
async getMembers(groupId) {
|
|
831
|
-
|
|
832
|
-
return this.http.get(`/social/chat/members/${groupId}`, void 0, this.headers());
|
|
1568
|
+
return this.request("GET", "/agents/chat/members", void 0, { groupId, page: "1", pageSize: "20" });
|
|
833
1569
|
}
|
|
834
1570
|
async getOrCreateDM(userId) {
|
|
835
|
-
|
|
836
|
-
return this.http.post("/social/chat/dm", { userId }, this.headers());
|
|
1571
|
+
return this.request("GET", "/agents/chat/group/by/user", void 0, { userId });
|
|
837
1572
|
}
|
|
838
1573
|
async acceptChat(groupId) {
|
|
839
|
-
|
|
840
|
-
return this.http.post("/social/chat/accept", { groupId }, this.headers());
|
|
1574
|
+
return this.request("GET", "/agents/chat/accept-chat", void 0, { groupId });
|
|
841
1575
|
}
|
|
842
|
-
//
|
|
1576
|
+
// ── Chat: Messages ──
|
|
843
1577
|
async sendMessage(groupId, text, replyId) {
|
|
844
|
-
|
|
845
|
-
|
|
1578
|
+
const body = { groupId, text, files: [] };
|
|
1579
|
+
if (replyId) body.replyId = replyId;
|
|
1580
|
+
return this.request("POST", "/agents/chat/message", body);
|
|
846
1581
|
}
|
|
847
1582
|
async getMessages(groupId, after) {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
return this.
|
|
1583
|
+
const q = { groupId };
|
|
1584
|
+
if (after) q.timeFrom = after;
|
|
1585
|
+
return this.request("GET", "/agents/chat/messages/a", void 0, q);
|
|
851
1586
|
}
|
|
852
1587
|
async searchMessages(q, groupId) {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
return this.
|
|
1588
|
+
const query = { searchQuery: q, limit: "20" };
|
|
1589
|
+
if (groupId) query.groupId = groupId;
|
|
1590
|
+
return this.request("GET", "/agents/chat/messages/search", void 0, query);
|
|
856
1591
|
}
|
|
857
1592
|
async getMentions(groupId) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return this.
|
|
1593
|
+
const q = { limit: "50" };
|
|
1594
|
+
if (groupId) q.groupId = groupId;
|
|
1595
|
+
return this.request("GET", "/agents/chat/messages/mentions", void 0, q);
|
|
861
1596
|
}
|
|
862
|
-
//
|
|
1597
|
+
// ── Chat: Reactions & Pins ──
|
|
863
1598
|
async react(messageId, groupId, reaction) {
|
|
864
|
-
|
|
865
|
-
return this.http.post("/social/chat/react", { messageId, groupId, reaction }, this.headers());
|
|
1599
|
+
return this.request("POST", "/agents/chat/react", { messageId, groupId, reaction });
|
|
866
1600
|
}
|
|
867
1601
|
async pinMessage(messageId, groupId, isPinned = true) {
|
|
868
|
-
|
|
869
|
-
return this.http.post("/social/chat/pin", { messageId, groupId, isPinned }, this.headers());
|
|
1602
|
+
return this.request("POST", "/agents/chat/message/pin", { messageId, groupId, isPinned });
|
|
870
1603
|
}
|
|
871
1604
|
async getPinnedMessages(groupId) {
|
|
872
|
-
|
|
873
|
-
return this.http.get(`/social/chat/pinned/${groupId}`, void 0, this.headers());
|
|
1605
|
+
return this.request("GET", `/agents/chat/messages/pinned/${groupId}`);
|
|
874
1606
|
}
|
|
875
|
-
//
|
|
1607
|
+
// ── Threads ──
|
|
876
1608
|
async createThread(content, replyToId) {
|
|
877
|
-
|
|
878
|
-
|
|
1609
|
+
const body = { content };
|
|
1610
|
+
if (replyToId) body.replyToId = replyToId;
|
|
1611
|
+
return this.request("POST", "/agents/threads", body);
|
|
879
1612
|
}
|
|
880
1613
|
async likeThread(threadId) {
|
|
881
|
-
|
|
882
|
-
return this.http.post("/social/thread/like", { threadId }, this.headers());
|
|
1614
|
+
return this.request("POST", "/agents/threads/like", { threadId });
|
|
883
1615
|
}
|
|
884
1616
|
async repost(threadId) {
|
|
885
|
-
|
|
886
|
-
|
|
1617
|
+
return this.request("POST", "/agents/threads/repost", { threadId });
|
|
1618
|
+
}
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1621
|
+
// src/modules/signals.ts
|
|
1622
|
+
var SignalsModule = class {
|
|
1623
|
+
assetCache = null;
|
|
1624
|
+
candleCache = /* @__PURE__ */ new Map();
|
|
1625
|
+
async hlPost(body) {
|
|
1626
|
+
const res = await fetch(HL_INFO, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
1627
|
+
if (!res.ok) throw new Error(`Hyperliquid API error ${res.status}`);
|
|
1628
|
+
return res.json();
|
|
1629
|
+
}
|
|
1630
|
+
/** Get all asset contexts (cached 10s) */
|
|
1631
|
+
async getAssetContexts() {
|
|
1632
|
+
if (this.assetCache && Date.now() - this.assetCache.ts < 1e4) return this.assetCache.data;
|
|
1633
|
+
const raw = await this.hlPost({ type: "metaAndAssetCtxs" });
|
|
1634
|
+
const result = { meta: raw[0].universe, contexts: raw[1] };
|
|
1635
|
+
this.assetCache = { data: result, ts: Date.now() };
|
|
1636
|
+
return result;
|
|
1637
|
+
}
|
|
1638
|
+
/** Get market signal for a specific asset */
|
|
1639
|
+
async getMarketSignal(coin) {
|
|
1640
|
+
const { meta, contexts } = await this.getAssetContexts();
|
|
1641
|
+
const idx = meta.findIndex((m2) => m2.name.toUpperCase() === coin.toUpperCase());
|
|
1642
|
+
if (idx === -1) throw new Error(`Asset ${coin} not found on Hyperliquid`);
|
|
1643
|
+
const m = meta[idx];
|
|
1644
|
+
const ctx = contexts[idx];
|
|
1645
|
+
const price = parseFloat(ctx.markPx);
|
|
1646
|
+
const prevPrice = parseFloat(ctx.prevDayPx);
|
|
1647
|
+
const funding = parseFloat(ctx.funding);
|
|
1648
|
+
const change24h = price - prevPrice;
|
|
1649
|
+
const change24hPct = prevPrice > 0 ? change24h / prevPrice * 100 : 0;
|
|
1650
|
+
let fundingBias = "neutral";
|
|
1651
|
+
if (funding > 1e-4) fundingBias = "long-heavy";
|
|
1652
|
+
else if (funding < -1e-4) fundingBias = "short-heavy";
|
|
1653
|
+
return {
|
|
1654
|
+
coin: m.name,
|
|
1655
|
+
price,
|
|
1656
|
+
oraclePrice: parseFloat(ctx.oraclePx),
|
|
1657
|
+
change24h,
|
|
1658
|
+
change24hPct: Math.round(change24hPct * 100) / 100,
|
|
1659
|
+
volume24h: Math.round(parseFloat(ctx.dayNtlVlm)),
|
|
1660
|
+
openInterest: Math.round(parseFloat(ctx.openInterest)),
|
|
1661
|
+
fundingRate: funding,
|
|
1662
|
+
fundingAnnualized: Math.round(funding * 8760 * 1e4) / 100,
|
|
1663
|
+
fundingBias,
|
|
1664
|
+
maxLeverage: m.maxLeverage
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
/** Get funding rate extremes across all markets */
|
|
1668
|
+
async getFundingExtremes(count = 10) {
|
|
1669
|
+
const { meta, contexts } = await this.getAssetContexts();
|
|
1670
|
+
const signals = meta.map((m, i) => this.buildSignal(m, contexts[i]));
|
|
1671
|
+
const sorted = [...signals].sort((a, b) => b.fundingRate - a.fundingRate);
|
|
1672
|
+
return { mostPositive: sorted.slice(0, count), mostNegative: sorted.slice(-count).reverse() };
|
|
1673
|
+
}
|
|
1674
|
+
/** Get candle data for technical analysis (cached 60s) */
|
|
1675
|
+
async getCandles(coin, interval = "1h", count = 100) {
|
|
1676
|
+
const cacheKey = `${coin}-${interval}`;
|
|
1677
|
+
const cached = this.candleCache.get(cacheKey);
|
|
1678
|
+
if (cached && Date.now() - cached.ts < 6e4) return cached.data.slice(-count);
|
|
1679
|
+
const startTime = Date.now() - count * this.intervalToMs(interval);
|
|
1680
|
+
const data = await this.hlPost({ type: "candleSnapshot", req: { coin: coin.toUpperCase(), interval, startTime, endTime: Date.now() } });
|
|
1681
|
+
const candles = data.map((c) => [c.t, parseFloat(c.o), parseFloat(c.h), parseFloat(c.l), parseFloat(c.c), parseFloat(c.v)]);
|
|
1682
|
+
this.candleCache.set(cacheKey, { data: candles, ts: Date.now() });
|
|
1683
|
+
return candles.slice(-count);
|
|
1684
|
+
}
|
|
1685
|
+
/** Compute technical signals from candle data */
|
|
1686
|
+
async getTechnicalSignal(coin, interval = "1h") {
|
|
1687
|
+
const candles = await this.getCandles(coin, interval, 100);
|
|
1688
|
+
const closes = candles.map((c) => c[4]);
|
|
1689
|
+
const highs = candles.map((c) => c[2]);
|
|
1690
|
+
const lows = candles.map((c) => c[3]);
|
|
1691
|
+
const price = closes[closes.length - 1];
|
|
1692
|
+
const sma20 = this.sma(closes, 20);
|
|
1693
|
+
const sma50 = this.sma(closes, 50);
|
|
1694
|
+
const rsi14 = this.rsi(closes, 14);
|
|
1695
|
+
let trend = "neutral";
|
|
1696
|
+
if (price > sma20 && price > sma50) trend = "bullish";
|
|
1697
|
+
else if (price < sma20 && price < sma50) trend = "bearish";
|
|
1698
|
+
let momentum = "moderate";
|
|
1699
|
+
if (rsi14 > 70 || rsi14 < 30) momentum = "strong";
|
|
1700
|
+
else if (rsi14 > 60 || rsi14 < 40) momentum = "moderate";
|
|
1701
|
+
else momentum = "weak";
|
|
1702
|
+
const returns = closes.slice(-21).map((c, i, arr) => i === 0 ? 0 : (c - arr[i - 1]) / arr[i - 1]);
|
|
1703
|
+
returns.shift();
|
|
1704
|
+
const volatility = Math.sqrt(returns.reduce((sum, r) => sum + r * r, 0) / returns.length) * 100;
|
|
1705
|
+
const resistance = Math.max(...highs.slice(-20));
|
|
1706
|
+
const support = Math.min(...lows.slice(-20));
|
|
1707
|
+
return {
|
|
1708
|
+
coin: coin.toUpperCase(),
|
|
1709
|
+
price,
|
|
1710
|
+
sma20: Math.round(sma20 * 100) / 100,
|
|
1711
|
+
sma50: Math.round(sma50 * 100) / 100,
|
|
1712
|
+
rsi14: Math.round(rsi14 * 100) / 100,
|
|
1713
|
+
trend,
|
|
1714
|
+
momentum,
|
|
1715
|
+
priceVsSma20: Math.round((price - sma20) / sma20 * 1e4) / 100,
|
|
1716
|
+
priceVsSma50: Math.round((price - sma50) / sma50 * 1e4) / 100,
|
|
1717
|
+
volatility: Math.round(volatility * 100) / 100,
|
|
1718
|
+
support: Math.round(support * 100) / 100,
|
|
1719
|
+
resistance: Math.round(resistance * 100) / 100
|
|
1720
|
+
};
|
|
1721
|
+
}
|
|
1722
|
+
/** Get whale positions from orderbook depth */
|
|
1723
|
+
async getWhalePositions(coin, minPositionUsd = 1e5) {
|
|
1724
|
+
const book = await this.hlPost({ type: "l2Book", coin: coin.toUpperCase() });
|
|
1725
|
+
const whales = [];
|
|
1726
|
+
for (const level of book.levels[0] ?? []) {
|
|
1727
|
+
const px = parseFloat(level.px);
|
|
1728
|
+
const sz = parseFloat(level.sz);
|
|
1729
|
+
const value = px * sz;
|
|
1730
|
+
if (value >= minPositionUsd) whales.push({ coin: coin.toUpperCase(), size: sz, entryPrice: px, side: "long", positionValue: Math.round(value) });
|
|
1731
|
+
}
|
|
1732
|
+
for (const level of book.levels[1] ?? []) {
|
|
1733
|
+
const px = parseFloat(level.px);
|
|
1734
|
+
const sz = parseFloat(level.sz);
|
|
1735
|
+
const value = px * sz;
|
|
1736
|
+
if (value >= minPositionUsd) whales.push({ coin: coin.toUpperCase(), size: sz, entryPrice: px, side: "short", positionValue: Math.round(value) });
|
|
1737
|
+
}
|
|
1738
|
+
return whales.sort((a, b) => b.positionValue - a.positionValue);
|
|
1739
|
+
}
|
|
1740
|
+
/** Full signal summary — everything an agent needs to decide */
|
|
1741
|
+
async summary(coin) {
|
|
1742
|
+
const [market, technical, whalePositions] = await Promise.all([
|
|
1743
|
+
this.getMarketSignal(coin),
|
|
1744
|
+
this.getTechnicalSignal(coin),
|
|
1745
|
+
this.getWhalePositions(coin)
|
|
1746
|
+
]);
|
|
1747
|
+
const topLongs = whalePositions.filter((w) => w.side === "long").slice(0, 5);
|
|
1748
|
+
const topShorts = whalePositions.filter((w) => w.side === "short").slice(0, 5);
|
|
1749
|
+
const longValue = topLongs.reduce((s, w) => s + w.positionValue, 0);
|
|
1750
|
+
const shortValue = topShorts.reduce((s, w) => s + w.positionValue, 0);
|
|
1751
|
+
let netBias = "neutral";
|
|
1752
|
+
if (longValue > shortValue * 1.3) netBias = "long";
|
|
1753
|
+
else if (shortValue > longValue * 1.3) netBias = "short";
|
|
1754
|
+
const reasons = [];
|
|
1755
|
+
let longScore = 0;
|
|
1756
|
+
let shortScore = 0;
|
|
1757
|
+
if (technical.trend === "bullish") {
|
|
1758
|
+
longScore += 2;
|
|
1759
|
+
reasons.push("Price above SMA20 & SMA50 (bullish trend)");
|
|
1760
|
+
} else if (technical.trend === "bearish") {
|
|
1761
|
+
shortScore += 2;
|
|
1762
|
+
reasons.push("Price below SMA20 & SMA50 (bearish trend)");
|
|
1763
|
+
}
|
|
1764
|
+
if (technical.rsi14 < 30) {
|
|
1765
|
+
longScore += 2;
|
|
1766
|
+
reasons.push(`RSI ${technical.rsi14} \u2014 oversold`);
|
|
1767
|
+
} else if (technical.rsi14 > 70) {
|
|
1768
|
+
shortScore += 2;
|
|
1769
|
+
reasons.push(`RSI ${technical.rsi14} \u2014 overbought`);
|
|
1770
|
+
}
|
|
1771
|
+
if (market.fundingBias === "long-heavy") {
|
|
1772
|
+
shortScore += 1;
|
|
1773
|
+
reasons.push("Crowded long \u2014 contrarian short signal");
|
|
1774
|
+
} else if (market.fundingBias === "short-heavy") {
|
|
1775
|
+
longScore += 1;
|
|
1776
|
+
reasons.push("Crowded short \u2014 contrarian long signal");
|
|
1777
|
+
}
|
|
1778
|
+
if (netBias === "long") {
|
|
1779
|
+
longScore += 1;
|
|
1780
|
+
reasons.push("Whale bias: more large bids");
|
|
1781
|
+
} else if (netBias === "short") {
|
|
1782
|
+
shortScore += 1;
|
|
1783
|
+
reasons.push("Whale bias: more large asks");
|
|
1784
|
+
}
|
|
1785
|
+
if (market.change24hPct > 3) {
|
|
1786
|
+
longScore += 1;
|
|
1787
|
+
reasons.push(`+${market.change24hPct}% momentum`);
|
|
1788
|
+
} else if (market.change24hPct < -3) {
|
|
1789
|
+
shortScore += 1;
|
|
1790
|
+
reasons.push(`${market.change24hPct}% selling`);
|
|
1791
|
+
}
|
|
1792
|
+
const diff = Math.abs(longScore - shortScore);
|
|
1793
|
+
let direction = "wait";
|
|
1794
|
+
let confidence = "low";
|
|
1795
|
+
if (diff >= 4) {
|
|
1796
|
+
direction = longScore > shortScore ? "long" : "short";
|
|
1797
|
+
confidence = "high";
|
|
1798
|
+
} else if (diff >= 2) {
|
|
1799
|
+
direction = longScore > shortScore ? "long" : "short";
|
|
1800
|
+
confidence = "medium";
|
|
1801
|
+
} else {
|
|
1802
|
+
reasons.push("Mixed signals \u2014 wait for better setup.");
|
|
1803
|
+
}
|
|
1804
|
+
return {
|
|
1805
|
+
coin: coin.toUpperCase(),
|
|
1806
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1807
|
+
market,
|
|
1808
|
+
technical,
|
|
1809
|
+
whaleActivity: { topLongs, topShorts, netBias, largestPosition: whalePositions[0] ?? null },
|
|
1810
|
+
verdict: { direction, confidence, reasons }
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
/** Scan for top trading opportunities */
|
|
1814
|
+
async scan(count = 5) {
|
|
1815
|
+
const [movers, funding] = await Promise.all([this.getTopMovers(10), this.getFundingExtremes(10)]);
|
|
1816
|
+
const candidates = /* @__PURE__ */ new Set(["BTC", "ETH", "SOL"]);
|
|
1817
|
+
movers.gainers.slice(0, 3).forEach((s) => candidates.add(s.coin));
|
|
1818
|
+
movers.losers.slice(0, 3).forEach((s) => candidates.add(s.coin));
|
|
1819
|
+
funding.mostPositive.slice(0, 2).forEach((s) => candidates.add(s.coin));
|
|
1820
|
+
funding.mostNegative.slice(0, 2).forEach((s) => candidates.add(s.coin));
|
|
1821
|
+
const summaries = await Promise.all(Array.from(candidates).slice(0, 15).map((c) => this.summary(c).catch(() => null)));
|
|
1822
|
+
const valid = summaries.filter((s) => s !== null && s.verdict.direction !== "wait");
|
|
1823
|
+
const order = { high: 3, medium: 2, low: 1 };
|
|
1824
|
+
valid.sort((a, b) => order[b.verdict.confidence] - order[a.verdict.confidence]);
|
|
1825
|
+
return valid.slice(0, count);
|
|
1826
|
+
}
|
|
1827
|
+
async getTopMovers(count) {
|
|
1828
|
+
const { meta, contexts } = await this.getAssetContexts();
|
|
1829
|
+
const signals = meta.map((m, i) => this.buildSignal(m, contexts[i]));
|
|
1830
|
+
const sorted = [...signals].sort((a, b) => b.change24hPct - a.change24hPct);
|
|
1831
|
+
return { gainers: sorted.slice(0, count), losers: sorted.slice(-count).reverse() };
|
|
1832
|
+
}
|
|
1833
|
+
buildSignal(m, ctx) {
|
|
1834
|
+
const price = parseFloat(ctx.markPx);
|
|
1835
|
+
const prevPrice = parseFloat(ctx.prevDayPx);
|
|
1836
|
+
const funding = parseFloat(ctx.funding);
|
|
1837
|
+
const change24hPct = prevPrice > 0 ? (price - prevPrice) / prevPrice * 100 : 0;
|
|
1838
|
+
let fundingBias = "neutral";
|
|
1839
|
+
if (funding > 1e-4) fundingBias = "long-heavy";
|
|
1840
|
+
else if (funding < -1e-4) fundingBias = "short-heavy";
|
|
1841
|
+
return {
|
|
1842
|
+
coin: m.name,
|
|
1843
|
+
price,
|
|
1844
|
+
oraclePrice: parseFloat(ctx.oraclePx),
|
|
1845
|
+
change24h: price - prevPrice,
|
|
1846
|
+
change24hPct: Math.round(change24hPct * 100) / 100,
|
|
1847
|
+
volume24h: Math.round(parseFloat(ctx.dayNtlVlm)),
|
|
1848
|
+
openInterest: Math.round(parseFloat(ctx.openInterest)),
|
|
1849
|
+
fundingRate: funding,
|
|
1850
|
+
fundingAnnualized: Math.round(funding * 8760 * 1e4) / 100,
|
|
1851
|
+
fundingBias,
|
|
1852
|
+
maxLeverage: m.maxLeverage
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
sma(data, period) {
|
|
1856
|
+
const s = data.slice(-period);
|
|
1857
|
+
return s.reduce((sum, v) => sum + v, 0) / s.length;
|
|
1858
|
+
}
|
|
1859
|
+
rsi(closes, period) {
|
|
1860
|
+
const changes = closes.slice(-period - 1).map((c, i, arr) => i === 0 ? 0 : c - arr[i - 1]);
|
|
1861
|
+
changes.shift();
|
|
1862
|
+
let avgGain = 0;
|
|
1863
|
+
let avgLoss = 0;
|
|
1864
|
+
for (const ch of changes) {
|
|
1865
|
+
if (ch > 0) avgGain += ch;
|
|
1866
|
+
else avgLoss += Math.abs(ch);
|
|
1867
|
+
}
|
|
1868
|
+
avgGain /= period;
|
|
1869
|
+
avgLoss /= period;
|
|
1870
|
+
if (avgLoss === 0) return 100;
|
|
1871
|
+
return 100 - 100 / (1 + avgGain / avgLoss);
|
|
1872
|
+
}
|
|
1873
|
+
intervalToMs(interval) {
|
|
1874
|
+
const map = { "1m": 6e4, "5m": 3e5, "15m": 9e5, "1h": 36e5, "4h": 144e5, "1d": 864e5 };
|
|
1875
|
+
return map[interval] ?? 36e5;
|
|
1876
|
+
}
|
|
1877
|
+
};
|
|
1878
|
+
|
|
1879
|
+
// src/modules/market.ts
|
|
1880
|
+
var COINGECKO_API = "https://api.coingecko.com/api/v3";
|
|
1881
|
+
var MarketModule = class {
|
|
1882
|
+
/** Get prices for one or more coins (by CoinGecko ID) */
|
|
1883
|
+
async price(ids) {
|
|
1884
|
+
const idStr = Array.isArray(ids) ? ids.join(",") : ids;
|
|
1885
|
+
const res = await fetch(`${COINGECKO_API}/simple/price?ids=${idStr}&vs_currencies=usd&include_24hr_change=true&include_market_cap=true&include_24hr_vol=true`);
|
|
1886
|
+
if (!res.ok) throw new Error(`CoinGecko price error ${res.status}`);
|
|
1887
|
+
return res.json();
|
|
1888
|
+
}
|
|
1889
|
+
/** Get trending coins */
|
|
1890
|
+
async trending() {
|
|
1891
|
+
const res = await fetch(`${COINGECKO_API}/search/trending`);
|
|
1892
|
+
if (!res.ok) throw new Error(`CoinGecko trending error ${res.status}`);
|
|
1893
|
+
const data = await res.json();
|
|
1894
|
+
const coins = (data.coins ?? []).map((c) => ({
|
|
1895
|
+
id: c.item.id,
|
|
1896
|
+
name: c.item.name,
|
|
1897
|
+
symbol: c.item.symbol,
|
|
1898
|
+
market_cap_rank: c.item.market_cap_rank,
|
|
1899
|
+
thumb: c.item.thumb,
|
|
1900
|
+
price_btc: c.item.price_btc
|
|
1901
|
+
}));
|
|
1902
|
+
return { coins };
|
|
1903
|
+
}
|
|
1904
|
+
/** Get top coins by market cap */
|
|
1905
|
+
async markets(count = 20, page = 1) {
|
|
1906
|
+
const res = await fetch(`${COINGECKO_API}/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=${count}&page=${page}&sparkline=false`);
|
|
1907
|
+
if (!res.ok) throw new Error(`CoinGecko markets error ${res.status}`);
|
|
1908
|
+
const data = await res.json();
|
|
1909
|
+
const coins = data.map((c) => ({
|
|
1910
|
+
id: c.id,
|
|
1911
|
+
symbol: c.symbol,
|
|
1912
|
+
name: c.name,
|
|
1913
|
+
current_price: c.current_price,
|
|
1914
|
+
market_cap: c.market_cap,
|
|
1915
|
+
price_change_percentage_24h: c.price_change_percentage_24h,
|
|
1916
|
+
total_volume: c.total_volume,
|
|
1917
|
+
market_cap_rank: c.market_cap_rank
|
|
1918
|
+
}));
|
|
1919
|
+
return { coins };
|
|
1920
|
+
}
|
|
1921
|
+
/** Search for coins by query */
|
|
1922
|
+
async search(query) {
|
|
1923
|
+
const res = await fetch(`${COINGECKO_API}/search?query=${encodeURIComponent(query)}`);
|
|
1924
|
+
if (!res.ok) throw new Error(`CoinGecko search error ${res.status}`);
|
|
1925
|
+
const data = await res.json();
|
|
1926
|
+
return { coins: (data.coins ?? []).map((c) => ({ id: c.id, name: c.name, symbol: c.symbol, market_cap_rank: c.market_cap_rank })) };
|
|
1927
|
+
}
|
|
1928
|
+
/** Get AVAX price specifically */
|
|
1929
|
+
async avaxPrice() {
|
|
1930
|
+
const data = await this.price("avalanche-2");
|
|
1931
|
+
const avax = data["avalanche-2"];
|
|
1932
|
+
return { usd: avax?.usd ?? 0, change24h: avax?.usd_24h_change ?? 0 };
|
|
1933
|
+
}
|
|
1934
|
+
/** Get ARENA price specifically */
|
|
1935
|
+
async arenaPrice() {
|
|
1936
|
+
const data = await this.price("arena-social");
|
|
1937
|
+
const arena = data["arena-social"];
|
|
1938
|
+
return { usd: arena?.usd ?? 0, change24h: arena?.usd_24h_change ?? 0 };
|
|
1939
|
+
}
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1942
|
+
// src/modules/defi.ts
|
|
1943
|
+
import { ethers as ethers7 } from "ethers";
|
|
1944
|
+
var SAVAX_ADDRESS = "0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE";
|
|
1945
|
+
var SAVAX_ABI = [
|
|
1946
|
+
"function submit() payable returns (uint256)",
|
|
1947
|
+
"function requestUnlock(uint256 shareAmount) returns (uint256)",
|
|
1948
|
+
"function getPooledAvaxByShares(uint256 shareAmount) view returns (uint256)",
|
|
1949
|
+
"function getSharesByPooledAvax(uint256 avaxAmount) view returns (uint256)",
|
|
1950
|
+
"function totalPooledAvax() view returns (uint256)",
|
|
1951
|
+
"function totalShares() view returns (uint256)",
|
|
1952
|
+
"function balanceOf(address) view returns (uint256)",
|
|
1953
|
+
"function cooldownPeriod() view returns (uint256)",
|
|
1954
|
+
"function exchangeRate() view returns (uint256)",
|
|
1955
|
+
"function approve(address,uint256) returns (bool)"
|
|
1956
|
+
];
|
|
1957
|
+
var VAULT_ABI = [
|
|
1958
|
+
"function asset() view returns (address)",
|
|
1959
|
+
"function totalAssets() view returns (uint256)",
|
|
1960
|
+
"function totalSupply() view returns (uint256)",
|
|
1961
|
+
"function balanceOf(address) view returns (uint256)",
|
|
1962
|
+
"function convertToShares(uint256 assets) view returns (uint256)",
|
|
1963
|
+
"function convertToAssets(uint256 shares) view returns (uint256)",
|
|
1964
|
+
"function maxDeposit(address) view returns (uint256)",
|
|
1965
|
+
"function maxWithdraw(address) view returns (uint256)",
|
|
1966
|
+
"function previewDeposit(uint256 assets) view returns (uint256)",
|
|
1967
|
+
"function previewWithdraw(uint256 assets) view returns (uint256)",
|
|
1968
|
+
"function deposit(uint256 assets, address receiver) returns (uint256)",
|
|
1969
|
+
"function withdraw(uint256 assets, address receiver, address owner) returns (uint256)",
|
|
1970
|
+
"function decimals() view returns (uint8)",
|
|
1971
|
+
"function symbol() view returns (string)",
|
|
1972
|
+
"function name() view returns (string)"
|
|
1973
|
+
];
|
|
1974
|
+
var ERC20_ABI2 = [
|
|
1975
|
+
"function balanceOf(address) view returns (uint256)",
|
|
1976
|
+
"function approve(address,uint256) returns (bool)",
|
|
1977
|
+
"function allowance(address,address) view returns (uint256)",
|
|
1978
|
+
"function decimals() view returns (uint8)",
|
|
1979
|
+
"function symbol() view returns (string)"
|
|
1980
|
+
];
|
|
1981
|
+
var DefiModule = class {
|
|
1982
|
+
constructor(provider) {
|
|
1983
|
+
this.provider = provider;
|
|
1984
|
+
this.savax = new ethers7.Contract(ethers7.getAddress(SAVAX_ADDRESS), SAVAX_ABI, provider);
|
|
1985
|
+
}
|
|
1986
|
+
savax;
|
|
1987
|
+
// ── sAVAX Liquid Staking ──
|
|
1988
|
+
/** Get sAVAX staking info: exchange rate, total staked, your balance */
|
|
1989
|
+
async sAvaxInfo(wallet) {
|
|
1990
|
+
const [totalPooled, totalShares] = await Promise.all([
|
|
1991
|
+
this.savax.totalPooledAvax(),
|
|
1992
|
+
this.savax.totalShares()
|
|
1993
|
+
]);
|
|
1994
|
+
const exchangeRate = totalShares > 0n ? ethers7.formatEther(totalPooled * ethers7.parseEther("1") / totalShares) : "1.0";
|
|
1995
|
+
const result = {
|
|
1996
|
+
exchangeRate,
|
|
1997
|
+
totalPooledAvax: ethers7.formatEther(totalPooled),
|
|
1998
|
+
totalShares: ethers7.formatEther(totalShares)
|
|
1999
|
+
};
|
|
2000
|
+
if (wallet) {
|
|
2001
|
+
const balance = await this.savax.balanceOf(wallet);
|
|
2002
|
+
const avaxValue = await this.savax.getPooledAvaxByShares(balance);
|
|
2003
|
+
result.balance = ethers7.formatEther(balance);
|
|
2004
|
+
result.balanceInAvax = ethers7.formatEther(avaxValue);
|
|
2005
|
+
}
|
|
2006
|
+
return result;
|
|
2007
|
+
}
|
|
2008
|
+
/** Quote: how much sAVAX for staking AVAX */
|
|
2009
|
+
async sAvaxStakeQuote(avaxAmount) {
|
|
2010
|
+
const avaxWei = ethers7.parseEther(avaxAmount);
|
|
2011
|
+
const shares = await this.savax.getSharesByPooledAvax(avaxWei);
|
|
2012
|
+
return { avaxIn: avaxAmount, savaxOut: ethers7.formatEther(shares) };
|
|
2013
|
+
}
|
|
2014
|
+
/** Build tx to stake AVAX → sAVAX */
|
|
2015
|
+
async buildSAvaxStake(avaxAmount) {
|
|
2016
|
+
const avaxWei = ethers7.parseEther(avaxAmount);
|
|
2017
|
+
const iface = new ethers7.Interface(SAVAX_ABI);
|
|
2018
|
+
const data = iface.encodeFunctionData("submit", []);
|
|
2019
|
+
return {
|
|
2020
|
+
transactions: [{
|
|
2021
|
+
to: ethers7.getAddress(SAVAX_ADDRESS),
|
|
2022
|
+
data,
|
|
2023
|
+
value: ethers7.toBeHex(avaxWei, 32),
|
|
2024
|
+
chainId: CHAIN_ID,
|
|
2025
|
+
gasLimit: "300000",
|
|
2026
|
+
description: `Stake ${avaxAmount} AVAX \u2192 sAVAX`
|
|
2027
|
+
}]
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
/** Build tx to request unstake sAVAX → AVAX (delayed) */
|
|
2031
|
+
async buildSAvaxUnstake(wallet, amount) {
|
|
2032
|
+
let shareAmount;
|
|
2033
|
+
if (amount === "max") {
|
|
2034
|
+
shareAmount = await this.savax.balanceOf(wallet);
|
|
2035
|
+
} else {
|
|
2036
|
+
shareAmount = ethers7.parseEther(amount);
|
|
2037
|
+
}
|
|
2038
|
+
if (shareAmount === 0n) throw new Error("Zero sAVAX balance");
|
|
2039
|
+
const iface = new ethers7.Interface(SAVAX_ABI);
|
|
2040
|
+
const data = iface.encodeFunctionData("requestUnlock", [shareAmount]);
|
|
2041
|
+
return {
|
|
2042
|
+
transactions: [{
|
|
2043
|
+
to: ethers7.getAddress(SAVAX_ADDRESS),
|
|
2044
|
+
data,
|
|
2045
|
+
value: "0",
|
|
2046
|
+
chainId: CHAIN_ID,
|
|
2047
|
+
gasLimit: "300000",
|
|
2048
|
+
description: `Request unstake ${ethers7.formatEther(shareAmount)} sAVAX`
|
|
2049
|
+
}]
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
// ── ERC-4626 Vaults ──
|
|
2053
|
+
/** Get info about any ERC-4626 vault */
|
|
2054
|
+
async vaultInfo(vaultAddress, wallet) {
|
|
2055
|
+
const vault = new ethers7.Contract(ethers7.getAddress(vaultAddress), VAULT_ABI, this.provider);
|
|
2056
|
+
const [name, symbol, asset, totalAssets, totalSupply, decimals] = await Promise.all([
|
|
2057
|
+
vault.name(),
|
|
2058
|
+
vault.symbol(),
|
|
2059
|
+
vault.asset(),
|
|
2060
|
+
vault.totalAssets(),
|
|
2061
|
+
vault.totalSupply(),
|
|
2062
|
+
vault.decimals()
|
|
2063
|
+
]);
|
|
2064
|
+
const sharePrice = totalSupply > 0n ? ethers7.formatUnits(totalAssets * 10n ** BigInt(decimals) / totalSupply, decimals) : "1.0";
|
|
2065
|
+
const result = {
|
|
2066
|
+
name,
|
|
2067
|
+
symbol,
|
|
2068
|
+
asset,
|
|
2069
|
+
totalAssets: ethers7.formatUnits(totalAssets, decimals),
|
|
2070
|
+
totalSupply: ethers7.formatUnits(totalSupply, decimals),
|
|
2071
|
+
sharePrice
|
|
2072
|
+
};
|
|
2073
|
+
if (wallet) {
|
|
2074
|
+
const shares = await vault.balanceOf(wallet);
|
|
2075
|
+
const assets = shares > 0n ? await vault.convertToAssets(shares) : 0n;
|
|
2076
|
+
result.userShares = ethers7.formatUnits(shares, decimals);
|
|
2077
|
+
result.userAssets = ethers7.formatUnits(assets, decimals);
|
|
2078
|
+
}
|
|
2079
|
+
return result;
|
|
2080
|
+
}
|
|
2081
|
+
/** Quote vault deposit — how many shares for given assets */
|
|
2082
|
+
async vaultDepositQuote(vaultAddress, amount) {
|
|
2083
|
+
const vault = new ethers7.Contract(ethers7.getAddress(vaultAddress), VAULT_ABI, this.provider);
|
|
2084
|
+
const decimals = Number(await vault.decimals());
|
|
2085
|
+
const assetsWei = ethers7.parseUnits(amount, decimals);
|
|
2086
|
+
const shares = await vault.previewDeposit(assetsWei);
|
|
2087
|
+
return { assetsIn: amount, sharesOut: ethers7.formatUnits(shares, decimals) };
|
|
2088
|
+
}
|
|
2089
|
+
/** Build txs to deposit into an ERC-4626 vault: [approve, deposit] */
|
|
2090
|
+
async buildVaultDeposit(wallet, vaultAddress, amount) {
|
|
2091
|
+
const vaultAddr = ethers7.getAddress(vaultAddress);
|
|
2092
|
+
const vault = new ethers7.Contract(vaultAddr, VAULT_ABI, this.provider);
|
|
2093
|
+
const assetAddr = await vault.asset();
|
|
2094
|
+
const assetToken = new ethers7.Contract(assetAddr, ERC20_ABI2, this.provider);
|
|
2095
|
+
const decimals = Number(await assetToken.decimals());
|
|
2096
|
+
let depositAmount;
|
|
2097
|
+
if (amount === "max") {
|
|
2098
|
+
depositAmount = await assetToken.balanceOf(wallet);
|
|
2099
|
+
} else {
|
|
2100
|
+
depositAmount = ethers7.parseUnits(amount, decimals);
|
|
2101
|
+
}
|
|
2102
|
+
if (depositAmount === 0n) throw new Error("Zero balance to deposit");
|
|
2103
|
+
const erc20Iface = new ethers7.Interface(ERC20_ABI2);
|
|
2104
|
+
const approveData = erc20Iface.encodeFunctionData("approve", [vaultAddr, depositAmount]);
|
|
2105
|
+
const vaultIface = new ethers7.Interface(VAULT_ABI);
|
|
2106
|
+
const depositData = vaultIface.encodeFunctionData("deposit", [depositAmount, wallet]);
|
|
2107
|
+
return {
|
|
2108
|
+
transactions: [
|
|
2109
|
+
{ to: assetAddr, data: approveData, value: "0", chainId: CHAIN_ID, gasLimit: "60000", description: `Approve asset for vault deposit` },
|
|
2110
|
+
{ to: vaultAddr, data: depositData, value: "0", chainId: CHAIN_ID, gasLimit: "300000", description: `Deposit ${ethers7.formatUnits(depositAmount, decimals)} into vault` }
|
|
2111
|
+
]
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
/** Build tx to withdraw from an ERC-4626 vault */
|
|
2115
|
+
async buildVaultWithdraw(wallet, vaultAddress, amount) {
|
|
2116
|
+
const vaultAddr = ethers7.getAddress(vaultAddress);
|
|
2117
|
+
const vault = new ethers7.Contract(vaultAddr, VAULT_ABI, this.provider);
|
|
2118
|
+
const decimals = Number(await vault.decimals());
|
|
2119
|
+
let withdrawAmount;
|
|
2120
|
+
if (amount === "max") {
|
|
2121
|
+
const shares = await vault.balanceOf(wallet);
|
|
2122
|
+
withdrawAmount = await vault.convertToAssets(shares);
|
|
2123
|
+
} else {
|
|
2124
|
+
withdrawAmount = ethers7.parseUnits(amount, decimals);
|
|
2125
|
+
}
|
|
2126
|
+
if (withdrawAmount === 0n) throw new Error("Nothing to withdraw");
|
|
2127
|
+
const iface = new ethers7.Interface(VAULT_ABI);
|
|
2128
|
+
const data = iface.encodeFunctionData("withdraw", [withdrawAmount, wallet, wallet]);
|
|
2129
|
+
return {
|
|
2130
|
+
transactions: [{
|
|
2131
|
+
to: vaultAddr,
|
|
2132
|
+
data,
|
|
2133
|
+
value: "0",
|
|
2134
|
+
chainId: CHAIN_ID,
|
|
2135
|
+
gasLimit: "300000",
|
|
2136
|
+
description: `Withdraw ${ethers7.formatUnits(withdrawAmount, decimals)} from vault`
|
|
2137
|
+
}]
|
|
2138
|
+
};
|
|
887
2139
|
}
|
|
888
2140
|
};
|
|
889
2141
|
|
|
890
2142
|
// src/client.ts
|
|
891
|
-
var
|
|
892
|
-
var LogiqicalClient = class {
|
|
2143
|
+
var Logiqical = class _Logiqical {
|
|
893
2144
|
/** Buy/sell ARENA tokens via LFJ DEX */
|
|
894
2145
|
swap;
|
|
895
2146
|
/** Stake ARENA tokens for rewards */
|
|
896
2147
|
staking;
|
|
897
2148
|
/** Discover, research, and trade launchpad tokens on bonding curves */
|
|
898
2149
|
launchpad;
|
|
899
|
-
/** Swap any Avalanche token via LFJ
|
|
2150
|
+
/** Swap any Avalanche token via LFJ DEX */
|
|
900
2151
|
dex;
|
|
901
2152
|
/** Trade perpetual futures on Hyperliquid via Arena */
|
|
902
2153
|
perps;
|
|
903
|
-
/** Cross-chain token bridging via Li.Fi
|
|
2154
|
+
/** Cross-chain token bridging via Li.Fi */
|
|
904
2155
|
bridge;
|
|
905
|
-
/** Buy/sell Arena tickets
|
|
2156
|
+
/** Buy/sell Arena tickets */
|
|
906
2157
|
tickets;
|
|
907
2158
|
/** Arena social: chat, DMs, posts, follow, user discovery */
|
|
908
2159
|
social;
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
2160
|
+
/** Market signals intelligence — scan, funding rates, whale tracking */
|
|
2161
|
+
signals;
|
|
2162
|
+
/** Market data — prices, trending, top coins via CoinGecko */
|
|
2163
|
+
market;
|
|
2164
|
+
/** DeFi — sAVAX liquid staking + ERC-4626 vaults */
|
|
2165
|
+
defi;
|
|
2166
|
+
/** The agent's wallet address */
|
|
2167
|
+
address;
|
|
2168
|
+
/** The local wallet (null if read-only mode) */
|
|
2169
|
+
agentWallet;
|
|
2170
|
+
/** The JSON-RPC provider for direct chain reads */
|
|
2171
|
+
provider;
|
|
2172
|
+
/** The spending policy engine */
|
|
2173
|
+
policyEngine;
|
|
2174
|
+
config;
|
|
913
2175
|
constructor(config) {
|
|
914
|
-
this.
|
|
915
|
-
this.
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
2176
|
+
this.config = config;
|
|
2177
|
+
this.policyEngine = new PolicyEngine(config.policy);
|
|
2178
|
+
if (config.privateKey) {
|
|
2179
|
+
this.agentWallet = AgentWallet.fromPrivateKey(config.privateKey, {
|
|
2180
|
+
network: config.network,
|
|
2181
|
+
rpcUrl: config.rpcUrl
|
|
2182
|
+
});
|
|
2183
|
+
this.provider = this.agentWallet.provider;
|
|
2184
|
+
this.address = this.agentWallet.address;
|
|
2185
|
+
} else if (config.mnemonic) {
|
|
2186
|
+
const hdWallet = ethers8.HDNodeWallet.fromPhrase(config.mnemonic);
|
|
2187
|
+
this.agentWallet = AgentWallet.fromPrivateKey(hdWallet.privateKey, {
|
|
2188
|
+
network: config.network,
|
|
2189
|
+
rpcUrl: config.rpcUrl
|
|
2190
|
+
});
|
|
2191
|
+
this.provider = this.agentWallet.provider;
|
|
2192
|
+
this.address = this.agentWallet.address;
|
|
2193
|
+
} else if (config.wallet) {
|
|
2194
|
+
this.agentWallet = null;
|
|
2195
|
+
const { provider } = _Logiqical.resolveProvider(config);
|
|
2196
|
+
this.provider = provider;
|
|
2197
|
+
this.address = config.wallet;
|
|
2198
|
+
} else {
|
|
2199
|
+
throw new Error("Provide privateKey, mnemonic, or wallet (read-only).");
|
|
2200
|
+
}
|
|
2201
|
+
this.swap = new SwapModule(this.provider);
|
|
2202
|
+
this.staking = new StakingModule(this.provider);
|
|
2203
|
+
this.launchpad = new LaunchpadModule(this.provider);
|
|
2204
|
+
this.dex = new DexModule(this.provider);
|
|
2205
|
+
this.tickets = new TicketsModule(this.provider);
|
|
2206
|
+
this.perps = new PerpsModule(config.arenaApiKey);
|
|
2207
|
+
this.social = new SocialModule(config.arenaApiKey);
|
|
2208
|
+
this.bridge = new BridgeModule();
|
|
2209
|
+
this.signals = new SignalsModule();
|
|
2210
|
+
this.market = new MarketModule();
|
|
2211
|
+
this.defi = new DefiModule(this.provider);
|
|
2212
|
+
}
|
|
2213
|
+
// ── Factory Methods ──
|
|
2214
|
+
/** Generate a brand new agent wallet */
|
|
2215
|
+
static generate(config = {}) {
|
|
2216
|
+
const wallet = AgentWallet.generate({ network: config.network, rpcUrl: config.rpcUrl });
|
|
2217
|
+
return new _Logiqical({ ...config, privateKey: wallet.privateKey });
|
|
2218
|
+
}
|
|
2219
|
+
/** Boot from encrypted keystore — creates wallet on first run, loads on subsequent runs */
|
|
2220
|
+
static async boot(config = {}) {
|
|
2221
|
+
const wallet = await AgentWallet.boot({
|
|
2222
|
+
network: config.network,
|
|
2223
|
+
rpcUrl: config.rpcUrl,
|
|
2224
|
+
password: config.password,
|
|
2225
|
+
keystoreName: config.keystoreName
|
|
2226
|
+
});
|
|
2227
|
+
return new _Logiqical({ ...config, privateKey: wallet.privateKey });
|
|
930
2228
|
}
|
|
2229
|
+
// ── Core Agent Operations ──
|
|
931
2230
|
/**
|
|
932
|
-
*
|
|
933
|
-
*
|
|
934
|
-
*
|
|
2231
|
+
* Execute any module result — policy check + simulate + sign + broadcast.
|
|
2232
|
+
*
|
|
2233
|
+
* ```ts
|
|
2234
|
+
* await agent.execute(agent.dex.buildSwap(agent.address, "AVAX", "USDC", "1.0"));
|
|
2235
|
+
* await agent.execute(agent.launchpad.buildBuy(agent.address, "42", "0.5"));
|
|
2236
|
+
* ```
|
|
935
2237
|
*/
|
|
936
|
-
async
|
|
937
|
-
|
|
938
|
-
|
|
2238
|
+
async execute(resultOrPromise, confirmations = 1) {
|
|
2239
|
+
this.requireWallet("execute");
|
|
2240
|
+
const result = await resultOrPromise;
|
|
2241
|
+
const txs = result.transactions ?? (result.transaction ? [result.transaction] : []);
|
|
2242
|
+
if (txs.length === 0) throw new Error("No transactions to execute.");
|
|
2243
|
+
const results = [];
|
|
2244
|
+
for (const utx of txs) {
|
|
2245
|
+
this.policyEngine.check(utx);
|
|
2246
|
+
if (this.policyEngine.shouldSimulate) {
|
|
2247
|
+
await this.simulate(utx);
|
|
2248
|
+
}
|
|
2249
|
+
if (this.policyEngine.isDryRun) {
|
|
2250
|
+
results.push({
|
|
2251
|
+
hash: "0x_dry_run",
|
|
2252
|
+
receipt: { status: 1, blockNumber: 0, gasUsed: "0", transactionHash: "0x_dry_run" }
|
|
2253
|
+
});
|
|
2254
|
+
continue;
|
|
2255
|
+
}
|
|
2256
|
+
const txResponse = await this.agentWallet.signAndBroadcast(utx);
|
|
2257
|
+
const receipt = await txResponse.wait(confirmations);
|
|
2258
|
+
if (!receipt) throw new Error(`Transaction ${txResponse.hash} failed \u2014 no receipt.`);
|
|
2259
|
+
const value = utx.value ? BigInt(utx.value) : 0n;
|
|
2260
|
+
if (value > 0n) this.policyEngine.recordSpend(value);
|
|
2261
|
+
results.push({
|
|
2262
|
+
hash: txResponse.hash,
|
|
2263
|
+
receipt: {
|
|
2264
|
+
status: receipt.status ?? 0,
|
|
2265
|
+
blockNumber: receipt.blockNumber,
|
|
2266
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
2267
|
+
transactionHash: receipt.hash
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
return results;
|
|
939
2272
|
}
|
|
940
2273
|
/**
|
|
941
|
-
*
|
|
2274
|
+
* Call any smart contract method — policy check + simulate + sign + broadcast.
|
|
2275
|
+
*
|
|
2276
|
+
* ```ts
|
|
2277
|
+
* await agent.call({
|
|
2278
|
+
* contract: "0x...",
|
|
2279
|
+
* abi: ["function transfer(address,uint256) returns (bool)"],
|
|
2280
|
+
* method: "transfer",
|
|
2281
|
+
* args: ["0xRecipient", ethers.parseUnits("100", 18)],
|
|
2282
|
+
* });
|
|
2283
|
+
* ```
|
|
942
2284
|
*/
|
|
943
|
-
async
|
|
944
|
-
|
|
945
|
-
|
|
2285
|
+
async call(intent) {
|
|
2286
|
+
this.requireWallet("call");
|
|
2287
|
+
const iface = new ethers8.Interface(intent.abi);
|
|
2288
|
+
const data = iface.encodeFunctionData(intent.method, intent.args ?? []);
|
|
2289
|
+
const valueWei = intent.value ? ethers8.parseEther(intent.value).toString() : "0";
|
|
2290
|
+
const policyTx = { to: intent.contract, data, value: valueWei, chainId: 43114 };
|
|
2291
|
+
this.policyEngine.check(policyTx);
|
|
2292
|
+
if (this.policyEngine.shouldSimulate) {
|
|
2293
|
+
await this.simulate(policyTx);
|
|
2294
|
+
}
|
|
2295
|
+
if (this.policyEngine.isDryRun) {
|
|
2296
|
+
return { hash: "0x_dry_run", receipt: { status: 1, blockNumber: 0, gasUsed: "0", transactionHash: "0x_dry_run" } };
|
|
2297
|
+
}
|
|
2298
|
+
const contract = new Contract(
|
|
2299
|
+
ethers8.getAddress(intent.contract),
|
|
2300
|
+
intent.abi,
|
|
2301
|
+
this.agentWallet.wallet
|
|
2302
|
+
);
|
|
2303
|
+
const tx = await contract[intent.method](
|
|
2304
|
+
...intent.args ?? [],
|
|
2305
|
+
{
|
|
2306
|
+
value: intent.value ? ethers8.parseEther(intent.value) : void 0,
|
|
2307
|
+
gasLimit: intent.gasLimit ?? void 0
|
|
2308
|
+
}
|
|
2309
|
+
);
|
|
2310
|
+
const receipt = await tx.wait(1);
|
|
2311
|
+
if (!receipt) throw new Error(`Transaction ${tx.hash} failed \u2014 no receipt.`);
|
|
2312
|
+
if (intent.value) {
|
|
2313
|
+
this.policyEngine.recordSpend(ethers8.parseEther(intent.value));
|
|
2314
|
+
}
|
|
2315
|
+
return {
|
|
2316
|
+
hash: tx.hash,
|
|
2317
|
+
receipt: {
|
|
2318
|
+
status: receipt.status ?? 0,
|
|
2319
|
+
blockNumber: receipt.blockNumber,
|
|
2320
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
2321
|
+
transactionHash: receipt.hash
|
|
2322
|
+
}
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
/** Send native token (AVAX) — policy check + simulate + sign + broadcast */
|
|
2326
|
+
async send(to, amount) {
|
|
2327
|
+
this.requireWallet("send");
|
|
2328
|
+
const valueWei = ethers8.parseEther(amount);
|
|
2329
|
+
const policyTx = { to, data: "0x", value: valueWei.toString(), chainId: 43114 };
|
|
2330
|
+
this.policyEngine.check(policyTx);
|
|
2331
|
+
if (this.policyEngine.isDryRun) {
|
|
2332
|
+
return { hash: "0x_dry_run", receipt: { status: 1, blockNumber: 0, gasUsed: "0", transactionHash: "0x_dry_run" } };
|
|
2333
|
+
}
|
|
2334
|
+
const tx = await this.agentWallet.send(to, amount);
|
|
2335
|
+
const receipt = await tx.wait(1);
|
|
2336
|
+
if (!receipt) throw new Error(`Transaction ${tx.hash} failed \u2014 no receipt.`);
|
|
2337
|
+
this.policyEngine.recordSpend(valueWei);
|
|
2338
|
+
return {
|
|
2339
|
+
hash: tx.hash,
|
|
2340
|
+
receipt: {
|
|
2341
|
+
status: receipt.status ?? 0,
|
|
2342
|
+
blockNumber: receipt.blockNumber,
|
|
2343
|
+
gasUsed: receipt.gasUsed.toString(),
|
|
2344
|
+
transactionHash: receipt.hash
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
/** Simulate a transaction via eth_call — throws PolicyError if it would revert */
|
|
2349
|
+
async simulate(tx) {
|
|
2350
|
+
try {
|
|
2351
|
+
await this.provider.call({
|
|
2352
|
+
to: tx.to,
|
|
2353
|
+
data: tx.data,
|
|
2354
|
+
value: tx.value ? BigInt(tx.value) : void 0,
|
|
2355
|
+
from: this.address
|
|
2356
|
+
});
|
|
2357
|
+
} catch (e) {
|
|
2358
|
+
throw new PolicyError(
|
|
2359
|
+
`Simulation failed: ${e.reason || e.message || "transaction would revert"}`,
|
|
2360
|
+
"SIMULATION_FAILED"
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
946
2363
|
}
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
2364
|
+
// ── Policy ──
|
|
2365
|
+
/** Get the current spending policy */
|
|
2366
|
+
getPolicy() {
|
|
2367
|
+
return this.policyEngine.getPolicy();
|
|
2368
|
+
}
|
|
2369
|
+
/** Replace the entire spending policy */
|
|
2370
|
+
setPolicy(policy) {
|
|
2371
|
+
this.policyEngine.setPolicy(policy);
|
|
2372
|
+
}
|
|
2373
|
+
/** Update specific policy fields */
|
|
2374
|
+
updatePolicy(updates) {
|
|
2375
|
+
this.policyEngine.updatePolicy(updates);
|
|
2376
|
+
}
|
|
2377
|
+
/** Get budget status: spent this hour, today, remaining */
|
|
2378
|
+
getBudgetStatus() {
|
|
2379
|
+
return this.policyEngine.getBudgetStatus();
|
|
2380
|
+
}
|
|
2381
|
+
// ── Read Operations (no signing needed) ──
|
|
2382
|
+
/** Check if this client has a local wallet for signing */
|
|
2383
|
+
get canSign() {
|
|
2384
|
+
return this.agentWallet !== null;
|
|
2385
|
+
}
|
|
2386
|
+
/** The private key (only available in wallet mode) */
|
|
2387
|
+
get privateKey() {
|
|
2388
|
+
this.requireWallet("privateKey");
|
|
2389
|
+
return this.agentWallet.privateKey;
|
|
2390
|
+
}
|
|
2391
|
+
/** Get native token balance (formatted) */
|
|
2392
|
+
async getBalance() {
|
|
2393
|
+
if (this.agentWallet) return this.agentWallet.getBalance();
|
|
2394
|
+
const balance = await this.provider.getBalance(this.address);
|
|
2395
|
+
return ethers8.formatEther(balance);
|
|
2396
|
+
}
|
|
2397
|
+
/** Sign a message */
|
|
2398
|
+
async signMessage(message) {
|
|
2399
|
+
this.requireWallet("signMessage");
|
|
2400
|
+
return this.agentWallet.signMessage(message);
|
|
2401
|
+
}
|
|
2402
|
+
/** Sign EIP-712 typed data */
|
|
2403
|
+
async signTypedData(domain, types, value) {
|
|
2404
|
+
this.requireWallet("signTypedData");
|
|
2405
|
+
return this.agentWallet.signTypedData(domain, types, value);
|
|
2406
|
+
}
|
|
2407
|
+
/** Switch to a different network (returns new Logiqical instance with same keys) */
|
|
2408
|
+
switchNetwork(network) {
|
|
2409
|
+
this.requireWallet("switchNetwork");
|
|
2410
|
+
return new _Logiqical({
|
|
2411
|
+
...this.config,
|
|
2412
|
+
privateKey: this.agentWallet.privateKey,
|
|
2413
|
+
mnemonic: void 0,
|
|
2414
|
+
wallet: void 0,
|
|
2415
|
+
network
|
|
2416
|
+
});
|
|
952
2417
|
}
|
|
953
|
-
/**
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
2418
|
+
/** Save the wallet to an encrypted keystore file */
|
|
2419
|
+
async saveKeystore(password, name) {
|
|
2420
|
+
this.requireWallet("saveKeystore");
|
|
2421
|
+
return this.agentWallet.saveKeystore(password, name);
|
|
2422
|
+
}
|
|
2423
|
+
// ── Low-level (advanced — bypasses policy) ──
|
|
2424
|
+
/** Sign and broadcast a single unsigned tx — bypasses policy engine */
|
|
2425
|
+
async signAndBroadcast(unsignedTx) {
|
|
2426
|
+
this.requireWallet("signAndBroadcast");
|
|
2427
|
+
return this.agentWallet.signAndBroadcast(unsignedTx);
|
|
2428
|
+
}
|
|
2429
|
+
/** Sign and broadcast multiple unsigned txs — bypasses policy engine */
|
|
2430
|
+
async signAndBroadcastAll(unsignedTxs, confirmations = 1) {
|
|
2431
|
+
this.requireWallet("signAndBroadcastAll");
|
|
2432
|
+
return this.agentWallet.signAndBroadcastAll(unsignedTxs, confirmations);
|
|
2433
|
+
}
|
|
2434
|
+
// ── Internal ──
|
|
2435
|
+
requireWallet(method) {
|
|
2436
|
+
if (!this.agentWallet) throw new Error(`No local wallet \u2014 cannot ${method}(). Provide privateKey or use Logiqical.boot().`);
|
|
2437
|
+
}
|
|
2438
|
+
static resolveProvider(config) {
|
|
2439
|
+
const networkKey = config.network ?? "avalanche";
|
|
2440
|
+
const chain = CHAINS[networkKey];
|
|
2441
|
+
if (!chain && !config.rpcUrl) {
|
|
2442
|
+
throw new Error(`Unknown network "${networkKey}". Use: ${Object.keys(CHAINS).join(", ")} \u2014 or provide rpcUrl.`);
|
|
974
2443
|
}
|
|
975
|
-
|
|
2444
|
+
const rpcUrl = config.rpcUrl ?? chain.rpcUrl;
|
|
2445
|
+
const chainId = chain?.chainId ?? void 0;
|
|
2446
|
+
return { provider: new JsonRpcProvider2(rpcUrl, chainId) };
|
|
2447
|
+
}
|
|
2448
|
+
};
|
|
2449
|
+
var LogiqicalClient = Logiqical;
|
|
2450
|
+
|
|
2451
|
+
// src/errors.ts
|
|
2452
|
+
var LogiqicalError = class _LogiqicalError extends Error {
|
|
2453
|
+
constructor(message, code, cause) {
|
|
2454
|
+
super(message);
|
|
2455
|
+
this.code = code;
|
|
2456
|
+
this.cause = cause;
|
|
2457
|
+
this.name = "LogiqicalError";
|
|
2458
|
+
}
|
|
2459
|
+
static from(e, code = "UNKNOWN") {
|
|
2460
|
+
if (e instanceof _LogiqicalError) return e;
|
|
2461
|
+
const cause = e instanceof Error ? e : new Error(String(e));
|
|
2462
|
+
return new _LogiqicalError(cause.message, code, cause);
|
|
976
2463
|
}
|
|
977
2464
|
};
|
|
978
2465
|
export {
|
|
2466
|
+
AgentWallet,
|
|
979
2467
|
BridgeModule,
|
|
2468
|
+
CHAINS,
|
|
2469
|
+
DefiModule,
|
|
980
2470
|
DexModule,
|
|
981
2471
|
LaunchpadModule,
|
|
982
|
-
|
|
2472
|
+
Logiqical,
|
|
983
2473
|
LogiqicalClient,
|
|
984
2474
|
LogiqicalError,
|
|
2475
|
+
MarketModule,
|
|
985
2476
|
PerpsModule,
|
|
2477
|
+
PolicyEngine,
|
|
2478
|
+
PolicyError,
|
|
2479
|
+
SignalsModule,
|
|
986
2480
|
SocialModule,
|
|
987
2481
|
StakingModule,
|
|
988
2482
|
SwapModule,
|
|
989
2483
|
TicketsModule
|
|
990
2484
|
};
|
|
2485
|
+
//# sourceMappingURL=index.mjs.map
|