@awaken-finance/agent-kit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +267 -0
- package/README.zh-CN.md +267 -0
- package/awaken_kline_skill.ts +54 -0
- package/awaken_query_skill.ts +126 -0
- package/awaken_trade_skill.ts +123 -0
- package/cli-helpers.ts +13 -0
- package/index.ts +56 -0
- package/lib/aelf-client.ts +175 -0
- package/lib/config.ts +118 -0
- package/lib/types.ts +287 -0
- package/mcp-config.example.json +18 -0
- package/openclaw.json +70 -0
- package/package.json +70 -0
- package/src/core/kline.ts +128 -0
- package/src/core/query.ts +279 -0
- package/src/core/trade.ts +278 -0
- package/src/mcp/server.ts +311 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Awaken Query Skill - CLI Adapter (thin shell)
|
|
4
|
+
// ============================================================
|
|
5
|
+
// Commands: quote, pair, balance, allowance, liquidity
|
|
6
|
+
// Core logic lives in src/core/query.ts
|
|
7
|
+
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import { getNetworkConfig } from './lib/config';
|
|
10
|
+
import { outputSuccess, outputError } from './cli-helpers';
|
|
11
|
+
import { getQuote, getPair, getTokenBalance, getTokenAllowance, getLiquidityPositions } from './src/core/query';
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('awaken-query')
|
|
17
|
+
.description('Awaken DEX read-only query tool')
|
|
18
|
+
.version('1.0.0')
|
|
19
|
+
.option('--network <network>', 'Network: mainnet or testnet', process.env.AWAKEN_NETWORK || 'mainnet');
|
|
20
|
+
|
|
21
|
+
// ---- quote ----
|
|
22
|
+
program
|
|
23
|
+
.command('quote')
|
|
24
|
+
.description('Get best swap route and price quote')
|
|
25
|
+
.requiredOption('--symbol-in <symbol>', 'Input token symbol (e.g. ELF)')
|
|
26
|
+
.requiredOption('--symbol-out <symbol>', 'Output token symbol (e.g. USDT)')
|
|
27
|
+
.option('--amount-in <amount>', 'Amount of input token (human-readable)')
|
|
28
|
+
.option('--amount-out <amount>', 'Amount of output token (human-readable)')
|
|
29
|
+
.action(async (opts) => {
|
|
30
|
+
try {
|
|
31
|
+
const config = getNetworkConfig(program.opts().network);
|
|
32
|
+
const result = await getQuote(config, {
|
|
33
|
+
symbolIn: opts.symbolIn,
|
|
34
|
+
symbolOut: opts.symbolOut,
|
|
35
|
+
amountIn: opts.amountIn,
|
|
36
|
+
amountOut: opts.amountOut,
|
|
37
|
+
});
|
|
38
|
+
outputSuccess(result);
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
outputError(err.message || 'Quote failed');
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ---- pair ----
|
|
45
|
+
program
|
|
46
|
+
.command('pair')
|
|
47
|
+
.description('Get trade pair information')
|
|
48
|
+
.requiredOption('--token0 <symbol>', 'Token 0 symbol')
|
|
49
|
+
.requiredOption('--token1 <symbol>', 'Token 1 symbol')
|
|
50
|
+
.option('--fee-rate <rate>', 'Fee rate (e.g. 0.3)', '0.3')
|
|
51
|
+
.action(async (opts) => {
|
|
52
|
+
try {
|
|
53
|
+
const config = getNetworkConfig(program.opts().network);
|
|
54
|
+
const result = await getPair(config, {
|
|
55
|
+
token0: opts.token0,
|
|
56
|
+
token1: opts.token1,
|
|
57
|
+
feeRate: opts.feeRate,
|
|
58
|
+
});
|
|
59
|
+
outputSuccess(result);
|
|
60
|
+
} catch (err: any) {
|
|
61
|
+
outputError(err.message || 'Pair query failed');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ---- balance ----
|
|
66
|
+
program
|
|
67
|
+
.command('balance')
|
|
68
|
+
.description('Query token balance for an address')
|
|
69
|
+
.requiredOption('--address <address>', 'Wallet address')
|
|
70
|
+
.requiredOption('--symbol <symbol>', 'Token symbol (e.g. ELF)')
|
|
71
|
+
.action(async (opts) => {
|
|
72
|
+
try {
|
|
73
|
+
const config = getNetworkConfig(program.opts().network);
|
|
74
|
+
const result = await getTokenBalance(config, {
|
|
75
|
+
address: opts.address,
|
|
76
|
+
symbol: opts.symbol,
|
|
77
|
+
});
|
|
78
|
+
outputSuccess(result);
|
|
79
|
+
} catch (err: any) {
|
|
80
|
+
outputError(err.message || 'Balance query failed');
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---- allowance ----
|
|
85
|
+
program
|
|
86
|
+
.command('allowance')
|
|
87
|
+
.description('Query token allowance')
|
|
88
|
+
.requiredOption('--owner <address>', 'Token owner address')
|
|
89
|
+
.requiredOption('--spender <address>', 'Spender contract address')
|
|
90
|
+
.requiredOption('--symbol <symbol>', 'Token symbol')
|
|
91
|
+
.action(async (opts) => {
|
|
92
|
+
try {
|
|
93
|
+
const config = getNetworkConfig(program.opts().network);
|
|
94
|
+
const result = await getTokenAllowance(config, {
|
|
95
|
+
owner: opts.owner,
|
|
96
|
+
spender: opts.spender,
|
|
97
|
+
symbol: opts.symbol,
|
|
98
|
+
});
|
|
99
|
+
outputSuccess(result);
|
|
100
|
+
} catch (err: any) {
|
|
101
|
+
outputError(err.message || 'Allowance query failed');
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ---- liquidity ----
|
|
106
|
+
program
|
|
107
|
+
.command('liquidity')
|
|
108
|
+
.description('Query liquidity positions for an address, including USD value')
|
|
109
|
+
.requiredOption('--address <address>', 'Wallet address')
|
|
110
|
+
.option('--token0 <symbol>', 'Filter by token0 symbol (e.g. ELF)')
|
|
111
|
+
.option('--token1 <symbol>', 'Filter by token1 symbol (e.g. USDT)')
|
|
112
|
+
.action(async (opts) => {
|
|
113
|
+
try {
|
|
114
|
+
const config = getNetworkConfig(program.opts().network);
|
|
115
|
+
const result = await getLiquidityPositions(config, {
|
|
116
|
+
address: opts.address,
|
|
117
|
+
token0: opts.token0,
|
|
118
|
+
token1: opts.token1,
|
|
119
|
+
});
|
|
120
|
+
outputSuccess(result);
|
|
121
|
+
} catch (err: any) {
|
|
122
|
+
outputError(err.message || 'Liquidity query failed');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
program.parse();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// ============================================================
|
|
3
|
+
// Awaken Trade Skill - CLI Adapter (thin shell)
|
|
4
|
+
// ============================================================
|
|
5
|
+
// Commands: swap, add-liquidity, remove-liquidity, approve
|
|
6
|
+
// Requires: AELF_PRIVATE_KEY env var
|
|
7
|
+
// Core logic lives in src/core/trade.ts
|
|
8
|
+
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import { getNetworkConfig, DEFAULT_SLIPPAGE } from './lib/config';
|
|
11
|
+
import { getWalletByPrivateKey } from './lib/aelf-client';
|
|
12
|
+
import { outputSuccess, outputError } from './cli-helpers';
|
|
13
|
+
import { executeSwap, addLiquidity, removeLiquidity, approveTokenSpending } from './src/core/trade';
|
|
14
|
+
|
|
15
|
+
const program = new Command();
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.name('awaken-trade')
|
|
19
|
+
.description('Awaken DEX trading tool (requires AELF_PRIVATE_KEY)')
|
|
20
|
+
.version('1.0.0')
|
|
21
|
+
.option('--network <network>', 'Network: mainnet or testnet', process.env.AWAKEN_NETWORK || 'mainnet');
|
|
22
|
+
|
|
23
|
+
// ---- swap ----
|
|
24
|
+
program
|
|
25
|
+
.command('swap')
|
|
26
|
+
.description('Execute a token swap')
|
|
27
|
+
.requiredOption('--symbol-in <symbol>', 'Input token symbol (e.g. ELF)')
|
|
28
|
+
.requiredOption('--symbol-out <symbol>', 'Output token symbol (e.g. USDT)')
|
|
29
|
+
.requiredOption('--amount-in <amount>', 'Amount of input token (human-readable)')
|
|
30
|
+
.option('--slippage <slippage>', 'Slippage tolerance (e.g. 0.005 = 0.5%)', DEFAULT_SLIPPAGE)
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
try {
|
|
33
|
+
const config = getNetworkConfig(program.opts().network);
|
|
34
|
+
const wallet = getWalletByPrivateKey();
|
|
35
|
+
const result = await executeSwap(config, wallet, {
|
|
36
|
+
symbolIn: opts.symbolIn,
|
|
37
|
+
symbolOut: opts.symbolOut,
|
|
38
|
+
amountIn: opts.amountIn,
|
|
39
|
+
slippage: opts.slippage,
|
|
40
|
+
});
|
|
41
|
+
outputSuccess(result);
|
|
42
|
+
} catch (err: any) {
|
|
43
|
+
outputError(err.message || 'Swap failed');
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// ---- add-liquidity ----
|
|
48
|
+
program
|
|
49
|
+
.command('add-liquidity')
|
|
50
|
+
.description('Add liquidity to a trading pair')
|
|
51
|
+
.requiredOption('--token-a <symbol>', 'Token A symbol')
|
|
52
|
+
.requiredOption('--token-b <symbol>', 'Token B symbol')
|
|
53
|
+
.requiredOption('--amount-a <amount>', 'Amount of token A (human-readable)')
|
|
54
|
+
.requiredOption('--amount-b <amount>', 'Amount of token B (human-readable)')
|
|
55
|
+
.option('--fee-rate <rate>', 'Fee rate tier (e.g. 0.3)', '0.3')
|
|
56
|
+
.option('--slippage <slippage>', 'Slippage tolerance', DEFAULT_SLIPPAGE)
|
|
57
|
+
.action(async (opts) => {
|
|
58
|
+
try {
|
|
59
|
+
const config = getNetworkConfig(program.opts().network);
|
|
60
|
+
const wallet = getWalletByPrivateKey();
|
|
61
|
+
const result = await addLiquidity(config, wallet, {
|
|
62
|
+
tokenA: opts.tokenA,
|
|
63
|
+
tokenB: opts.tokenB,
|
|
64
|
+
amountA: opts.amountA,
|
|
65
|
+
amountB: opts.amountB,
|
|
66
|
+
feeRate: opts.feeRate,
|
|
67
|
+
slippage: opts.slippage,
|
|
68
|
+
});
|
|
69
|
+
outputSuccess(result);
|
|
70
|
+
} catch (err: any) {
|
|
71
|
+
outputError(err.message || 'Add liquidity failed');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// ---- remove-liquidity ----
|
|
76
|
+
program
|
|
77
|
+
.command('remove-liquidity')
|
|
78
|
+
.description('Remove liquidity from a trading pair')
|
|
79
|
+
.requiredOption('--token-a <symbol>', 'Token A symbol')
|
|
80
|
+
.requiredOption('--token-b <symbol>', 'Token B symbol')
|
|
81
|
+
.requiredOption('--lp-amount <amount>', 'LP token amount to remove (human-readable)')
|
|
82
|
+
.option('--fee-rate <rate>', 'Fee rate tier', '0.3')
|
|
83
|
+
.option('--slippage <slippage>', 'Slippage tolerance', DEFAULT_SLIPPAGE)
|
|
84
|
+
.action(async (opts) => {
|
|
85
|
+
try {
|
|
86
|
+
const config = getNetworkConfig(program.opts().network);
|
|
87
|
+
const wallet = getWalletByPrivateKey();
|
|
88
|
+
const result = await removeLiquidity(config, wallet, {
|
|
89
|
+
tokenA: opts.tokenA,
|
|
90
|
+
tokenB: opts.tokenB,
|
|
91
|
+
lpAmount: opts.lpAmount,
|
|
92
|
+
feeRate: opts.feeRate,
|
|
93
|
+
slippage: opts.slippage,
|
|
94
|
+
});
|
|
95
|
+
outputSuccess(result);
|
|
96
|
+
} catch (err: any) {
|
|
97
|
+
outputError(err.message || 'Remove liquidity failed');
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// ---- approve ----
|
|
102
|
+
program
|
|
103
|
+
.command('approve')
|
|
104
|
+
.description('Approve token spending for a contract')
|
|
105
|
+
.requiredOption('--symbol <symbol>', 'Token symbol')
|
|
106
|
+
.requiredOption('--spender <address>', 'Spender contract address')
|
|
107
|
+
.requiredOption('--amount <amount>', 'Amount to approve (human-readable)')
|
|
108
|
+
.action(async (opts) => {
|
|
109
|
+
try {
|
|
110
|
+
const config = getNetworkConfig(program.opts().network);
|
|
111
|
+
const wallet = getWalletByPrivateKey();
|
|
112
|
+
const result = await approveTokenSpending(config, wallet, {
|
|
113
|
+
symbol: opts.symbol,
|
|
114
|
+
spender: opts.spender,
|
|
115
|
+
amount: opts.amount,
|
|
116
|
+
});
|
|
117
|
+
outputSuccess(result);
|
|
118
|
+
} catch (err: any) {
|
|
119
|
+
outputError(err.message || 'Approve failed');
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
program.parse();
|
package/cli-helpers.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CLI Output Helpers - Used ONLY by CLI adapter layer
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Core layer throws errors; CLI layer catches and formats output.
|
|
5
|
+
|
|
6
|
+
export function outputSuccess(data: any): void {
|
|
7
|
+
console.log(JSON.stringify({ status: 'success', data }, null, 2));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function outputError(message: string): never {
|
|
11
|
+
console.error(`[ERROR] ${message}`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Awaken Agent Kit - Package Entry
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Import core functions directly for use in LangChain, LlamaIndex,
|
|
5
|
+
// or any TypeScript/JavaScript project.
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// import { getQuote, executeSwap, getNetworkConfig } from '@awaken-finance/agent-kit';
|
|
9
|
+
|
|
10
|
+
// ---- Core: Query ----
|
|
11
|
+
export { getQuote, getPair, getTokenBalance, getTokenAllowance, getLiquidityPositions } from './src/core/query';
|
|
12
|
+
|
|
13
|
+
// ---- Core: Trade ----
|
|
14
|
+
export { executeSwap, addLiquidity, removeLiquidity, approveTokenSpending } from './src/core/trade';
|
|
15
|
+
|
|
16
|
+
// ---- Core: K-Line ----
|
|
17
|
+
export { fetchKline, getKlineIntervals } from './src/core/kline';
|
|
18
|
+
|
|
19
|
+
// ---- Config ----
|
|
20
|
+
export { getNetworkConfig, getRouterAddress, DEFAULT_SLIPPAGE } from './lib/config';
|
|
21
|
+
|
|
22
|
+
// ---- aelf Client Utilities ----
|
|
23
|
+
export { getWalletByPrivateKey, getTokenInfo, timesDecimals, divDecimals } from './lib/aelf-client';
|
|
24
|
+
|
|
25
|
+
// ---- Types ----
|
|
26
|
+
export type {
|
|
27
|
+
NetworkConfig,
|
|
28
|
+
TokenInfo,
|
|
29
|
+
GetQuoteParams,
|
|
30
|
+
QuoteResult,
|
|
31
|
+
SwapRouteDistribution,
|
|
32
|
+
GetPairParams,
|
|
33
|
+
TradePairItem,
|
|
34
|
+
GetBalanceParams,
|
|
35
|
+
BalanceResult,
|
|
36
|
+
GetAllowanceParams,
|
|
37
|
+
AllowanceResult,
|
|
38
|
+
LiquidityQueryParams,
|
|
39
|
+
LiquidityResult,
|
|
40
|
+
LiquidityPosition,
|
|
41
|
+
LiquidityPortfolioItem,
|
|
42
|
+
KLineBar,
|
|
43
|
+
FetchKlineParams,
|
|
44
|
+
KlineResult,
|
|
45
|
+
SwapParams,
|
|
46
|
+
SwapResult,
|
|
47
|
+
LiquidityAddParams,
|
|
48
|
+
LiquidityAddResult,
|
|
49
|
+
LiquidityRemoveParams,
|
|
50
|
+
LiquidityRemoveResult,
|
|
51
|
+
ApproveParams,
|
|
52
|
+
ApproveResult,
|
|
53
|
+
TxResult,
|
|
54
|
+
} from './lib/types';
|
|
55
|
+
|
|
56
|
+
export { KLINE_INTERVALS } from './lib/types';
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Awaken OpenClaw Skills - aelf SDK Client
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Wraps aelf-sdk for both view (read) and send (write) contract calls.
|
|
5
|
+
// For send methods, requires AELF_PRIVATE_KEY env var.
|
|
6
|
+
|
|
7
|
+
import AElf from 'aelf-sdk';
|
|
8
|
+
import BigNumber from 'bignumber.js';
|
|
9
|
+
import type { NetworkConfig, TxResult } from './types';
|
|
10
|
+
|
|
11
|
+
// ---- Caches ----
|
|
12
|
+
const aelfInstances: Record<string, any> = {};
|
|
13
|
+
const viewContracts: Record<string, any> = {};
|
|
14
|
+
|
|
15
|
+
// ---- AElf Instance ----
|
|
16
|
+
|
|
17
|
+
export function getAElfInstance(rpcUrl: string) {
|
|
18
|
+
if (!aelfInstances[rpcUrl]) {
|
|
19
|
+
aelfInstances[rpcUrl] = new AElf(new AElf.providers.HttpProvider(rpcUrl));
|
|
20
|
+
}
|
|
21
|
+
return aelfInstances[rpcUrl];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---- Wallets ----
|
|
25
|
+
|
|
26
|
+
let _viewWallet: any = null;
|
|
27
|
+
export function getViewWallet() {
|
|
28
|
+
if (!_viewWallet) _viewWallet = AElf.wallet.createNewWallet();
|
|
29
|
+
return _viewWallet;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getWalletByPrivateKey(privateKey?: string) {
|
|
33
|
+
const pk = privateKey || process.env.AELF_PRIVATE_KEY;
|
|
34
|
+
if (!pk) {
|
|
35
|
+
throw new Error('[ERROR] AELF_PRIVATE_KEY environment variable is required for send transactions.');
|
|
36
|
+
}
|
|
37
|
+
return AElf.wallet.getWalletByPrivateKey(pk);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---- View Contract ----
|
|
41
|
+
|
|
42
|
+
export async function getViewContract(rpcUrl: string, contractAddress: string) {
|
|
43
|
+
const key = `${rpcUrl}:${contractAddress}`;
|
|
44
|
+
if (!viewContracts[key]) {
|
|
45
|
+
const aelf = getAElfInstance(rpcUrl);
|
|
46
|
+
const wallet = getViewWallet();
|
|
47
|
+
viewContracts[key] = await aelf.chain.contractAt(contractAddress, wallet);
|
|
48
|
+
}
|
|
49
|
+
return viewContracts[key];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function callViewMethod(
|
|
53
|
+
rpcUrl: string,
|
|
54
|
+
contractAddress: string,
|
|
55
|
+
methodName: string,
|
|
56
|
+
args?: any,
|
|
57
|
+
): Promise<any> {
|
|
58
|
+
const contract = await getViewContract(rpcUrl, contractAddress);
|
|
59
|
+
return contract[methodName].call(args);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- Send Contract ----
|
|
63
|
+
|
|
64
|
+
export async function callSendMethod(
|
|
65
|
+
rpcUrl: string,
|
|
66
|
+
contractAddress: string,
|
|
67
|
+
methodName: string,
|
|
68
|
+
wallet: any,
|
|
69
|
+
args?: any,
|
|
70
|
+
): Promise<any> {
|
|
71
|
+
const aelf = getAElfInstance(rpcUrl);
|
|
72
|
+
const contract = await aelf.chain.contractAt(contractAddress, wallet);
|
|
73
|
+
return contract[methodName](args);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---- TX Result Polling ----
|
|
77
|
+
|
|
78
|
+
export async function getTxResult(
|
|
79
|
+
rpcUrl: string,
|
|
80
|
+
transactionId: string,
|
|
81
|
+
maxRetries = 30,
|
|
82
|
+
): Promise<TxResult> {
|
|
83
|
+
const aelf = getAElfInstance(rpcUrl);
|
|
84
|
+
|
|
85
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
86
|
+
try {
|
|
87
|
+
const result = await aelf.chain.getTxResult(transactionId);
|
|
88
|
+
const status = (result?.Status || result?.result?.Status || '').toLowerCase();
|
|
89
|
+
|
|
90
|
+
if (status === 'mined') {
|
|
91
|
+
return { transactionId, status: 'mined' };
|
|
92
|
+
}
|
|
93
|
+
if (status === 'failed' || status === 'nodevalidationfailed') {
|
|
94
|
+
const error = result?.Error || result?.result?.Error || 'Transaction failed';
|
|
95
|
+
throw new Error(`[ERROR] TX ${transactionId} failed: ${error}`);
|
|
96
|
+
}
|
|
97
|
+
// pending / notexisted - keep waiting
|
|
98
|
+
} catch (err: any) {
|
|
99
|
+
if (err.message?.startsWith('[ERROR]')) throw err;
|
|
100
|
+
// network error, retry
|
|
101
|
+
}
|
|
102
|
+
await sleep(1000);
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`[ERROR] TX ${transactionId} did not confirm after ${maxRetries}s`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- Balance & Allowance ----
|
|
108
|
+
|
|
109
|
+
export async function getBalance(
|
|
110
|
+
rpcUrl: string,
|
|
111
|
+
tokenContract: string,
|
|
112
|
+
symbol: string,
|
|
113
|
+
owner: string,
|
|
114
|
+
): Promise<string> {
|
|
115
|
+
const result = await callViewMethod(rpcUrl, tokenContract, 'GetBalance', { symbol, owner });
|
|
116
|
+
return result?.balance ?? result?.amount ?? '0';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function getAllowance(
|
|
120
|
+
rpcUrl: string,
|
|
121
|
+
tokenContract: string,
|
|
122
|
+
symbol: string,
|
|
123
|
+
owner: string,
|
|
124
|
+
spender: string,
|
|
125
|
+
): Promise<string> {
|
|
126
|
+
const result = await callViewMethod(rpcUrl, tokenContract, 'GetAllowance', { symbol, owner, spender });
|
|
127
|
+
return result?.allowance ?? '0';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function getTokenInfo(
|
|
131
|
+
rpcUrl: string,
|
|
132
|
+
tokenContract: string,
|
|
133
|
+
symbol: string,
|
|
134
|
+
): Promise<{ symbol: string; decimals: number }> {
|
|
135
|
+
const result = await callViewMethod(rpcUrl, tokenContract, 'GetTokenInfo', { symbol });
|
|
136
|
+
return {
|
|
137
|
+
symbol: result?.symbol ?? symbol,
|
|
138
|
+
decimals: result?.decimals ?? 8,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- Approve ----
|
|
143
|
+
|
|
144
|
+
export async function approveToken(
|
|
145
|
+
config: NetworkConfig,
|
|
146
|
+
wallet: any,
|
|
147
|
+
symbol: string,
|
|
148
|
+
spender: string,
|
|
149
|
+
amount: string,
|
|
150
|
+
): Promise<TxResult> {
|
|
151
|
+
const result = await callSendMethod(config.rpcUrl, config.tokenContract, 'Approve', wallet, {
|
|
152
|
+
symbol,
|
|
153
|
+
spender,
|
|
154
|
+
amount,
|
|
155
|
+
});
|
|
156
|
+
const txId = result?.TransactionId || result?.transactionId;
|
|
157
|
+
if (!txId) throw new Error('[ERROR] Approve failed: no transactionId returned');
|
|
158
|
+
return getTxResult(config.rpcUrl, txId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---- Utility ----
|
|
162
|
+
|
|
163
|
+
export function timesDecimals(value: string | number, decimals: number): BigNumber {
|
|
164
|
+
return new BigNumber(value).times(new BigNumber(10).pow(decimals));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function divDecimals(value: string | number, decimals: number): BigNumber {
|
|
168
|
+
return new BigNumber(value).div(new BigNumber(10).pow(decimals));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function sleep(ms: number) {
|
|
172
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Note: outputSuccess/outputError moved to cli-helpers.ts (CLI adapter layer only)
|
package/lib/config.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Awaken Agent Kit - Network Configuration
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Config priority (high → low):
|
|
5
|
+
// 1. Function params (SDK callers pass config directly)
|
|
6
|
+
// 2. CLI args (--network, --slippage)
|
|
7
|
+
// 3. MCP env block (mcp.json env: {})
|
|
8
|
+
// 4. Environment variables (AWAKEN_*)
|
|
9
|
+
// 5. .env file (Bun auto-loads)
|
|
10
|
+
// 6. Code defaults (below)
|
|
11
|
+
|
|
12
|
+
import type { NetworkConfig } from './types';
|
|
13
|
+
|
|
14
|
+
// ---- Built-in defaults per network ----
|
|
15
|
+
|
|
16
|
+
const MAINNET_DEFAULTS: NetworkConfig = {
|
|
17
|
+
chainId: 'tDVV',
|
|
18
|
+
rpcUrl: 'https://tdvv-public-node.aelf.io',
|
|
19
|
+
apiBaseUrl: 'https://app.awaken.finance',
|
|
20
|
+
socketUrl: 'https://app.awaken.finance/signalr-hubs/trade',
|
|
21
|
+
explorerUrl: 'https://aelfscan.io/tDVV',
|
|
22
|
+
tokenContract: '7RzVGiuVWkvL4VfVHdZfQF2Tri3sgLe9U991bohHFfSRZXuGX',
|
|
23
|
+
swapHookContract: 'T3mdFC35CQSatUXQ5bQ886pULo2TnzS9rfXxmsoZSGnTq2a2S',
|
|
24
|
+
router: {
|
|
25
|
+
'0.05': '83ju3fGGnvQzCmtjApUTwvBpuLQLQvt5biNMv4FXCvWKdZgJf',
|
|
26
|
+
'0.3': 'JvDB3rguLJtpFsovre8udJeXJLhsV1EPScGz2u1FFneahjBQm',
|
|
27
|
+
'0.1': 'hyiwdsbDnyoG1uZiw2JabQ4tLiWT6yAuDfNBFbHhCZwAqU1os',
|
|
28
|
+
'3': '2q7NLAr6eqF4CTsnNeXnBZ9k4XcmiUeM61CLWYaym6WsUmbg1k',
|
|
29
|
+
'5': 'UYdd84gLMsVdHrgkr3ogqe1ukhKwen8oj32Ks4J1dg6KH9PYC',
|
|
30
|
+
},
|
|
31
|
+
factory: {
|
|
32
|
+
'0.05': '2b7Gf7YqVmjhZXir7uehmZoRwsYo1KNFTo9JDZiiByxPBQS1d8',
|
|
33
|
+
'0.3': '2AJXAXSwyHbKTHQhKFiaYozakUUQDeh3xrHW9FGi3vYDMBjtiS',
|
|
34
|
+
'0.1': '25CkLPA8qwDRGQci2kFg77i6pZXVivvX4DHW78i1B7rPHdBkoK',
|
|
35
|
+
'3': '2PwfVguYDmYcpJVPmoH9doEpBgd8L28NCcUDiuq77CzvEWzuKZ',
|
|
36
|
+
'5': '2eJ4MnRWFo7YJXB92qj2AF3NWoB3umBggzNLhbGeahkwDYYLAD',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const TESTNET_DEFAULTS: NetworkConfig = {
|
|
41
|
+
chainId: 'tDVW',
|
|
42
|
+
rpcUrl: 'https://tdvw-test-node.aelf.io',
|
|
43
|
+
apiBaseUrl: 'https://test-app.awaken.finance',
|
|
44
|
+
socketUrl: 'https://test-app.awaken.finance/signalr-hubs/trade',
|
|
45
|
+
explorerUrl: 'https://testnet.aelfscan.io/tDVW',
|
|
46
|
+
tokenContract: 'ASh2Wt7nSEmYqnGxPPzp4pnVDU4uhj1XW9Se5VeZcX2UDdyjx',
|
|
47
|
+
swapHookContract: '2vahJs5WeWVJruzd1DuTAu3TwK8jktpJ2NNeALJJWEbPQCUW4Y',
|
|
48
|
+
router: {
|
|
49
|
+
'0.05': 'fGa81UPViGsVvTM13zuAAwk1QHovL3oSqTrCznitS4hAawPpk',
|
|
50
|
+
'0.3': '2YnkipJ9mty5r6tpTWQAwnomeeKUT7qCWLHKaSeV1fejYEyCdX',
|
|
51
|
+
'0.1': 'LzkrbEK2zweeuE4P8Y23BMiFY2oiKMWyHuy5hBBbF1pAPD2hh',
|
|
52
|
+
'3': 'EG73zzQqC8JencoFEgCtrEUvMBS2zT22xoRse72XkyhuuhyTC',
|
|
53
|
+
'5': '23dh2s1mXnswi4yNW7eWNKWy7iac8KrXJYitECgUctgfwjeZwP',
|
|
54
|
+
},
|
|
55
|
+
factory: {
|
|
56
|
+
'0.05': 'pVHzzPLV8U3XEAb3utFPnuFL7p6AZtxemgX1yX4tCvKQDQNud',
|
|
57
|
+
'0.3': '2L8uLZRJDUNdmeoA7RT6QbB7TZvu2xHra2gTz2bGrv9Wxs7KPS',
|
|
58
|
+
'0.1': '5KN5uqSC1vz521Lpfh9H1ZLWpU96x6ypEdHrTZF8WdjMmQFQ5',
|
|
59
|
+
'3': '2iFrdeaSKHwpNGWviSMVacjHjdgtZbfrkNeoV1opRzsfBrPVsm',
|
|
60
|
+
'5': 'T25QvHLdWsyHaAeLKu9hvk33MTZrkWD1M7D4cZyU58JfPwhTh',
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const NETWORK_DEFAULTS: Record<string, NetworkConfig> = {
|
|
65
|
+
mainnet: MAINNET_DEFAULTS,
|
|
66
|
+
testnet: TESTNET_DEFAULTS,
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// ---- Env override layer ----
|
|
70
|
+
// Any AWAKEN_* env var overrides the corresponding field in the selected network config.
|
|
71
|
+
|
|
72
|
+
function applyEnvOverrides(config: NetworkConfig): NetworkConfig {
|
|
73
|
+
return {
|
|
74
|
+
...config,
|
|
75
|
+
rpcUrl: process.env.AWAKEN_RPC_URL || config.rpcUrl,
|
|
76
|
+
apiBaseUrl: process.env.AWAKEN_API_BASE_URL || config.apiBaseUrl,
|
|
77
|
+
socketUrl: process.env.AWAKEN_SOCKET_URL || config.socketUrl,
|
|
78
|
+
explorerUrl: process.env.AWAKEN_EXPLORER_URL || config.explorerUrl,
|
|
79
|
+
tokenContract: process.env.AWAKEN_TOKEN_CONTRACT || config.tokenContract,
|
|
80
|
+
swapHookContract: process.env.AWAKEN_SWAP_HOOK_CONTRACT || config.swapHookContract,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---- Public API ----
|
|
85
|
+
|
|
86
|
+
export function getNetworkConfig(override?: string): NetworkConfig {
|
|
87
|
+
const network = override || process.env.AWAKEN_NETWORK || 'mainnet';
|
|
88
|
+
const defaults = NETWORK_DEFAULTS[network];
|
|
89
|
+
if (!defaults) {
|
|
90
|
+
throw new Error(`[ERROR] Unknown network: ${network}. Use "mainnet" or "testnet".`);
|
|
91
|
+
}
|
|
92
|
+
return applyEnvOverrides(defaults);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function getRouterAddress(config: NetworkConfig, feeRate: string): string {
|
|
96
|
+
const addr = config.router[feeRate];
|
|
97
|
+
if (!addr) {
|
|
98
|
+
throw new Error(`[ERROR] No router for feeRate=${feeRate}. Available: ${Object.keys(config.router).join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
return addr;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Default slippage tolerance (env: AWAKEN_DEFAULT_SLIPPAGE) */
|
|
104
|
+
export const DEFAULT_SLIPPAGE = process.env.AWAKEN_DEFAULT_SLIPPAGE || '0.005';
|
|
105
|
+
|
|
106
|
+
/** Default deadline: 20 minutes from now */
|
|
107
|
+
export function getDeadline(minutes = 20): number {
|
|
108
|
+
return Math.floor(Date.now() / 1000) + minutes * 60;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Channel ID */
|
|
112
|
+
export const CHANNEL_ID = '';
|
|
113
|
+
|
|
114
|
+
/** Labs fee rate for swap (basis points) */
|
|
115
|
+
export const SWAP_LABS_FEE_RATE = 15;
|
|
116
|
+
|
|
117
|
+
/** Safety cap for E2E tests: max amount in base units */
|
|
118
|
+
export const E2E_MAX_AMOUNT = '1000000'; // 0.01 ELF (8 decimals)
|