@four-meme/four-meme-ai 1.0.7 → 1.0.8
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/CLAUDE.md +91 -91
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/bin/fourmeme.cjs +7 -4
- package/package.json +1 -1
- package/skills/four-meme-integration/SKILL.md +11 -8
- package/skills/four-meme-integration/references/token-query-api.md +28 -24
- package/skills/four-meme-integration/scripts/8004-balance.ts +52 -52
- package/skills/four-meme-integration/scripts/8004-register.ts +108 -108
- package/skills/four-meme-integration/scripts/execute-buy.ts +198 -198
- package/skills/four-meme-integration/scripts/execute-sell.ts +150 -150
- package/skills/four-meme-integration/scripts/send-token.ts +98 -98
- package/skills/four-meme-integration/scripts/token-list.ts +102 -20
- package/skills/four-meme-integration/scripts/token-rankings.ts +122 -14
|
@@ -1,108 +1,108 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* EIP-8004 NFT – register agent (mint identity NFT).
|
|
4
|
-
* Builds agentURI as data:application/json;base64,<payload> and calls contract.register(agentURI).
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx tsx 8004-register.ts <name> [imageUrl] [description]
|
|
8
|
-
* - name: required
|
|
9
|
-
* - imageUrl: optional (URL string)
|
|
10
|
-
* - description: optional
|
|
11
|
-
*
|
|
12
|
-
* Env: PRIVATE_KEY. Optional: BSC_RPC_URL, 8004_NFT_ADDRESS.
|
|
13
|
-
* Default contract: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 (BSC).
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { createPublicClient, createWalletClient, decodeEventLog, http, parseAbi } from 'viem';
|
|
17
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
18
|
-
import { bsc } from 'viem/chains';
|
|
19
|
-
|
|
20
|
-
const DEFAULT_8004_NFT = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432' as const;
|
|
21
|
-
|
|
22
|
-
const REGISTRATION_TYPE = 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1';
|
|
23
|
-
|
|
24
|
-
const ABI = parseAbi([
|
|
25
|
-
'function register(string agentURI) returns (uint256 agentId)',
|
|
26
|
-
'event Registered(uint256 indexed agentId, string agentURI, address indexed owner)',
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
function buildAgentURI(name: string, imageUrl: string, description: string): string {
|
|
30
|
-
const payload = {
|
|
31
|
-
type: REGISTRATION_TYPE,
|
|
32
|
-
name: name || '',
|
|
33
|
-
description: description || 'I\'m four.meme trading agent',
|
|
34
|
-
image: imageUrl || '',
|
|
35
|
-
active: true,
|
|
36
|
-
supportedTrust: [''],
|
|
37
|
-
};
|
|
38
|
-
const json = JSON.stringify(payload);
|
|
39
|
-
const base64 = Buffer.from(json, 'utf8').toString('base64');
|
|
40
|
-
return `data:application/json;base64,${base64}`;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function main() {
|
|
44
|
-
const privateKey = process.env.PRIVATE_KEY;
|
|
45
|
-
if (!privateKey) {
|
|
46
|
-
console.error('Set PRIVATE_KEY');
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
|
|
50
|
-
const account = privateKeyToAccount(pk);
|
|
51
|
-
|
|
52
|
-
const name = process.argv[2];
|
|
53
|
-
const imageUrl = process.argv[3] ?? '';
|
|
54
|
-
const description = process.argv[4] ?? '';
|
|
55
|
-
|
|
56
|
-
if (!name || name.trim() === '') {
|
|
57
|
-
console.error('Usage: 8004-register.ts <name> [imageUrl] [description]');
|
|
58
|
-
console.error(' name: required. imageUrl and description are optional.');
|
|
59
|
-
process.exit(1);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const agentURI = buildAgentURI(name.trim(), imageUrl.trim(), description.trim());
|
|
63
|
-
const contractAddress = (process.env['8004_NFT_ADDRESS'] || process.env.EIP8004_NFT_ADDRESS || DEFAULT_8004_NFT) as `0x${string}`;
|
|
64
|
-
const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
65
|
-
|
|
66
|
-
const wallet = createWalletClient({
|
|
67
|
-
account,
|
|
68
|
-
chain: bsc,
|
|
69
|
-
transport: http(rpcUrl),
|
|
70
|
-
});
|
|
71
|
-
const publicClient = createPublicClient({
|
|
72
|
-
chain: bsc,
|
|
73
|
-
transport: http(rpcUrl),
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const txHash = await wallet.writeContract({
|
|
77
|
-
address: contractAddress,
|
|
78
|
-
abi: ABI,
|
|
79
|
-
functionName: 'register',
|
|
80
|
-
args: [agentURI],
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
84
|
-
let agentId: number | null = null;
|
|
85
|
-
for (const l of receipt.logs) {
|
|
86
|
-
if (l.address.toLowerCase() !== contractAddress.toLowerCase()) continue;
|
|
87
|
-
try {
|
|
88
|
-
const d = decodeEventLog({
|
|
89
|
-
abi: ABI,
|
|
90
|
-
data: l.data,
|
|
91
|
-
topics: l.topics,
|
|
92
|
-
});
|
|
93
|
-
if (d.eventName === 'Registered') {
|
|
94
|
-
agentId = Number((d.args as { agentId: bigint }).agentId);
|
|
95
|
-
break;
|
|
96
|
-
}
|
|
97
|
-
} catch {
|
|
98
|
-
/* ignore */
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log(JSON.stringify({ txHash, agentId, agentURI }, null, 2));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
main().catch((e) => {
|
|
106
|
-
console.error(e.message || e);
|
|
107
|
-
process.exit(1);
|
|
108
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* EIP-8004 NFT – register agent (mint identity NFT).
|
|
4
|
+
* Builds agentURI as data:application/json;base64,<payload> and calls contract.register(agentURI).
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx tsx 8004-register.ts <name> [imageUrl] [description]
|
|
8
|
+
* - name: required
|
|
9
|
+
* - imageUrl: optional (URL string)
|
|
10
|
+
* - description: optional
|
|
11
|
+
*
|
|
12
|
+
* Env: PRIVATE_KEY. Optional: BSC_RPC_URL, 8004_NFT_ADDRESS.
|
|
13
|
+
* Default contract: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 (BSC).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createPublicClient, createWalletClient, decodeEventLog, http, parseAbi } from 'viem';
|
|
17
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
18
|
+
import { bsc } from 'viem/chains';
|
|
19
|
+
|
|
20
|
+
const DEFAULT_8004_NFT = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432' as const;
|
|
21
|
+
|
|
22
|
+
const REGISTRATION_TYPE = 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1';
|
|
23
|
+
|
|
24
|
+
const ABI = parseAbi([
|
|
25
|
+
'function register(string agentURI) returns (uint256 agentId)',
|
|
26
|
+
'event Registered(uint256 indexed agentId, string agentURI, address indexed owner)',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
function buildAgentURI(name: string, imageUrl: string, description: string): string {
|
|
30
|
+
const payload = {
|
|
31
|
+
type: REGISTRATION_TYPE,
|
|
32
|
+
name: name || '',
|
|
33
|
+
description: description || 'I\'m four.meme trading agent',
|
|
34
|
+
image: imageUrl || '',
|
|
35
|
+
active: true,
|
|
36
|
+
supportedTrust: [''],
|
|
37
|
+
};
|
|
38
|
+
const json = JSON.stringify(payload);
|
|
39
|
+
const base64 = Buffer.from(json, 'utf8').toString('base64');
|
|
40
|
+
return `data:application/json;base64,${base64}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
45
|
+
if (!privateKey) {
|
|
46
|
+
console.error('Set PRIVATE_KEY');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
|
|
50
|
+
const account = privateKeyToAccount(pk);
|
|
51
|
+
|
|
52
|
+
const name = process.argv[2];
|
|
53
|
+
const imageUrl = process.argv[3] ?? '';
|
|
54
|
+
const description = process.argv[4] ?? '';
|
|
55
|
+
|
|
56
|
+
if (!name || name.trim() === '') {
|
|
57
|
+
console.error('Usage: 8004-register.ts <name> [imageUrl] [description]');
|
|
58
|
+
console.error(' name: required. imageUrl and description are optional.');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const agentURI = buildAgentURI(name.trim(), imageUrl.trim(), description.trim());
|
|
63
|
+
const contractAddress = (process.env['8004_NFT_ADDRESS'] || process.env.EIP8004_NFT_ADDRESS || DEFAULT_8004_NFT) as `0x${string}`;
|
|
64
|
+
const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
65
|
+
|
|
66
|
+
const wallet = createWalletClient({
|
|
67
|
+
account,
|
|
68
|
+
chain: bsc,
|
|
69
|
+
transport: http(rpcUrl),
|
|
70
|
+
});
|
|
71
|
+
const publicClient = createPublicClient({
|
|
72
|
+
chain: bsc,
|
|
73
|
+
transport: http(rpcUrl),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const txHash = await wallet.writeContract({
|
|
77
|
+
address: contractAddress,
|
|
78
|
+
abi: ABI,
|
|
79
|
+
functionName: 'register',
|
|
80
|
+
args: [agentURI],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
84
|
+
let agentId: number | null = null;
|
|
85
|
+
for (const l of receipt.logs) {
|
|
86
|
+
if (l.address.toLowerCase() !== contractAddress.toLowerCase()) continue;
|
|
87
|
+
try {
|
|
88
|
+
const d = decodeEventLog({
|
|
89
|
+
abi: ABI,
|
|
90
|
+
data: l.data,
|
|
91
|
+
topics: l.topics,
|
|
92
|
+
});
|
|
93
|
+
if (d.eventName === 'Registered') {
|
|
94
|
+
agentId = Number((d.args as { agentId: bigint }).agentId);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
/* ignore */
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(JSON.stringify({ txHash, agentId, agentURI }, null, 2));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
main().catch((e) => {
|
|
106
|
+
console.error(e.message || e);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Four.meme - execute buy on TokenManager2 (BSC only).
|
|
4
|
-
* Usage:
|
|
5
|
-
* npx tsx execute-buy.ts <tokenAddress> amount <amountWei> <maxFundsWei> # buy fixed token amount
|
|
6
|
-
* npx tsx execute-buy.ts <tokenAddress> funds <fundsWei> <minAmountWei> # spend fixed quote (e.g. BNB)
|
|
7
|
-
* Env: PRIVATE_KEY. Only V2 tokens (version 2 from getTokenInfo).
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
11
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
-
import { bsc } from 'viem/chains';
|
|
13
|
-
|
|
14
|
-
const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;
|
|
15
|
-
const ZERO = '0x0000000000000000000000000000000000000000' as const;
|
|
16
|
-
|
|
17
|
-
const HELPER_ABI = [
|
|
18
|
-
{
|
|
19
|
-
name: 'getTokenInfo',
|
|
20
|
-
type: 'function',
|
|
21
|
-
stateMutability: 'view',
|
|
22
|
-
inputs: [{ name: 'token', type: 'address' }],
|
|
23
|
-
outputs: [
|
|
24
|
-
{ name: 'version', type: 'uint256' },
|
|
25
|
-
{ name: 'tokenManager', type: 'address' },
|
|
26
|
-
{ name: 'quote', type: 'address' },
|
|
27
|
-
{ name: 'lastPrice', type: 'uint256' },
|
|
28
|
-
{ name: 'tradingFeeRate', type: 'uint256' },
|
|
29
|
-
{ name: 'minTradingFee', type: 'uint256' },
|
|
30
|
-
{ name: 'launchTime', type: 'uint256' },
|
|
31
|
-
{ name: 'offers', type: 'uint256' },
|
|
32
|
-
{ name: 'maxOffers', type: 'uint256' },
|
|
33
|
-
{ name: 'funds', type: 'uint256' },
|
|
34
|
-
{ name: 'maxFunds', type: 'uint256' },
|
|
35
|
-
{ name: 'liquidityAdded', type: 'bool' },
|
|
36
|
-
],
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: 'tryBuy',
|
|
40
|
-
type: 'function',
|
|
41
|
-
stateMutability: 'view',
|
|
42
|
-
inputs: [
|
|
43
|
-
{ name: 'token', type: 'address' },
|
|
44
|
-
{ name: 'amount', type: 'uint256' },
|
|
45
|
-
{ name: 'funds', type: 'uint256' },
|
|
46
|
-
],
|
|
47
|
-
outputs: [
|
|
48
|
-
{ name: 'tokenManager', type: 'address' },
|
|
49
|
-
{ name: 'quote', type: 'address' },
|
|
50
|
-
{ name: 'estimatedAmount', type: 'uint256' },
|
|
51
|
-
{ name: 'estimatedCost', type: 'uint256' },
|
|
52
|
-
{ name: 'estimatedFee', type: 'uint256' },
|
|
53
|
-
{ name: 'amountMsgValue', type: 'uint256' },
|
|
54
|
-
{ name: 'amountApproval', type: 'uint256' },
|
|
55
|
-
{ name: 'amountFunds', type: 'uint256' },
|
|
56
|
-
],
|
|
57
|
-
},
|
|
58
|
-
] as const;
|
|
59
|
-
|
|
60
|
-
const TM2_ABI = [
|
|
61
|
-
{
|
|
62
|
-
name: 'buyToken',
|
|
63
|
-
type: 'function',
|
|
64
|
-
stateMutability: 'payable',
|
|
65
|
-
inputs: [
|
|
66
|
-
{ name: 'token', type: 'address' },
|
|
67
|
-
{ name: 'amount', type: 'uint256' },
|
|
68
|
-
{ name: 'maxFunds', type: 'uint256' },
|
|
69
|
-
],
|
|
70
|
-
outputs: [],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'buyTokenAMAP',
|
|
74
|
-
type: 'function',
|
|
75
|
-
stateMutability: 'payable',
|
|
76
|
-
inputs: [
|
|
77
|
-
{ name: 'token', type: 'address' },
|
|
78
|
-
{ name: 'funds', type: 'uint256' },
|
|
79
|
-
{ name: 'minAmount', type: 'uint256' },
|
|
80
|
-
],
|
|
81
|
-
outputs: [],
|
|
82
|
-
},
|
|
83
|
-
] as const;
|
|
84
|
-
|
|
85
|
-
const ERC20_ABI = [
|
|
86
|
-
{
|
|
87
|
-
name: 'approve',
|
|
88
|
-
type: 'function',
|
|
89
|
-
stateMutability: 'nonpayable',
|
|
90
|
-
inputs: [
|
|
91
|
-
{ name: 'spender', type: 'address' },
|
|
92
|
-
{ name: 'amount', type: 'uint256' },
|
|
93
|
-
],
|
|
94
|
-
outputs: [{ type: 'bool' }],
|
|
95
|
-
},
|
|
96
|
-
] as const;
|
|
97
|
-
|
|
98
|
-
const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
99
|
-
|
|
100
|
-
async function main() {
|
|
101
|
-
const pk = process.env.PRIVATE_KEY;
|
|
102
|
-
if (!pk) {
|
|
103
|
-
console.error('Set PRIVATE_KEY');
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
const account = privateKeyToAccount(
|
|
107
|
-
(pk.startsWith('0x') ? pk : '0x' + pk) as `0x${string}`
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
const tokenAddress = process.argv[2] as `0x${string}`;
|
|
111
|
-
const mode = process.argv[3]; // 'amount' | 'funds'
|
|
112
|
-
const arg1 = process.argv[4];
|
|
113
|
-
const arg2 = process.argv[5];
|
|
114
|
-
|
|
115
|
-
if (!tokenAddress || !mode || !arg1 || !arg2) {
|
|
116
|
-
console.error('Usage:');
|
|
117
|
-
console.error(' execute-buy.ts <token> amount <amountWei> <maxFundsWei>');
|
|
118
|
-
console.error(' execute-buy.ts <token> funds <fundsWei> <minAmountWei>');
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const publicClient = createPublicClient({
|
|
123
|
-
chain: bsc,
|
|
124
|
-
transport: http(RPC_URL),
|
|
125
|
-
});
|
|
126
|
-
const walletClient = createWalletClient({
|
|
127
|
-
account,
|
|
128
|
-
chain: bsc,
|
|
129
|
-
transport: http(RPC_URL),
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const [version, tokenManager, quote] = await publicClient.readContract({
|
|
133
|
-
address: HELPER_ADDRESS,
|
|
134
|
-
abi: HELPER_ABI,
|
|
135
|
-
functionName: 'getTokenInfo',
|
|
136
|
-
args: [tokenAddress],
|
|
137
|
-
}).then((r) => [r[0], r[1], r[2]] as const);
|
|
138
|
-
|
|
139
|
-
if (Number(version) !== 2) {
|
|
140
|
-
console.error('Only TokenManager2 (V2) tokens are supported. This token is version', version);
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const amountWei = mode === 'amount' ? BigInt(arg1) : 0n;
|
|
145
|
-
const maxFundsWei = mode === 'amount' ? BigInt(arg2) : 0n;
|
|
146
|
-
const fundsWei = mode === 'funds' ? BigInt(arg1) : 0n;
|
|
147
|
-
const minAmountWei = mode === 'funds' ? BigInt(arg2) : 0n;
|
|
148
|
-
|
|
149
|
-
const tryBuyResult = await publicClient.readContract({
|
|
150
|
-
address: HELPER_ADDRESS,
|
|
151
|
-
abi: HELPER_ABI,
|
|
152
|
-
functionName: 'tryBuy',
|
|
153
|
-
args: [tokenAddress, amountWei, fundsWei],
|
|
154
|
-
});
|
|
155
|
-
const amountMsgValue = tryBuyResult[5];
|
|
156
|
-
const amountApproval = tryBuyResult[6];
|
|
157
|
-
|
|
158
|
-
if (quote !== ZERO && amountApproval > 0n) {
|
|
159
|
-
const approveHash = await walletClient.writeContract({
|
|
160
|
-
address: quote,
|
|
161
|
-
abi: ERC20_ABI,
|
|
162
|
-
functionName: 'approve',
|
|
163
|
-
args: [tokenManager, amountApproval],
|
|
164
|
-
});
|
|
165
|
-
console.error('Approved quote token. Tx:', approveHash);
|
|
166
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
167
|
-
if (receipt.status !== 'success') {
|
|
168
|
-
console.error('Approve failed');
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
let hash: `0x${string}`;
|
|
174
|
-
if (mode === 'amount') {
|
|
175
|
-
hash = await walletClient.writeContract({
|
|
176
|
-
address: tokenManager as `0x${string}`,
|
|
177
|
-
abi: TM2_ABI,
|
|
178
|
-
functionName: 'buyToken',
|
|
179
|
-
args: [tokenAddress, amountWei, maxFundsWei],
|
|
180
|
-
value: amountMsgValue,
|
|
181
|
-
});
|
|
182
|
-
} else {
|
|
183
|
-
hash = await walletClient.writeContract({
|
|
184
|
-
address: tokenManager as `0x${string}`,
|
|
185
|
-
abi: TM2_ABI,
|
|
186
|
-
functionName: 'buyTokenAMAP',
|
|
187
|
-
args: [tokenAddress, fundsWei, minAmountWei],
|
|
188
|
-
value: amountMsgValue,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
console.log(JSON.stringify({ txHash: hash }, null, 2));
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
main().catch((e) => {
|
|
196
|
-
console.error(e.message || e);
|
|
197
|
-
process.exit(1);
|
|
198
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Four.meme - execute buy on TokenManager2 (BSC only).
|
|
4
|
+
* Usage:
|
|
5
|
+
* npx tsx execute-buy.ts <tokenAddress> amount <amountWei> <maxFundsWei> # buy fixed token amount
|
|
6
|
+
* npx tsx execute-buy.ts <tokenAddress> funds <fundsWei> <minAmountWei> # spend fixed quote (e.g. BNB)
|
|
7
|
+
* Env: PRIVATE_KEY. Only V2 tokens (version 2 from getTokenInfo).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
11
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
12
|
+
import { bsc } from 'viem/chains';
|
|
13
|
+
|
|
14
|
+
const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;
|
|
15
|
+
const ZERO = '0x0000000000000000000000000000000000000000' as const;
|
|
16
|
+
|
|
17
|
+
const HELPER_ABI = [
|
|
18
|
+
{
|
|
19
|
+
name: 'getTokenInfo',
|
|
20
|
+
type: 'function',
|
|
21
|
+
stateMutability: 'view',
|
|
22
|
+
inputs: [{ name: 'token', type: 'address' }],
|
|
23
|
+
outputs: [
|
|
24
|
+
{ name: 'version', type: 'uint256' },
|
|
25
|
+
{ name: 'tokenManager', type: 'address' },
|
|
26
|
+
{ name: 'quote', type: 'address' },
|
|
27
|
+
{ name: 'lastPrice', type: 'uint256' },
|
|
28
|
+
{ name: 'tradingFeeRate', type: 'uint256' },
|
|
29
|
+
{ name: 'minTradingFee', type: 'uint256' },
|
|
30
|
+
{ name: 'launchTime', type: 'uint256' },
|
|
31
|
+
{ name: 'offers', type: 'uint256' },
|
|
32
|
+
{ name: 'maxOffers', type: 'uint256' },
|
|
33
|
+
{ name: 'funds', type: 'uint256' },
|
|
34
|
+
{ name: 'maxFunds', type: 'uint256' },
|
|
35
|
+
{ name: 'liquidityAdded', type: 'bool' },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'tryBuy',
|
|
40
|
+
type: 'function',
|
|
41
|
+
stateMutability: 'view',
|
|
42
|
+
inputs: [
|
|
43
|
+
{ name: 'token', type: 'address' },
|
|
44
|
+
{ name: 'amount', type: 'uint256' },
|
|
45
|
+
{ name: 'funds', type: 'uint256' },
|
|
46
|
+
],
|
|
47
|
+
outputs: [
|
|
48
|
+
{ name: 'tokenManager', type: 'address' },
|
|
49
|
+
{ name: 'quote', type: 'address' },
|
|
50
|
+
{ name: 'estimatedAmount', type: 'uint256' },
|
|
51
|
+
{ name: 'estimatedCost', type: 'uint256' },
|
|
52
|
+
{ name: 'estimatedFee', type: 'uint256' },
|
|
53
|
+
{ name: 'amountMsgValue', type: 'uint256' },
|
|
54
|
+
{ name: 'amountApproval', type: 'uint256' },
|
|
55
|
+
{ name: 'amountFunds', type: 'uint256' },
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
] as const;
|
|
59
|
+
|
|
60
|
+
const TM2_ABI = [
|
|
61
|
+
{
|
|
62
|
+
name: 'buyToken',
|
|
63
|
+
type: 'function',
|
|
64
|
+
stateMutability: 'payable',
|
|
65
|
+
inputs: [
|
|
66
|
+
{ name: 'token', type: 'address' },
|
|
67
|
+
{ name: 'amount', type: 'uint256' },
|
|
68
|
+
{ name: 'maxFunds', type: 'uint256' },
|
|
69
|
+
],
|
|
70
|
+
outputs: [],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'buyTokenAMAP',
|
|
74
|
+
type: 'function',
|
|
75
|
+
stateMutability: 'payable',
|
|
76
|
+
inputs: [
|
|
77
|
+
{ name: 'token', type: 'address' },
|
|
78
|
+
{ name: 'funds', type: 'uint256' },
|
|
79
|
+
{ name: 'minAmount', type: 'uint256' },
|
|
80
|
+
],
|
|
81
|
+
outputs: [],
|
|
82
|
+
},
|
|
83
|
+
] as const;
|
|
84
|
+
|
|
85
|
+
const ERC20_ABI = [
|
|
86
|
+
{
|
|
87
|
+
name: 'approve',
|
|
88
|
+
type: 'function',
|
|
89
|
+
stateMutability: 'nonpayable',
|
|
90
|
+
inputs: [
|
|
91
|
+
{ name: 'spender', type: 'address' },
|
|
92
|
+
{ name: 'amount', type: 'uint256' },
|
|
93
|
+
],
|
|
94
|
+
outputs: [{ type: 'bool' }],
|
|
95
|
+
},
|
|
96
|
+
] as const;
|
|
97
|
+
|
|
98
|
+
const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
99
|
+
|
|
100
|
+
async function main() {
|
|
101
|
+
const pk = process.env.PRIVATE_KEY;
|
|
102
|
+
if (!pk) {
|
|
103
|
+
console.error('Set PRIVATE_KEY');
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
const account = privateKeyToAccount(
|
|
107
|
+
(pk.startsWith('0x') ? pk : '0x' + pk) as `0x${string}`
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const tokenAddress = process.argv[2] as `0x${string}`;
|
|
111
|
+
const mode = process.argv[3]; // 'amount' | 'funds'
|
|
112
|
+
const arg1 = process.argv[4];
|
|
113
|
+
const arg2 = process.argv[5];
|
|
114
|
+
|
|
115
|
+
if (!tokenAddress || !mode || !arg1 || !arg2) {
|
|
116
|
+
console.error('Usage:');
|
|
117
|
+
console.error(' execute-buy.ts <token> amount <amountWei> <maxFundsWei>');
|
|
118
|
+
console.error(' execute-buy.ts <token> funds <fundsWei> <minAmountWei>');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const publicClient = createPublicClient({
|
|
123
|
+
chain: bsc,
|
|
124
|
+
transport: http(RPC_URL),
|
|
125
|
+
});
|
|
126
|
+
const walletClient = createWalletClient({
|
|
127
|
+
account,
|
|
128
|
+
chain: bsc,
|
|
129
|
+
transport: http(RPC_URL),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const [version, tokenManager, quote] = await publicClient.readContract({
|
|
133
|
+
address: HELPER_ADDRESS,
|
|
134
|
+
abi: HELPER_ABI,
|
|
135
|
+
functionName: 'getTokenInfo',
|
|
136
|
+
args: [tokenAddress],
|
|
137
|
+
}).then((r) => [r[0], r[1], r[2]] as const);
|
|
138
|
+
|
|
139
|
+
if (Number(version) !== 2) {
|
|
140
|
+
console.error('Only TokenManager2 (V2) tokens are supported. This token is version', version);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const amountWei = mode === 'amount' ? BigInt(arg1) : 0n;
|
|
145
|
+
const maxFundsWei = mode === 'amount' ? BigInt(arg2) : 0n;
|
|
146
|
+
const fundsWei = mode === 'funds' ? BigInt(arg1) : 0n;
|
|
147
|
+
const minAmountWei = mode === 'funds' ? BigInt(arg2) : 0n;
|
|
148
|
+
|
|
149
|
+
const tryBuyResult = await publicClient.readContract({
|
|
150
|
+
address: HELPER_ADDRESS,
|
|
151
|
+
abi: HELPER_ABI,
|
|
152
|
+
functionName: 'tryBuy',
|
|
153
|
+
args: [tokenAddress, amountWei, fundsWei],
|
|
154
|
+
});
|
|
155
|
+
const amountMsgValue = tryBuyResult[5];
|
|
156
|
+
const amountApproval = tryBuyResult[6];
|
|
157
|
+
|
|
158
|
+
if (quote !== ZERO && amountApproval > 0n) {
|
|
159
|
+
const approveHash = await walletClient.writeContract({
|
|
160
|
+
address: quote,
|
|
161
|
+
abi: ERC20_ABI,
|
|
162
|
+
functionName: 'approve',
|
|
163
|
+
args: [tokenManager, amountApproval],
|
|
164
|
+
});
|
|
165
|
+
console.error('Approved quote token. Tx:', approveHash);
|
|
166
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
167
|
+
if (receipt.status !== 'success') {
|
|
168
|
+
console.error('Approve failed');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
let hash: `0x${string}`;
|
|
174
|
+
if (mode === 'amount') {
|
|
175
|
+
hash = await walletClient.writeContract({
|
|
176
|
+
address: tokenManager as `0x${string}`,
|
|
177
|
+
abi: TM2_ABI,
|
|
178
|
+
functionName: 'buyToken',
|
|
179
|
+
args: [tokenAddress, amountWei, maxFundsWei],
|
|
180
|
+
value: amountMsgValue,
|
|
181
|
+
});
|
|
182
|
+
} else {
|
|
183
|
+
hash = await walletClient.writeContract({
|
|
184
|
+
address: tokenManager as `0x${string}`,
|
|
185
|
+
abi: TM2_ABI,
|
|
186
|
+
functionName: 'buyTokenAMAP',
|
|
187
|
+
args: [tokenAddress, fundsWei, minAmountWei],
|
|
188
|
+
value: amountMsgValue,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
console.log(JSON.stringify({ txHash: hash }, null, 2));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
main().catch((e) => {
|
|
196
|
+
console.error(e.message || e);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|