@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 CHANGED
@@ -1,3 +1,87 @@
1
- # Temporary Holding Version
1
+ # Gang CLI (`gang`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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)
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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))