Back to skills
SkillHub ClubAnalyze Data & AIFull StackData / AI

four-meme-ai

CLI tool for creating and trading meme tokens on Four.Meme (BSC), with structured JSON outputs for config, token details, pricing quotes, on-chain events, and TaxToken fee configuration.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
3,072
Hot score
99
Updated
March 20, 2026
Overall rating
C4.0
Composite score
4.0
Best-practice grade
C64.8

Install command

npx @skill-hub/cli install openclaw-skills-four-meme-ai

Repository

openclaw/skills

Skill path: skills/four-meme-community/four-meme-ai

CLI tool for creating and trading meme tokens on Four.Meme (BSC), with structured JSON outputs for config, token details, pricing quotes, on-chain events, and TaxToken fee configuration.

Open repository

Best for

Primary workflow: Analyze Data & AI.

Technical facets: Full Stack, Data / AI.

Target audience: everyone.

License: MIT.

Original source

Catalog source: SkillHub Club.

Repository owner: openclaw.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install four-meme-ai into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/openclaw/skills before adding four-meme-ai to shared team environments
  • Use four-meme-ai for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: four-meme-ai
description: |
  CLI tool for creating and trading meme tokens on Four.Meme (BSC), with structured JSON outputs for config, token details, pricing quotes, on-chain events, and TaxToken fee configuration.

  

allowed-tools:
  - Bash(fourmeme *)
  - Bash(npx fourmeme *)
license: MIT
metadata:
  {"author":"Four.meme AI Skill","version":"1.0.4","openclaw":{"requires":{"env":["PRIVATE_KEY"]},"primaryEnv":"PRIVATE_KEY","optionalEnv":["BSC_RPC_URL"]}}
---

## [Agent must follow] User agreement and security notice on first use

When responding to any user request about fourmeme or this skill, **you must first** present the content below in this order: **User Agreement, then Security Notice**, and state clearly that by choosing to continue, the user agrees to the User Agreement.  
Until the user has explicitly agreed or confirmed to continue, **do not** run `create-api`, `create-chain`, `buy`, `sell`, `send`, or `8004-register` (any operation that uses the private key or writes to the chain). Read-only commands (e.g. `config`, `token-info`, `quote-buy`, `8004-balance`) may be run while or after presenting the notice.

**Language selection**: Present the User Agreement and Security Notice **in the user’s language**. If the user is writing in **Traditional Chinese (繁體中文)**, use the **繁體中文** block below; otherwise use the **English** block.

---

### User Agreement & Security Notice (繁體中文)

**用戶協議**

**前情提示**:使用本插件及本 skill 所涉功能(包括但不限於代幣創建、買賣、轉帳、8004 註冊等)前,請您閱讀以下協議。**若您選擇繼續使用本插件及本 skill 功能,即表示您已閱讀、理解並同意本協議。**

**本插件性質與責任限制**:本插件僅提供純本地的命令列互動能力(私鑰透過環境變數或本地設定使用),**不會收集、上傳或儲存您的私鑰**。因任何原因(包括但不限於插件被竄改、環境遭入侵、誤操作、第三方插件等)導致的私鑰洩露或資產損失,**本插件及提供方不承擔責任**。

**安全警示**

使用本插件進行代幣創建、買賣、轉帳等操作時,請務必注意:

- **保護私鑰**:切勿在聊天對話中輸入、貼上或洩露私鑰;不要將私鑰分享給任何人或任何第三方。
- **交易錢包僅存小額資金**:用於執行操作的錢包(即提供 PRIVATE_KEY 的錢包)建議只存放少量資金,以降低因洩露或誤操作導致的損失。
- **及時轉出資金**:完成交易後,請及時將交易錢包中的資產轉移到您自己控制的、更安全的錢包或冷錢包中。
- **謹慎安裝 Agent/插件**:下載或安裝任何 Agent、瀏覽器插件或第三方工具時,請確認來源可信,避免惡意插件竊取私鑰或助記詞。

---

### User Agreement & Security Notice (English)

**User Agreement**

**Notice**: Before using this plugin and this skill (including but not limited to token creation, buy/sell, transfers, 8004 registration), please read the following. **By choosing to continue using this plugin and this skill, you have read, understood, and agreed to this agreement.**

**Plugin nature and limitation of liability**: This plugin provides local-only CLI interaction (private key is used via environment or local config). It **does not collect, upload, or store your private key**. The plugin and its providers **are not liable** for private key disclosure or asset loss due to any cause (including but not limited to tampered plugin, compromised environment, user error, or third-party plugins).

**Security Notice**

When using this plugin for token creation, trading, or transfers, please:

- **Protect your private key**: Do not type, paste, or expose your private key in chat; do not share it with anyone or any third party.
- **Keep only small amounts in the trading wallet**: The wallet used for operations (the one whose PRIVATE_KEY you provide) should hold only a small amount of funds to limit loss from disclosure or mistakes.
- **Move funds out promptly**: After trading, move assets from the trading wallet to a wallet or cold storage you control.
- **Install agents/plugins carefully**: When installing any agent, browser extension, or third-party tool, verify the source to avoid malware that could steal your private key or seed phrase.

---

## fourmeme capability overview

After you agree to the above and confirm to continue, this skill can help you with the following (all via the `fourmeme` CLI on BSC):

| Category | Capability | Description |
|----------|-------------|-------------|
| **Create** | Create token | Upload image + name/symbol/description/label; optional tax-token params; API returns signature then on-chain create. |
| **Query** | Public config | Get raisedToken and other public config (no auth). |
| **Query** | Token info (on-chain) | By address: version, tokenManager, price, offers (Helper3). |
| **Query** | Token list / detail / rankings | REST: filtered paginated list, single-token detail and trading info, hot/24h volume/newest/graduated rankings. |
| **Trading** | Buy/sell quotes | Estimate cost or proceeds for buy or sell (no transaction sent). |
| **Trading** | Execute buy | Buy a given token by amount or by quote spent (requires PRIVATE_KEY). |
| **Trading** | Execute sell | Sell a given token amount; optional minimum quote received (requires PRIVATE_KEY). |
| **Other** | Event listening | Fetch TokenCreate, TokenPurchase, TokenSale, LiquidityAdded on-chain. |
| **Other** | Tax token fees | Query on-chain fee and burn/dividend/liquidity config for a token. |
| **Other** | Send | Send BNB or ERC20 to a given address (requires PRIVATE_KEY). |
| **Other** | EIP-8004 | Register 8004 identity NFT; query balance by address. |

See the **CLI (fourmeme)** table and sections below for commands and arguments.

---

# fourmeme CLI

BSC only; all commands output JSON.

## CLI (fourmeme)

**Installation (required):** `npm install -g @four-meme/four-meme-ai@latest`. After install, run `fourmeme <command> [args]`; with local install only, use `npx fourmeme <command> [args]` from the project root. Run `fourmeme --help` for usage.

This skill provides: token creation (API + chain), buy/sell quotes and execution, token info/list/rankings, event listening, Tax token fee queries, send, and EIP-8004 identity NFT register and balance. Contract addresses: [references/contract-addresses.md](references/contract-addresses.md). **TokenManager V1 is not supported.**

### PRIVATE_KEY and BSC_RPC_URL

**When using OpenClaw**  
This skill declares `requires.env: ["PRIVATE_KEY"]` and `primaryEnv: "PRIVATE_KEY"` in metadata; OpenClaw injects them only when an agent runs with **this skill enabled** (other skills cannot access them).

**Required steps:**
1. **Configure private key**: In the Skill management page, set the four-meme-ai skill’s **apiKey** (corresponds to `primaryEnv: "PRIVATE_KEY"`), or set `PRIVATE_KEY` under `skills.entries["four-meme-ai"].env` in `~/.openclaw/openclaw.json`. Optionally set **BSC_RPC_URL** in global env if needed.
2. **Enable this skill**: In the agent or session, ensure the **four-meme-ai** skill is **enabled**. Only when the skill is enabled will OpenClaw inject **PRIVATE_KEY** into the process; otherwise create/buy/sell/send/8004-register will fail with missing key. **BSC_RPC_URL** is optional (metadata: `optionalEnv`); if not set, scripts use a default BSC RPC.

**When not using OpenClaw (standalone)**  
Set **PRIVATE_KEY** and optionally **BSC_RPC_URL** via the process environment so they are available when running `npx fourmeme` or `node bin/fourmeme.cjs`:

- **.env file**: Put a `.env` file in **the directory where you run the `fourmeme` command** (i.e. your project / working directory). Example: if you run `fourmeme quote-buy ...` from `/path/to/my-project`, place `.env` at `/path/to/my-project/.env`. The CLI automatically loads `.env` from that current working directory. Use lines like `PRIVATE_KEY=...` and `BSC_RPC_URL=...`. Do not commit `.env`; add it to `.gitignore`.
- **Shell export**: `export PRIVATE_KEY=your_hex_key` and `export BSC_RPC_URL=https://bsc-dataseed.binance.org` (or another BSC RPC), then run `npx fourmeme <command> ...`.

### Declared and optional environment variables

- **Declared in registry metadata** (injected by OpenClaw when skill is enabled): **PRIVATE_KEY** (required for write operations). Optional in metadata: **BSC_RPC_URL** (scripts fall back to default BSC RPC if unset).
- **Not in metadata; optional, may be set in env or project `.env`**: **BSC_RPC_URL**, **CREATION_FEE_WEI** (extra BNB on create), **WEB_URL**, **TWITTER_URL**, **TELEGRAM_URL**, **PRE_SALE**, **FEE_PLAN**, **8004_NFT_ADDRESS** / **EIP8004_NFT_ADDRESS**. Only **PRIVATE_KEY** is required for signing; others have defaults or are used only for specific commands (see Create token flow, EIP-8004, etc.). Tax token params use CLI only (`--tax-options=` or `--tax-token --tax-fee-rate=...`).

### Execution and install

- **Invocation**: The agent must run commands only via the **fourmeme** CLI: `fourmeme <command> [args]` or `npx fourmeme <command> [args]` (allowed-tools). Do not invoke scripts or `npx tsx` directly; the CLI entry (`bin/fourmeme.cjs`) dispatches to the correct script and loads `.env` from the current working directory.

| Need | Command | When |
|------|---------|------|
| Public config | `fourmeme config` | Get raisedToken / config (no auth) |
| Token info (on-chain) | `fourmeme token-info <tokenAddress>` | Version, tokenManager, price, offers (BSC Helper3) |
| Token list (REST) | `fourmeme token-list [--orderBy=] [--pageIndex=] [--pageSize=] [--tokenName=] [--symbol=] [--labels=] [--listedPancake=]` | Filtered, paginated token list |
| Token detail (REST) | `fourmeme token-get <tokenAddress>` | Token detail and trading info (get/v2) |
| Token rankings (REST) | `fourmeme token-rankings <orderBy> [--barType=HOUR24]` | Time / ProgressDesc / TradingDesc / Hot / Graduated; barType for TradingDesc |
| Buy quote | `fourmeme quote-buy <token> <amountWei> [fundsWei]` | Estimate only; no transaction |
| Sell quote | `fourmeme quote-sell <token> <amountWei>` | Estimate only; no transaction |
| **Execute buy** | `fourmeme buy <token> amount <amountWei> <maxFundsWei>` | Buy fixed amount (needs PRIVATE_KEY) |
| **Execute buy** | `fourmeme buy <token> funds <fundsWei> <minAmountWei>` | Spend fixed quote (e.g. BNB) (needs PRIVATE_KEY) |
| **Execute sell** | `fourmeme sell <token> <amountWei> [minFundsWei]` | Sell (needs PRIVATE_KEY) |
| **Send** | `fourmeme send <toAddress> <amountWei> [tokenAddress]` | Send BNB or ERC20 to address (needs PRIVATE_KEY) |
| **EIP-8004 register** | `fourmeme 8004-register <name> [imageUrl] [description]` | Register 8004 identity NFT (needs PRIVATE_KEY) |
| **EIP-8004 query** | `fourmeme 8004-balance <ownerAddress>` | Query 8004 NFT balance (read-only) |
| Events | `fourmeme events <fromBlock> [toBlock]` | TokenCreate / Purchase / Sale / LiquidityAdded |
| Tax token info | `fourmeme tax-info <tokenAddress>` | Fee/tax config for TaxToken |
| Read-only check | `fourmeme verify` | Run config + events (last 50 blocks) |

Chain: **BSC only** (Arbitrum/Base not supported).

### Create token (full flow)

**1. Ask user for required information (must be done first)**

Before calling `create-instant`, the Agent **must** ask the user for and confirm:

| Info | Required | Description |
|------|----------|-------------|
| **Image path** (imagePath) | Yes | Local logo path; jpeg/png/gif/bmp/webp |
| **Token name** (name) | Yes | Full token name |
| **Token symbol** (shortName) | Yes | e.g. MTK, DOGE |
| **Description** (desc) | Yes | Token description text |
| **Label** (label) | Yes | One of: Meme \| AI \| Defi \| Games \| Infra \| De-Sci \| Social \| Depin \| Charity \| Others |
| **Tax token?** | No | If yes, ask for tokenTaxInfo (feeRate, four rates, recipientAddress, minSharing); see "tokenTaxInfo parameters" below |

Optional: `--web-url=`, `--twitter-url=`, `--telegram-url=`, `--pre-sale=` (BNB), `--fee-plan=`; may be provided or left at defaults.

**2. Technical flow (done by create-instant)**

After collecting the above, execute in this order (handled by scripts or CLI):

1. **Get nonce** — `POST /private/user/nonce/generate` with body accountAddress, verifyType, networkCode (BSC).
2. **Login** — Sign `You are sign in Meme {nonce}` with wallet; `POST /private/user/login/dex` to get access_token.
3. **Upload image** — `POST /private/token/upload` with access_token in header and image as body; get imgUrl.
4. **Create** — `fourmeme create-instant --image= --name= --short-name= --desc= --label= [options]` runs API create and submits `TokenManager2.createToken` on BSC in one command.

> **Split flow (optional):** Step 4 — GET `/public/config` for raisedToken; `POST /private/token/create` with name, shortName, desc, imgUrl, label, raisedToken, etc.; get createArg, signature. Step 5 — Call `TokenManager2.createToken(createArg, sign)` on BSC. Use `fourmeme create-api` then `fourmeme create-chain` for this split flow.

**Commands:** `fourmeme create-instant --image= --name= --short-name= --desc= --label= [options]` (recommended). Or split: `fourmeme create-api ...` then `fourmeme create-chain <createArgHex> <signatureHex>`. Full params: `fourmeme --help`. References: [api-create-token.md](references/api-create-token.md), [create-token-scripts.md](references/create-token-scripts.md), [token-tax-info.md](references/token-tax-info.md).

**Tax token**  
- **Option 1**: `--tax-options=<path>` — path to JSON file with `{ "tokenTaxInfo": { ... } }`; fields see “tokenTaxInfo parameters” below.  
- **Option 2**: CLI: `--tax-token --tax-fee-rate=5 --tax-burn-rate=0 --tax-divide-rate=0 --tax-liquidity-rate=100 --tax-recipient-rate=0 --tax-recipient-address= --tax-min-sharing=100000` (burn+divide+liquidity+recipient = 100). See [references/token-tax-info.md](references/token-tax-info.md).

**tokenTaxInfo parameters** (required for tax token, via JSON or CLI):

| Field | Type | Description | Constraint |
|-------|------|-------------|------------|
| `feeRate` | number | Trading fee rate (%) | **Only** `1`, `3`, `5`, `10` |
| `burnRate` | number | Burn share (%) | Sum with next three = 100 |
| `divideRate` | number | Dividend share (%) | Same |
| `liquidityRate` | number | Liquidity share (%) | Same |
| `recipientRate` | number | Recipient share (%) | 0 if unused |
| `recipientAddress` | string | Recipient address | `""` if unused |
| `minSharing` | number | Min balance for dividend (ether units) | d×10ⁿ, n≥5, 1≤d≤9; e.g. 100000, 1000000 |

Example (5% fee, 20% burn, 30% dividend, 40% liquidity, 10% recipient):

```json
{
  "tokenTaxInfo": {
    "feeRate": 5,
    "burnRate": 20,
    "divideRate": 30,
    "liquidityRate": 40,
    "recipientRate": 10,
    "recipientAddress": "0x1234567890123456789012345678901234567890",
    "minSharing": 100000
  }
}
```

### token-info

```bash
fourmeme token-info <tokenAddress>
```
On-chain query (Helper3); returns version, tokenManager, price, offers, etc.

### token-list / token-get / token-rankings (REST)

Four.meme REST API; use `Accept: application/json`; no login or cookie.

**Token list (filter / paginate)**  
```bash
fourmeme token-list [--orderBy=Hot] [--pageIndex=1] [--pageSize=30] [--tokenName=] [--symbol=] [--labels=] [--listedPancake=false]
```

**Token detail and trading info**  
```bash
fourmeme token-get <tokenAddress>
```
API: GET `/private/token/get/v2?address=...`

**Rankings**  
```bash
fourmeme token-rankings <orderBy> [--barType=HOUR24]
```
orderBy: `Time` (newest) | `ProgressDesc` | `TradingDesc` (24h volume; barType HOUR24) | `Hot` | `Graduated`. Output JSON.

### quote-buy / quote-sell (estimate only; no transaction)

```bash
fourmeme quote-buy <tokenAddress> <amountWei> [fundsWei]
fourmeme quote-sell <tokenAddress> <amountWei>
```
- amountWei: token amount (use 0 when buying by quote amount); fundsWei: quote to spend (omit or 0 when buying by token amount).

### buy / sell (execute; requires PRIVATE_KEY)

**Buy** (one of):  
- By amount: `fourmeme buy <token> amount <amountWei> <maxFundsWei>` (spend at most maxFundsWei to buy amountWei tokens).  
- By funds: `fourmeme buy <token> funds <fundsWei> <minAmountWei>` (spend fundsWei quote, receive at least minAmountWei tokens).

**Sell**: Script performs approve then sell.  
```bash
fourmeme sell <tokenAddress> <amountWei> [minFundsWei]
```
- minFundsWei optional (slippage: minimum quote received). V2 tokens only.

### send (send BNB or ERC20 to an address)

Send **native BNB** or **ERC20** from the current wallet (PRIVATE_KEY) to a given address (BSC).

```bash
fourmeme send <toAddress> <amountWei> [tokenAddress]
```

| Argument | Description |
|----------|-------------|
| `toAddress` | Recipient address (0x...) |
| `amountWei` | Amount in wei (BNB or token smallest unit) |
| `tokenAddress` | Optional. Omit or use `BNB` / `0x0` for native BNB; otherwise ERC20 contract address |

- Env: `PRIVATE_KEY`. Optional: `BSC_RPC_URL`.
- Output: JSON with `txHash`, `to`, `amountWei`, `native` (whether BNB).

Examples:
```bash
# Send 0.1 BNB (1e17 wei)
fourmeme send 0x1234...abcd 100000000000000000

# Send 1000 units of an ERC20 (18 decimals)
fourmeme send 0x1234...abcd 1000000000000000000000 0xTokenContractAddress
```

### EIP-8004 identity NFT (register and query)

EIP-8004 identity NFT: **register** (mint) and **query balance**. Default contract: `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` (BSC). Override with env `8004_NFT_ADDRESS` or `EIP8004_NFT_ADDRESS`.

**Register**: Requires `PRIVATE_KEY`. User provides name (required), image URL (optional), description (optional). CLI builds EIP-8004 payload (type, name, description, image, active, supportedTrust), encodes as `data:application/json;base64,<base64>` for `agentURI`, and calls `register(string agentURI)`.

```bash
fourmeme 8004-register <name> [imageUrl] [description]
```

| Argument | Description |
|----------|-------------|
| `name` | Required. Identity name |
| `imageUrl` | Optional. Avatar/image URL (must be publicly reachable) |
| `description` | Optional. Description |

- Output JSON: `txHash`, `agentId` (from event), `agentURI`.

**Query**: Read-only; number of 8004 NFTs held by an address.

```bash
fourmeme 8004-balance <ownerAddress>
```

- Output JSON: `owner`, `balance`.

Examples:
```bash
fourmeme 8004-register "myagent" "https://example.com/logo.png" "My agent description"
fourmeme 8004-balance 0x1234567890123456789012345678901234567890
```

### events (TokenManager2 V2 only)

Fetch TokenCreate, TokenPurchase, TokenSale, LiquidityAdded on BSC.

```bash
fourmeme events <fromBlock> [toBlock]
```
Omit toBlock for latest. Real-time subscription: [references/event-listening.md](references/event-listening.md).

### tax-info (TaxToken fee/tax)

Only for TaxToken (creatorType 5).

```bash
fourmeme tax-info <tokenAddress>
```
See [references/tax-token-query.md](references/tax-token-query.md).

## Agent workflow: buy/sell from rankings or events

This skill supports a flow to discover tokens, get details, quote, and execute. The following is an example workflow, not a trading recommendation: discover → detail → quote → execute.

1. **Discover** (one or more of):  
   - **Rankings**: `fourmeme token-rankings <orderBy>` (orderBy = Hot, TradingDesc, Time, ProgressDesc, Graduated); use token addresses from the response.  
   - **List**: `fourmeme token-list [--orderBy=] [--labels=] ...` to filter and get addresses.  
   - **On-chain events**: `fourmeme events <fromBlock> [toBlock]`; parse token addresses from TokenCreate/TokenPurchase, etc., for "newly created" or "recent trades" strategies.
2. **Get details**: For each candidate, call `fourmeme token-get <address>` (REST detail and trading info) or `fourmeme token-info <address>` (on-chain version, tokenManager, price, offers) to filter or display.
3. **Quote**: `fourmeme quote-buy <token> <amountWei> [fundsWei]` / `fourmeme quote-sell <token> <amountWei>` for estimated cost or proceeds.
4. **Execute**: `fourmeme buy ...` / `fourmeme sell ...` (requires PRIVATE_KEY). **Before executing, the Agent must confirm user intent** (e.g. user said "buy 0.05 BNB each for top 5 by 24h volume" or "auto-buy 0.01 BNB for new tokens") and obtain explicit confirmation before first automated execution to avoid unauthorized use of funds.

When the user asks to "buy/sell based on rankings or activity", the Agent should clarify: which ranking (hot, 24h volume, newest, graduated, etc.), amount per token, and whether to quote only or also execute; then run the appropriate commands.

## Trading (Buy / Sell)

- **Version** – Use TokenManagerHelper3 `getTokenInfo(token)`. If `version === 1` use V1 TokenManager; if `version === 2` use TokenManager2 (and check for X Mode / TaxToken / AntiSniperFeeMode if needed).
- **Quote (pre-calc)** – TokenManagerHelper3:  
  - Buy: `tryBuy(token, amount, funds)` – use `amount > 0` for "buy X tokens", or `funds > 0` for "spend X quote".  
  - Sell: `trySell(token, amount)`.
- **Execute** – Use the `tokenManager` address from `getTokenInfo` and call the corresponding contract:  
  - V1: `purchaseToken` / `purchaseTokenAMAP`, `saleToken`.  
  - V2: `buyToken` / `buyTokenAMAP`, `sellToken`. For sell, user must `ERC20.approve(tokenManager, amount)` first.  
  - X Mode tokens: use TokenManager2 `buyToken(bytes args, uint256 time, bytes signature)` with encoded `BuyTokenParams`.

## API and config reference

- **Token detail (REST)**: `GET /private/token/get?address=<token>`, `GET /private/token/getById?id=<requestId>` (requestId from TokenCreate event). Response may include `data.aiCreator` (token created by Agent). List/rankings: [references/token-query-api.md](references/token-query-api.md).
- **Agent Creator / Agent wallets**: On-chain — token template bit 85 for “created by agent”; AgentIdentifier contract (`isAgent(wallet)`) on BSC to identify agent wallets. See [references/agent-creator-and-wallets.md](references/agent-creator-and-wallets.md) and [references/contract-addresses.md](references/contract-addresses.md).
- **raisedToken**: `GET https://four.meme/meme-api/v1/public/config` for current raisedToken; use as-is in create body; do not modify its fields.

## References

| Document | Description |
|----------|-------------|
| [contract-addresses.md](references/contract-addresses.md) | TokenManager / TokenManager2 / Helper3 addresses (BSC) |
| [api-create-token.md](references/api-create-token.md) | Create token API (nonce / login / upload / create) |
| [create-token-scripts.md](references/create-token-scripts.md) | Create token script flow and examples |
| [token-tax-info.md](references/token-tax-info.md) | Tax token tokenTaxInfo parameters and constraints |
| [token-query-api.md](references/token-query-api.md) | Token list / detail / rankings REST API |
| [errors.md](references/errors.md) | buy/sell error codes; X Mode / TaxToken / AntiSniperFeeMode; Agent Creator |
| [agent-creator-and-wallets.md](references/agent-creator-and-wallets.md) | Token created by Agent Creator; AgentIdentifier contract and Agent wallets |
| [execute-trade.md](references/execute-trade.md) | Execute buy/sell CLI and contract usage |
| [event-listening.md](references/event-listening.md) | TokenManager2 event listening (V2) |
| [tax-token-query.md](references/tax-token-query.md) | TaxToken on-chain fee/tax query (tax-info) |
| **Official four.meme API and contracts (online)**: [Protocol Integration](https://four-meme.gitbook.io/four.meme/brand/protocol-integration) | API documents, ABIs: TokenManager, TokenManager2, Helper3, TaxToken, AgentIdentifier, etc. |


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/contract-addresses.md

```markdown
# Four.meme Contract Addresses (BSC only)

This skill supports **BSC only** (chainId 56). Arbitrum and Base are not supported.

## TokenManager (V1)

Used for tokens created **before** September 5, 2024. Only trading (no create).

| Chain | Address |
|-------|---------|
| BSC   | `0xEC4549caDcE5DA21Df6E6422d448034B5233bFbC` |

## TokenManager2 (V2)

Used for tokens created **after** September 5, 2024. Create and trade.

| Chain | Address |
|-------|---------|
| BSC   | `0x5c952063c7fc8610FFDB798152D69F0B9550762b` |

## TokenManagerHelper3 (V3)

Use for **getTokenInfo**, **tryBuy**, **trySell**. Supports both V1 and V2 tokens.

| Chain | Address |
|-------|---------|
| BSC   | `0xF251F83e40a78868FcfA3FA4599Dad6494E46034` |

## AgentIdentifier (BSC)

Used to check if a wallet is an **Agent wallet** (holds an Agent NFT). See [agent-creator-and-wallets.md](agent-creator-and-wallets.md).

| Chain | Address |
|-------|---------|
| BSC   | `0x09B44A633de9F9EBF6FB9Bdd5b5629d3DD2cef13` |

- **Interface**: `isAgent(address wallet)`, `nftCount()`, `nftAt(uint256 index)`.

---

## Usage

1. Call **TokenManagerHelper3.getTokenInfo(token)** to get `version` and `tokenManager`.
2. If `version === 1` → use TokenManager V1 for buy/sell.
3. If `version === 2` → use TokenManager2 for buy/sell (and check X Mode / TaxToken / AntiSniperFeeMode if needed).
4. Token creation is only via **TokenManager2** on BSC after obtaining `createArg` and `signature` from the four.meme API.
5. **Agent Creator**: Token created by agent wallet — on-chain `(template & (1 << 85)) != 0`; off-chain API `data.aiCreator === true`. Agent wallets: call **AgentIdentifier.isAgent(wallet)** on BSC.

```

### references/api-create-token.md

```markdown
# Four.meme API – Create Token

Base URL: `https://four.meme/meme-api/v1`

## Endpoints (in order)

| Step | Method | Path | Description |
|------|--------|------|-------------|
| 1 | POST | `/private/user/nonce/generate` | Get nonce |
| 2 | POST | `/private/user/login/dex` | Login → access_token |
| 3 | POST | `/private/token/upload` | Upload image → imgUrl |
| 4 | POST | `/private/token/create` | Create → createArg, signature |

## 1. Nonce

- **URL**: `https://four.meme/meme-api/v1/private/user/nonce/generate`
- **Body**: `{ "accountAddress": "<wallet>", "verifyType": "LOGIN", "networkCode": "BSC" }`
- **Response**: `{ "code": "0", "data": "<nonce>" }`

## 2. Login

- **URL**: `https://four.meme/meme-api/v1/private/user/login/dex`
- **Body**: Include `verifyInfo.signature` = sign(`You are sign in Meme {nonce}`) with wallet, `verifyInfo.address`, `verifyInfo.networkCode` ("BSC"), `verifyInfo.verifyType` ("LOGIN"), plus region, langType, walletName, etc.
- **Response**: `{ "code": "0", "data": "<access_token>" }`

## 3. Upload image

- **URL**: `https://four.meme/meme-api/v1/private/token/upload`
- **Headers**: `meme-web-access: <access_token>`, `Content-Type: multipart/form-data`
- **Body**: `file` = image (jpeg/png/gif/bmp/webp)
- **Response**: `{ "code": "0", "data": "<imgUrl>" }`

## 4. Create token

- **URL**: `https://four.meme/meme-api/v1/private/token/create`
- **Headers**: `meme-web-access: <access_token>`, `Content-Type: application/json`
- **Body**: **Required**: **raisedAmount** (raise amount; use 24 or raisedToken.totalBAmount from public config), **raisedToken** (from `GET /public/config`; do not modify). Customizable: name, shortName, desc, imgUrl, launchTime, label, lpTradingFee (0.0025), webUrl, twitterUrl, telegramUrl, preSale, onlyMPC, feePlan, tokenTaxInfo (optional).
- **Response**: `{ "code": "0", "data": { "createArg": "...", "signature": "..." } }`

Then call **TokenManager2.createToken(createArg, sign)** on BSC with these values (as bytes).

## Labels

Meme, AI, Defi, Games, Infra, De-Sci, Social, Depin, Charity, Others.

## tokenTaxInfo (optional; Tax-type token)

Omit to create a normal token; include to create a tax-type token.

- **feeRate**: Trading fee; must be **1, 3, 5, or 10** (1%/3%/5%/10%).
- **burnRate**, **divideRate**, **liquidityRate**, **recipientRate**: All percentages; **sum of the four must be 100**.
- **recipientAddress**: Address that receives the recipient share; use `""` when not used, and **recipientRate = 0**.
- **minSharing**: Min balance to participate in dividends (ether units); must satisfy **minSharing = d×10ⁿ**, n≥5, 1≤d≤9, e.g. 100000, 1000000.

Full field description and examples: [token-tax-info.md](token-tax-info.md).

```

### references/create-token-scripts.md

```markdown
# Create Token Scripts (Four.meme)

**Recommended:** Use `fourmeme create-instant` for one-shot token creation (API + on-chain in one command). Use the step-by-step flow only when you need to inspect or modify the API output before submitting on-chain.

## One-shot (create-instant) — recommended

**create-token-instant.ts** runs API create + on-chain submit in one command. Same args as create-token-api; on success submits createToken and outputs `txHash`.

- All options as `--key=value`; no positionals.
- **Required**: `--image=`, `--name=`, `--short-name=`, `--desc=`, `--label=`.
- **Optional**: `--web-url=`, `--twitter-url=`, `--telegram-url=` (only sent when non-empty); `--pre-sale=0` (**presale in ether units**, e.g. `0.001` for 0.001 BNB, not wei); `--fee-plan=false`, `--tax-options=<path>`; `--value=<wei>` (default to be auto calculated, override BNB value sent; otherwise API output `creationFeeWei` is used).
- **Tax token**: `--tax-options=tax.json` or `--tax-token` with `--tax-fee-rate=5` `--tax-burn-rate=0` `--tax-divide-rate=0` `--tax-liquidity-rate=100` `--tax-recipient-rate=0` `--tax-recipient-address=` `--tax-min-sharing=100000` (burn+divide+liquidity+recipient=100).
- **Label** (exactly one): `Meme` | `AI` | `Defi` | `Games` | `Infra` | `De-Sci` | `Social` | `Depin` | `Charity` | `Others`.
- **Env**: `PRIVATE_KEY`; RPC via `BSC_RPC_URL`.
- **Flow**: nonce → login → upload image → GET public config → POST create → submit `TokenManager2.createToken` on BSC.
- **Output**: JSON `{ "txHash" }`.

```bash
# Via CLI (recommended)
fourmeme create-instant --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI

# With presale (BNB, ether units)
fourmeme create-instant --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI --pre-sale=0.001

# Tax token
fourmeme create-instant --image=./logo.png --name=TaxToken --short-name=TAX --desc="Tax" --label=Meme --tax-options=tax.json
```

---

## Step-by-step flow (create-api → create-chain)

1. **get-public-config.ts** (optional)  
   Fetches `raisedToken` from `https://four.meme/meme-api/v1/public/config`. Use when building the create body manually.

2. **create-token-api.ts**  
   - All options as `--key=value`; no positionals.  
   - Required: `--image=`, `--name=`, `--short-name=`, `--desc=`, `--label=`.  
   - Optional: `--web-url=`, `--twitter-url=`, `--telegram-url=` (only sent when non-empty); `--pre-sale=0` (**presale in ether units**, e.g. `0.001` for 0.001 BNB, not wei); `--fee-plan=false`, `--tax-options=<path>`.  
   - Tax token: `--tax-options=tax.json` or `--tax-token` with `--tax-fee-rate=5` etc. (burn+divide+liquidity+recipient=100).  
   - Env: `PRIVATE_KEY`; RPC via `BSC_RPC_URL`.  
   - Flow: nonce → login → upload image → GET public config → POST create.  
   - Output: JSON `{ "createArg", "signature", "creationFeeWei" }`; script hints required value for chain step.

3. **create-token-chain.ts**  
   - Env: `PRIVATE_KEY`.  
   - Input: `createArg`, `signature` (positional or stdin JSON with `--`).  
   - Optional (CLI overrides env): `--value=<wei>` (env `CREATION_FEE_WEI`). RPC via env `BSC_RPC_URL`.  
   - Calls `TokenManager2.createToken(createArg, sign)`; excess BNB is refunded by the contract.

## createToken value (CREATION_FEE_WEI) formula

**Formula**

- **No presale (or quote is ERC20)**  
  `value = launch_fee`  
  `launch_fee`: read from TokenManager2 `_launchFee()` (wei).

- **With presale and quote is BNB**  
  `value = launch_fee + presale_wei + trading_fee`  
  - `presale_wei`: presale amount in wei (API sends preSale in BNB/ether; script converts to wei for value).  
  - `trading_fee`: see below.

**How to compute trading_fee**

- Contract uses basis-point rate: `trading_fee = presale_wei × fee_rate / 10000` (integer division).  
- `fee_rate` from TokenManager2 `_tradingFeeRate()` (basis points).  
- If the contract enforces a minimum fee per trade, use `max(computed trading_fee, minimum_fee)`.

## Example (instant, recommended)

```bash
export PRIVATE_KEY=your_hex_private_key
fourmeme create-instant --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI
```

## Example (piped)

```bash
export PRIVATE_KEY=your_hex_private_key
npx tsx skills/four-meme-integration/scripts/create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI \
  | npx tsx skills/four-meme-integration/scripts/create-token-chain.ts --
```

The chain script reads `creationFeeWei` from stdin JSON when present; you can also pass `--value=<wei>` explicitly.

## Example (two steps)

```bash
export PRIVATE_KEY=your_hex_private_key
npx tsx skills/four-meme-integration/scripts/create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI > create-out.json
npx tsx skills/four-meme-integration/scripts/create-token-chain.ts "$(jq -r .createArg create-out.json)" "$(jq -r .signature create-out.json)" --value=$(jq -r .creationFeeWei create-out.json)
```

## Example (with presale)

`--pre-sale` is in **ether units** (e.g. `0.001` = 0.001 BNB; do not pass wei).

```bash
npx tsx .../create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI --pre-sale=0.01
```

## Tax token

Tax options file must contain `tokenTaxInfo`. Example **tax-options.json**:

```json
{
  "tokenTaxInfo": {
    "feeRate": 5,
    "burnRate": 20,
    "divideRate": 30,
    "liquidityRate": 40,
    "recipientRate": 10,
    "recipientAddress": "0x1234567890123456789012345678901234567890",
    "minSharing": 100000
  }
}
```

`feeRate` must be 1, 3, 5, or 10; burn+divide+liquidity+recipient=100. See [token-tax-info.md](token-tax-info.md).

```bash
# Option A: --tax-options= path to JSON file
npx tsx .../create-token-api.ts --image=./logo.png --name=TaxToken --short-name=TAX --desc="Tax token" --label=AI --tax-options=tax-options.json

# Option B: --tax-token with rate args
npx tsx .../create-token-api.ts --image=./logo.png --name=TaxToken --short-name=TAX --desc="Tax token" --label=AI --tax-token --tax-fee-rate=5 --tax-burn-rate=20 --tax-divide-rate=30 --tax-liquidity-rate=40 --tax-recipient-rate=10 --tax-recipient-address=0x... --tax-min-sharing=100000
```

Chain and Labels: see [SKILL.md](../SKILL.md) or [api-create-token.md](api-create-token.md).

```

### references/token-tax-info.md

```markdown
# Tax Token parameters (tokenTaxInfo)

When creating a **Tax-type token**, send a `tokenTaxInfo` object in the create request body. If omitted, a normal token is created. Agent interaction (whether to create a tax token, parameter order) is in [SKILL.md](../SKILL.md) “Create token (full flow)”. This page only lists fields and constraints.

## Field summary

| Parameter | Type | Description | Allowed values / constraint |
|-----------|------|-------------|------------------------------|
| **feeRate** | number | Trading fee rate (%) | **Exactly one of**: `1`, `3`, `5`, `10` |
| **burnRate** | number | Burn share (%) | Custom; see constraints below |
| **divideRate** | number | Dividend share (%) | Custom; see below |
| **liquidityRate** | number | Liquidity share (%) | Custom; see below |
| **recipientRate** | number | Recipient share (%) | Custom; use `0` if not used |
| **recipientAddress** | string | Address that receives allocation | Use `""` if not used |
| **minSharing** | number | Min balance for dividends (ether units) | Must satisfy `d × 10ⁿ`, n≥5, 1≤d≤9 |

## Constraints

1. **feeRate** must be one of **1, 3, 5, 10** (1%, 3%, 5%, 10%).
2. **burnRate + divideRate + liquidityRate + recipientRate = 100** (the four shares must sum to 100).
3. If no recipient: **recipientAddress = ""** and **recipientRate = 0**.
4. **minSharing** examples: `100000` (1×10⁵), `200000`, `500000`, `1000000` (1×10⁶), `9000000`, `10000000` (1×10⁷), i.e. `d × 10ⁿ` with n≥5, 1≤d≤9.

## Examples

**Example 1: 5% fee, 20% burn, 30% dividend, 40% liquidity, 10% recipient**

```json
{
  "tokenTaxInfo": {
    "feeRate": 5,
    "burnRate": 20,
    "divideRate": 30,
    "liquidityRate": 40,
    "recipientRate": 10,
    "recipientAddress": "0x1234567890123456789012345678901234567890",
    "minSharing": 100000
  }
}
```

**Example 2: 3% fee, 100% liquidity, no dividend/burn/recipient**

```json
{
  "tokenTaxInfo": {
    "feeRate": 3,
    "burnRate": 0,
    "divideRate": 0,
    "liquidityRate": 100,
    "recipientRate": 0,
    "recipientAddress": "",
    "minSharing": 100000
  }
}
```

**Example 3: 10% fee, 50% dividend, 50% burn, min share 1×10⁶**

```json
{
  "tokenTaxInfo": {
    "feeRate": 10,
    "burnRate": 50,
    "divideRate": 50,
    "liquidityRate": 0,
    "recipientRate": 0,
    "recipientAddress": "",
    "minSharing": 1000000
  }
}
```

## Use in scripts

- **Option 1 (recommended)**: Write the above `tokenTaxInfo` to a JSON file (can contain only `tokenTaxInfo`) and pass that file path as the last argument to `create-token-api.ts`; the script merges it into the create body.
- **Option 2**: Build tax params via env vars (see “Create token (API)” optional env in SKILL.md).

```

### references/event-listening.md

```markdown
# Event listening (TokenManager2, V2 only)

Only **TokenManager2 (V2)** is supported. V1 is not supported. Contract address (BSC): `0x5c952063c7fc8610FFDB798152D69F0B9550762b`.

The agent can react to **token creation, buy/sell, and liquidity add** by listening to the events below (e.g. copy-trading).

## Event list

| Event           | Meaning           | Main args                                                                 |
|-----------------|-------------------|---------------------------------------------------------------------------|
| **TokenCreate** | New token created | `creator`, `token`, `requestId`, `name`, `symbol`, `totalSupply`, `launchTime`, `launchFee` |
| **TokenPurchase** | Buy             | `token`, `account`, `price`, `amount`, `cost`, `fee`, `offers`, `funds`   |
| **TokenSale**   | Sell              | `token`, `account`, `price`, `amount`, `cost`, `fee`, `offers`, `funds`   |
| **LiquidityAdded** | Liquidity added | `base`, `offers`, `quote`, `funds`                                        |

- **TokenCreate**: With `token` (new token address) and `requestId`, you can call Helper3 `getTokenInfo(token)` or execute buy/sell immediately.
- **TokenPurchase / TokenSale**: Use for copy-trading, stats, etc.; `token` is the token address, `account` is the buyer/seller.
- **LiquidityAdded**: `base` is the token address; the token has been added to the pool and can be traded on the DEX.

## How to listen (viem)

### Option 1: Poll historical blocks with `getLogs`

```typescript
import { createPublicClient, http, parseAbiItem } from 'viem';
import { bsc } from 'viem/chains';

const TOKEN_MANAGER2 = '0x5c952063c7fc8610FFDB798152D69F0B9550762b';

const client = createPublicClient({
  chain: bsc,
  transport: http(process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org'),
});

const logs = await client.getLogs({
  address: TOKEN_MANAGER2,
  events: [
    parseAbiItem('event TokenCreate(address creator, address token, uint256 requestId, string name, string symbol, uint256 totalSupply, uint256 launchTime, uint256 launchFee)'),
    parseAbiItem('event TokenPurchase(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'),
    parseAbiItem('event TokenSale(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'),
    parseAbiItem('event LiquidityAdded(address base, uint256 offers, address quote, uint256 funds)'),
  ],
  fromBlock: fromBlockNumber,
  toBlock: toBlockNumber,
});
```

### Option 2: Real-time subscription with `watchContractEvent`

```typescript
import { createPublicClient, http, parseAbiItem } from 'viem';
import { bsc } from 'viem/chains';

const client = createPublicClient({ chain: bsc, transport: http(rpcUrl) });

const unwatch = client.watchContractEvent({
  address: TOKEN_MANAGER2,
  events: [
    parseAbiItem('event TokenCreate(address creator, address token, uint256 requestId, string name, string symbol, uint256 totalSupply, uint256 launchTime, uint256 launchFee)'),
    parseAbiItem('event TokenPurchase(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'),
    parseAbiItem('event TokenSale(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'),
    parseAbiItem('event LiquidityAdded(address base, uint256 offers, address quote, uint256 funds)'),
  ],
  onLogs: (logs) => {
    for (const log of logs) {
      console.log(log.eventName, log.args);
    }
  },
});
// To stop: unwatch()
```

## Script

Use `fourmeme events <fromBlock> [toBlock]` (script `get-recent-events.ts`) to fetch the four event types in a block range and output JSON. See the events section in [SKILL.md](../SKILL.md).

```

### references/tax-token-query.md

```markdown
# Query Tax Token fee/tax info

Only for **TaxToken** (creatorType 5). The token contract address is the TaxToken contract; call view functions on that address.

## On-chain view functions (TaxToken.abi)

| Function / variable | Description | Unit / meaning |
|---------------------|-------------|----------------|
| **feeRate** | Trading fee rate | Basis points; 10000 = 100%, e.g. 500 = 5% |
| **rateFounder** | Founder allocation share | Percent; 100 = 100%, e.g. 10 = 10% |
| **rateHolder** | Holder dividend share | Same |
| **rateBurn** | Burn share | Same |
| **rateLiquidity** | Liquidity add share | Same; rateFounder + rateHolder + rateBurn + rateLiquidity = 100 |
| **minDispatch** | Cumulative fee threshold to trigger distribution | Token units |
| **minShare** | Min balance to participate in dividends | Token units (ether); below this, no dividend |
| **quote** | Quote token address | 0 = BNB/ETH |
| **founder** | Founder address | Receives founder allocation |

See also **claimableFee(account)**, **claimedFee(account)**, **userInfo(account)** for claimable/claimed fees and user info in the [official API/Contract docs](https://four-meme.gitbook.io/four.meme/brand/protocol-integration) (e.g. API-Contract-TaxToken).

## Script

```bash
npx fourmeme tax-info <tokenAddress>
```

Or run: `npx tsx .../get-tax-token-info.ts <tokenAddress>`. BSC only.

Example output (JSON): `feeRateBps`, `feeRatePercent`, `rateFounder`, `rateHolder`, `rateBurn`, `rateLiquidity`, `minDispatch`, `minShare`, `quote`, `founder`.

## How to identify a TaxToken

- **Off-chain**: Call four.meme API `GET /v1/private/token/get?address=<token>`; if response `data` contains `taxInfo`, it is a TaxToken.
- **On-chain**: In TokenManager2, `(template >> 10) & 0x3F === 5` means TaxToken. Or run `get-tax-token-info`; if it succeeds and e.g. `feeRateBps > 0`, treat as TaxToken.

## References

- [Protocol Integration](https://four-meme.gitbook.io/four.meme/brand/protocol-integration) – API documents (API-Contract-TaxToken, etc.) and ABIs (TaxToken.abi)

```

### references/token-query-api.md

```markdown
# Token query REST API (Four.meme)

Base: `https://four.meme/meme-api/v1`. Requests need `Accept: application/json`; POST needs `Content-Type: application/json`. No login or cookie required.

## 1. Token list (filter / paginate)

**GET** `/private/token/query`

| Parameter | Description | Example |
|-----------|-------------|---------|
| orderBy | Sort order | Hot, Time, ... |
| tokenName | Filter by token name | Empty or name |
| symbol | Filter by symbol | Empty or symbol |
| labels | Filter by label | Empty or label |
| listedPancake | Listed on Pancake | false / true |
| pageIndex | Page number | 1 |
| pageSize | Page size | 30 |

CLI: `fourmeme token-list [--orderBy=Hot] [--pageIndex=1] [--pageSize=30] [--tokenName=] [--symbol=] [--labels=] [--listedPancake=false]`

## 2. Token detail and trading info

**GET** `/private/token/get/v2?address=<tokenAddress>`

CLI: `fourmeme token-get <tokenAddress>`

## 3. Rankings (advanced)

**POST** `/private/token/query/advanced`  
Body (JSON): `{ "orderBy": "<value>" }` or `{ "orderBy": "TradingDesc", "barType": "HOUR24" }`

| orderBy | Description |
|---------|-------------|
| Time | Newest tokens |
| ProgressDesc | Fundraise progress ranking |
| TradingDesc | 24h trading volume (can use barType: HOUR24) |
| Hot | Hot ranking |
| Graduated | Recently graduated / launched |

CLI: `fourmeme token-rankings <orderBy> [--barType=HOUR24]`

```

### references/agent-creator-and-wallets.md

```markdown
# Agent Creator & Agent Wallets

This document covers how to identify tokens created by an AI agent (Agent Creator) and how to identify agent wallets on-chain.

---

## How to Identify Token Created By Agent Creator

Tokens created by AI agents can be identified via both on-chain template bits and off-chain API fields. This flag only marks that the token creator is an **Agent wallet** and **does not imply any new special trading mode or behavior** of the token itself.

### On-chain method

Read the token's `template` from TokenManager2:

```solidity
template = TokenManager2._tokenInfos[tokenAddress].template;
bool isCreatedByAgent = (template & (1 << 85)) != 0;
```

- If `isCreatedByAgent` is `true`, the token was **created by an AI agent**.
- If `isCreatedByAgent` is `false`, the token was **not** created by an AI agent.

### Off-chain method

Query token info via:

- `GET https://four.meme/meme-api/v1/private/token/get?address=<token address>`
- `GET https://four.meme/meme-api/v1/private/token/getById?id=<requestId>` (requestId from TokenCreate event)

Check the `aiCreator` field in the response `data`:

- If `aiCreator === true`, the token was **created by an AI agent**.
- If `aiCreator === false`, the token was **not** created by an AI agent.

**Example response:**

```json
{
  "code": "0",
  "data": {
    "aiCreator": true,
    ...
  }
}
```

---

## How to Identify Agent Wallets

You can determine whether a wallet address is an **Agent wallet** by calling the **AgentIdentifier** contract.

### On-chain method

**Contract (BSC)**

- **Address**: `0x09B44A633de9F9EBF6FB9Bdd5b5629d3DD2cef13`
- **ABI**: See official Four.meme API / Protocol Integration docs (`AgentIdentifier.abi`).

**Interface**

```solidity
interface IAgentIdentifier {
    function isAgent(address wallet) external view returns (bool);
    function nftCount() external view returns (uint256);
    function nftAt(uint256 index) external view returns (address);
}
```

**Usage**

Call `isAgent(wallet)` on the AgentIdentifier contract:

```solidity
IAgentIdentifier ai = IAgentIdentifier(0x09B44A633de9F9EBF6FB9Bdd5b5629d3DD2cef13);
bool isAgent = ai.isAgent(wallet);
```

- Returns `true` if the wallet is an Agent wallet, otherwise `false`.
- Logic: `isAgent(wallet)` is `true` when the wallet holds any Agent NFT (`balanceOf(wallet) > 0`).

**Querying configured Agent NFTs**

- `nftCount()` — number of Agent NFT contracts registered.
- `nftAt(index)` — Agent NFT contract address at `index`.

Tokens created by wallets with `isAgent == true` are marked as **Agent Creator** and can be identified as **Token Created By Agent Creator** using the methods above.

```

### references/errors.md

```markdown
# Four.meme Error Codes

## buyToken

| Code       | Meaning |
|-----------|---------|
| GW (GWEI) | Amount precision not aligned to GWEI |
| ZA        | Zero Address – `to` must not be address(0) |
| TO        | Invalid to – `to` must not be PancakePair address |
| Slippage  | Spent amount &gt; maxFunds |
| More BNB  | Insufficient BNB in msg.value |
| A         | X Mode token bought with wrong method – use X Mode buy |

## sellToken

| Code      | Meaning |
|----------|---------|
| GW (GWEI)| Amount precision not aligned to GWEI |
| FR (FeeRate) | Fee rate &gt; 5% |
| SO (Small Order) | Order amount too small |
| Slippage | Received amount &lt; minFunds |

## Identification

- **X Mode (exclusive)**: Off-chain – API token info `version === "V8"`. On-chain – `TokenManager2._tokenInfos[token].template & 0x10000 > 0`.
- **TaxToken**: Off-chain – API response has `taxInfo`. On-chain – `(template >> 10) & 0x3F === 5`.
- **AntiSniperFeeMode**: Off-chain – API `feePlan === true`. On-chain – `TokenManager2._tokenInfoEx1s[token].feeSetting > 0`.
- **Token created by Agent Creator**: Off-chain – API `data.aiCreator === true`. On-chain – `(TokenManager2._tokenInfos[token].template & (1 << 85)) != 0`. Does not change trading behavior.
- **Agent wallets**: Call **AgentIdentifier.isAgent(wallet)** on BSC; see [agent-creator-and-wallets.md](agent-creator-and-wallets.md) and [contract-addresses.md](contract-addresses.md).

```

### references/execute-trade.md

```markdown
# Execute Buy / Sell (Four.meme BSC)

Executes buy/sell (sends on-chain transactions). Requires `PRIVATE_KEY`. Only **TokenManager2 (V2)** tokens are supported.

## Buy

- **By amount**: `buyToken(token, amount, maxFunds)`  
  CLI: `fourmeme buy <token> amount <amountWei> <maxFundsWei>`  
  Meaning: Spend at most `maxFundsWei` of quote (BNB or BEP20) to buy `amountWei` tokens.

- **By funds**: `buyTokenAMAP(token, funds, minAmount)`  
  CLI: `fourmeme buy <token> funds <fundsWei> <minAmountWei>`  
  Meaning: Spend `fundsWei` of quote and receive at least `minAmountWei` tokens.

If quote is BEP20 (not BNB), the script will `approve` TokenManager2 first, then send the buy transaction.

## Sell

- Contract requirement: First `approve(tokenManager, amount)` on TokenManager2, then call `sellToken(token, amount)` or `sellToken(origin, token, amount, minFunds)` for slippage protection.
- The CLI runs approve then sell automatically; no need to do both manually.

```bash
fourmeme sell <tokenAddress> <amountWei> [minFundsWei]
```

- `minFundsWei` is optional: minimum quote to receive (slippage protection).

## Relation to quote commands

- `quote-buy` / `quote-sell`: Read-only estimates; no transaction is sent.
- `buy` / `sell`: Actually send transactions. Recommended: use `quote-buy` / `quote-sell` first to get estimates, then choose `maxFundsWei`, `minAmountWei`, `minFundsWei` and call `buy` / `sell`.

```



---

## Skill Companion Files

> Additional files collected from the skill directory layout.

### _meta.json

```json
{
  "owner": "four-meme-community",
  "slug": "four-meme-ai",
  "displayName": "FourMeme",
  "latest": {
    "version": "1.0.7",
    "publishedAt": 1772942243891,
    "commit": "https://github.com/openclaw/skills/commit/16f68a8843a9e1eaeb3ca9e494482bc405fd14e8"
  },
  "history": [
    {
      "version": "1.0.4",
      "publishedAt": 1772561784503,
      "commit": "https://github.com/openclaw/skills/commit/26369f375951eab5d157687457824de32eb1d1ed"
    },
    {
      "version": "1.0.3",
      "publishedAt": 1772515426012,
      "commit": "https://github.com/openclaw/skills/commit/cc9d1bae3ecb5a03a03c8611f9e7a91a8f178393"
    }
  ]
}

```

### scripts/8004-balance.ts

```typescript
#!/usr/bin/env node
/**
 * EIP-8004 NFT – query balance (number of identity NFTs owned by address).
 *
 * Usage:
 *   npx tsx 8004-balance.ts <ownerAddress>
 *
 * Optional env: BSC_RPC_URL, 8004_NFT_ADDRESS.
 * Default contract: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 (BSC).
 */

import { createPublicClient, http, parseAbi } from 'viem';
import { bsc } from 'viem/chains';

const DEFAULT_8004_NFT = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432' as const;

const ABI = parseAbi(['function balanceOf(address owner) view returns (uint256)']);

function isAddress(s: string): boolean {
  return /^0x[0-9a-fA-F]{40}$/.test(s);
}

async function main() {
  const ownerAddress = process.argv[2];
  if (!ownerAddress || !isAddress(ownerAddress)) {
    console.error('Usage: 8004-balance.ts <ownerAddress>');
    console.error('  ownerAddress: 0x... wallet address');
    process.exit(1);
  }

  const contractAddress = (process.env['8004_NFT_ADDRESS'] || process.env.EIP8004_NFT_ADDRESS || DEFAULT_8004_NFT) as `0x${string}`;
  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

  const client = createPublicClient({
    chain: bsc,
    transport: http(rpcUrl),
  });

  const balance = await client.readContract({
    address: contractAddress,
    abi: ABI,
    functionName: 'balanceOf',
    args: [ownerAddress as `0x${string}`],
  });

  console.log(JSON.stringify({ owner: ownerAddress, balance: Number(balance) }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/8004-register.ts

```typescript
#!/usr/bin/env node
/**
 * EIP-8004 NFT – register agent (mint identity NFT).
 * Builds agentURI as data:application/json;base64,<payload> and calls contract.register(agentURI).
 *
 * Usage:
 *   npx tsx 8004-register.ts <name> [imageUrl] [description]
 *   - name: required
 *   - imageUrl: optional (URL string)
 *   - description: optional
 *
 * Env: PRIVATE_KEY. Optional: BSC_RPC_URL, 8004_NFT_ADDRESS.
 * Default contract: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 (BSC).
 */

import { createPublicClient, createWalletClient, decodeEventLog, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';

const DEFAULT_8004_NFT = '0x8004A169FB4a3325136EB29fA0ceB6D2e539a432' as const;

const REGISTRATION_TYPE = 'https://eips.ethereum.org/EIPS/eip-8004#registration-v1';

const ABI = parseAbi([
  'function register(string agentURI) returns (uint256 agentId)',
  'event Registered(uint256 indexed agentId, string agentURI, address indexed owner)',
]);

function buildAgentURI(name: string, imageUrl: string, description: string): string {
  const payload = {
    type: REGISTRATION_TYPE,
    name: name || '',
    description: description || 'I\'m four.meme trading agent',
    image: imageUrl || '',
    active: true,
    supportedTrust: [''],
  };
  const json = JSON.stringify(payload);
  const base64 = Buffer.from(json, 'utf8').toString('base64');
  return `data:application/json;base64,${base64}`;
}

async function main() {
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }
  const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
  const account = privateKeyToAccount(pk);

  const name = process.argv[2];
  const imageUrl = process.argv[3] ?? '';
  const description = process.argv[4] ?? '';

  if (!name || name.trim() === '') {
    console.error('Usage: 8004-register.ts <name> [imageUrl] [description]');
    console.error('  name: required. imageUrl and description are optional.');
    process.exit(1);
  }

  const agentURI = buildAgentURI(name.trim(), imageUrl.trim(), description.trim());
  const contractAddress = (process.env['8004_NFT_ADDRESS'] || process.env.EIP8004_NFT_ADDRESS || DEFAULT_8004_NFT) as `0x${string}`;
  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

  const wallet = createWalletClient({
    account,
    chain: bsc,
    transport: http(rpcUrl),
  });
  const publicClient = createPublicClient({
    chain: bsc,
    transport: http(rpcUrl),
  });

  const txHash = await wallet.writeContract({
    address: contractAddress,
    abi: ABI,
    functionName: 'register',
    args: [agentURI],
  });

  const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
  let agentId: number | null = null;
  for (const l of receipt.logs) {
    if (l.address.toLowerCase() !== contractAddress.toLowerCase()) continue;
    try {
      const d = decodeEventLog({
        abi: ABI,
        data: l.data,
        topics: l.topics,
      });
      if (d.eventName === 'Registered') {
        agentId = Number((d.args as { agentId: bigint }).agentId);
        break;
      }
    } catch {
      /* ignore */
    }
  }

  console.log(JSON.stringify({ txHash, agentId, agentURI }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/create-token-api.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - create token API flow (nonce → login → upload image → create).
 * Outputs createArg and signature (hex) for use with create-token-chain.ts.
 *
 * Usage: all options as --key=value
 *   npx tsx create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI [options]
 *
 * Required: --image= --name= --short-name= --desc= --label=
 * Optional: --web-url= --twitter-url= --telegram-url= (omit if empty); --pre-sale=0 (in BNB/ether, e.g. 0.001); --fee-plan=false --tax-options=<path>
 * Tax token: --tax-options=tax.json or --tax-token --tax-fee-rate=5 ... (burn+divide+liquidity+recipient=100)
 * Labels: Meme | AI | Defi | Games | Infra | De-Sci | Social | Depin | Charity | Others
 * Env: PRIVATE_KEY
 */

import { createPublicClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';
import { readFileSync, existsSync } from 'node:fs';
import { basename } from 'node:path';

const API_BASE = 'https://four.meme/meme-api/v1';
const TOKEN_MANAGER2_BSC = '0x5c952063c7fc8610FFDB798152D69F0B9550762b' as const;
const TM2_ABI = parseAbi([
  'function _launchFee() view returns (uint256)',
  'function _tradingFeeRate() view returns (uint256)',
]);
const NETWORK_CODE = 'BSC';

/** Get option from argv: --key=value or --key value; fallback to env (key as UPPER_SNAKE). */
function getOpt(key: string, defaultValue: string): string {
  const prefix = key + '=';
  for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg === key && i + 1 < process.argv.length) return process.argv[i + 1];
    if (arg.startsWith(prefix)) return arg.slice(prefix.length);
  }
  return process.env[key.replace(/-/g, '_').toUpperCase()] ?? defaultValue;
}

/** Get boolean option from argv: --fee-plan or --fee-plan=true; fallback to env. */
function getOptBool(key: string, defaultValue: boolean): boolean {
  const prefix = key + '=';
  for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg === key) return true;
    if (arg.startsWith(prefix)) {
      const v = arg.slice(prefix.length).toLowerCase();
      return v === '1' || v === 'true' || v === 'yes';
    }
  }
  const envKey = key.replace(/-/g, '_').toUpperCase();
  if (process.env[envKey] !== undefined) {
    const v = process.env[envKey]!.toLowerCase();
    return v === '1' || v === 'true' || v === 'yes';
  }
  return defaultValue;
}

function toHex(value: string): string {
  if (value.startsWith('0x')) return value;
  if (/^[0-9a-fA-F]+$/.test(value)) return '0x' + value;
  const buf = Buffer.from(value, 'base64');
  return '0x' + buf.toString('hex');
}

async function main() {
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }
  const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
  const account = privateKeyToAccount(pk);
  const address = account.address;

  const imagePath = getOpt('--image', '');
  const name = getOpt('--name', '');
  const shortName = getOpt('--short-name', '');
  const desc = getOpt('--desc', '');
  const label = getOpt('--label', '');
  const taxOptionsPath = getOpt('--tax-options', '');

  if (!imagePath || !name || !shortName || !desc || !label) {
    console.error(
      'Usage: npx tsx create-token-api.ts --image=<path> --name= --short-name= --desc= --label= [options]'
    );
    console.error('Example: npx tsx create-token-api.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI');
    console.error('Required: --image= --name= --short-name= --desc= --label=');
    console.error('Optional: --web-url= --twitter-url= --telegram-url= --pre-sale=0 --fee-plan=false --tax-options=<path>');
    process.exit(1);
  }
  if (!existsSync(imagePath)) {
    console.error('Image file not found:', imagePath);
    process.exit(1);
  }

  const validLabels = ['Meme', 'AI', 'Defi', 'Games', 'Infra', 'De-Sci', 'Social', 'Depin', 'Charity', 'Others'];
  const labelNorm = validLabels.find((l) => l.toLowerCase() === label.toLowerCase());
  if (!labelNorm) {
    console.error('Invalid label. Use one of:', validLabels.join(', '));
    process.exit(1);
  }
  const labelCanonical = labelNorm; // API expects exact label from list (case-sensitive)

  // 1. Get nonce
  const nonceRes = await fetch(`${API_BASE}/private/user/nonce/generate`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      accountAddress: address,
      verifyType: 'LOGIN',
      networkCode: NETWORK_CODE,
    }),
  });
  const nonceData = await nonceRes.json();
  if (nonceData.code !== '0' && nonceData.code !== 0) {
    throw new Error('Nonce failed: ' + JSON.stringify(nonceData));
  }
  const nonce = nonceData.data;

  // 2. Sign and login
  const message = `You are sign in Meme ${nonce}`;
  const signature = await account.signMessage({ message });

  const loginRes = await fetch(`${API_BASE}/private/user/login/dex`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      region: 'WEB',
      langType: 'EN',
      loginIp: '',
      inviteCode: '',
      verifyInfo: {
        address,
        networkCode: NETWORK_CODE,
        signature,
        verifyType: 'LOGIN',
      },
      walletName: 'MetaMask',
    }),
  });
  const loginData = await loginRes.json();
  if (loginData.code !== '0' && loginData.code !== 0) {
    throw new Error('Login failed: ' + JSON.stringify(loginData));
  }
  const accessToken = loginData.data;

  // 3. Upload image
  const imageBuffer = readFileSync(imagePath);
  const form = new FormData();
  form.append('file', new Blob([imageBuffer]), basename(imagePath));

  const uploadRes = await fetch(`${API_BASE}/private/token/upload`, {
    method: 'POST',
    headers: { 'meme-web-access': accessToken },
    body: form as unknown as BodyInit,
  });
  const uploadData = await uploadRes.json();
  if (uploadData.code !== '0' && uploadData.code !== 0) {
    throw new Error('Upload failed: ' + JSON.stringify(uploadData));
  }
  const imgUrl = uploadData.data;

  // 4. Public config for raisedToken (data[]: symbol, symbolAddress, totalBAmount, status=PUBLISH|INIT, ...)
  const configRes = await fetch(`${API_BASE}/public/config`);
  if (!configRes.ok) {
    throw new Error('Public config request failed: ' + configRes.status + ' ' + configRes.statusText);
  }
  const configData = await configRes.json();
  if (configData.code !== '0' && configData.code !== 0) {
    throw new Error('Invalid public config response: ' + JSON.stringify(configData));
  }
  const symbols = configData.data;
  if (!Array.isArray(symbols) || symbols.length === 0) {
    throw new Error('Invalid public config (no raisedToken): ' + JSON.stringify(configData));
  }
  // Prefer BNB with status PUBLISH for BSC; else first PUBLISH; else first item
  const published = symbols.filter((c: { status?: string }) => c.status === 'PUBLISH');
  const list = published.length > 0 ? published : symbols;
  const config =
    list.find((c: { symbol?: string }) => c.symbol === 'BNB') ?? list[0];
  const raisedToken = config;
  if (!raisedToken || !raisedToken.symbol) {
    throw new Error('Invalid public config (no raisedToken): ' + JSON.stringify(configData));
  }

  // 5. Build create body and optional tokenTaxInfo
  // raisedAmount / totalSupply / saleRate from raisedToken or docs (API-CreateToken)
  const launchTime = Date.now();
  const totalSupply =
    typeof (raisedToken as { totalAmount?: string | number }).totalAmount !== 'undefined'
      ? Number((raisedToken as { totalAmount?: string | number }).totalAmount)
      : 1000000000;
  const raisedAmount =
    typeof (raisedToken as { totalBAmount?: string | number }).totalBAmount !== 'undefined'
      ? Number((raisedToken as { totalBAmount?: string | number }).totalBAmount)
      : 24;
  const body: Record<string, unknown> = {
    name,
    shortName,
    desc,
    totalSupply,
    raisedAmount,
    saleRate:
      typeof (raisedToken as { saleRate?: string | number }).saleRate !== 'undefined'
        ? Number((raisedToken as { saleRate?: string | number }).saleRate)
        : 0.8,
    reserveRate: 0,
    imgUrl,
    raisedToken,
    launchTime,
    funGroup: false,
    label: labelCanonical,
    lpTradingFee: 0.0025,
    preSale: getOpt('--pre-sale', '0'),
    clickFun: false,
    symbol: (raisedToken as { symbol: string }).symbol,
    dexType: 'PANCAKE_SWAP',
    rushMode: false,
    onlyMPC: false,
    feePlan: getOptBool('--fee-plan', false),
  };
  const webUrl = getOpt('--web-url', '');
  const twitterUrl = getOpt('--twitter-url', '');
  const telegramUrl = getOpt('--telegram-url', '');
  if (webUrl != null && webUrl !== '') body.webUrl = webUrl;
  if (twitterUrl != null && twitterUrl !== '') body.twitterUrl = twitterUrl;
  if (telegramUrl != null && telegramUrl !== '') body.telegramUrl = telegramUrl;

  let tokenTaxInfo: Record<string, unknown> | null = null;
  if (taxOptionsPath && existsSync(taxOptionsPath)) {
    const taxOpts = JSON.parse(readFileSync(taxOptionsPath, 'utf8'));
    if (taxOpts.tokenTaxInfo && typeof taxOpts.tokenTaxInfo === 'object') {
      tokenTaxInfo = taxOpts.tokenTaxInfo as Record<string, unknown>;
    }
  }
  const taxFromCli =
    getOptBool('--tax-token', false) ||
    getOpt('--tax-fee-rate', '') !== '' ||
    process.env.TAX_TOKEN === '1';
  if (!tokenTaxInfo && taxFromCli) {
    const feeRate = Number(getOpt('--tax-fee-rate', '5'));
    const burnRate = Number(getOpt('--tax-burn-rate', '0'));
    const divideRate = Number(getOpt('--tax-divide-rate', '0'));
    const liquidityRate = Number(getOpt('--tax-liquidity-rate', '100'));
    const recipientRate = Number(getOpt('--tax-recipient-rate', '0'));
    const recipientAddress = getOpt('--tax-recipient-address', '');
    const minSharing = Number(getOpt('--tax-min-sharing', '100000'));
    const sum = burnRate + divideRate + liquidityRate + recipientRate;
    if (sum !== 100) {
      throw new Error(`Tax rates must sum to 100 (burn+divide+liquidity+recipient). Got ${sum}.`);
    }
    if (![1, 3, 5, 10].includes(feeRate)) {
      throw new Error('TAX_FEE_RATE must be 1, 3, 5, or 10.');
    }
    tokenTaxInfo = {
      feeRate,
      burnRate,
      divideRate,
      liquidityRate,
      recipientRate,
      recipientAddress,
      minSharing,
    };
  }
  if (tokenTaxInfo) {
    body.tokenTaxInfo = tokenTaxInfo;
  }

  const createRes = await fetch(`${API_BASE}/private/token/create`, {
    method: 'POST',
    headers: {
      'meme-web-access': accessToken,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  const createData = await createRes.json();
  if (createData.code !== '0' && createData.code !== 0) {
    throw new Error('Create API failed: ' + JSON.stringify(createData));
  }
  const { createArg: rawArg, signature: rawSig } = createData.data;
  const createArgHex = toHex(rawArg);
  const signatureHex = toHex(rawSig);

  // Estimate required value (CREATION_FEE_WEI) for createToken tx
  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
  const client = createPublicClient({ chain: bsc, transport: http(rpcUrl) });
  const launchFee = await client.readContract({
    address: TOKEN_MANAGER2_BSC,
    abi: TM2_ABI,
    functionName: '_launchFee',
  });
  const preSaleStr = String(body.preSale ?? '0');
  // API preSale is in ether (BNB); convert to wei for value calculation
  const presaleWei = BigInt(Math.round(parseFloat(preSaleStr || '0') * 1e18));
  const quoteIsBnb = (raisedToken as { symbol?: string }).symbol === 'BNB';
  let requiredValueWei = launchFee;
  if (presaleWei > 0n && quoteIsBnb) {
    const feeRate = await client.readContract({
      address: TOKEN_MANAGER2_BSC,
      abi: TM2_ABI,
      functionName: '_tradingFeeRate',
    });
    const tradingFee = (presaleWei * feeRate) / 10000n;
    requiredValueWei = launchFee + presaleWei + tradingFee;
  }
  const creationFeeWei = requiredValueWei.toString();

  const out = { createArg: createArgHex, signature: signatureHex, creationFeeWei };
  console.log(JSON.stringify(out, null, 2));
  console.error(
    `\n→ For create-token-chain pass --value=${creationFeeWei} (or more).`
  );
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/create-token-chain.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - submit createToken tx on BSC (TokenManager2.createToken).
 * Uses createArg and signature from create-token-api.ts output.
 *
 * Usage:
 *   npx tsx create-token-chain.ts <createArgHex> <signatureHex> [--value=wei]
 *   ... | npx tsx create-token-chain.ts -- [--value=wei]
 *
 * Env: PRIVATE_KEY. Optional: CREATION_FEE_WEI, BSC_RPC_URL.
 */

import { createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';

const TOKEN_MANAGER2_BSC = '0x5c952063c7fc8610FFDB798152D69F0B9550762b' as const;

const ABI = parseAbi([
  'function createToken(bytes args, bytes signature) payable',
]);

/** Get option from argv --key=value, or env var. */
function getOpt(key: string, envKey: string, defaultValue: string): string {
  const prefix = key + '=';
  for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg.startsWith(prefix)) return arg.slice(prefix.length);
  }
  return process.env[envKey] ?? defaultValue;
}

function toHex(s: string): `0x${string}` {
  if (s.startsWith('0x')) return s as `0x${string}`;
  if (/^[0-9a-fA-F]+$/.test(s)) return ('0x' + s) as `0x${string}`;
  const buf = Buffer.from(s, 'base64');
  return ('0x' + buf.toString('hex')) as `0x${string}`;
}

async function main() {
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }
  const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
  const account = privateKeyToAccount(pk);

  const positionals = process.argv.slice(2).filter((a) => !a.startsWith('--') || a === '--');
  let createArgHex: `0x${string}`;
  let signatureHex: `0x${string}`;
  let creationFeeWei = 0n;
  let stdinJson: { createArg: string; signature: string; creationFeeWei?: string } | null = null;

  if (positionals[0] === '--' || !positionals[0]) {
    const chunks: Buffer[] = [];
    for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
    const parsed = JSON.parse(Buffer.concat(chunks).toString('utf8')) as {
      createArg: string;
      signature: string;
      creationFeeWei?: string;
    };
    stdinJson = parsed;
    createArgHex = toHex(parsed.createArg);
    signatureHex = toHex(parsed.signature);
  } else {
    const arg2 = positionals[1];
    if (!arg2) {
      console.error('Usage: npx tsx create-token-chain.ts <createArgHex> <signatureHex> [--value=wei]');
      console.error('   or: ... | npx tsx create-token-chain.ts -- [--value=wei]');
      process.exit(1);
    }
    createArgHex = toHex(positionals[0]);
    signatureHex = toHex(arg2);
  }

  const valueStr = getOpt('--value', 'CREATION_FEE_WEI', '');
  if (valueStr) creationFeeWei = BigInt(valueStr);
  else if (stdinJson?.creationFeeWei) creationFeeWei = BigInt(stdinJson.creationFeeWei);

  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
  const client = createWalletClient({
    account,
    chain: bsc,
    transport: http(rpcUrl),
  });

  const hash = await client.writeContract({
    address: TOKEN_MANAGER2_BSC,
    abi: ABI,
    functionName: 'createToken',
    args: [createArgHex, signatureHex],
    value: creationFeeWei,
  });

  console.log(JSON.stringify({ txHash: hash }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/create-token-instant.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - One-shot create token: API (create-token-api) + on-chain (create-token-chain).
 * Same args as create-token-api; on success submits createToken tx and outputs txHash.
 *
 * Usage: same as create-token-api, all --key=value
 *   npx tsx create-token-instant.ts --image=./logo.png --name=MyToken --short-name=MTK --desc="My desc" --label=AI [options]
 *
 * Env: PRIVATE_KEY. Optional: BSC_RPC_URL.
 * Options: same as create-token-api (--web-url=, --pre-sale=0, --fee-plan, --tax-options=, --tax-token, etc.).
 * Value for createToken uses API output creationFeeWei; override with --value=wei.
 */

import { spawnSync } from 'node:child_process';
import { createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

const TOKEN_MANAGER2_BSC = '0x5c952063c7fc8610FFDB798152D69F0B9550762b' as const;
const ABI = parseAbi([
  'function createToken(bytes args, bytes signature) payable',
]);

function getOpt(key: string, envKey: string, defaultValue: string): string {
  const prefix = key + '=';
  for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg.startsWith(prefix)) return arg.slice(prefix.length);
  }
  return process.env[envKey] ?? defaultValue;
}

function toHex(s: string): `0x${string}` {
  if (s.startsWith('0x')) return s as `0x${string}`;
  if (/^[0-9a-fA-F]+$/.test(s)) return ('0x' + s) as `0x${string}`;
  const buf = Buffer.from(s, 'base64');
  return ('0x' + buf.toString('hex')) as `0x${string}`;
}

async function main() {
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }

  const apiScript = path.join(__dirname, 'create-token-api.ts');
  const args = process.argv.slice(2);
  const child = spawnSync('npx', ['tsx', apiScript, ...args], {
    env: process.env,
    encoding: 'utf8',
    stdio: ['inherit', 'pipe', 'inherit'],
  });

  if (child.status !== 0) {
    process.exit(child.status ?? 1);
  }

  const out = child.stdout?.trim();
  if (!out) {
    console.error('create-token-api produced no output');
    process.exit(1);
  }

  let data: { createArg: string; signature: string; creationFeeWei?: string };
  try {
    data = JSON.parse(out) as typeof data;
  } catch {
    console.error('create-token-api output is not valid JSON');
    process.exit(1);
  }

  const createArgHex = toHex(data.createArg);
  const signatureHex = toHex(data.signature);
  const valueStr = getOpt('--value', 'CREATION_FEE_WEI', '');
  const creationFeeWei = valueStr ? BigInt(valueStr) : BigInt(data.creationFeeWei ?? '0');

  const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
  const account = privateKeyToAccount(pk);
  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

  const client = createWalletClient({
    account,
    chain: bsc,
    transport: http(rpcUrl),
  });

  const hash = await client.writeContract({
    address: TOKEN_MANAGER2_BSC,
    abi: ABI,
    functionName: 'createToken',
    args: [createArgHex, signatureHex],
    value: creationFeeWei,
  });

  console.log(JSON.stringify({ txHash: hash }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/execute-buy.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - execute buy on TokenManager2 (BSC only).
 * Usage:
 *   npx tsx execute-buy.ts <tokenAddress> amount <amountWei> <maxFundsWei>   # buy fixed token amount
 *   npx tsx execute-buy.ts <tokenAddress> funds <fundsWei> <minAmountWei>   # spend fixed quote (e.g. BNB)
 * Env: PRIVATE_KEY. Only V2 tokens (version 2 from getTokenInfo).
 */

import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';

const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;
const ZERO = '0x0000000000000000000000000000000000000000' as const;

const HELPER_ABI = [
  {
    name: 'getTokenInfo',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'token', type: 'address' }],
    outputs: [
      { name: 'version', type: 'uint256' },
      { name: 'tokenManager', type: 'address' },
      { name: 'quote', type: 'address' },
      { name: 'lastPrice', type: 'uint256' },
      { name: 'tradingFeeRate', type: 'uint256' },
      { name: 'minTradingFee', type: 'uint256' },
      { name: 'launchTime', type: 'uint256' },
      { name: 'offers', type: 'uint256' },
      { name: 'maxOffers', type: 'uint256' },
      { name: 'funds', type: 'uint256' },
      { name: 'maxFunds', type: 'uint256' },
      { name: 'liquidityAdded', type: 'bool' },
    ],
  },
  {
    name: 'tryBuy',
    type: 'function',
    stateMutability: 'view',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'funds', type: 'uint256' },
    ],
    outputs: [
      { name: 'tokenManager', type: 'address' },
      { name: 'quote', type: 'address' },
      { name: 'estimatedAmount', type: 'uint256' },
      { name: 'estimatedCost', type: 'uint256' },
      { name: 'estimatedFee', type: 'uint256' },
      { name: 'amountMsgValue', type: 'uint256' },
      { name: 'amountApproval', type: 'uint256' },
      { name: 'amountFunds', type: 'uint256' },
    ],
  },
] as const;

const TM2_ABI = [
  {
    name: 'buyToken',
    type: 'function',
    stateMutability: 'payable',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'maxFunds', type: 'uint256' },
    ],
    outputs: [],
  },
  {
    name: 'buyTokenAMAP',
    type: 'function',
    stateMutability: 'payable',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'funds', type: 'uint256' },
      { name: 'minAmount', type: 'uint256' },
    ],
    outputs: [],
  },
] as const;

const ERC20_ABI = [
  {
    name: 'approve',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'spender', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ type: 'bool' }],
  },
] as const;

const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

async function main() {
  const pk = process.env.PRIVATE_KEY;
  if (!pk) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }
  const account = privateKeyToAccount(
    (pk.startsWith('0x') ? pk : '0x' + pk) as `0x${string}`
  );

  const tokenAddress = process.argv[2] as `0x${string}`;
  const mode = process.argv[3]; // 'amount' | 'funds'
  const arg1 = process.argv[4];
  const arg2 = process.argv[5];

  if (!tokenAddress || !mode || !arg1 || !arg2) {
    console.error('Usage:');
    console.error('  execute-buy.ts <token> amount <amountWei> <maxFundsWei>');
    console.error('  execute-buy.ts <token> funds <fundsWei> <minAmountWei>');
    process.exit(1);
  }

  const publicClient = createPublicClient({
    chain: bsc,
    transport: http(RPC_URL),
  });
  const walletClient = createWalletClient({
    account,
    chain: bsc,
    transport: http(RPC_URL),
  });

  const [version, tokenManager, quote] = await publicClient.readContract({
    address: HELPER_ADDRESS,
    abi: HELPER_ABI,
    functionName: 'getTokenInfo',
    args: [tokenAddress],
  }).then((r) => [r[0], r[1], r[2]] as const);

  if (Number(version) !== 2) {
    console.error('Only TokenManager2 (V2) tokens are supported. This token is version', version);
    process.exit(1);
  }

  const amountWei = mode === 'amount' ? BigInt(arg1) : 0n;
  const maxFundsWei = mode === 'amount' ? BigInt(arg2) : 0n;
  const fundsWei = mode === 'funds' ? BigInt(arg1) : 0n;
  const minAmountWei = mode === 'funds' ? BigInt(arg2) : 0n;

  const tryBuyResult = await publicClient.readContract({
    address: HELPER_ADDRESS,
    abi: HELPER_ABI,
    functionName: 'tryBuy',
    args: [tokenAddress, amountWei, fundsWei],
  });
  const amountMsgValue = tryBuyResult[5];
  const amountApproval = tryBuyResult[6];

  if (quote !== ZERO && amountApproval > 0n) {
    const approveHash = await walletClient.writeContract({
      address: quote,
      abi: ERC20_ABI,
      functionName: 'approve',
      args: [tokenManager, amountApproval],
    });
    console.error('Approved quote token. Tx:', approveHash);
    const receipt = await publicClient.waitForTransactionReceipt({ hash: approveHash });
    if (receipt.status !== 'success') {
      console.error('Approve failed');
      process.exit(1);
    }
  }

  let hash: `0x${string}`;
  if (mode === 'amount') {
    hash = await walletClient.writeContract({
      address: tokenManager as `0x${string}`,
      abi: TM2_ABI,
      functionName: 'buyToken',
      args: [tokenAddress, amountWei, maxFundsWei],
      value: amountMsgValue,
    });
  } else {
    hash = await walletClient.writeContract({
      address: tokenManager as `0x${string}`,
      abi: TM2_ABI,
      functionName: 'buyTokenAMAP',
      args: [tokenAddress, fundsWei, minAmountWei],
      value: amountMsgValue,
    });
  }

  console.log(JSON.stringify({ txHash: hash }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/execute-sell.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - execute sell on TokenManager2 (BSC only).
 * Usage: npx tsx execute-sell.ts <tokenAddress> <amountWei> [minFundsWei]
 * Env: PRIVATE_KEY. Sends approve(tokenManager, amount) then sellToken(token, amount).
 */

import { createPublicClient, createWalletClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';

const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;

const HELPER_ABI = [
  {
    name: 'getTokenInfo',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'token', type: 'address' }],
    outputs: [
      { name: 'version', type: 'uint256' },
      { name: 'tokenManager', type: 'address' },
      { name: 'quote', type: 'address' },
      { name: 'lastPrice', type: 'uint256' },
      { name: 'tradingFeeRate', type: 'uint256' },
      { name: 'minTradingFee', type: 'uint256' },
      { name: 'launchTime', type: 'uint256' },
      { name: 'offers', type: 'uint256' },
      { name: 'maxOffers', type: 'uint256' },
      { name: 'funds', type: 'uint256' },
      { name: 'maxFunds', type: 'uint256' },
      { name: 'liquidityAdded', type: 'bool' },
    ],
  },
] as const;

const TM2_ABI_SIMPLE = [
  {
    name: 'sellToken',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [],
  },
] as const;

const TM2_ABI_WITH_MIN_FUNDS = [
  {
    name: 'sellToken',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'origin', type: 'uint256' },
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'minFunds', type: 'uint256' },
    ],
    outputs: [],
  },
] as const;

const ERC20_ABI = [
  {
    name: 'approve',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'spender', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ type: 'bool' }],
  },
] as const;

const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

async function main() {
  const pk = process.env.PRIVATE_KEY;
  if (!pk) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }
  const account = privateKeyToAccount(
    (pk.startsWith('0x') ? pk : '0x' + pk) as `0x${string}`
  );

  const tokenAddress = process.argv[2] as `0x${string}`;
  const amountWei = BigInt(process.argv[3] ?? '0');
  const minFundsWei = process.argv[4] ? BigInt(process.argv[4]) : null;

  if (!tokenAddress || amountWei <= 0n) {
    console.error('Usage: npx tsx execute-sell.ts <tokenAddress> <amountWei> [minFundsWei]');
    process.exit(1);
  }

  const publicClient = createPublicClient({
    chain: bsc,
    transport: http(RPC_URL),
  });
  const walletClient = createWalletClient({
    account,
    chain: bsc,
    transport: http(RPC_URL),
  });

  const [, tokenManager] = await publicClient.readContract({
    address: HELPER_ADDRESS,
    abi: HELPER_ABI,
    functionName: 'getTokenInfo',
    args: [tokenAddress],
  }).then((r) => [r[0], r[1]] as const);

  const approveHash = await walletClient.writeContract({
    address: tokenAddress,
    abi: ERC20_ABI,
    functionName: 'approve',
    args: [tokenManager as `0x${string}`, amountWei],
  });
  const receipt = await publicClient.waitForTransactionReceipt({ hash: approveHash });
  if (receipt.status !== 'success') {
    console.error('Approve failed');
    process.exit(1);
  }

  const tm2 = tokenManager as `0x${string}`;
  const hash: `0x${string}` =
    minFundsWei !== null
      ? await walletClient.writeContract({
          address: tm2,
          abi: TM2_ABI_WITH_MIN_FUNDS,
          functionName: 'sellToken',
          args: [0n, tokenAddress, amountWei, minFundsWei],
        })
      : await walletClient.writeContract({
          address: tm2,
          abi: TM2_ABI_SIMPLE,
          functionName: 'sellToken',
          args: [tokenAddress, amountWei],
        });

  console.log(JSON.stringify({ txHash: hash }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/get-public-config.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - fetch public config (raisedToken for create-token API)
 * Usage: npx tsx skills/four-meme-integration/scripts/get-public-config.ts
 * Output: JSON with raisedToken and other config (use raisedToken in create body as-is).
 */

const API_BASE = 'https://four.meme/meme-api/v1';

async function main() {
  const res = await fetch(`${API_BASE}/public/config`);
  if (!res.ok) {
    throw new Error(`Config request failed: ${res.status} ${await res.text()}`);
  }
  const data = await res.json();
  if (data.code !== '0' && data.code !== 0) {
    throw new Error(`Config error: ${JSON.stringify(data)}`);
  }
  console.log(JSON.stringify(data.data ?? data, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/get-recent-events.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - get TokenCreate, TokenPurchase, TokenSale, LiquidityAdded from TokenManager2 (V2 only).
 * Usage: npx tsx get-recent-events.ts <chainId> <fromBlock> [toBlock]
 * If toBlock omitted, uses "latest". chainId 56 = BSC (TokenManager2 on BSC only for create; events on BSC).
 */

import { createPublicClient, http, parseAbiItem } from 'viem';
import { bsc } from 'viem/chains';

const TOKEN_MANAGER2_BSC = '0x5c952063c7fc8610FFDB798152D69F0B9550762b' as const;

const EVENTS = [
  parseAbiItem(
    'event TokenCreate(address creator, address token, uint256 requestId, string name, string symbol, uint256 totalSupply, uint256 launchTime, uint256 launchFee)'
  ),
  parseAbiItem(
    'event TokenPurchase(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'
  ),
  parseAbiItem(
    'event TokenSale(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'
  ),
  parseAbiItem('event LiquidityAdded(address base, uint256 offers, address quote, uint256 funds)'),
];

function getRpcUrl(chainId: number): string {
  if (chainId !== 56) {
    console.error('Events are only on BSC (chainId 56). TokenManager2 is on BSC.');
    process.exit(1);
  }
  return process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
}

async function main() {
  const chainId = parseInt(process.argv[2], 10);
  const fromBlock = process.argv[3];
  const toBlock = process.argv[4]; // optional, "latest" if omitted

  if (!chainId || isNaN(chainId) || chainId !== 56 || !fromBlock) {
    console.error('Usage: npx tsx get-recent-events.ts 56 <fromBlock> [toBlock]');
    console.error('Example: npx tsx get-recent-events.ts 56 45000000 45000100');
    process.exit(1);
  }

  const client = createPublicClient({
    chain: bsc,
    transport: http(getRpcUrl(chainId)),
  });

  const fromBlockBigInt = fromBlock === 'latest' ? 'latest' : BigInt(fromBlock);
  const toBlockOpt: 'latest' | bigint | undefined = !toBlock
    ? undefined
    : toBlock === 'latest'
      ? 'latest'
      : BigInt(toBlock);

  const logs = await client.getLogs({
    address: TOKEN_MANAGER2_BSC,
    events: EVENTS,
    fromBlock: fromBlockBigInt,
    ...(toBlockOpt !== undefined && { toBlock: toBlockOpt }),
  });

  const out = logs.map((log) => ({
    eventName: log.eventName,
    blockNumber: Number(log.blockNumber),
    transactionHash: log.transactionHash,
    args: log.args as object,
  }));
  console.log(JSON.stringify(out, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/get-tax-token-info.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - query TaxToken contract for fee/tax config (BSC only).
 * Only works for TaxToken type (creatorType 5). Usage: npx tsx get-tax-token-info.ts <tokenAddress>
 */

import { createPublicClient, http } from 'viem';
import { bsc } from 'viem/chains';

const TAX_TOKEN_ABI = [
  { name: 'feeRate', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'rateFounder', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'rateHolder', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'rateBurn', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'rateLiquidity', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'minDispatch', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'minShare', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
  { name: 'quote', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
  { name: 'founder', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'address' }] },
] as const;

const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

async function main() {
  const tokenAddress = process.argv[2] as `0x${string}`;

  if (!tokenAddress) {
    console.error('Usage: npx tsx get-tax-token-info.ts <tokenAddress>');
    console.error('BSC only. Token must be TaxToken type.');
    process.exit(1);
  }

  const client = createPublicClient({
    chain: bsc,
    transport: http(RPC_URL),
  });

  const [feeRate, rateFounder, rateHolder, rateBurn, rateLiquidity, minDispatch, minShare, quote, founder] =
    await Promise.all([
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'feeRate' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'rateFounder' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'rateHolder' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'rateBurn' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'rateLiquidity' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'minDispatch' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'minShare' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'quote' }),
      client.readContract({ address: tokenAddress, abi: TAX_TOKEN_ABI, functionName: 'founder' }),
    ]);

  const out = {
    feeRateBps: Number(feeRate),
    feeRatePercent: Number(feeRate) / 100,
    rateFounder: Number(rateFounder),
    rateHolder: Number(rateHolder),
    rateBurn: Number(rateBurn),
    rateLiquidity: Number(rateLiquidity),
    minDispatch: minDispatch.toString(),
    minShare: minShare.toString(),
    quote: quote === '0x0000000000000000000000000000000000000000' ? null : quote,
    founder: founder === '0x0000000000000000000000000000000000000000' ? null : founder,
  };
  console.log(JSON.stringify(out, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/get-token-info.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - get token info via TokenManagerHelper3 (BSC only).
 * Usage: npx tsx get-token-info.ts <tokenAddress>
 */

import { createPublicClient, http } from 'viem';
import { bsc } from 'viem/chains';

const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;

const HELPER_ABI = [
  {
    name: 'getTokenInfo',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'token', type: 'address' }],
    outputs: [
      { name: 'version', type: 'uint256' },
      { name: 'tokenManager', type: 'address' },
      { name: 'quote', type: 'address' },
      { name: 'lastPrice', type: 'uint256' },
      { name: 'tradingFeeRate', type: 'uint256' },
      { name: 'minTradingFee', type: 'uint256' },
      { name: 'launchTime', type: 'uint256' },
      { name: 'offers', type: 'uint256' },
      { name: 'maxOffers', type: 'uint256' },
      { name: 'funds', type: 'uint256' },
      { name: 'maxFunds', type: 'uint256' },
      { name: 'liquidityAdded', type: 'bool' },
    ],
  },
] as const;

const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

async function main() {
  const tokenAddress = process.argv[2] as `0x${string}`;

  if (!tokenAddress) {
    console.error('Usage: npx tsx get-token-info.ts <tokenAddress>');
    console.error('BSC only.');
    process.exit(1);
  }

  const client = createPublicClient({
    chain: bsc,
    transport: http(RPC_URL),
  });

  const helperAddress = HELPER_ADDRESS;
  const result = await client.readContract({
    address: helperAddress,
    abi: HELPER_ABI,
    functionName: 'getTokenInfo',
    args: [tokenAddress],
  });

  const [
    version,
    tokenManager,
    quote,
    lastPrice,
    tradingFeeRate,
    minTradingFee,
    launchTime,
    offers,
    maxOffers,
    funds,
    maxFunds,
    liquidityAdded,
  ] = result;

  const out = {
    version: Number(version),
    tokenManager,
    quote: quote === '0x0000000000000000000000000000000000000000' ? null : quote,
    lastPrice: lastPrice.toString(),
    tradingFeeRate: Number(tradingFeeRate) / 10000,
    minTradingFee: minTradingFee.toString(),
    launchTime: Number(launchTime),
    offers: offers.toString(),
    maxOffers: maxOffers.toString(),
    funds: funds.toString(),
    maxFunds: maxFunds.toString(),
    liquidityAdded,
  };
  console.log(JSON.stringify(out, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/quote-buy.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - buy quote via TokenManagerHelper3.tryBuy (BSC only).
 * Usage: npx tsx quote-buy.ts <tokenAddress> <amountWei> [fundsWei]
 */

import { createPublicClient, http } from 'viem';
import { bsc } from 'viem/chains';

const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;

const HELPER_ABI = [
  {
    name: 'tryBuy',
    type: 'function',
    stateMutability: 'view',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'funds', type: 'uint256' },
    ],
    outputs: [
      { name: 'tokenManager', type: 'address' },
      { name: 'quote', type: 'address' },
      { name: 'estimatedAmount', type: 'uint256' },
      { name: 'estimatedCost', type: 'uint256' },
      { name: 'estimatedFee', type: 'uint256' },
      { name: 'amountMsgValue', type: 'uint256' },
      { name: 'amountApproval', type: 'uint256' },
      { name: 'amountFunds', type: 'uint256' },
    ],
  },
] as const;

const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

async function main() {
  const tokenAddress = process.argv[2] as `0x${string}`;
  const amountWei = BigInt(process.argv[3] ?? '0');
  const fundsWei = BigInt(process.argv[4] ?? '0');

  if (!tokenAddress) {
    console.error('Usage: npx tsx quote-buy.ts <tokenAddress> <amountWei> [fundsWei]');
    console.error('BSC only. amountWei: token amount (0 for funds-based); fundsWei: quote to spend (0 for amount-based)');
    process.exit(1);
  }

  const client = createPublicClient({
    chain: bsc,
    transport: http(RPC_URL),
  });

  const [
    tokenManager,
    quote,
    estimatedAmount,
    estimatedCost,
    estimatedFee,
    amountMsgValue,
    amountApproval,
    amountFunds,
  ] = await client.readContract({
    address: HELPER_ADDRESS,
    abi: HELPER_ABI,
    functionName: 'tryBuy',
    args: [tokenAddress, amountWei, fundsWei],
  });

  const out = {
    tokenManager,
    quote: quote === '0x0000000000000000000000000000000000000000' ? 'native' : quote,
    estimatedAmount: estimatedAmount.toString(),
    estimatedCost: estimatedCost.toString(),
    estimatedFee: estimatedFee.toString(),
    amountMsgValue: amountMsgValue.toString(),
    amountApproval: amountApproval.toString(),
    amountFunds: amountFunds.toString(),
  };
  console.log(JSON.stringify(out, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/quote-sell.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - sell quote via TokenManagerHelper3.trySell (BSC only).
 * Usage: npx tsx quote-sell.ts <tokenAddress> <amountWei>
 */

import { createPublicClient, http } from 'viem';
import { bsc } from 'viem/chains';

const HELPER_ADDRESS = '0xF251F83e40a78868FcfA3FA4599Dad6494E46034' as const;

const HELPER_ABI = [
  {
    name: 'trySell',
    type: 'function',
    stateMutability: 'view',
    inputs: [
      { name: 'token', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [
      { name: 'tokenManager', type: 'address' },
      { name: 'quote', type: 'address' },
      { name: 'funds', type: 'uint256' },
      { name: 'fee', type: 'uint256' },
    ],
  },
] as const;

const RPC_URL = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';

async function main() {
  const tokenAddress = process.argv[2] as `0x${string}`;
  const amountWei = BigInt(process.argv[3] ?? '0');

  if (!tokenAddress) {
    console.error('Usage: npx tsx quote-sell.ts <tokenAddress> <amountWei>');
    console.error('BSC only.');
    process.exit(1);
  }

  const client = createPublicClient({
    chain: bsc,
    transport: http(RPC_URL),
  });

  const [tokenManager, quote, funds, fee] = await client.readContract({
    address: HELPER_ADDRESS,
    abi: HELPER_ABI,
    functionName: 'trySell',
    args: [tokenAddress, amountWei],
  });

  const out = {
    tokenManager,
    quote: quote === '0x0000000000000000000000000000000000000000' ? 'native' : quote,
    funds: funds.toString(),
    fee: fee.toString(),
  };
  console.log(JSON.stringify(out, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/send-token.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - send BNB or ERC20 from current wallet to a target address (BSC).
 *
 * Usage:
 *   npx tsx send-token.ts <toAddress> <amountWei> [tokenAddress]
 *   - toAddress: recipient wallet address (0x...)
 *   - amountWei: amount in wei (for BNB or token decimals)
 *   - tokenAddress: optional; omit or use "BNB" / "0x0" for native BNB; otherwise ERC20 token contract address
 *
 * Env: PRIVATE_KEY. Optional: BSC_RPC_URL.
 */

import { createWalletClient, http, parseAbi } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { bsc } from 'viem/chains';

const ZERO = '0x0000000000000000000000000000000000000000' as const;

const ERC20_ABI = parseAbi([
  'function transfer(address to, uint256 amount) returns (bool)',
]);

function isAddress(s: string): boolean {
  return /^0x[0-9a-fA-F]{40}$/.test(s);
}

async function main() {
  const privateKey = process.env.PRIVATE_KEY;
  if (!privateKey) {
    console.error('Set PRIVATE_KEY');
    process.exit(1);
  }
  const pk = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`);
  const account = privateKeyToAccount(pk);

  const toAddress = process.argv[2];
  const amountWeiRaw = process.argv[3];
  const tokenAddress = process.argv[4]; // optional: BNB if omitted or "BNB" or 0x0

  if (!toAddress || !amountWeiRaw) {
    console.error('Usage: send-token.ts <toAddress> <amountWei> [tokenAddress]');
    console.error('  toAddress:   recipient 0x... address');
    console.error('  amountWei:   amount in wei (for BNB or token smallest unit)');
    console.error('  tokenAddress: optional; omit or BNB/0x0 = native BNB; else ERC20 contract address');
    process.exit(1);
  }
  if (!isAddress(toAddress)) {
    console.error('Invalid toAddress:', toAddress);
    process.exit(1);
  }

  const amountWei = BigInt(amountWeiRaw);
  if (amountWei <= 0n) {
    console.error('amountWei must be positive');
    process.exit(1);
  }

  const isNative =
    !tokenAddress ||
    tokenAddress.toUpperCase() === 'BNB' ||
    tokenAddress === ZERO ||
    tokenAddress.toLowerCase() === '0x0000000000000000000000000000000000000000';

  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
  const client = createWalletClient({
    account,
    chain: bsc,
    transport: http(rpcUrl),
  });

  let txHash: `0x${string}`;

  if (isNative) {
    txHash = await client.sendTransaction({
      to: toAddress as `0x${string}`,
      value: amountWei,
    });
  } else {
    if (!isAddress(tokenAddress)) {
      console.error('Invalid tokenAddress:', tokenAddress);
      process.exit(1);
    }
    txHash = await client.writeContract({
      address: tokenAddress as `0x${string}`,
      abi: ERC20_ABI,
      functionName: 'transfer',
      args: [toAddress as `0x${string}`, amountWei],
    });
  }

  console.log(JSON.stringify({ txHash, to: toAddress, amountWei: amountWei.toString(), native: isNative }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/token-get.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - token detail + trading info (REST API)
 * GET /meme-api/v1/private/token/get/v2?address=<address>
 * Usage: npx tsx token-get.ts <tokenAddress>
 * Output: JSON token info and trading info.
 */

const API_BASE = 'https://four.meme/meme-api/v1';

async function main() {
  const address = process.argv[2];
  if (!address) {
    console.error('Usage: npx tsx token-get.ts <tokenAddress>');
    process.exit(1);
  }
  const url = `${API_BASE}/private/token/get/v2?address=${encodeURIComponent(address)}`;
  const res = await fetch(url, {
    headers: { Accept: 'application/json' },
  });
  if (!res.ok) {
    throw new Error(`token/get/v2 failed: ${res.status} ${await res.text()}`);
  }
  const data = await res.json();
  console.log(JSON.stringify(data, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/token-list.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - token list (REST API)
 * GET /meme-api/v1/private/token/query
 * Usage: npx tsx token-list.ts [--orderBy=Hot] [--pageIndex=1] [--pageSize=30] [--tokenName=] [--symbol=] [--labels=] [--listedPancake=false]
 * Output: JSON list of tokens.
 */

const API_BASE = 'https://four.meme/meme-api/v1';

function parseArg(name: string, defaultValue: string): string {
  const prefix = `--${name}=`;
  for (let i = 2; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg.startsWith(prefix)) return arg.slice(prefix.length);
  }
  return defaultValue;
}

async function main() {
  const orderBy = parseArg('orderBy', 'Hot');
  const tokenName = parseArg('tokenName', '');
  const listedPancake = parseArg('listedPancake', 'false');
  const pageIndex = parseArg('pageIndex', '1');
  const pageSize = parseArg('pageSize', '30');
  const symbol = parseArg('symbol', '');
  const labels = parseArg('labels', '');

  const params = new URLSearchParams({
    orderBy,
    tokenName,
    listedPancake,
    pageIndex,
    pageSize,
    symbol,
    labels,
  });
  const url = `${API_BASE}/private/token/query?${params.toString()}`;
  const res = await fetch(url, {
    headers: { Accept: 'application/json' },
  });
  if (!res.ok) {
    throw new Error(`token/query failed: ${res.status} ${await res.text()}`);
  }
  const data = await res.json();
  console.log(JSON.stringify(data, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/token-rankings.ts

```typescript
#!/usr/bin/env node
/**
 * Four.meme - token rankings (REST API)
 * POST /meme-api/v1/private/token/query/advanced
 * Body: { orderBy: "Time"|"ProgressDesc"|"TradingDesc"|"Hot"|"Graduated" [, barType: "HOUR24" ] }
 * Usage: npx tsx token-rankings.ts <orderBy> [--barType=HOUR24]
 *   orderBy: Time | ProgressDesc | TradingDesc | Hot | Graduated
 *   --barType only for TradingDesc (24h volume)
 * Output: JSON ranking list.
 */

const API_BASE = 'https://four.meme/meme-api/v1';

function parseArg(name: string): string | undefined {
  const prefix = `--${name}=`;
  for (let i = 3; i < process.argv.length; i++) {
    const arg = process.argv[i];
    if (arg.startsWith(prefix)) return arg.slice(prefix.length);
  }
  return undefined;
}

async function main() {
  const orderBy = process.argv[2];
  const validOrderBy = ['Time', 'ProgressDesc', 'TradingDesc', 'Hot', 'Graduated'];
  if (!orderBy || !validOrderBy.includes(orderBy)) {
    console.error('Usage: npx tsx token-rankings.ts <orderBy> [--barType=HOUR24]');
    console.error('orderBy: Time | ProgressDesc | TradingDesc | Hot | Graduated');
    process.exit(1);
  }
  const body: Record<string, string> = { orderBy };
  const barType = parseArg('barType');
  if (barType) body.barType = barType;

  const url = `${API_BASE}/private/token/query/advanced`;
  const res = await fetch(url, {
    method: 'POST',
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  });
  if (!res.ok) {
    throw new Error(`token/query/advanced failed: ${res.status} ${await res.text()}`);
  }
  const data = await res.json();
  console.log(JSON.stringify(data, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

### scripts/verify-events.ts

```typescript
#!/usr/bin/env node
/**
 * Verification helper: fetch TokenManager2 events for the last N blocks on BSC.
 * Usage: npx tsx verify-events.ts [blockCount]
 * Default blockCount 50. No private key or tx, read-only.
 */

import { createPublicClient, http, parseAbiItem } from 'viem';
import { bsc } from 'viem/chains';

const TOKEN_MANAGER2_BSC = '0x5c952063c7fc8610FFDB798152D69F0B9550762b' as const;
const EVENTS = [
  parseAbiItem(
    'event TokenCreate(address creator, address token, uint256 requestId, string name, string symbol, uint256 totalSupply, uint256 launchTime, uint256 launchFee)'
  ),
  parseAbiItem(
    'event TokenPurchase(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'
  ),
  parseAbiItem(
    'event TokenSale(address token, address account, uint256 price, uint256 amount, uint256 cost, uint256 fee, uint256 offers, uint256 funds)'
  ),
  parseAbiItem('event LiquidityAdded(address base, uint256 offers, address quote, uint256 funds)'),
];

async function main() {
  const blockCount = parseInt(process.argv[2] ?? '50', 10) || 50;
  const rpcUrl = process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org';
  const client = createPublicClient({
    chain: bsc,
    transport: http(rpcUrl),
  });
  const block = await client.getBlockNumber();
  const fromBlock = block - BigInt(blockCount);
  const toBlock = block;
  const logs = await client.getLogs({
    address: TOKEN_MANAGER2_BSC,
    events: EVENTS,
    fromBlock: fromBlock < 0n ? 0n : fromBlock,
    toBlock,
  });
  console.log(JSON.stringify({ fromBlock: String(fromBlock), toBlock: String(toBlock), count: logs.length, events: logs.map((l) => ({ eventName: l.eventName, blockNumber: Number(l.blockNumber), args: l.args })) }, null, 2));
}

main().catch((e) => {
  console.error(e.message || e);
  process.exit(1);
});

```

four-meme-ai | SkillHub