@gangfun/cli 0.0.0-stage → 0.1.3
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 +86 -2
- package/README.zh-CN.md +87 -0
- package/package.json +38 -4
- package/src/abi.js +30 -0
- package/src/baw.js +41 -0
- package/src/chain.js +89 -0
- package/src/index.js +136 -0
package/README.md
CHANGED
|
@@ -1,3 +1,87 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Gang CLI (`gang`)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Operate [gang.fun](https://gang.fun) from a Binance **Agentic Wallet**. Curve buys require a SPACE ID `.ai` Agent ID. This CLI builds calldata; `baw` signs — no keys in this package.
|
|
4
|
+
|
|
5
|
+
Not an open-source product dump: install and run steps only.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Node ≥ 18. Also install Binance Agentic Wallet CLI (`baw`):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @binance/agentic-wallet
|
|
13
|
+
npm i -g @gangfun/cli@0.1.3 --registry=https://npm.pkg.github.com
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Package: [GitHub Packages `@gangfun/cli`](https://github.com/gangfun/cli/pkgs/npm/cli). GitHub Packages needs auth — one-liner: `npm login --registry=https://npm.pkg.github.com` (use a token with `read:packages`). Or put the token in `~/.npmrc` (or `NODE_AUTH_TOKEN`):
|
|
17
|
+
|
|
18
|
+
```ini
|
|
19
|
+
@gangfun:registry=https://npm.pkg.github.com
|
|
20
|
+
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Then you can omit the `--registry` flag: `npm i -g @gangfun/cli@0.1.3`.
|
|
24
|
+
|
|
25
|
+
**Alternate** (no Packages token; installs from the public git repo `main` tip = `@gangfun/cli@0.1.3` / Launchpad v4.1):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm i -g github:gangfun/cli
|
|
29
|
+
# or one-shot:
|
|
30
|
+
npx --yes github:gangfun/cli preflight
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If a previous global is stale, clear it then reinstall:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm uninstall -g @gangfun/cli
|
|
37
|
+
npm i -g github:gangfun/cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
After install, run `gang preflight` — Launchpad must be `0x315Ba2e8952D06094CC636b2B84229FA32fb23A7` (v4.1). If you still see `0x86854…dDcc`, the old global is still in use; uninstall and reinstall.
|
|
41
|
+
|
|
42
|
+
Skill install (Claude Code / Cursor / Codex / OpenClaw / Binance AI Pro):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx skills add gangfun/gang-launchpad/skill
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Setup
|
|
49
|
+
|
|
50
|
+
1. Binance App → Wallet → Agentic Wallet → create; claim a `.ai` Agent ID.
|
|
51
|
+
2. Enable **Developer Mode** and **Trade all tokens**.
|
|
52
|
+
3. `baw auth signin` → confirm in the App.
|
|
53
|
+
4. `gang preflight`
|
|
54
|
+
|
|
55
|
+
## Commands
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gang preflight
|
|
59
|
+
gang list
|
|
60
|
+
gang token --token <addr>
|
|
61
|
+
gang quote --token <addr> --bnb <n>
|
|
62
|
+
gang buy --token <addr> --bnb <n> [--slippage 1]
|
|
63
|
+
gang sell --token <addr> (--amount <tokens> | --all) [--slippage 1]
|
|
64
|
+
gang create --name <n> --symbol <s>
|
|
65
|
+
gang execute --request-id <id>
|
|
66
|
+
gang tx --hash <0x..>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Buy / sell / create: **preview → you confirm → execute**. `requestId` expires in ~5 minutes. Sells may need App confirmation.
|
|
70
|
+
|
|
71
|
+
## Rules (short)
|
|
72
|
+
|
|
73
|
+
- 1% per-wallet cap on the curve; excess BNB refunded in-tx.
|
|
74
|
+
- 20 BNB → graduate to PancakeSwap V2, LP burned.
|
|
75
|
+
- Before graduation: no transfers, curve only.
|
|
76
|
+
- Graduated: trade on PancakeSwap (`pair` from `gang token`).
|
|
77
|
+
|
|
78
|
+
## Env
|
|
79
|
+
|
|
80
|
+
| Var | Meaning |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `GANG_LAUNCHPAD` | Launchpad address (default v4.1 `0x315Ba2e8952D06094CC636b2B84229FA32fb23A7`; override via env) |
|
|
83
|
+
| `GANG_RPC_URL` | BSC HTTP RPC |
|
|
84
|
+
|
|
85
|
+
## Version
|
|
86
|
+
|
|
87
|
+
`0.1.3` · repo [gangfun/cli](https://github.com/gangfun/cli) · distribute via [GitHub Packages](https://github.com/gangfun/cli/pkgs/npm/cli) (not npmjs)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Gang CLI(`gang`)
|
|
2
|
+
|
|
3
|
+
用 Binance **Agentic Wallet** 操作 [gang.fun](https://gang.fun)。内盘买入需要 SPACE ID `.ai` Agent ID。本 CLI 只组 calldata,签名走 `baw`,不持有私钥。
|
|
4
|
+
|
|
5
|
+
这里只写操作步骤,不是开源说明书。
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
Node ≥ 18。另需安装 Binance Agentic Wallet CLI(`baw`):
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm i -g @binance/agentic-wallet
|
|
13
|
+
npm i -g @gangfun/cli@0.1.3 --registry=https://npm.pkg.github.com
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
包地址:[GitHub Packages `@gangfun/cli`](https://github.com/gangfun/cli/pkgs/npm/cli)。GitHub Packages 需要登录——一句:`npm login --registry=https://npm.pkg.github.com`(token 需带 `read:packages`)。或把 token 写进 `~/.npmrc`(或环境变量 `NODE_AUTH_TOKEN`):
|
|
17
|
+
|
|
18
|
+
```ini
|
|
19
|
+
@gangfun:registry=https://npm.pkg.github.com
|
|
20
|
+
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
配好后可省略 `--registry`:`npm i -g @gangfun/cli@0.1.3`。
|
|
24
|
+
|
|
25
|
+
**备选**(不需要 Packages token;从公开 git 仓 `main` tip 装 = `@gangfun/cli@0.1.3` / Launchpad v4.1):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm i -g github:gangfun/cli
|
|
29
|
+
# 或一次性:
|
|
30
|
+
npx --yes github:gangfun/cli preflight
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
若之前装过旧全局包,先卸再装:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm uninstall -g @gangfun/cli
|
|
37
|
+
npm i -g github:gangfun/cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
装完跑 `gang preflight`——Launchpad 必须是 `0x315Ba2e8952D06094CC636b2B84229FA32fb23A7`(v4.1)。若仍看到 `0x86854…dDcc`,说明旧全局还在,卸掉重装。
|
|
41
|
+
|
|
42
|
+
Skill(Claude Code / Cursor / Codex / OpenClaw / Binance AI Pro):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx skills add gangfun/gang-launchpad/skill
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 准备
|
|
49
|
+
|
|
50
|
+
1. Binance App → 钱包 → Agentic Wallet → 创建;领取 `.ai` Agent ID。
|
|
51
|
+
2. 打开 **Developer Mode** 和 **Trade all tokens**。
|
|
52
|
+
3. `baw auth signin`,在 App 确认。
|
|
53
|
+
4. `gang preflight`
|
|
54
|
+
|
|
55
|
+
## 命令
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
gang preflight
|
|
59
|
+
gang list
|
|
60
|
+
gang token --token <addr>
|
|
61
|
+
gang quote --token <addr> --bnb <n>
|
|
62
|
+
gang buy --token <addr> --bnb <n> [--slippage 1]
|
|
63
|
+
gang sell --token <addr> (--amount <tokens> | --all) [--slippage 1]
|
|
64
|
+
gang create --name <n> --symbol <s>
|
|
65
|
+
gang execute --request-id <id>
|
|
66
|
+
gang tx --hash <0x..>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
买 / 卖 / 发币:**预览 → 你确认 → execute**。`requestId` 约 5 分钟过期。卖出可能要在 App 里点确认。
|
|
70
|
+
|
|
71
|
+
## 规则(短)
|
|
72
|
+
|
|
73
|
+
- 内盘每钱包 1% 上限;多付的 BNB 同笔退回。
|
|
74
|
+
- 筹满 20 BNB → 毕业到 PancakeSwap V2,LP 销毁。
|
|
75
|
+
- 毕业前不可转账,只能走曲线。
|
|
76
|
+
- 已毕业:去 PancakeSwap(`gang token` 里的 `pair`)。
|
|
77
|
+
|
|
78
|
+
## 环境变量
|
|
79
|
+
|
|
80
|
+
| 变量 | 含义 |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `GANG_LAUNCHPAD` | Launchpad 地址(默认 v4.1 `0x315Ba2e8952D06094CC636b2B84229FA32fb23A7`;可用环境变量覆盖) |
|
|
83
|
+
| `GANG_RPC_URL` | BSC HTTP RPC |
|
|
84
|
+
|
|
85
|
+
## 版本
|
|
86
|
+
|
|
87
|
+
`0.1.3` · 仓库 [gangfun/cli](https://github.com/gangfun/cli) · 分发走 [GitHub Packages](https://github.com/gangfun/cli/pkgs/npm/cli)(不上 npmjs)
|
package/package.json
CHANGED
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gangfun/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"description": "Gang launchpad CLI for Binance Agentic Wallet agents: list, quote, buy, sell, create on the agent-only bonding curve.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gang": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"README.md",
|
|
12
|
+
"README.zh-CN.md"
|
|
13
|
+
],
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"license": "UNLICENSED",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/gangfun/cli.git"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://gang.fun",
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/gangfun/cli/issues"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"gang",
|
|
28
|
+
"gang.fun",
|
|
29
|
+
"bnb",
|
|
30
|
+
"bsc",
|
|
31
|
+
"launchpad",
|
|
32
|
+
"bonding-curve",
|
|
33
|
+
"binance",
|
|
34
|
+
"agentic-wallet",
|
|
35
|
+
"agent-id"
|
|
36
|
+
],
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"viem": "^2.0.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/abi.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const LAUNCHPAD_ABI = [
|
|
2
|
+
{ type: 'function', name: 'create', stateMutability: 'nonpayable', inputs: [{ name: 'name', type: 'string' }, { name: 'symbol', type: 'string' }], outputs: [{ type: 'address' }] },
|
|
3
|
+
{ type: 'function', name: 'buy', stateMutability: 'payable', inputs: [{ name: 'token', type: 'address' }, { name: 'minTokensOut', type: 'uint256' }], outputs: [{ type: 'uint256' }] },
|
|
4
|
+
{ type: 'function', name: 'withdraw', stateMutability: 'nonpayable', inputs: [], outputs: [] },
|
|
5
|
+
{ type: 'function', name: 'pendingWithdrawals', stateMutability: 'view', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
6
|
+
{ type: 'function', name: 'sell', stateMutability: 'nonpayable', inputs: [{ name: 'token', type: 'address' }, { name: 'tokensIn', type: 'uint256' }, { name: 'minBnbOut', type: 'uint256' }], outputs: [{ type: 'uint256' }] },
|
|
7
|
+
{ type: 'function', name: 'quoteBuy', stateMutability: 'view', inputs: [{ name: 'token', type: 'address' }, { name: 'buyer', type: 'address' }, { name: 'bnbGross', type: 'uint256' }], outputs: [{ name: 'tokensOut', type: 'uint256' }, { name: 'bnbIn', type: 'uint256' }, { name: 'fee', type: 'uint256' }, { name: 'refund', type: 'uint256' }] },
|
|
8
|
+
{ type: 'function', name: 'quoteSell', stateMutability: 'view', inputs: [{ name: 'token', type: 'address' }, { name: 'tokensIn', type: 'uint256' }], outputs: [{ name: 'bnbToUser', type: 'uint256' }, { name: 'fee', type: 'uint256' }] },
|
|
9
|
+
{ type: 'function', name: 'curves', stateMutability: 'view', inputs: [{ name: 'token', type: 'address' }], outputs: [{ name: 'creator', type: 'address' }, { name: 'realBnb', type: 'uint128' }, { name: 'sold', type: 'uint128' }, { name: 'graduated', type: 'bool' }, { name: 'pair', type: 'address' }] },
|
|
10
|
+
{ type: 'function', name: 'allTokens', stateMutability: 'view', inputs: [{ type: 'uint256' }], outputs: [{ type: 'address' }] },
|
|
11
|
+
{ type: 'function', name: 'tokenCount', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
|
|
12
|
+
{ type: 'function', name: 'spotPrice', stateMutability: 'view', inputs: [{ name: 'token', type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
13
|
+
{ type: 'function', name: 'isAgent', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'bool' }] },
|
|
14
|
+
{ type: 'function', name: 'walletRoom', stateMutability: 'view', inputs: [{ name: 'token', type: 'address' }, { name: 'buyer', type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
15
|
+
{ type: 'function', name: 'GRADUATION_BNB', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
|
|
16
|
+
{ type: 'function', name: 'MAX_PER_WALLET', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
|
|
17
|
+
{ type: 'event', name: 'Created', inputs: [{ name: 'token', type: 'address', indexed: true }, { name: 'creator', type: 'address', indexed: true }, { name: 'name', type: 'string', indexed: false }, { name: 'symbol', type: 'string', indexed: false }] },
|
|
18
|
+
{ type: 'event', name: 'Buy', inputs: [{ name: 'token', type: 'address', indexed: true }, { name: 'buyer', type: 'address', indexed: true }, { name: 'bnbIn', type: 'uint256', indexed: false }, { name: 'fee', type: 'uint256', indexed: false }, { name: 'tokensOut', type: 'uint256', indexed: false }, { name: 'realBnb', type: 'uint256', indexed: false }] },
|
|
19
|
+
{ type: 'event', name: 'Sell', inputs: [{ name: 'token', type: 'address', indexed: true }, { name: 'seller', type: 'address', indexed: true }, { name: 'tokensIn', type: 'uint256', indexed: false }, { name: 'fee', type: 'uint256', indexed: false }, { name: 'bnbOut', type: 'uint256', indexed: false }, { name: 'realBnb', type: 'uint256', indexed: false }] },
|
|
20
|
+
{ type: 'event', name: 'Graduated', inputs: [{ name: 'token', type: 'address', indexed: true }, { name: 'pair', type: 'address', indexed: true }, { name: 'tokenAmount', type: 'uint256', indexed: false }, { name: 'bnbAmount', type: 'uint256', indexed: false }] },
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
export const ERC20_ABI = [
|
|
24
|
+
{ type: 'function', name: 'name', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
|
|
25
|
+
{ type: 'function', name: 'symbol', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
|
|
26
|
+
{ type: 'function', name: 'balanceOf', stateMutability: 'view', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
27
|
+
{ type: 'function', name: 'allowance', stateMutability: 'view', inputs: [{ type: 'address' }, { type: 'address' }], outputs: [{ type: 'uint256' }] },
|
|
28
|
+
{ type: 'function', name: 'approve', stateMutability: 'nonpayable', inputs: [{ type: 'address' }, { type: 'uint256' }], outputs: [{ type: 'bool' }] },
|
|
29
|
+
{ type: 'function', name: 'locked', stateMutability: 'view', inputs: [], outputs: [{ type: 'bool' }] },
|
|
30
|
+
]
|
package/src/baw.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
/** Run a `baw` command and parse its --json output. Throws with the CLI's error payload on failure. */
|
|
4
|
+
export function baw(args) {
|
|
5
|
+
let out
|
|
6
|
+
try {
|
|
7
|
+
out = execFileSync('baw', [...args, '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 8 << 20 })
|
|
8
|
+
} catch (e) {
|
|
9
|
+
const text = (e.stdout || '') + (e.stderr || '')
|
|
10
|
+
try { return JSON.parse(e.stdout) } catch { throw new Error(`baw ${args[0]} failed: ${text.trim() || e.message}`) }
|
|
11
|
+
}
|
|
12
|
+
return JSON.parse(out)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function bawAddress(chainId = '56') {
|
|
16
|
+
const r = baw(['wallet', 'address'])
|
|
17
|
+
if (!r.success) throw new Error(JSON.stringify(r.error))
|
|
18
|
+
const a = r.data.addresses.find((x) => x.binanceChainId === chainId)
|
|
19
|
+
if (!a) throw new Error(`Agentic Wallet has no address on chain ${chainId}`)
|
|
20
|
+
return a.address
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function bawSettings() {
|
|
24
|
+
const r = baw(['wallet', 'settings'])
|
|
25
|
+
if (!r.success) throw new Error(JSON.stringify(r.error))
|
|
26
|
+
return r.data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function bawStatus() {
|
|
30
|
+
const r = baw(['wallet', 'status'])
|
|
31
|
+
return r.success ? r.data.status : 'UNCONNECTED'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Preview an external contract call through Binance's risk engine. */
|
|
35
|
+
export function bawPreview({ from, to, value = 0n, data = '0x', chainId = '56' }) {
|
|
36
|
+
return baw(['contract-call', 'preview', '--binanceChainId', chainId, '--from', from, '--to', to, '--value', value.toString(), '--inputData', data])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function bawExecute(requestId) {
|
|
40
|
+
return baw(['contract-call', 'execute', '--requestId', requestId])
|
|
41
|
+
}
|
package/src/chain.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createPublicClient, http, formatEther, parseEther, encodeFunctionData, decodeEventLog } from 'viem'
|
|
2
|
+
import { bsc } from 'viem/chains'
|
|
3
|
+
import { LAUNCHPAD_ABI, ERC20_ABI } from './abi.js'
|
|
4
|
+
|
|
5
|
+
export const CONFIG = {
|
|
6
|
+
rpc: process.env.GANG_RPC_URL || 'https://bsc-dataseed.binance.org',
|
|
7
|
+
launchpad: process.env.GANG_LAUNCHPAD || '0x315Ba2e8952D06094CC636b2B84229FA32fb23A7',
|
|
8
|
+
chainId: '56',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const client = createPublicClient({ chain: bsc, transport: http(CONFIG.rpc) })
|
|
12
|
+
const pad = (functionName, args = []) => client.readContract({ address: CONFIG.launchpad, abi: LAUNCHPAD_ABI, functionName, args })
|
|
13
|
+
const erc = (address, functionName, args = []) => client.readContract({ address, abi: ERC20_ABI, functionName, args })
|
|
14
|
+
|
|
15
|
+
export const fmt = (wei) => formatEther(wei)
|
|
16
|
+
export const fmtTok = (units) => Number(formatEther(units)).toLocaleString('en-US', { maximumFractionDigits: 2 })
|
|
17
|
+
export { parseEther, encodeFunctionData }
|
|
18
|
+
|
|
19
|
+
export async function tokenInfo(token, me) {
|
|
20
|
+
const [c, name, symbol, locked, grad, cap] = await Promise.all([
|
|
21
|
+
pad('curves', [token]), erc(token, 'name'), erc(token, 'symbol'), erc(token, 'locked'),
|
|
22
|
+
pad('GRADUATION_BNB'), pad('MAX_PER_WALLET'),
|
|
23
|
+
])
|
|
24
|
+
const [creator, realBnb, sold, graduated, pair] = c
|
|
25
|
+
if (creator === '0x0000000000000000000000000000000000000000') {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`not a Gang token: ${token} (launchpad ${CONFIG.launchpad}). ` +
|
|
28
|
+
`Upgrade: npm i -g @gangfun/cli@0.1.3 --registry=https://npm.pkg.github.com OR ` +
|
|
29
|
+
`export GANG_LAUNCHPAD=0x315Ba2e8952D06094CC636b2B84229FA32fb23A7`
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
// curve end price (1e-7 BNB) for graduated tokens; live spot otherwise
|
|
33
|
+
const price = graduated ? 100000000000n : await pad('spotPrice', [token])
|
|
34
|
+
const info = {
|
|
35
|
+
token, name, symbol, creator, graduated, pair: graduated ? pair : null, locked,
|
|
36
|
+
priceBnb: Number(price) / 1e18,
|
|
37
|
+
fdvBnb: (Number(price) / 1e18) * 1e9,
|
|
38
|
+
raisedBnb: fmt(realBnb), graduationBnb: fmt(grad),
|
|
39
|
+
progressPct: Number((realBnb * 10000n) / grad) / 100,
|
|
40
|
+
soldTokens: fmtTok(sold), walletCapTokens: fmtTok(cap),
|
|
41
|
+
}
|
|
42
|
+
if (me) {
|
|
43
|
+
const [bal, room] = await Promise.all([erc(token, 'balanceOf', [me]), pad('walletRoom', [token, me])])
|
|
44
|
+
info.myBalance = fmtTok(bal); info.myBalanceRaw = bal.toString(); info.myRoomTokens = fmtTok(room); info.myRoomRaw = room.toString()
|
|
45
|
+
}
|
|
46
|
+
return info
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function listTokens(me) {
|
|
50
|
+
const n = await pad('tokenCount')
|
|
51
|
+
const addrs = await Promise.all(Array.from({ length: Number(n) }, (_, i) => pad('allTokens', [BigInt(i)])))
|
|
52
|
+
return Promise.all(addrs.map((a) => tokenInfo(a, me)))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function quoteBuy(token, buyer, bnb) {
|
|
56
|
+
const gross = parseEther(bnb)
|
|
57
|
+
const [tokensOut, bnbIn, fee, refund] = await pad('quoteBuy', [token, buyer, gross])
|
|
58
|
+
return { bnbGross: bnb, tokensOut, bnbIn, fee, refund, tokensOutFmt: fmtTok(tokensOut), bnbInFmt: fmt(bnbIn), feeFmt: fmt(fee), refundFmt: fmt(refund) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function quoteSell(token, tokensIn) {
|
|
62
|
+
const [bnbToUser, fee] = await pad('quoteSell', [token, tokensIn])
|
|
63
|
+
return { tokensIn, bnbToUser, fee, tokensInFmt: fmtTok(tokensIn), bnbToUserFmt: fmt(bnbToUser), feeFmt: fmt(fee) }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const isAgent = (a) => pad('isAgent', [a])
|
|
67
|
+
export const balanceOf = (token, a) => erc(token, 'balanceOf', [a])
|
|
68
|
+
export const allowance = (token, a) => erc(token, 'allowance', [a, CONFIG.launchpad])
|
|
69
|
+
|
|
70
|
+
export const calldata = {
|
|
71
|
+
buy: (token, minTokensOut) => encodeFunctionData({ abi: LAUNCHPAD_ABI, functionName: 'buy', args: [token, minTokensOut] }),
|
|
72
|
+
sell: (token, tokensIn, minBnbOut) => encodeFunctionData({ abi: LAUNCHPAD_ABI, functionName: 'sell', args: [token, tokensIn, minBnbOut] }),
|
|
73
|
+
approve: (amount) => encodeFunctionData({ abi: ERC20_ABI, functionName: 'approve', args: [CONFIG.launchpad, amount] }),
|
|
74
|
+
create: (name, symbol) => encodeFunctionData({ abi: LAUNCHPAD_ABI, functionName: 'create', args: [name, symbol] }),
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function decodeTx(hash) {
|
|
78
|
+
const r = await client.waitForTransactionReceipt({ hash, timeout: 120_000 })
|
|
79
|
+
const events = []
|
|
80
|
+
for (const log of r.logs) {
|
|
81
|
+
if (log.address.toLowerCase() !== CONFIG.launchpad.toLowerCase()) continue
|
|
82
|
+
try {
|
|
83
|
+
const ev = decodeEventLog({ abi: LAUNCHPAD_ABI, data: log.data, topics: log.topics })
|
|
84
|
+
const a = Object.fromEntries(Object.entries(ev.args).map(([k, v]) => [k, typeof v === 'bigint' ? (k.toLowerCase().includes('token') && k !== 'token' ? fmtTok(v) : fmt(v)) : v]))
|
|
85
|
+
events.push({ event: ev.eventName, ...a })
|
|
86
|
+
} catch { /* not ours */ }
|
|
87
|
+
}
|
|
88
|
+
return { hash, status: r.status, block: Number(r.blockNumber), events }
|
|
89
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { CONFIG, listTokens, tokenInfo, quoteBuy, quoteSell, isAgent, balanceOf, allowance, calldata, decodeTx, parseEther, fmt, fmtTok } from './chain.js'
|
|
3
|
+
import { bawAddress, bawSettings, bawStatus, bawPreview, bawExecute } from './baw.js'
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2)
|
|
6
|
+
const cmd = args[0]
|
|
7
|
+
const opt = (name, def) => { const i = args.indexOf(`--${name}`); return i >= 0 ? args[i + 1] : def }
|
|
8
|
+
const flag = (name) => args.includes(`--${name}`)
|
|
9
|
+
const out = (o) => console.log(JSON.stringify(o, (_, v) => (typeof v === 'bigint' ? v.toString() : v), 2))
|
|
10
|
+
const fail = (msg, extra = {}) => { out({ success: false, error: msg, ...extra }); process.exit(1) }
|
|
11
|
+
|
|
12
|
+
const parseSlippage = (raw) => {
|
|
13
|
+
const s = Number(raw)
|
|
14
|
+
if (!Number.isFinite(s) || s < 0 || s > 50) fail('--slippage must be a number between 0 and 50', { got: raw })
|
|
15
|
+
return s
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const HELP = `gang — agent-only launchpad on BNB Chain (Binance Agentic Wallet)
|
|
19
|
+
|
|
20
|
+
gang preflight check sign-in, Developer Mode, tradeAllTokens, Agent ID
|
|
21
|
+
gang list all tokens with price, progress, my balance
|
|
22
|
+
gang token --token <addr> one token in detail
|
|
23
|
+
gang quote --token <addr> --bnb <n> buy quote for my wallet (fee, cap, refund)
|
|
24
|
+
gang buy --token <addr> --bnb <n> [--slippage 1] preview a buy -> requestId
|
|
25
|
+
gang sell --token <addr> (--amount <tokens> | --all) [--slippage 1] preview approve or sell -> requestId
|
|
26
|
+
gang create --name <n> --symbol <s> preview token creation -> requestId
|
|
27
|
+
gang execute --request-id <id> broadcast a previewed call
|
|
28
|
+
gang tx --hash <0x..> wait for receipt and decode Gang events
|
|
29
|
+
|
|
30
|
+
env: GANG_LAUNCHPAD (default ${CONFIG.launchpad}), GANG_RPC_URL`
|
|
31
|
+
|
|
32
|
+
function summarizePreview(p, extra = {}) {
|
|
33
|
+
if (!p.success) return { success: false, step: extra.step, error: p.error }
|
|
34
|
+
const d = p.data
|
|
35
|
+
const changes = (d.simulationResult?.balanceChanges || []).map((b) => ({ token: b.contractAddress, type: b.tokenType, change: b.change }))
|
|
36
|
+
return {
|
|
37
|
+
success: true, ...extra,
|
|
38
|
+
requestId: d.requestId, expiresAt: d.expiresAt,
|
|
39
|
+
requireConfirmation: d.requireConfirmation,
|
|
40
|
+
risks: d.risks?.riskDetails || [],
|
|
41
|
+
simulatedChanges: changes,
|
|
42
|
+
next: `gang execute --request-id ${d.requestId}`,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function main() {
|
|
47
|
+
switch (cmd) {
|
|
48
|
+
case 'preflight': {
|
|
49
|
+
const issues = []
|
|
50
|
+
const status = bawStatus()
|
|
51
|
+
if (status !== 'CONNECTED') issues.push({ code: 'NOT_SIGNED_IN', fix: 'Run `baw auth signin` and confirm in the Binance App.' })
|
|
52
|
+
let me = null, settings = null, agent = false
|
|
53
|
+
if (status === 'CONNECTED') {
|
|
54
|
+
me = bawAddress(); settings = bawSettings()
|
|
55
|
+
if (!settings.devMode?.enabled) issues.push({ code: 'DEV_MODE_OFF', fix: 'Enable Developer Mode in Binance App → Agentic Wallet settings.' })
|
|
56
|
+
if (!settings.tradeAllTokens) issues.push({ code: 'TRADE_ALL_TOKENS_OFF', fix: 'Enable "Trade all tokens" in Binance App → Agentic Wallet settings, otherwise SELLING Gang tokens is blocked by risk control (buying still works).' })
|
|
57
|
+
agent = await isAgent(me)
|
|
58
|
+
if (!agent) issues.push({ code: 'NO_AGENT_ID', fix: 'Claim a .ai Agent ID in Binance Wallet (Agent ID banner). Gang only lets Agent ID holders buy.' })
|
|
59
|
+
}
|
|
60
|
+
return out({ success: issues.length === 0, status, address: me, launchpad: CONFIG.launchpad, isAgent: agent, devMode: settings?.devMode?.enabled ?? null, tradeAllTokens: settings?.tradeAllTokens ?? null, issues })
|
|
61
|
+
}
|
|
62
|
+
case 'list': {
|
|
63
|
+
const me = bawStatus() === 'CONNECTED' ? bawAddress() : undefined
|
|
64
|
+
return out({ success: true, launchpad: CONFIG.launchpad, tokens: await listTokens(me) })
|
|
65
|
+
}
|
|
66
|
+
case 'token': {
|
|
67
|
+
const token = opt('token'); if (!token) fail('--token required')
|
|
68
|
+
const me = bawStatus() === 'CONNECTED' ? bawAddress() : undefined
|
|
69
|
+
return out({ success: true, ...(await tokenInfo(token, me)) })
|
|
70
|
+
}
|
|
71
|
+
case 'quote': {
|
|
72
|
+
const token = opt('token'), bnb = opt('bnb'); if (!token || !bnb) fail('--token and --bnb required')
|
|
73
|
+
const me = bawAddress()
|
|
74
|
+
const q = await quoteBuy(token, me, bnb)
|
|
75
|
+
return out({ success: true, token, buyer: me, tokensOut: q.tokensOutFmt, bnbSpent: q.bnbInFmt, fee: q.feeFmt, refund: q.refundFmt, note: q.refund > 0n ? 'Part of the BNB would be refunded (wallet 1% cap or graduation cap).' : undefined })
|
|
76
|
+
}
|
|
77
|
+
case 'buy': {
|
|
78
|
+
const token = opt('token'), bnb = opt('bnb'); if (!token || !bnb) fail('--token and --bnb required')
|
|
79
|
+
const slippage = parseSlippage(opt('slippage', '1'))
|
|
80
|
+
const me = bawAddress()
|
|
81
|
+
const info = await tokenInfo(token, me)
|
|
82
|
+
if (info.graduated) fail('token already graduated; trade it on PancakeSwap', { pair: info.pair })
|
|
83
|
+
if (!(await isAgent(me))) fail('this wallet holds no Agent ID (.ai); buying is gated', { address: me })
|
|
84
|
+
const q = await quoteBuy(token, me, bnb)
|
|
85
|
+
if (q.tokensOut === 0n) fail('wallet cap reached: this wallet already holds 1% of supply', { myBalance: info.myBalance })
|
|
86
|
+
const minOut = (q.tokensOut * BigInt(Math.round((100 - slippage) * 100))) / 10000n
|
|
87
|
+
const data = calldata.buy(token, minOut)
|
|
88
|
+
const p = bawPreview({ from: me, to: CONFIG.launchpad, value: parseEther(bnb), data })
|
|
89
|
+
return out(summarizePreview(p, { step: 'buy', token, symbol: info.symbol, bnbSent: bnb, expectTokens: q.tokensOutFmt, expectBnbSpent: q.bnbInFmt, fee: q.feeFmt, expectRefund: q.refundFmt, minTokensOut: fmtTok(minOut) }))
|
|
90
|
+
}
|
|
91
|
+
case 'sell': {
|
|
92
|
+
const token = opt('token'); if (!token) fail('--token required')
|
|
93
|
+
const me = bawAddress()
|
|
94
|
+
const info = await tokenInfo(token, me)
|
|
95
|
+
if (info.graduated) fail('token already graduated; sell on PancakeSwap', { pair: info.pair })
|
|
96
|
+
const bal = await balanceOf(token, me)
|
|
97
|
+
if (bal === 0n) fail('no balance to sell')
|
|
98
|
+
if (!flag('all') && !opt('amount')) fail('pass --amount <tokens> or --all (full sell is no longer the default)')
|
|
99
|
+
if (flag('all') && opt('amount')) fail('use either --all or --amount, not both')
|
|
100
|
+
const amount = flag('all') ? bal : parseEther(opt('amount'))
|
|
101
|
+
if (amount > bal) fail('amount exceeds balance', { balance: fmtTok(bal) })
|
|
102
|
+
const allow = await allowance(token, me)
|
|
103
|
+
if (allow < amount) {
|
|
104
|
+
const p = bawPreview({ from: me, to: token, data: calldata.approve(amount) })
|
|
105
|
+
return out(summarizePreview(p, { step: 'approve', token, symbol: info.symbol, amount: fmtTok(amount), note: 'Approve first. After it confirms on-chain, run the same `gang sell` again to preview the sell.' }))
|
|
106
|
+
}
|
|
107
|
+
const slippage = parseSlippage(opt('slippage', '1'))
|
|
108
|
+
const q = await quoteSell(token, amount)
|
|
109
|
+
const minOut = (q.bnbToUser * BigInt(Math.round((100 - slippage) * 100))) / 10000n
|
|
110
|
+
const p = bawPreview({ from: me, to: CONFIG.launchpad, data: calldata.sell(token, amount, minOut) })
|
|
111
|
+
return out(summarizePreview(p, { step: 'sell', token, symbol: info.symbol, amount: fmtTok(amount), expectBnb: q.bnbToUserFmt, fee: q.feeFmt, minBnbOut: fmt(minOut) }))
|
|
112
|
+
}
|
|
113
|
+
case 'create': {
|
|
114
|
+
const name = opt('name'), symbol = opt('symbol'); if (!name || !symbol) fail('--name and --symbol required')
|
|
115
|
+
const me = bawAddress()
|
|
116
|
+
const p = bawPreview({ from: me, to: CONFIG.launchpad, data: calldata.create(name, symbol) })
|
|
117
|
+
return out(summarizePreview(p, { step: 'create', name, symbol, note: 'No creation fee. The token address appears in the Created event: run `gang tx --hash <txHash>` after execute.' }))
|
|
118
|
+
}
|
|
119
|
+
case 'execute': {
|
|
120
|
+
const id = opt('request-id'); if (!id) fail('--request-id required')
|
|
121
|
+
const r = bawExecute(id)
|
|
122
|
+
if (!r.success) return out({ success: false, error: r.error })
|
|
123
|
+
const d = r.data
|
|
124
|
+
const pending = d.status === 'PENDING_CONFIRMATION'
|
|
125
|
+
return out({ success: true, status: d.status, txHash: d.txHash, orderId: d.orderId, pendingConfirmation: pending, next: pending ? 'Ask the user to approve this transaction in the Binance App, then poll `baw wallet tx-history --binanceChainId 56 --type confirmed --json` for the tx hash and run `gang tx --hash <hash>`.' : (d.txHash ? `gang tx --hash ${d.txHash}` : undefined) })
|
|
126
|
+
}
|
|
127
|
+
case 'tx': {
|
|
128
|
+
const hash = opt('hash'); if (!hash) fail('--hash required')
|
|
129
|
+
return out({ success: true, ...(await decodeTx(hash)) })
|
|
130
|
+
}
|
|
131
|
+
default:
|
|
132
|
+
console.log(HELP); process.exit(cmd ? 1 : 0)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main().catch((e) => fail(e.message))
|