@four-meme/four-meme-ai 1.0.6 → 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 +5 -5
- package/skills/four-meme-integration/SKILL.md +374 -454
- package/skills/four-meme-integration/references/create-token-scripts.md +27 -7
- 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,150 +1,150 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Four.meme - execute sell on TokenManager2 (BSC only).
|
|
4
|
-
* Usage: npx tsx execute-sell.ts <tokenAddress> <amountWei> [minFundsWei]
|
|
5
|
-
* Env: PRIVATE_KEY. Sends approve(tokenManager, amount) then sellToken(token, amount).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
9
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
10
|
-
import { bsc } from 'viem/chains';
|
|
11
|
-
|
|
12
|
-
const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;
|
|
13
|
-
|
|
14
|
-
const HELPER_ABI = [
|
|
15
|
-
{
|
|
16
|
-
name: 'getTokenInfo',
|
|
17
|
-
type: 'function',
|
|
18
|
-
stateMutability: 'view',
|
|
19
|
-
inputs: [{ name: 'token', type: 'address' }],
|
|
20
|
-
outputs: [
|
|
21
|
-
{ name: 'version', type: 'uint256' },
|
|
22
|
-
{ name: 'tokenManager', type: 'address' },
|
|
23
|
-
{ name: 'quote', type: 'address' },
|
|
24
|
-
{ name: 'lastPrice', type: 'uint256' },
|
|
25
|
-
{ name: 'tradingFeeRate', type: 'uint256' },
|
|
26
|
-
{ name: 'minTradingFee', type: 'uint256' },
|
|
27
|
-
{ name: 'launchTime', type: 'uint256' },
|
|
28
|
-
{ name: 'offers', type: 'uint256' },
|
|
29
|
-
{ name: 'maxOffers', type: 'uint256' },
|
|
30
|
-
{ name: 'funds', type: 'uint256' },
|
|
31
|
-
{ name: 'maxFunds', type: 'uint256' },
|
|
32
|
-
{ name: 'liquidityAdded', type: 'bool' },
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
] as const;
|
|
36
|
-
|
|
37
|
-
const TM2_ABI_SIMPLE = [
|
|
38
|
-
{
|
|
39
|
-
name: 'sellToken',
|
|
40
|
-
type: 'function',
|
|
41
|
-
stateMutability: 'nonpayable',
|
|
42
|
-
inputs: [
|
|
43
|
-
{ name: 'token', type: 'address' },
|
|
44
|
-
{ name: 'amount', type: 'uint256' },
|
|
45
|
-
],
|
|
46
|
-
outputs: [],
|
|
47
|
-
},
|
|
48
|
-
] as const;
|
|
49
|
-
|
|
50
|
-
const TM2_ABI_WITH_MIN_FUNDS = [
|
|
51
|
-
{
|
|
52
|
-
name: 'sellToken',
|
|
53
|
-
type: 'function',
|
|
54
|
-
stateMutability: 'nonpayable',
|
|
55
|
-
inputs: [
|
|
56
|
-
{ name: 'origin', type: 'uint256' },
|
|
57
|
-
{ name: 'token', type: 'address' },
|
|
58
|
-
{ name: 'amount', type: 'uint256' },
|
|
59
|
-
{ name: 'minFunds', type: 'uint256' },
|
|
60
|
-
],
|
|
61
|
-
outputs: [],
|
|
62
|
-
},
|
|
63
|
-
] as const;
|
|
64
|
-
|
|
65
|
-
const ERC20_ABI = [
|
|
66
|
-
{
|
|
67
|
-
name: 'approve',
|
|
68
|
-
type: 'function',
|
|
69
|
-
stateMutability: 'nonpayable',
|
|
70
|
-
inputs: [
|
|
71
|
-
{ name: 'spender', type: 'address' },
|
|
72
|
-
{ name: 'amount', type: 'uint256' },
|
|
73
|
-
],
|
|
74
|
-
outputs: [{ type: 'bool' }],
|
|
75
|
-
},
|
|
76
|
-
] as const;
|
|
77
|
-
|
|
78
|
-
const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
79
|
-
|
|
80
|
-
async function main() {
|
|
81
|
-
const pk = process.env.PRIVATE_KEY;
|
|
82
|
-
if (!pk) {
|
|
83
|
-
console.error('Set PRIVATE_KEY');
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
const account = privateKeyToAccount(
|
|
87
|
-
(pk.startsWith('0x') ? pk : '0x' + pk) as `0x${string}`
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
const tokenAddress = process.argv[2] as `0x${string}`;
|
|
91
|
-
const amountWei = BigInt(process.argv[3] ?? '0');
|
|
92
|
-
const minFundsWei = process.argv[4] ? BigInt(process.argv[4]) : null;
|
|
93
|
-
|
|
94
|
-
if (!tokenAddress || amountWei <= 0n) {
|
|
95
|
-
console.error('Usage: npx tsx execute-sell.ts <tokenAddress> <amountWei> [minFundsWei]');
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const publicClient = createPublicClient({
|
|
100
|
-
chain: bsc,
|
|
101
|
-
transport: http(RPC_URL),
|
|
102
|
-
});
|
|
103
|
-
const walletClient = createWalletClient({
|
|
104
|
-
account,
|
|
105
|
-
chain: bsc,
|
|
106
|
-
transport: http(RPC_URL),
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const [, tokenManager] = await publicClient.readContract({
|
|
110
|
-
address: HELPER_ADDRESS,
|
|
111
|
-
abi: HELPER_ABI,
|
|
112
|
-
functionName: 'getTokenInfo',
|
|
113
|
-
args: [tokenAddress],
|
|
114
|
-
}).then((r) => [r[0], r[1]] as const);
|
|
115
|
-
|
|
116
|
-
const approveHash = await walletClient.writeContract({
|
|
117
|
-
address: tokenAddress,
|
|
118
|
-
abi: ERC20_ABI,
|
|
119
|
-
functionName: 'approve',
|
|
120
|
-
args: [tokenManager as `0x${string}`, amountWei],
|
|
121
|
-
});
|
|
122
|
-
const receipt = await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
123
|
-
if (receipt.status !== 'success') {
|
|
124
|
-
console.error('Approve failed');
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const tm2 = tokenManager as `0x${string}`;
|
|
129
|
-
const hash: `0x${string}` =
|
|
130
|
-
minFundsWei !== null
|
|
131
|
-
? await walletClient.writeContract({
|
|
132
|
-
address: tm2,
|
|
133
|
-
abi: TM2_ABI_WITH_MIN_FUNDS,
|
|
134
|
-
functionName: 'sellToken',
|
|
135
|
-
args: [0n, tokenAddress, amountWei, minFundsWei],
|
|
136
|
-
})
|
|
137
|
-
: await walletClient.writeContract({
|
|
138
|
-
address: tm2,
|
|
139
|
-
abi: TM2_ABI_SIMPLE,
|
|
140
|
-
functionName: 'sellToken',
|
|
141
|
-
args: [tokenAddress, amountWei],
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
console.log(JSON.stringify({ txHash: hash }, null, 2));
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
main().catch((e) => {
|
|
148
|
-
console.error(e.message || e);
|
|
149
|
-
process.exit(1);
|
|
150
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Four.meme - execute sell on TokenManager2 (BSC only).
|
|
4
|
+
* Usage: npx tsx execute-sell.ts <tokenAddress> <amountWei> [minFundsWei]
|
|
5
|
+
* Env: PRIVATE_KEY. Sends approve(tokenManager, amount) then sellToken(token, amount).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { createPublicClient, createWalletClient, http } from 'viem';
|
|
9
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
10
|
+
import { bsc } from 'viem/chains';
|
|
11
|
+
|
|
12
|
+
const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;
|
|
13
|
+
|
|
14
|
+
const HELPER_ABI = [
|
|
15
|
+
{
|
|
16
|
+
name: 'getTokenInfo',
|
|
17
|
+
type: 'function',
|
|
18
|
+
stateMutability: 'view',
|
|
19
|
+
inputs: [{ name: 'token', type: 'address' }],
|
|
20
|
+
outputs: [
|
|
21
|
+
{ name: 'version', type: 'uint256' },
|
|
22
|
+
{ name: 'tokenManager', type: 'address' },
|
|
23
|
+
{ name: 'quote', type: 'address' },
|
|
24
|
+
{ name: 'lastPrice', type: 'uint256' },
|
|
25
|
+
{ name: 'tradingFeeRate', type: 'uint256' },
|
|
26
|
+
{ name: 'minTradingFee', type: 'uint256' },
|
|
27
|
+
{ name: 'launchTime', type: 'uint256' },
|
|
28
|
+
{ name: 'offers', type: 'uint256' },
|
|
29
|
+
{ name: 'maxOffers', type: 'uint256' },
|
|
30
|
+
{ name: 'funds', type: 'uint256' },
|
|
31
|
+
{ name: 'maxFunds', type: 'uint256' },
|
|
32
|
+
{ name: 'liquidityAdded', type: 'bool' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
const TM2_ABI_SIMPLE = [
|
|
38
|
+
{
|
|
39
|
+
name: 'sellToken',
|
|
40
|
+
type: 'function',
|
|
41
|
+
stateMutability: 'nonpayable',
|
|
42
|
+
inputs: [
|
|
43
|
+
{ name: 'token', type: 'address' },
|
|
44
|
+
{ name: 'amount', type: 'uint256' },
|
|
45
|
+
],
|
|
46
|
+
outputs: [],
|
|
47
|
+
},
|
|
48
|
+
] as const;
|
|
49
|
+
|
|
50
|
+
const TM2_ABI_WITH_MIN_FUNDS = [
|
|
51
|
+
{
|
|
52
|
+
name: 'sellToken',
|
|
53
|
+
type: 'function',
|
|
54
|
+
stateMutability: 'nonpayable',
|
|
55
|
+
inputs: [
|
|
56
|
+
{ name: 'origin', type: 'uint256' },
|
|
57
|
+
{ name: 'token', type: 'address' },
|
|
58
|
+
{ name: 'amount', type: 'uint256' },
|
|
59
|
+
{ name: 'minFunds', type: 'uint256' },
|
|
60
|
+
],
|
|
61
|
+
outputs: [],
|
|
62
|
+
},
|
|
63
|
+
] as const;
|
|
64
|
+
|
|
65
|
+
const ERC20_ABI = [
|
|
66
|
+
{
|
|
67
|
+
name: 'approve',
|
|
68
|
+
type: 'function',
|
|
69
|
+
stateMutability: 'nonpayable',
|
|
70
|
+
inputs: [
|
|
71
|
+
{ name: 'spender', type: 'address' },
|
|
72
|
+
{ name: 'amount', type: 'uint256' },
|
|
73
|
+
],
|
|
74
|
+
outputs: [{ type: 'bool' }],
|
|
75
|
+
},
|
|
76
|
+
] as const;
|
|
77
|
+
|
|
78
|
+
const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
79
|
+
|
|
80
|
+
async function main() {
|
|
81
|
+
const pk = process.env.PRIVATE_KEY;
|
|
82
|
+
if (!pk) {
|
|
83
|
+
console.error('Set PRIVATE_KEY');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
const account = privateKeyToAccount(
|
|
87
|
+
(pk.startsWith('0x') ? pk : '0x' + pk) as `0x${string}`
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const tokenAddress = process.argv[2] as `0x${string}`;
|
|
91
|
+
const amountWei = BigInt(process.argv[3] ?? '0');
|
|
92
|
+
const minFundsWei = process.argv[4] ? BigInt(process.argv[4]) : null;
|
|
93
|
+
|
|
94
|
+
if (!tokenAddress || amountWei <= 0n) {
|
|
95
|
+
console.error('Usage: npx tsx execute-sell.ts <tokenAddress> <amountWei> [minFundsWei]');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const publicClient = createPublicClient({
|
|
100
|
+
chain: bsc,
|
|
101
|
+
transport: http(RPC_URL),
|
|
102
|
+
});
|
|
103
|
+
const walletClient = createWalletClient({
|
|
104
|
+
account,
|
|
105
|
+
chain: bsc,
|
|
106
|
+
transport: http(RPC_URL),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const [, tokenManager] = await publicClient.readContract({
|
|
110
|
+
address: HELPER_ADDRESS,
|
|
111
|
+
abi: HELPER_ABI,
|
|
112
|
+
functionName: 'getTokenInfo',
|
|
113
|
+
args: [tokenAddress],
|
|
114
|
+
}).then((r) => [r[0], r[1]] as const);
|
|
115
|
+
|
|
116
|
+
const approveHash = await walletClient.writeContract({
|
|
117
|
+
address: tokenAddress,
|
|
118
|
+
abi: ERC20_ABI,
|
|
119
|
+
functionName: 'approve',
|
|
120
|
+
args: [tokenManager as `0x${string}`, amountWei],
|
|
121
|
+
});
|
|
122
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: approveHash });
|
|
123
|
+
if (receipt.status !== 'success') {
|
|
124
|
+
console.error('Approve failed');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tm2 = tokenManager as `0x${string}`;
|
|
129
|
+
const hash: `0x${string}` =
|
|
130
|
+
minFundsWei !== null
|
|
131
|
+
? await walletClient.writeContract({
|
|
132
|
+
address: tm2,
|
|
133
|
+
abi: TM2_ABI_WITH_MIN_FUNDS,
|
|
134
|
+
functionName: 'sellToken',
|
|
135
|
+
args: [0n, tokenAddress, amountWei, minFundsWei],
|
|
136
|
+
})
|
|
137
|
+
: await walletClient.writeContract({
|
|
138
|
+
address: tm2,
|
|
139
|
+
abi: TM2_ABI_SIMPLE,
|
|
140
|
+
functionName: 'sellToken',
|
|
141
|
+
args: [tokenAddress, amountWei],
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
console.log(JSON.stringify({ txHash: hash }, null, 2));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
main().catch((e) => {
|
|
148
|
+
console.error(e.message || e);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
});
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Four.meme - send BNB or ERC20 from current wallet to a target address (BSC).
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* npx tsx send-token.ts <toAddress> <amountWei> [tokenAddress]
|
|
7
|
-
* - toAddress: recipient wallet address (0x...)
|
|
8
|
-
* - amountWei: amount in wei (for BNB or token decimals)
|
|
9
|
-
* - tokenAddress: optional; omit or use "BNB" / "0x0" for native BNB; otherwise ERC20 token contract address
|
|
10
|
-
*
|
|
11
|
-
* Env: PRIVATE_KEY. Optional: BSC_RPC_URL.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { createWalletClient, http, parseAbi } from 'viem';
|
|
15
|
-
import { privateKeyToAccount } from 'viem/accounts';
|
|
16
|
-
import { bsc } from 'viem/chains';
|
|
17
|
-
|
|
18
|
-
const ZERO = '0x0000000000000000000000000000000000000000' as const;
|
|
19
|
-
|
|
20
|
-
const ERC20_ABI = parseAbi([
|
|
21
|
-
'function transfer(address to, uint256 amount) returns (bool)',
|
|
22
|
-
]);
|
|
23
|
-
|
|
24
|
-
function isAddress(s: string): boolean {
|
|
25
|
-
return /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function main() {
|
|
29
|
-
const privateKey = process.env.PRIVATE_KEY;
|
|
30
|
-
if (!privateKey) {
|
|
31
|
-
console.error('Set PRIVATE_KEY');
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
|
|
35
|
-
const account = privateKeyToAccount(pk);
|
|
36
|
-
|
|
37
|
-
const toAddress = process.argv[2];
|
|
38
|
-
const amountWeiRaw = process.argv[3];
|
|
39
|
-
const tokenAddress = process.argv[4]; // optional: BNB if omitted or "BNB" or 0x0
|
|
40
|
-
|
|
41
|
-
if (!toAddress || !amountWeiRaw) {
|
|
42
|
-
console.error('Usage: send-token.ts <toAddress> <amountWei> [tokenAddress]');
|
|
43
|
-
console.error(' toAddress: recipient 0x... address');
|
|
44
|
-
console.error(' amountWei: amount in wei (for BNB or token smallest unit)');
|
|
45
|
-
console.error(' tokenAddress: optional; omit or BNB/0x0 = native BNB; else ERC20 contract address');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
if (!isAddress(toAddress)) {
|
|
49
|
-
console.error('Invalid toAddress:', toAddress);
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const amountWei = BigInt(amountWeiRaw);
|
|
54
|
-
if (amountWei <= 0n) {
|
|
55
|
-
console.error('amountWei must be positive');
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const isNative =
|
|
60
|
-
!tokenAddress ||
|
|
61
|
-
tokenAddress.toUpperCase() === 'BNB' ||
|
|
62
|
-
tokenAddress === ZERO ||
|
|
63
|
-
tokenAddress.toLowerCase() === '0x0000000000000000000000000000000000000000';
|
|
64
|
-
|
|
65
|
-
const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
66
|
-
const client = createWalletClient({
|
|
67
|
-
account,
|
|
68
|
-
chain: bsc,
|
|
69
|
-
transport: http(rpcUrl),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
let txHash: `0x${string}`;
|
|
73
|
-
|
|
74
|
-
if (isNative) {
|
|
75
|
-
txHash = await client.sendTransaction({
|
|
76
|
-
to: toAddress as `0x${string}`,
|
|
77
|
-
value: amountWei,
|
|
78
|
-
});
|
|
79
|
-
} else {
|
|
80
|
-
if (!isAddress(tokenAddress)) {
|
|
81
|
-
console.error('Invalid tokenAddress:', tokenAddress);
|
|
82
|
-
process.exit(1);
|
|
83
|
-
}
|
|
84
|
-
txHash = await client.writeContract({
|
|
85
|
-
address: tokenAddress as `0x${string}`,
|
|
86
|
-
abi: ERC20_ABI,
|
|
87
|
-
functionName: 'transfer',
|
|
88
|
-
args: [toAddress as `0x${string}`, amountWei],
|
|
89
|
-
});
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
console.log(JSON.stringify({ txHash, to: toAddress, amountWei: amountWei.toString(), native: isNative }, null, 2));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
main().catch((e) => {
|
|
96
|
-
console.error(e.message || e);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Four.meme - send BNB or ERC20 from current wallet to a target address (BSC).
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx tsx send-token.ts <toAddress> <amountWei> [tokenAddress]
|
|
7
|
+
* - toAddress: recipient wallet address (0x...)
|
|
8
|
+
* - amountWei: amount in wei (for BNB or token decimals)
|
|
9
|
+
* - tokenAddress: optional; omit or use "BNB" / "0x0" for native BNB; otherwise ERC20 token contract address
|
|
10
|
+
*
|
|
11
|
+
* Env: PRIVATE_KEY. Optional: BSC_RPC_URL.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createWalletClient, http, parseAbi } from 'viem';
|
|
15
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
16
|
+
import { bsc } from 'viem/chains';
|
|
17
|
+
|
|
18
|
+
const ZERO = '0x0000000000000000000000000000000000000000' as const;
|
|
19
|
+
|
|
20
|
+
const ERC20_ABI = parseAbi([
|
|
21
|
+
'function transfer(address to, uint256 amount) returns (bool)',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
function isAddress(s: string): boolean {
|
|
25
|
+
return /^0x[0-9a-fA-F]{40}$/.test(s);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function main() {
|
|
29
|
+
const privateKey = process.env.PRIVATE_KEY;
|
|
30
|
+
if (!privateKey) {
|
|
31
|
+
console.error('Set PRIVATE_KEY');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
|
|
35
|
+
const account = privateKeyToAccount(pk);
|
|
36
|
+
|
|
37
|
+
const toAddress = process.argv[2];
|
|
38
|
+
const amountWeiRaw = process.argv[3];
|
|
39
|
+
const tokenAddress = process.argv[4]; // optional: BNB if omitted or "BNB" or 0x0
|
|
40
|
+
|
|
41
|
+
if (!toAddress || !amountWeiRaw) {
|
|
42
|
+
console.error('Usage: send-token.ts <toAddress> <amountWei> [tokenAddress]');
|
|
43
|
+
console.error(' toAddress: recipient 0x... address');
|
|
44
|
+
console.error(' amountWei: amount in wei (for BNB or token smallest unit)');
|
|
45
|
+
console.error(' tokenAddress: optional; omit or BNB/0x0 = native BNB; else ERC20 contract address');
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (!isAddress(toAddress)) {
|
|
49
|
+
console.error('Invalid toAddress:', toAddress);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const amountWei = BigInt(amountWeiRaw);
|
|
54
|
+
if (amountWei <= 0n) {
|
|
55
|
+
console.error('amountWei must be positive');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isNative =
|
|
60
|
+
!tokenAddress ||
|
|
61
|
+
tokenAddress.toUpperCase() === 'BNB' ||
|
|
62
|
+
tokenAddress === ZERO ||
|
|
63
|
+
tokenAddress.toLowerCase() === '0x0000000000000000000000000000000000000000';
|
|
64
|
+
|
|
65
|
+
const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
|
|
66
|
+
const client = createWalletClient({
|
|
67
|
+
account,
|
|
68
|
+
chain: bsc,
|
|
69
|
+
transport: http(rpcUrl),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
let txHash: `0x${string}`;
|
|
73
|
+
|
|
74
|
+
if (isNative) {
|
|
75
|
+
txHash = await client.sendTransaction({
|
|
76
|
+
to: toAddress as `0x${string}`,
|
|
77
|
+
value: amountWei,
|
|
78
|
+
});
|
|
79
|
+
} else {
|
|
80
|
+
if (!isAddress(tokenAddress)) {
|
|
81
|
+
console.error('Invalid tokenAddress:', tokenAddress);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
txHash = await client.writeContract({
|
|
85
|
+
address: tokenAddress as `0x${string}`,
|
|
86
|
+
abi: ERC20_ABI,
|
|
87
|
+
functionName: 'transfer',
|
|
88
|
+
args: [toAddress as `0x${string}`, amountWei],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(JSON.stringify({ txHash, to: toAddress, amountWei: amountWei.toString(), native: isNative }, null, 2));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
main().catch((e) => {
|
|
96
|
+
console.error(e.message || e);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
});
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Four.meme - token list (REST API)
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* POST /meme-api/v1/public/token/search
|
|
5
|
+
*
|
|
6
|
+
* Legacy (still supported): [--orderBy=Hot] [--tokenName=] [--listedPancake=false] ...
|
|
7
|
+
* New params: [--type=HOT] [--listType=NOR] [--keyword=] [--tag=a,b] [--status=PUBLISH|TRADE|ALL] [--sort=DESC|ASC] [--version=V9]
|
|
8
|
+
* Legacy shortcut: [--queryMode=Binance|USD1] (infers listType BIN/BIN_DEX or USD1/USD1_DEX using listedPancake)
|
|
9
|
+
*
|
|
10
|
+
* Usage: npx tsx token-list.ts [options]
|
|
6
11
|
* Output: JSON list of tokens.
|
|
7
12
|
*/
|
|
8
13
|
|
|
@@ -17,30 +22,107 @@ function parseArg(name: string, defaultValue: string): string {
|
|
|
17
22
|
return defaultValue;
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
function hasArg(name: string): boolean {
|
|
26
|
+
const prefix = `--${name}=`;
|
|
27
|
+
return process.argv.some((a) => a.startsWith(prefix));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Map legacy orderBy → public search `type` (sort controlled by --sort=; default DESC). */
|
|
31
|
+
function typeFromLegacyOrderBy(orderBy: string): string {
|
|
32
|
+
const k = orderBy.replace(/\s/g, '').toLowerCase();
|
|
33
|
+
const map: Record<string, string> = {
|
|
34
|
+
hot: 'HOT',
|
|
35
|
+
time: 'NEW',
|
|
36
|
+
timedesc: 'NEW',
|
|
37
|
+
new: 'NEW',
|
|
38
|
+
progress: 'PROGRESS',
|
|
39
|
+
progressdesc: 'PROGRESS',
|
|
40
|
+
trading: 'VOL',
|
|
41
|
+
tradingdesc: 'VOL',
|
|
42
|
+
vol: 'VOL',
|
|
43
|
+
volume: 'VOL',
|
|
44
|
+
last: 'LAST',
|
|
45
|
+
cap: 'CAP',
|
|
46
|
+
dex: 'DEX',
|
|
47
|
+
graduated: 'DEX',
|
|
48
|
+
burn: 'BURN',
|
|
49
|
+
};
|
|
50
|
+
return map[k] ?? 'HOT';
|
|
51
|
+
}
|
|
52
|
+
|
|
20
53
|
async function main() {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
54
|
+
const pageIndex = Math.max(1, parseInt(parseArg('pageIndex', '1'), 10) || 1);
|
|
55
|
+
const pageSize = Math.min(100, Math.max(1, parseInt(parseArg('pageSize', '30'), 10) || 30));
|
|
56
|
+
const sort = (parseArg('sort', 'DESC').toUpperCase() === 'ASC' ? 'ASC' : 'DESC') as 'ASC' | 'DESC';
|
|
57
|
+
|
|
58
|
+
const listedPancake = parseArg('listedPancake', 'false').toLowerCase() === 'true';
|
|
59
|
+
|
|
60
|
+
// New API listType: allow explicit listType, or infer from legacy queryMode + listedPancake.
|
|
61
|
+
const explicitListType = hasArg('listType') ? parseArg('listType', 'NOR') : '';
|
|
62
|
+
const queryMode = parseArg('queryMode', '').trim().toLowerCase();
|
|
63
|
+
const inferredListType = (() => {
|
|
64
|
+
if (!queryMode) return '';
|
|
65
|
+
if (queryMode === 'binance' || queryMode === 'bnb' || queryMode === 'bnb_mpc') {
|
|
66
|
+
return listedPancake ? 'BIN_DEX' : 'BIN';
|
|
67
|
+
}
|
|
68
|
+
if (queryMode === 'usd1') {
|
|
69
|
+
return listedPancake ? 'USD1_DEX' : 'USD1';
|
|
70
|
+
}
|
|
71
|
+
return '';
|
|
72
|
+
})();
|
|
73
|
+
const listType = explicitListType || inferredListType || parseArg('listType', 'NOR');
|
|
74
|
+
const type = hasArg('type')
|
|
75
|
+
? parseArg('type', 'HOT')
|
|
76
|
+
: typeFromLegacyOrderBy(parseArg('orderBy', 'Hot'));
|
|
77
|
+
|
|
78
|
+
let status: string;
|
|
79
|
+
if (hasArg('status')) {
|
|
80
|
+
status = parseArg('status', 'ALL').toUpperCase();
|
|
81
|
+
} else {
|
|
82
|
+
status = listedPancake ? 'TRADE' : 'PUBLISH';
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const keywordRaw = hasArg('keyword') ? parseArg('keyword', '') : parseArg('tokenName', '');
|
|
86
|
+
const labelsRaw = hasArg('tag') ? parseArg('tag', '') : parseArg('labels', '');
|
|
87
|
+
const symbol = parseArg('symbol', '').trim();
|
|
88
|
+
|
|
89
|
+
const body: Record<string, unknown> = {
|
|
90
|
+
type,
|
|
91
|
+
listType,
|
|
33
92
|
pageIndex,
|
|
34
93
|
pageSize,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
94
|
+
status,
|
|
95
|
+
sort,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
if (keywordRaw.trim() !== '') {
|
|
99
|
+
body.keyword = keywordRaw.trim();
|
|
100
|
+
}
|
|
101
|
+
if (symbol !== '') {
|
|
102
|
+
body.symbol = symbol;
|
|
103
|
+
}
|
|
104
|
+
if (labelsRaw.trim() !== '') {
|
|
105
|
+
body.tag = labelsRaw
|
|
106
|
+
.split(',')
|
|
107
|
+
.map((t) => t.trim())
|
|
108
|
+
.filter(Boolean);
|
|
109
|
+
}
|
|
110
|
+
const version = parseArg('version', '').trim();
|
|
111
|
+
if (version !== '') {
|
|
112
|
+
body.version = version;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const url = `${API_BASE}/public/token/search`;
|
|
39
116
|
const res = await fetch(url, {
|
|
40
|
-
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: {
|
|
119
|
+
Accept: 'application/json',
|
|
120
|
+
'Content-Type': 'application/json',
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify(body),
|
|
41
123
|
});
|
|
42
124
|
if (!res.ok) {
|
|
43
|
-
throw new Error(`token/
|
|
125
|
+
throw new Error(`token/search failed: ${res.status} ${await res.text()}`);
|
|
44
126
|
}
|
|
45
127
|
const data = await res.json();
|
|
46
128
|
console.log(JSON.stringify(data, null, 2));
|