dexe-otc
An OTC DAO sells its own token via on-chain TokenSaleProposal tiers. Each step
is one MCP call; calldata auto-broadcasts when DEXE_PRIVATE_KEY is set.
Validate on BSC testnet (chain 97) first. Full recipe + runnable proof:
docs/OTC.md and scripts/lifecycle-otc.mjs.
The five composites
| Tool |
Role |
dexe_otc_dao_open_sale |
multi-tier createTiers envelope + IPFS metadata + deposit + createProposalAndVote. buildOnly:true returns just the envelope. |
dexe_otc_list_sales_for_dao |
list a DAO's tiers (prices, totalSold, isOff, UTC times). |
dexe_otc_buyer_status |
render-ready buyer view: prices, claimable, vesting, auto-merkle proof. |
dexe_otc_buyer_buy |
preflight balance/allowance + approve + buy(); native path uses the sentinel + value. |
dexe_otc_buyer_claim_all |
claims tiers with canClaim && !isClaimed, withdraws vested; noop when nothing pending. |
Owner flow
dexe_dao_predict_addresses → get govPool / govToken / govTokenSale.
- Deploy with treasury pre-seeded (mint to the predicted
govPool via
tokenParams.users[]) — see [[dexe-create-dao]].
dexe_otc_dao_open_sale with tiers (schema below).
- Poll
dexe_proposal_state until SucceededFor (index 4; it briefly sits in
Locked=6), then dexe_vote_build_execute → broadcast.
Buyer flow
dexe_otc_buyer_status → dexe_otc_buyer_buy → (after the sale window closes)
dexe_otc_buyer_claim_all.
Critical gotchas
- Buyer amounts accept human units — a decimal string (
"50.0") is scaled
by the token's real on-chain decimals; digits-only stays raw wei.
dexe_otc_buyer_buy converts the 18-dec-normalized buy amount to the
payment token's native decimals for the balance check and the exact
approve — no silent under-pay on <18-dec payment tokens.
- Exchange rate is PRECISION 1e25, not 1e18. 1:1 =
"10000000000000000000000000".
- Native BNB =
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE (ETHEREUM_ADDRESS),
never 0x0…0 — a zero-address purchase token makes the tier unbuyable. Buy
tools accept 0x0 as an alias but emit the sentinel.
canClaim needs block.timestamp ≥ saleEndTime + claimLockDuration — buyers
wait for the window to close even when claimLockDuration:0.
maxAllocationPerUser == 0 means unlimited, not zero — set a real cap.
- Treasury must hold the sale token before the sale opens — pre-seed at deploy.
vestingPercentage is a human percent 0–100 (auto-scaled by 1e25).
- Merkle tiers need the whitelist JSON on IPFS;
open_sale auto-uploads when
uri is empty (needs DEXE_PINATA_JWT). Roots via dexe_merkle_build.
Tier schema (abridged)
{
"name": "Public Tier 1", "description": "",
"totalTokenProvided": "<wei>",
"saleStartTime": "<unix sec>", "saleEndTime": "<unix sec>", "claimLockDuration": "0",
"saleTokenAddress": "0x…",
"purchaseTokenAddresses": ["0xEeee…EEeE"], // native, or ERC20 addresses
"exchangeRates": ["10000000000000000000000000"],
"minAllocationPerUser": "0", "maxAllocationPerUser": "<wei, non-zero>",
"vestingSettings": { "vestingPercentage": "0", "vestingDuration": "0", "cliffPeriod": "0", "unlockStep": "0" },
"participation": [] // AND-list; empty = open tier
}
Do not modify OTC tool behavior lightly — the v0.11.x OTC surface is validated
E2E on mainnet. See docs/OTC.md for the exhaustive reference.
Canonical recipe (generated from src/knowledge/ — edit there, then npm run gen:knowledge)
Open an OTC / token sale (otc_sale)
Open a multi-tier token sale (proposal → vote → execute → live), then buyers check status / buy / claim via the dexe_otc_* composites.
Ask the user:
govPool — Which DAO (govPool address)?
saleTokenAmount — How many DAO tokens to sell in this tier? · ⚠ This amount moves from the treasury into the sale at execute.
price — Price per DAO token, and in which purchase token (address, or native BNB)? · ⚠ Rates are stored ×1e25 on-chain — always pass human units and let the composite scale; a mis-scaled hand-made rate can sell the allocation for pennies.
saleWindow — Sale start and end times? (ask in the user's words — e.g. 'starts tomorrow, runs 7 days' — then compute Unix seconds from the CURRENT time; never guess the date) · ⚠ The window must be in the future at EXECUTE time (add voting-period headroom). A past window creates a dead tier — every buy reverts.
vesting (optional) — Vesting? (RECOMMEND 0 on newly deployed DAOs — vested funds are currently unrecoverable there) · default 0 · ⚠ vestingWithdraw is blocked by SphereX on fresh pools — a non-zero vestingPercentage strands the vested portion (confirmed protocol bug).
whitelist (optional) — Open to everyone, or a whitelist of addresses?
Steps:
dexe_otc_dao_open_sale — Build + create the TokenSaleProposal tiers proposal (auto-uploads merkle whitelist JSON if given).
dexe_proposal_vote_and_execute — Vote + execute the sale proposal — the tier goes live at execute.
dexe_otc_list_sales_for_dao — Confirm the tier is live and show its parameters (times are UTC).
Pitfalls (danger first):
- 🔴 TokenSaleProposal.vestingWithdraw is blocked ('SphereX error: disallowed tx pattern') in EVERY call shape on fresh pools — the VESTED portion of a purchase CANNOT be withdrawn (confirmed protocol bug, escalated upstream). Until fixed: open tiers with vestingPercentage:"0" on new DAOs. claim (the instant portion) works fine.
- 🔴 NEVER guess dates for sale/staking windows — compute Unix timestamps from the CURRENT time (ask the user or read the latest block; your idea of 'now' may be a stale year). Windows must be in the future AT EXECUTE TIME (add headroom for the voting period). A staking tier with a past deadline is SILENTLY rejected on-chain (the execute succeeds, a StakingRejected event fires, NO tier exists, the reward bounces back); a sale tier with a past window is created dead-on-arrival (every buy reverts 'TSP: token sale is over'). The builders refuse past end-times before any transaction.
- ⚠ Native BNB in sale tiers uses the sentinel address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, NOT the zero address (0x0 reverts 'TSP: incorrect token'). A native-currency buy must send msg.value equal to the buy amount. The dexe_otc_* composites canonicalize this.
- ⚠ Tier exchange rates are scaled ×1e25 (PRECISION) on-chain. Pass human units to the dexe_otc_* composites and they scale for you; only ADVANCED raw structs need pre-scaled values. A hand-scaled rate off by 10^2 sells the whole allocation for pennies.
- ℹ Whitelisted tiers use a merkle root; the tier's uri must point to IPFS JSON {"list":[lowercased addresses]} so buyers can regenerate proofs — dexe_otc_dao_open_sale auto-uploads it when uri is empty (needs DEXE_PINATA_JWT). Buyer-side reads need the proof BEFORE getUserViews or canParticipate reads false.
- ℹ Amount strings: digits-only = RAW smallest units (wei); a decimal point ("12.5") = human units scaled by the token's REAL on-chain decimals (never assumed 18). Durations and delays are SECONDS (86400 = 1 day). Composite quorum/percent params are plain percent numbers (51).
For the machine-readable plan (interview questions with risk notes, step templates with flowContext chaining), call the dexe_guide tool with flow:"otc_sale".
1---2name: dexe-otc3description: Run a DeXe OTC token sale end-to-end — open a multi-tier TokenSaleProposal, vote+execute it, then buyers check status / buy / claim. Covers the five dexe_otc_* composites plus the PRECISION-1e25 rate, native-BNB sentinel, and claim-timing gotchas. Use when the user mentions "OTC", "token sale", "sell DAO tokens", "buy from a tier", "claim vested tokens".4---56# dexe-otc78An OTC DAO sells its own token via on-chain `TokenSaleProposal` tiers. Each step9is one MCP call; calldata auto-broadcasts when `DEXE_PRIVATE_KEY` is set.10Validate on **BSC testnet (chain 97)** first. Full recipe + runnable proof:11`docs/OTC.md` and `scripts/lifecycle-otc.mjs`.1213## The five composites1415| Tool | Role |16|---|---|17| `dexe_otc_dao_open_sale` | multi-tier `createTiers` envelope + IPFS metadata + deposit + `createProposalAndVote`. `buildOnly:true` returns just the envelope. |18| `dexe_otc_list_sales_for_dao` | list a DAO's tiers (prices, `totalSold`, `isOff`, UTC times). |19| `dexe_otc_buyer_status` | render-ready buyer view: prices, claimable, vesting, auto-merkle proof. |20| `dexe_otc_buyer_buy` | preflight balance/allowance + approve + `buy()`; native path uses the sentinel + `value`. |21| `dexe_otc_buyer_claim_all` | claims tiers with `canClaim && !isClaimed`, withdraws vested; `noop` when nothing pending. |2223## Owner flow24251. `dexe_dao_predict_addresses` → get `govPool` / `govToken` / `govTokenSale`.262. Deploy with treasury pre-seeded (mint to the predicted `govPool` via27 `tokenParams.users[]`) — see [[dexe-create-dao]].283. `dexe_otc_dao_open_sale` with tiers (schema below).294. Poll `dexe_proposal_state` until `SucceededFor` (index 4; it briefly sits in30 `Locked`=6), then `dexe_vote_build_execute` → broadcast.3132## Buyer flow3334`dexe_otc_buyer_status` → `dexe_otc_buyer_buy` → (after the sale window closes)35`dexe_otc_buyer_claim_all`.3637## Critical gotchas3839- **Buyer amounts accept human units** — a decimal string (`"50.0"`) is scaled40 by the token's real on-chain decimals; digits-only stays raw wei.41 `dexe_otc_buyer_buy` converts the 18-dec-normalized buy amount to the42 **payment token's native decimals** for the balance check and the exact43 approve — no silent under-pay on <18-dec payment tokens.44- **Exchange rate is PRECISION 1e25**, not 1e18. 1:1 = `"10000000000000000000000000"`.45- **Native BNB = `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE`** (ETHEREUM_ADDRESS),46 never `0x0…0` — a zero-address purchase token makes the tier unbuyable. Buy47 tools accept `0x0` as an alias but emit the sentinel.48- **`canClaim` needs `block.timestamp ≥ saleEndTime + claimLockDuration`** — buyers49 wait for the window to close even when `claimLockDuration:0`.50- **`maxAllocationPerUser == 0` means unlimited**, not zero — set a real cap.51- **Treasury must hold the sale token before the sale opens** — pre-seed at deploy.52- **`vestingPercentage` is a human percent 0–100** (auto-scaled by 1e25).53- **Merkle tiers** need the whitelist JSON on IPFS; `open_sale` auto-uploads when54 `uri` is empty (needs `DEXE_PINATA_JWT`). Roots via `dexe_merkle_build`.5556## Tier schema (abridged)5758```jsonc59{60 "name": "Public Tier 1", "description": "",61 "totalTokenProvided": "<wei>",62 "saleStartTime": "<unix sec>", "saleEndTime": "<unix sec>", "claimLockDuration": "0",63 "saleTokenAddress": "0x…",64 "purchaseTokenAddresses": ["0xEeee…EEeE"], // native, or ERC20 addresses65 "exchangeRates": ["10000000000000000000000000"],66 "minAllocationPerUser": "0", "maxAllocationPerUser": "<wei, non-zero>",67 "vestingSettings": { "vestingPercentage": "0", "vestingDuration": "0", "cliffPeriod": "0", "unlockStep": "0" },68 "participation": [] // AND-list; empty = open tier69}70```7172Do not modify OTC tool behavior lightly — the v0.11.x OTC surface is validated73E2E on mainnet. See `docs/OTC.md` for the exhaustive reference.7475## Canonical recipe (generated from src/knowledge/ — edit there, then `npm run gen:knowledge`)7677<!-- BEGIN GENERATED: flow-recipe -->78### Open an OTC / token sale (`otc_sale`)7980Open a multi-tier token sale (proposal → vote → execute → live), then buyers check status / buy / claim via the dexe_otc_* composites.8182**Ask the user:**83- `govPool` — Which DAO (govPool address)?84- `saleTokenAmount` — How many DAO tokens to sell in this tier? · ⚠ This amount moves from the treasury into the sale at execute.85- `price` — Price per DAO token, and in which purchase token (address, or native BNB)? · ⚠ Rates are stored ×1e25 on-chain — always pass human units and let the composite scale; a mis-scaled hand-made rate can sell the allocation for pennies.86- `saleWindow` — Sale start and end times? (ask in the user's words — e.g. 'starts tomorrow, runs 7 days' — then compute Unix seconds from the CURRENT time; never guess the date) · ⚠ The window must be in the future at EXECUTE time (add voting-period headroom). A past window creates a dead tier — every buy reverts.87- `vesting` (optional) — Vesting? (RECOMMEND 0 on newly deployed DAOs — vested funds are currently unrecoverable there) · default `0` · ⚠ vestingWithdraw is blocked by SphereX on fresh pools — a non-zero vestingPercentage strands the vested portion (confirmed protocol bug).88- `whitelist` (optional) — Open to everyone, or a whitelist of addresses?8990**Steps:**911. `dexe_otc_dao_open_sale` — Build + create the TokenSaleProposal tiers proposal (auto-uploads merkle whitelist JSON if given).922. `dexe_proposal_vote_and_execute` — Vote + execute the sale proposal — the tier goes live at execute.933. `dexe_otc_list_sales_for_dao` — Confirm the tier is live and show its parameters (times are UTC).9495**Pitfalls (danger first):**96- 🔴 TokenSaleProposal.vestingWithdraw is blocked ('SphereX error: disallowed tx pattern') in EVERY call shape on fresh pools — the VESTED portion of a purchase CANNOT be withdrawn (confirmed protocol bug, escalated upstream). Until fixed: open tiers with vestingPercentage:"0" on new DAOs. claim (the instant portion) works fine.97- 🔴 NEVER guess dates for sale/staking windows — compute Unix timestamps from the CURRENT time (ask the user or read the latest block; your idea of 'now' may be a stale year). Windows must be in the future AT EXECUTE TIME (add headroom for the voting period). A staking tier with a past deadline is SILENTLY rejected on-chain (the execute succeeds, a StakingRejected event fires, NO tier exists, the reward bounces back); a sale tier with a past window is created dead-on-arrival (every buy reverts 'TSP: token sale is over'). The builders refuse past end-times before any transaction.98- ⚠ Native BNB in sale tiers uses the sentinel address 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE, NOT the zero address (0x0 reverts 'TSP: incorrect token'). A native-currency buy must send msg.value equal to the buy amount. The dexe_otc_* composites canonicalize this.99- ⚠ Tier exchange rates are scaled ×1e25 (PRECISION) on-chain. Pass human units to the dexe_otc_* composites and they scale for you; only ADVANCED raw structs need pre-scaled values. A hand-scaled rate off by 10^2 sells the whole allocation for pennies.100- ℹ Whitelisted tiers use a merkle root; the tier's uri must point to IPFS JSON {"list":[lowercased addresses]} so buyers can regenerate proofs — dexe_otc_dao_open_sale auto-uploads it when uri is empty (needs DEXE_PINATA_JWT). Buyer-side reads need the proof BEFORE getUserViews or canParticipate reads false.101- ℹ Amount strings: digits-only = RAW smallest units (wei); a decimal point ("12.5") = human units scaled by the token's REAL on-chain decimals (never assumed 18). Durations and delays are SECONDS (86400 = 1 day). Composite quorum/percent params are plain percent numbers (51).102103_For the machine-readable plan (interview questions with risk notes, step templates with `flowContext` chaining), call the `dexe_guide` tool with `flow:"otc_sale"`._104<!-- END GENERATED: flow-recipe -->