logiqical 0.3.1 → 0.5.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 +113 -25
- package/dist/admin.mjs +2946 -0
- package/dist/cli.mjs +382 -68
- package/dist/index.d.mts +120 -1
- package/dist/index.d.ts +120 -1
- package/dist/index.js +297 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +296 -68
- package/dist/index.mjs.map +1 -1
- package/dist/vault.mjs +271 -0
- package/package.json +10 -5
- package/skills/logiqical/CLAUDE.md +180 -0
- package/skills/logiqical/CODEX.md +48 -0
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import { ethers as
|
|
2
|
+
import { ethers as ethers9, JsonRpcProvider as JsonRpcProvider3, Contract as Contract2 } from "ethers";
|
|
3
3
|
|
|
4
4
|
// src/wallet.ts
|
|
5
5
|
import {
|
|
@@ -440,6 +440,10 @@ var FRACTION_SCALER = 100;
|
|
|
440
440
|
var ARENA_SOCIAL_API = "https://api.starsarena.com";
|
|
441
441
|
var LIFI_API = "https://li.quest/v1";
|
|
442
442
|
var HL_INFO = "https://api.hyperliquid.xyz/info";
|
|
443
|
+
var HL_DEPOSIT_ADDRESS = "0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7";
|
|
444
|
+
var USDC_ARBITRUM = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831";
|
|
445
|
+
var ARBITRUM_CHAIN_ID = 42161;
|
|
446
|
+
var ARBITRUM_RPC = "https://arb1.arbitrum.io/rpc";
|
|
443
447
|
var DEFAULT_SLIPPAGE_BPS = 500;
|
|
444
448
|
var ERC20_ABI = [
|
|
445
449
|
"function balanceOf(address owner) view returns (uint256)",
|
|
@@ -1136,6 +1140,7 @@ var DexModule = class {
|
|
|
1136
1140
|
};
|
|
1137
1141
|
|
|
1138
1142
|
// src/modules/perps.ts
|
|
1143
|
+
import { ethers as ethers6, JsonRpcProvider as JsonRpcProvider2, Contract } from "ethers";
|
|
1139
1144
|
var PerpsModule = class {
|
|
1140
1145
|
constructor(arenaApiKey) {
|
|
1141
1146
|
this.arenaApiKey = arenaApiKey;
|
|
@@ -1229,6 +1234,51 @@ var PerpsModule = class {
|
|
|
1229
1234
|
async getOpenOrders(wallet) {
|
|
1230
1235
|
return this.hlPost({ type: "openOrders", user: wallet });
|
|
1231
1236
|
}
|
|
1237
|
+
// ── USDC Deposit to Hyperliquid (on Arbitrum) ──
|
|
1238
|
+
/** Check USDC balance on Arbitrum */
|
|
1239
|
+
async getArbitrumUSDCBalance(wallet) {
|
|
1240
|
+
const provider = new JsonRpcProvider2(ARBITRUM_RPC, ARBITRUM_CHAIN_ID);
|
|
1241
|
+
const usdc = new Contract(USDC_ARBITRUM, ERC20_ABI, provider);
|
|
1242
|
+
const balance = await usdc.balanceOf(wallet);
|
|
1243
|
+
return ethers6.formatUnits(balance, 6);
|
|
1244
|
+
}
|
|
1245
|
+
/** Check ETH balance on Arbitrum (needed for gas) */
|
|
1246
|
+
async getArbitrumETHBalance(wallet) {
|
|
1247
|
+
const provider = new JsonRpcProvider2(ARBITRUM_RPC, ARBITRUM_CHAIN_ID);
|
|
1248
|
+
const balance = await provider.getBalance(wallet);
|
|
1249
|
+
return ethers6.formatEther(balance);
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Build a USDC deposit transaction for Hyperliquid on Arbitrum.
|
|
1253
|
+
* Returns an unsigned tx — use with agent.execute() after switching to Arbitrum network.
|
|
1254
|
+
*
|
|
1255
|
+
* Flow: agent.switchNetwork("arbitrum") → agent.execute(agent.perps.buildDepositUSDC(...))
|
|
1256
|
+
*/
|
|
1257
|
+
buildDepositUSDC(amount) {
|
|
1258
|
+
const iface = new ethers6.Interface(["function transfer(address to, uint256 amount) returns (bool)"]);
|
|
1259
|
+
const amountWei = ethers6.parseUnits(amount, 6);
|
|
1260
|
+
const data = iface.encodeFunctionData("transfer", [HL_DEPOSIT_ADDRESS, amountWei]);
|
|
1261
|
+
return {
|
|
1262
|
+
transaction: {
|
|
1263
|
+
to: USDC_ARBITRUM,
|
|
1264
|
+
data,
|
|
1265
|
+
value: "0",
|
|
1266
|
+
chainId: ARBITRUM_CHAIN_ID,
|
|
1267
|
+
description: `Deposit ${amount} USDC to Hyperliquid`
|
|
1268
|
+
}
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
/** Get deposit info (addresses + chain details) */
|
|
1272
|
+
getDepositInfo() {
|
|
1273
|
+
return {
|
|
1274
|
+
chain: "Arbitrum One",
|
|
1275
|
+
chainId: ARBITRUM_CHAIN_ID,
|
|
1276
|
+
usdcAddress: USDC_ARBITRUM,
|
|
1277
|
+
depositAddress: HL_DEPOSIT_ADDRESS,
|
|
1278
|
+
decimals: 6,
|
|
1279
|
+
tip: "Bridge USDC to Arbitrum first (use bridge module), then deposit to Hyperliquid"
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1232
1282
|
};
|
|
1233
1283
|
|
|
1234
1284
|
// src/modules/bridge.ts
|
|
@@ -1385,37 +1435,37 @@ var BridgeModule = class {
|
|
|
1385
1435
|
};
|
|
1386
1436
|
|
|
1387
1437
|
// src/modules/tickets.ts
|
|
1388
|
-
import { ethers as
|
|
1438
|
+
import { ethers as ethers7 } from "ethers";
|
|
1389
1439
|
var TicketsModule = class {
|
|
1390
1440
|
constructor(provider) {
|
|
1391
1441
|
this.provider = provider;
|
|
1392
|
-
this.contract = new
|
|
1442
|
+
this.contract = new ethers7.Contract(ethers7.getAddress(ARENA_SHARES_CONTRACT), SHARES_ABI, provider);
|
|
1393
1443
|
}
|
|
1394
1444
|
contract;
|
|
1395
1445
|
async getBuyPrice(subject, amount = "1") {
|
|
1396
1446
|
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1397
|
-
const subjectAddr =
|
|
1447
|
+
const subjectAddr = ethers7.getAddress(subject);
|
|
1398
1448
|
const [price, priceWithFee] = await Promise.all([
|
|
1399
1449
|
this.contract.getBuyPriceForFractionalShares(subjectAddr, fractionalAmount),
|
|
1400
1450
|
this.contract.getBuyPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount)
|
|
1401
1451
|
]);
|
|
1402
|
-
return { price:
|
|
1452
|
+
return { price: ethers7.formatEther(price), priceWithFee: ethers7.formatEther(priceWithFee), tickets: amount, fractionalAmount };
|
|
1403
1453
|
}
|
|
1404
1454
|
async getSellPrice(subject, amount = "1") {
|
|
1405
1455
|
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1406
|
-
const subjectAddr =
|
|
1456
|
+
const subjectAddr = ethers7.getAddress(subject);
|
|
1407
1457
|
const [price, priceAfterFee] = await Promise.all([
|
|
1408
1458
|
this.contract.getSellPriceForFractionalShares(subjectAddr, fractionalAmount),
|
|
1409
1459
|
this.contract.getSellPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount)
|
|
1410
1460
|
]);
|
|
1411
|
-
return { price:
|
|
1461
|
+
return { price: ethers7.formatEther(price), priceAfterFee: ethers7.formatEther(priceAfterFee), tickets: amount, fractionalAmount };
|
|
1412
1462
|
}
|
|
1413
1463
|
async getBalance(subject, user) {
|
|
1414
|
-
const frac = await this.contract.getMyFractionalShares(
|
|
1464
|
+
const frac = await this.contract.getMyFractionalShares(ethers7.getAddress(subject), ethers7.getAddress(user));
|
|
1415
1465
|
return { tickets: (Number(frac) / FRACTION_SCALER).toString(), fractionalAmount: frac.toString() };
|
|
1416
1466
|
}
|
|
1417
1467
|
async getSupply(subject) {
|
|
1418
|
-
const subjectAddr =
|
|
1468
|
+
const subjectAddr = ethers7.getAddress(subject);
|
|
1419
1469
|
const [wholeSupply, fracSupply] = await Promise.all([
|
|
1420
1470
|
this.contract.getSharesSupply(subjectAddr),
|
|
1421
1471
|
this.contract.getTotalFractionalSupply(subjectAddr)
|
|
@@ -1433,42 +1483,42 @@ var TicketsModule = class {
|
|
|
1433
1483
|
}
|
|
1434
1484
|
async buildBuyTx(wallet, subject, amount = "1") {
|
|
1435
1485
|
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1436
|
-
const subjectAddr =
|
|
1437
|
-
const userAddr =
|
|
1486
|
+
const subjectAddr = ethers7.getAddress(subject);
|
|
1487
|
+
const userAddr = ethers7.getAddress(wallet);
|
|
1438
1488
|
const cost = await this.contract.getBuyPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount);
|
|
1439
1489
|
if (cost === 0n) throw new Error("Could not get buy price \u2014 subject may not exist");
|
|
1440
|
-
const iface = new
|
|
1490
|
+
const iface = new ethers7.Interface(SHARES_ABI);
|
|
1441
1491
|
const data = iface.encodeFunctionData("buyFractionalShares", [subjectAddr, userAddr, fractionalAmount]);
|
|
1442
1492
|
return {
|
|
1443
1493
|
transaction: {
|
|
1444
|
-
to:
|
|
1494
|
+
to: ethers7.getAddress(ARENA_SHARES_CONTRACT),
|
|
1445
1495
|
data,
|
|
1446
|
-
value:
|
|
1496
|
+
value: ethers7.toBeHex(cost, 32),
|
|
1447
1497
|
chainId: CHAIN_ID,
|
|
1448
1498
|
gasLimit: "200000",
|
|
1449
|
-
description: `Buy ${amount} ticket(s) for ~${
|
|
1499
|
+
description: `Buy ${amount} ticket(s) for ~${ethers7.formatEther(cost)} AVAX`
|
|
1450
1500
|
}
|
|
1451
1501
|
};
|
|
1452
1502
|
}
|
|
1453
1503
|
async buildSellTx(wallet, subject, amount = "1") {
|
|
1454
1504
|
const fractionalAmount = this.ticketsToFractional(amount);
|
|
1455
|
-
const subjectAddr =
|
|
1456
|
-
const userAddr =
|
|
1505
|
+
const subjectAddr = ethers7.getAddress(subject);
|
|
1506
|
+
const userAddr = ethers7.getAddress(wallet);
|
|
1457
1507
|
const balance = await this.contract.getMyFractionalShares(subjectAddr, userAddr);
|
|
1458
1508
|
if (balance < BigInt(fractionalAmount)) {
|
|
1459
1509
|
throw new Error(`Insufficient tickets: have ${Number(balance) / FRACTION_SCALER}, trying to sell ${amount}`);
|
|
1460
1510
|
}
|
|
1461
1511
|
const proceeds = await this.contract.getSellPriceForFractionalSharesAfterFee(subjectAddr, fractionalAmount);
|
|
1462
|
-
const iface = new
|
|
1512
|
+
const iface = new ethers7.Interface(SHARES_ABI);
|
|
1463
1513
|
const data = iface.encodeFunctionData("sellFractionalShares", [subjectAddr, userAddr, fractionalAmount]);
|
|
1464
1514
|
return {
|
|
1465
1515
|
transaction: {
|
|
1466
|
-
to:
|
|
1516
|
+
to: ethers7.getAddress(ARENA_SHARES_CONTRACT),
|
|
1467
1517
|
data,
|
|
1468
1518
|
value: "0x0",
|
|
1469
1519
|
chainId: CHAIN_ID,
|
|
1470
1520
|
gasLimit: "200000",
|
|
1471
|
-
description: `Sell ${amount} ticket(s) for ~${
|
|
1521
|
+
description: `Sell ${amount} ticket(s) for ~${ethers7.formatEther(proceeds)} AVAX`
|
|
1472
1522
|
}
|
|
1473
1523
|
};
|
|
1474
1524
|
}
|
|
@@ -1487,6 +1537,53 @@ var SocialModule = class {
|
|
|
1487
1537
|
setArenaApiKey(key) {
|
|
1488
1538
|
this.arenaApiKey = key;
|
|
1489
1539
|
}
|
|
1540
|
+
// ── Agent Registration (no auth needed — returns API key) ──
|
|
1541
|
+
/**
|
|
1542
|
+
* Register a new AI agent on Arena. Returns API key (shown once — save immediately).
|
|
1543
|
+
* After registration, owner must claim by posting: "I'm claiming my AI Agent \"<name>\"\nVerification Code: <code>"
|
|
1544
|
+
*/
|
|
1545
|
+
static async registerAgent(opts) {
|
|
1546
|
+
const res = await fetch(`${ARENA_SOCIAL_API}/agents/register`, {
|
|
1547
|
+
method: "POST",
|
|
1548
|
+
headers: { "Content-Type": "application/json" },
|
|
1549
|
+
body: JSON.stringify(opts)
|
|
1550
|
+
});
|
|
1551
|
+
const data = await res.json();
|
|
1552
|
+
if (!res.ok) throw new Error(data.message || data.error || `Registration failed (${res.status})`);
|
|
1553
|
+
return data;
|
|
1554
|
+
}
|
|
1555
|
+
// ── Feed Auto-Posting (trade updates) ──
|
|
1556
|
+
/** Post a formatted trade update to the Arena feed */
|
|
1557
|
+
async postTradeUpdate(trade) {
|
|
1558
|
+
const lines = [];
|
|
1559
|
+
const emoji = {
|
|
1560
|
+
buy: "\u{1F7E2}",
|
|
1561
|
+
sell: "\u{1F534}",
|
|
1562
|
+
swap: "\u{1F504}",
|
|
1563
|
+
bridge: "\u{1F309}",
|
|
1564
|
+
stake: "\u{1F512}",
|
|
1565
|
+
long: "\u{1F4C8}",
|
|
1566
|
+
short: "\u{1F4C9}",
|
|
1567
|
+
close: "\u2705"
|
|
1568
|
+
};
|
|
1569
|
+
const icon = emoji[trade.action] || "\u{1F4B0}";
|
|
1570
|
+
if (trade.action === "swap" && trade.fromToken && trade.toToken) {
|
|
1571
|
+
lines.push(`${icon} Swapped ${trade.amount || ""} ${trade.fromToken} \u2192 ${trade.toToken}`);
|
|
1572
|
+
} else if (trade.action === "bridge" && trade.fromToken) {
|
|
1573
|
+
lines.push(`${icon} Bridged ${trade.amount || ""} ${trade.fromToken}`);
|
|
1574
|
+
} else if (trade.action === "long" || trade.action === "short") {
|
|
1575
|
+
lines.push(`${icon} Opened ${trade.action.toUpperCase()} ${trade.token || ""} ${trade.amount ? `(${trade.amount})` : ""}`);
|
|
1576
|
+
} else if (trade.action === "close") {
|
|
1577
|
+
lines.push(`${icon} Closed ${trade.token || ""} position${trade.pnl ? ` | PnL: ${trade.pnl}` : ""}`);
|
|
1578
|
+
} else {
|
|
1579
|
+
lines.push(`${icon} ${trade.action.toUpperCase()} ${trade.amount || ""} ${trade.token || ""}`);
|
|
1580
|
+
}
|
|
1581
|
+
if (trade.price) lines.push(`Price: $${trade.price}`);
|
|
1582
|
+
if (trade.hash) lines.push(`tx: ${trade.hash}`);
|
|
1583
|
+
if (trade.extra) lines.push(trade.extra);
|
|
1584
|
+
const content = lines.join("<br>");
|
|
1585
|
+
return this.createThread(content);
|
|
1586
|
+
}
|
|
1490
1587
|
async request(method, path, body, query) {
|
|
1491
1588
|
if (!this.arenaApiKey) throw new Error("Arena API key required for social endpoints. Pass arenaApiKey in config.");
|
|
1492
1589
|
let url = `${ARENA_SOCIAL_API}${path}`;
|
|
@@ -1940,7 +2037,7 @@ var MarketModule = class {
|
|
|
1940
2037
|
};
|
|
1941
2038
|
|
|
1942
2039
|
// src/modules/defi.ts
|
|
1943
|
-
import { ethers as
|
|
2040
|
+
import { ethers as ethers8 } from "ethers";
|
|
1944
2041
|
var SAVAX_ADDRESS = "0x2b2C81e08f1Af8835a78Bb2A90AE924ACE0eA4bE";
|
|
1945
2042
|
var SAVAX_ABI = [
|
|
1946
2043
|
"function submit() payable returns (uint256)",
|
|
@@ -1981,7 +2078,7 @@ var ERC20_ABI2 = [
|
|
|
1981
2078
|
var DefiModule = class {
|
|
1982
2079
|
constructor(provider) {
|
|
1983
2080
|
this.provider = provider;
|
|
1984
|
-
this.savax = new
|
|
2081
|
+
this.savax = new ethers8.Contract(ethers8.getAddress(SAVAX_ADDRESS), SAVAX_ABI, provider);
|
|
1985
2082
|
}
|
|
1986
2083
|
savax;
|
|
1987
2084
|
// ── sAVAX Liquid Staking ──
|
|
@@ -1991,36 +2088,36 @@ var DefiModule = class {
|
|
|
1991
2088
|
this.savax.totalPooledAvax(),
|
|
1992
2089
|
this.savax.totalShares()
|
|
1993
2090
|
]);
|
|
1994
|
-
const exchangeRate = totalShares > 0n ?
|
|
2091
|
+
const exchangeRate = totalShares > 0n ? ethers8.formatEther(totalPooled * ethers8.parseEther("1") / totalShares) : "1.0";
|
|
1995
2092
|
const result = {
|
|
1996
2093
|
exchangeRate,
|
|
1997
|
-
totalPooledAvax:
|
|
1998
|
-
totalShares:
|
|
2094
|
+
totalPooledAvax: ethers8.formatEther(totalPooled),
|
|
2095
|
+
totalShares: ethers8.formatEther(totalShares)
|
|
1999
2096
|
};
|
|
2000
2097
|
if (wallet) {
|
|
2001
2098
|
const balance = await this.savax.balanceOf(wallet);
|
|
2002
2099
|
const avaxValue = await this.savax.getPooledAvaxByShares(balance);
|
|
2003
|
-
result.balance =
|
|
2004
|
-
result.balanceInAvax =
|
|
2100
|
+
result.balance = ethers8.formatEther(balance);
|
|
2101
|
+
result.balanceInAvax = ethers8.formatEther(avaxValue);
|
|
2005
2102
|
}
|
|
2006
2103
|
return result;
|
|
2007
2104
|
}
|
|
2008
2105
|
/** Quote: how much sAVAX for staking AVAX */
|
|
2009
2106
|
async sAvaxStakeQuote(avaxAmount) {
|
|
2010
|
-
const avaxWei =
|
|
2107
|
+
const avaxWei = ethers8.parseEther(avaxAmount);
|
|
2011
2108
|
const shares = await this.savax.getSharesByPooledAvax(avaxWei);
|
|
2012
|
-
return { avaxIn: avaxAmount, savaxOut:
|
|
2109
|
+
return { avaxIn: avaxAmount, savaxOut: ethers8.formatEther(shares) };
|
|
2013
2110
|
}
|
|
2014
2111
|
/** Build tx to stake AVAX → sAVAX */
|
|
2015
2112
|
async buildSAvaxStake(avaxAmount) {
|
|
2016
|
-
const avaxWei =
|
|
2017
|
-
const iface = new
|
|
2113
|
+
const avaxWei = ethers8.parseEther(avaxAmount);
|
|
2114
|
+
const iface = new ethers8.Interface(SAVAX_ABI);
|
|
2018
2115
|
const data = iface.encodeFunctionData("submit", []);
|
|
2019
2116
|
return {
|
|
2020
2117
|
transactions: [{
|
|
2021
|
-
to:
|
|
2118
|
+
to: ethers8.getAddress(SAVAX_ADDRESS),
|
|
2022
2119
|
data,
|
|
2023
|
-
value:
|
|
2120
|
+
value: ethers8.toBeHex(avaxWei, 32),
|
|
2024
2121
|
chainId: CHAIN_ID,
|
|
2025
2122
|
gasLimit: "300000",
|
|
2026
2123
|
description: `Stake ${avaxAmount} AVAX \u2192 sAVAX`
|
|
@@ -2033,26 +2130,26 @@ var DefiModule = class {
|
|
|
2033
2130
|
if (amount === "max") {
|
|
2034
2131
|
shareAmount = await this.savax.balanceOf(wallet);
|
|
2035
2132
|
} else {
|
|
2036
|
-
shareAmount =
|
|
2133
|
+
shareAmount = ethers8.parseEther(amount);
|
|
2037
2134
|
}
|
|
2038
2135
|
if (shareAmount === 0n) throw new Error("Zero sAVAX balance");
|
|
2039
|
-
const iface = new
|
|
2136
|
+
const iface = new ethers8.Interface(SAVAX_ABI);
|
|
2040
2137
|
const data = iface.encodeFunctionData("requestUnlock", [shareAmount]);
|
|
2041
2138
|
return {
|
|
2042
2139
|
transactions: [{
|
|
2043
|
-
to:
|
|
2140
|
+
to: ethers8.getAddress(SAVAX_ADDRESS),
|
|
2044
2141
|
data,
|
|
2045
2142
|
value: "0",
|
|
2046
2143
|
chainId: CHAIN_ID,
|
|
2047
2144
|
gasLimit: "300000",
|
|
2048
|
-
description: `Request unstake ${
|
|
2145
|
+
description: `Request unstake ${ethers8.formatEther(shareAmount)} sAVAX`
|
|
2049
2146
|
}]
|
|
2050
2147
|
};
|
|
2051
2148
|
}
|
|
2052
2149
|
// ── ERC-4626 Vaults ──
|
|
2053
2150
|
/** Get info about any ERC-4626 vault */
|
|
2054
2151
|
async vaultInfo(vaultAddress, wallet) {
|
|
2055
|
-
const vault = new
|
|
2152
|
+
const vault = new ethers8.Contract(ethers8.getAddress(vaultAddress), VAULT_ABI, this.provider);
|
|
2056
2153
|
const [name, symbol, asset, totalAssets, totalSupply, decimals] = await Promise.all([
|
|
2057
2154
|
vault.name(),
|
|
2058
2155
|
vault.symbol(),
|
|
@@ -2061,70 +2158,70 @@ var DefiModule = class {
|
|
|
2061
2158
|
vault.totalSupply(),
|
|
2062
2159
|
vault.decimals()
|
|
2063
2160
|
]);
|
|
2064
|
-
const sharePrice = totalSupply > 0n ?
|
|
2161
|
+
const sharePrice = totalSupply > 0n ? ethers8.formatUnits(totalAssets * 10n ** BigInt(decimals) / totalSupply, decimals) : "1.0";
|
|
2065
2162
|
const result = {
|
|
2066
2163
|
name,
|
|
2067
2164
|
symbol,
|
|
2068
2165
|
asset,
|
|
2069
|
-
totalAssets:
|
|
2070
|
-
totalSupply:
|
|
2166
|
+
totalAssets: ethers8.formatUnits(totalAssets, decimals),
|
|
2167
|
+
totalSupply: ethers8.formatUnits(totalSupply, decimals),
|
|
2071
2168
|
sharePrice
|
|
2072
2169
|
};
|
|
2073
2170
|
if (wallet) {
|
|
2074
2171
|
const shares = await vault.balanceOf(wallet);
|
|
2075
2172
|
const assets = shares > 0n ? await vault.convertToAssets(shares) : 0n;
|
|
2076
|
-
result.userShares =
|
|
2077
|
-
result.userAssets =
|
|
2173
|
+
result.userShares = ethers8.formatUnits(shares, decimals);
|
|
2174
|
+
result.userAssets = ethers8.formatUnits(assets, decimals);
|
|
2078
2175
|
}
|
|
2079
2176
|
return result;
|
|
2080
2177
|
}
|
|
2081
2178
|
/** Quote vault deposit — how many shares for given assets */
|
|
2082
2179
|
async vaultDepositQuote(vaultAddress, amount) {
|
|
2083
|
-
const vault = new
|
|
2180
|
+
const vault = new ethers8.Contract(ethers8.getAddress(vaultAddress), VAULT_ABI, this.provider);
|
|
2084
2181
|
const decimals = Number(await vault.decimals());
|
|
2085
|
-
const assetsWei =
|
|
2182
|
+
const assetsWei = ethers8.parseUnits(amount, decimals);
|
|
2086
2183
|
const shares = await vault.previewDeposit(assetsWei);
|
|
2087
|
-
return { assetsIn: amount, sharesOut:
|
|
2184
|
+
return { assetsIn: amount, sharesOut: ethers8.formatUnits(shares, decimals) };
|
|
2088
2185
|
}
|
|
2089
2186
|
/** Build txs to deposit into an ERC-4626 vault: [approve, deposit] */
|
|
2090
2187
|
async buildVaultDeposit(wallet, vaultAddress, amount) {
|
|
2091
|
-
const vaultAddr =
|
|
2092
|
-
const vault = new
|
|
2188
|
+
const vaultAddr = ethers8.getAddress(vaultAddress);
|
|
2189
|
+
const vault = new ethers8.Contract(vaultAddr, VAULT_ABI, this.provider);
|
|
2093
2190
|
const assetAddr = await vault.asset();
|
|
2094
|
-
const assetToken = new
|
|
2191
|
+
const assetToken = new ethers8.Contract(assetAddr, ERC20_ABI2, this.provider);
|
|
2095
2192
|
const decimals = Number(await assetToken.decimals());
|
|
2096
2193
|
let depositAmount;
|
|
2097
2194
|
if (amount === "max") {
|
|
2098
2195
|
depositAmount = await assetToken.balanceOf(wallet);
|
|
2099
2196
|
} else {
|
|
2100
|
-
depositAmount =
|
|
2197
|
+
depositAmount = ethers8.parseUnits(amount, decimals);
|
|
2101
2198
|
}
|
|
2102
2199
|
if (depositAmount === 0n) throw new Error("Zero balance to deposit");
|
|
2103
|
-
const erc20Iface = new
|
|
2200
|
+
const erc20Iface = new ethers8.Interface(ERC20_ABI2);
|
|
2104
2201
|
const approveData = erc20Iface.encodeFunctionData("approve", [vaultAddr, depositAmount]);
|
|
2105
|
-
const vaultIface = new
|
|
2202
|
+
const vaultIface = new ethers8.Interface(VAULT_ABI);
|
|
2106
2203
|
const depositData = vaultIface.encodeFunctionData("deposit", [depositAmount, wallet]);
|
|
2107
2204
|
return {
|
|
2108
2205
|
transactions: [
|
|
2109
2206
|
{ 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 ${
|
|
2207
|
+
{ to: vaultAddr, data: depositData, value: "0", chainId: CHAIN_ID, gasLimit: "300000", description: `Deposit ${ethers8.formatUnits(depositAmount, decimals)} into vault` }
|
|
2111
2208
|
]
|
|
2112
2209
|
};
|
|
2113
2210
|
}
|
|
2114
2211
|
/** Build tx to withdraw from an ERC-4626 vault */
|
|
2115
2212
|
async buildVaultWithdraw(wallet, vaultAddress, amount) {
|
|
2116
|
-
const vaultAddr =
|
|
2117
|
-
const vault = new
|
|
2213
|
+
const vaultAddr = ethers8.getAddress(vaultAddress);
|
|
2214
|
+
const vault = new ethers8.Contract(vaultAddr, VAULT_ABI, this.provider);
|
|
2118
2215
|
const decimals = Number(await vault.decimals());
|
|
2119
2216
|
let withdrawAmount;
|
|
2120
2217
|
if (amount === "max") {
|
|
2121
2218
|
const shares = await vault.balanceOf(wallet);
|
|
2122
2219
|
withdrawAmount = await vault.convertToAssets(shares);
|
|
2123
2220
|
} else {
|
|
2124
|
-
withdrawAmount =
|
|
2221
|
+
withdrawAmount = ethers8.parseUnits(amount, decimals);
|
|
2125
2222
|
}
|
|
2126
2223
|
if (withdrawAmount === 0n) throw new Error("Nothing to withdraw");
|
|
2127
|
-
const iface = new
|
|
2224
|
+
const iface = new ethers8.Interface(VAULT_ABI);
|
|
2128
2225
|
const data = iface.encodeFunctionData("withdraw", [withdrawAmount, wallet, wallet]);
|
|
2129
2226
|
return {
|
|
2130
2227
|
transactions: [{
|
|
@@ -2133,12 +2230,139 @@ var DefiModule = class {
|
|
|
2133
2230
|
value: "0",
|
|
2134
2231
|
chainId: CHAIN_ID,
|
|
2135
2232
|
gasLimit: "300000",
|
|
2136
|
-
description: `Withdraw ${
|
|
2233
|
+
description: `Withdraw ${ethers8.formatUnits(withdrawAmount, decimals)} from vault`
|
|
2137
2234
|
}]
|
|
2138
2235
|
};
|
|
2139
2236
|
}
|
|
2140
2237
|
};
|
|
2141
2238
|
|
|
2239
|
+
// src/modules/copytrading.ts
|
|
2240
|
+
var CopyTradingModule = class {
|
|
2241
|
+
perps;
|
|
2242
|
+
constructor(perps) {
|
|
2243
|
+
this.perps = perps;
|
|
2244
|
+
}
|
|
2245
|
+
/** Fetch all open positions for a Hyperliquid wallet */
|
|
2246
|
+
async getTargetPositions(targetWallet) {
|
|
2247
|
+
const state = await this.perps.getPositions(targetWallet);
|
|
2248
|
+
const assetPositions = state?.assetPositions ?? [];
|
|
2249
|
+
return assetPositions.map((ap) => ap.position).filter((p) => p && parseFloat(p.szi) !== 0);
|
|
2250
|
+
}
|
|
2251
|
+
/** Fetch your agent's current positions */
|
|
2252
|
+
async getAgentPositions(agentWallet) {
|
|
2253
|
+
const state = await this.perps.getPositions(agentWallet);
|
|
2254
|
+
const assetPositions = state?.assetPositions ?? [];
|
|
2255
|
+
return assetPositions.map((ap) => ap.position).filter((p) => p && parseFloat(p.szi) !== 0);
|
|
2256
|
+
}
|
|
2257
|
+
/**
|
|
2258
|
+
* Compare target wallet positions with agent positions and return orders needed to mirror.
|
|
2259
|
+
* Uses proportional sizing based on the scale factor.
|
|
2260
|
+
*
|
|
2261
|
+
* @param targetWallet - Wallet to copy from
|
|
2262
|
+
* @param agentWallet - Your agent's wallet
|
|
2263
|
+
* @param scaleFactor - Position scale (0.1 = 10% of target size, 1.0 = same size)
|
|
2264
|
+
*/
|
|
2265
|
+
async calculateMirrorOrders(targetWallet, agentWallet, scaleFactor = 0.1) {
|
|
2266
|
+
const [targetPositions, agentPositions] = await Promise.all([
|
|
2267
|
+
this.getTargetPositions(targetWallet),
|
|
2268
|
+
this.getAgentPositions(agentWallet)
|
|
2269
|
+
]);
|
|
2270
|
+
const agentMap = /* @__PURE__ */ new Map();
|
|
2271
|
+
for (const p of agentPositions) {
|
|
2272
|
+
agentMap.set(p.coin, p);
|
|
2273
|
+
}
|
|
2274
|
+
const targetMap = /* @__PURE__ */ new Map();
|
|
2275
|
+
for (const p of targetPositions) {
|
|
2276
|
+
targetMap.set(p.coin, p);
|
|
2277
|
+
}
|
|
2278
|
+
const orders = [];
|
|
2279
|
+
for (const [coin, targetPos] of targetMap) {
|
|
2280
|
+
const targetSize = parseFloat(targetPos.szi);
|
|
2281
|
+
const direction = targetSize > 0 ? "long" : "short";
|
|
2282
|
+
const desiredSize = Math.abs(targetSize) * scaleFactor;
|
|
2283
|
+
const agentPos = agentMap.get(coin);
|
|
2284
|
+
if (!agentPos) {
|
|
2285
|
+
orders.push({ symbol: coin, direction, size: desiredSize, action: "open", reason: `Mirror ${coin} ${direction} from target` });
|
|
2286
|
+
} else {
|
|
2287
|
+
const agentSize = parseFloat(agentPos.szi);
|
|
2288
|
+
const agentDir = agentSize > 0 ? "long" : "short";
|
|
2289
|
+
if (agentDir !== direction) {
|
|
2290
|
+
orders.push({ symbol: coin, direction: agentDir, size: Math.abs(agentSize), action: "close", reason: `Close ${agentDir} to flip to ${direction}` });
|
|
2291
|
+
orders.push({ symbol: coin, direction, size: desiredSize, action: "open", reason: `Mirror ${coin} ${direction} from target` });
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
}
|
|
2295
|
+
for (const [coin, agentPos] of agentMap) {
|
|
2296
|
+
if (!targetMap.has(coin)) {
|
|
2297
|
+
const agentSize = parseFloat(agentPos.szi);
|
|
2298
|
+
const dir = agentSize > 0 ? "long" : "short";
|
|
2299
|
+
orders.push({ symbol: coin, direction: dir, size: Math.abs(agentSize), action: "close", reason: `Target closed ${coin} position` });
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
return { orders, targetPositions, agentPositions };
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Execute mirror orders via the perps module (Arena API).
|
|
2306
|
+
* Returns results for each order.
|
|
2307
|
+
*/
|
|
2308
|
+
async executeMirrorOrders(orders, currentPrices) {
|
|
2309
|
+
const results = [];
|
|
2310
|
+
for (const order of orders) {
|
|
2311
|
+
const price = currentPrices[order.symbol];
|
|
2312
|
+
if (!price) {
|
|
2313
|
+
results.push({ order, error: `No price for ${order.symbol}` });
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
try {
|
|
2317
|
+
if (order.action === "close") {
|
|
2318
|
+
const result = await this.perps.closePosition(order.symbol, order.direction, order.size, price);
|
|
2319
|
+
results.push({ order, result });
|
|
2320
|
+
} else {
|
|
2321
|
+
const result = await this.perps.placeOrder([{
|
|
2322
|
+
symbol: order.symbol,
|
|
2323
|
+
direction: order.direction,
|
|
2324
|
+
orderType: "market",
|
|
2325
|
+
leverageType: "cross",
|
|
2326
|
+
size: order.size,
|
|
2327
|
+
leverage: 1
|
|
2328
|
+
}]);
|
|
2329
|
+
results.push({ order, result });
|
|
2330
|
+
}
|
|
2331
|
+
} catch (e) {
|
|
2332
|
+
results.push({ order, error: e.message });
|
|
2333
|
+
}
|
|
2334
|
+
}
|
|
2335
|
+
return results;
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* One-shot copy: calculate + execute mirror orders in one call.
|
|
2339
|
+
* Fetches current prices from Hyperliquid for execution.
|
|
2340
|
+
*/
|
|
2341
|
+
async copyOnce(targetWallet, agentWallet, scaleFactor = 0.1) {
|
|
2342
|
+
const { orders } = await this.calculateMirrorOrders(targetWallet, agentWallet, scaleFactor);
|
|
2343
|
+
if (orders.length === 0) return { orders, results: [] };
|
|
2344
|
+
const pairs = await this.perps.getTradingPairs();
|
|
2345
|
+
const midPrices = await this.fetchMidPrices(orders.map((o) => o.symbol), pairs.pairs);
|
|
2346
|
+
const results = await this.executeMirrorOrders(orders, midPrices);
|
|
2347
|
+
return { orders, results };
|
|
2348
|
+
}
|
|
2349
|
+
/** Fetch mid-market prices from Hyperliquid for given symbols */
|
|
2350
|
+
async fetchMidPrices(symbols, pairs) {
|
|
2351
|
+
const res = await fetch(HL_INFO, {
|
|
2352
|
+
method: "POST",
|
|
2353
|
+
headers: { "Content-Type": "application/json" },
|
|
2354
|
+
body: JSON.stringify({ type: "allMids" })
|
|
2355
|
+
});
|
|
2356
|
+
if (!res.ok) throw new Error(`Failed to fetch mid prices: ${res.status}`);
|
|
2357
|
+
const mids = await res.json();
|
|
2358
|
+
const prices = {};
|
|
2359
|
+
for (const sym of symbols) {
|
|
2360
|
+
if (mids[sym]) prices[sym] = parseFloat(mids[sym]);
|
|
2361
|
+
}
|
|
2362
|
+
return prices;
|
|
2363
|
+
}
|
|
2364
|
+
};
|
|
2365
|
+
|
|
2142
2366
|
// src/client.ts
|
|
2143
2367
|
var Logiqical = class _Logiqical {
|
|
2144
2368
|
/** Buy/sell ARENA tokens via LFJ DEX */
|
|
@@ -2163,6 +2387,8 @@ var Logiqical = class _Logiqical {
|
|
|
2163
2387
|
market;
|
|
2164
2388
|
/** DeFi — sAVAX liquid staking + ERC-4626 vaults */
|
|
2165
2389
|
defi;
|
|
2390
|
+
/** Copy trading — mirror Hyperliquid wallet positions */
|
|
2391
|
+
copyTrading;
|
|
2166
2392
|
/** The agent's wallet address */
|
|
2167
2393
|
address;
|
|
2168
2394
|
/** The local wallet (null if read-only mode) */
|
|
@@ -2183,7 +2409,7 @@ var Logiqical = class _Logiqical {
|
|
|
2183
2409
|
this.provider = this.agentWallet.provider;
|
|
2184
2410
|
this.address = this.agentWallet.address;
|
|
2185
2411
|
} else if (config.mnemonic) {
|
|
2186
|
-
const hdWallet =
|
|
2412
|
+
const hdWallet = ethers9.HDNodeWallet.fromPhrase(config.mnemonic);
|
|
2187
2413
|
this.agentWallet = AgentWallet.fromPrivateKey(hdWallet.privateKey, {
|
|
2188
2414
|
network: config.network,
|
|
2189
2415
|
rpcUrl: config.rpcUrl
|
|
@@ -2209,6 +2435,7 @@ var Logiqical = class _Logiqical {
|
|
|
2209
2435
|
this.signals = new SignalsModule();
|
|
2210
2436
|
this.market = new MarketModule();
|
|
2211
2437
|
this.defi = new DefiModule(this.provider);
|
|
2438
|
+
this.copyTrading = new CopyTradingModule(this.perps);
|
|
2212
2439
|
}
|
|
2213
2440
|
// ── Factory Methods ──
|
|
2214
2441
|
/** Generate a brand new agent wallet */
|
|
@@ -2284,9 +2511,9 @@ var Logiqical = class _Logiqical {
|
|
|
2284
2511
|
*/
|
|
2285
2512
|
async call(intent) {
|
|
2286
2513
|
this.requireWallet("call");
|
|
2287
|
-
const iface = new
|
|
2514
|
+
const iface = new ethers9.Interface(intent.abi);
|
|
2288
2515
|
const data = iface.encodeFunctionData(intent.method, intent.args ?? []);
|
|
2289
|
-
const valueWei = intent.value ?
|
|
2516
|
+
const valueWei = intent.value ? ethers9.parseEther(intent.value).toString() : "0";
|
|
2290
2517
|
const policyTx = { to: intent.contract, data, value: valueWei, chainId: 43114 };
|
|
2291
2518
|
this.policyEngine.check(policyTx);
|
|
2292
2519
|
if (this.policyEngine.shouldSimulate) {
|
|
@@ -2295,22 +2522,22 @@ var Logiqical = class _Logiqical {
|
|
|
2295
2522
|
if (this.policyEngine.isDryRun) {
|
|
2296
2523
|
return { hash: "0x_dry_run", receipt: { status: 1, blockNumber: 0, gasUsed: "0", transactionHash: "0x_dry_run" } };
|
|
2297
2524
|
}
|
|
2298
|
-
const contract = new
|
|
2299
|
-
|
|
2525
|
+
const contract = new Contract2(
|
|
2526
|
+
ethers9.getAddress(intent.contract),
|
|
2300
2527
|
intent.abi,
|
|
2301
2528
|
this.agentWallet.wallet
|
|
2302
2529
|
);
|
|
2303
2530
|
const tx = await contract[intent.method](
|
|
2304
2531
|
...intent.args ?? [],
|
|
2305
2532
|
{
|
|
2306
|
-
value: intent.value ?
|
|
2533
|
+
value: intent.value ? ethers9.parseEther(intent.value) : void 0,
|
|
2307
2534
|
gasLimit: intent.gasLimit ?? void 0
|
|
2308
2535
|
}
|
|
2309
2536
|
);
|
|
2310
2537
|
const receipt = await tx.wait(1);
|
|
2311
2538
|
if (!receipt) throw new Error(`Transaction ${tx.hash} failed \u2014 no receipt.`);
|
|
2312
2539
|
if (intent.value) {
|
|
2313
|
-
this.policyEngine.recordSpend(
|
|
2540
|
+
this.policyEngine.recordSpend(ethers9.parseEther(intent.value));
|
|
2314
2541
|
}
|
|
2315
2542
|
return {
|
|
2316
2543
|
hash: tx.hash,
|
|
@@ -2325,7 +2552,7 @@ var Logiqical = class _Logiqical {
|
|
|
2325
2552
|
/** Send native token (AVAX) — policy check + simulate + sign + broadcast */
|
|
2326
2553
|
async send(to, amount) {
|
|
2327
2554
|
this.requireWallet("send");
|
|
2328
|
-
const valueWei =
|
|
2555
|
+
const valueWei = ethers9.parseEther(amount);
|
|
2329
2556
|
const policyTx = { to, data: "0x", value: valueWei.toString(), chainId: 43114 };
|
|
2330
2557
|
this.policyEngine.check(policyTx);
|
|
2331
2558
|
if (this.policyEngine.isDryRun) {
|
|
@@ -2392,7 +2619,7 @@ var Logiqical = class _Logiqical {
|
|
|
2392
2619
|
async getBalance() {
|
|
2393
2620
|
if (this.agentWallet) return this.agentWallet.getBalance();
|
|
2394
2621
|
const balance = await this.provider.getBalance(this.address);
|
|
2395
|
-
return
|
|
2622
|
+
return ethers9.formatEther(balance);
|
|
2396
2623
|
}
|
|
2397
2624
|
/** Sign a message */
|
|
2398
2625
|
async signMessage(message) {
|
|
@@ -2443,7 +2670,7 @@ var Logiqical = class _Logiqical {
|
|
|
2443
2670
|
}
|
|
2444
2671
|
const rpcUrl = config.rpcUrl ?? chain.rpcUrl;
|
|
2445
2672
|
const chainId = chain?.chainId ?? void 0;
|
|
2446
|
-
return { provider: new
|
|
2673
|
+
return { provider: new JsonRpcProvider3(rpcUrl, chainId) };
|
|
2447
2674
|
}
|
|
2448
2675
|
};
|
|
2449
2676
|
var LogiqicalClient = Logiqical;
|
|
@@ -2466,6 +2693,7 @@ export {
|
|
|
2466
2693
|
AgentWallet,
|
|
2467
2694
|
BridgeModule,
|
|
2468
2695
|
CHAINS,
|
|
2696
|
+
CopyTradingModule,
|
|
2469
2697
|
DefiModule,
|
|
2470
2698
|
DexModule,
|
|
2471
2699
|
LaunchpadModule,
|