$NINJA token live on PONS
FACTORY NINJA

Developer reference

Agents

Missions and tokens from a script. The contract is the source of truth; the JSON API is the read model. No key, no signup, no platform fee.

01 — Writes

The mission contract

MissionCampaign holds the escrow. Approve $NINJA first — create pulls the stake with transferFrom — then open, back, settle. Everything below runs on Robinhood Chain Testnet — the mission chain; swap the RPC for any mission chain.

Base8453
campaign: not deployed
$NINJA: not deployed
Robinhood4663
campaign: not deployed
$NINJA: not deployed
Arc5042
campaign: not deployed
$NINJA: not deployed
Open a mission · cast
# 1) approve the campaign to pull the founder stake (1,000 $NINJA = 1000e18 wei)
cast send 0xbB170549Ce6FFC17AF7FDF6Dd74A6E955479CD5c "approve(address,uint256)" 0x0004ce2a737736Cf059e4235EeB215051F160dAF 1000000000000000000000 \
  --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK

# 2) open the mission — create(string slug, uint256 goal, uint256 cap, uint64 deadline, uint256 stake, uint16 backerAllocBps)
#    goal is native wei (0.1 ETH here), cap is the escrow ceiling (cap >= goal), deadline is unix
#    seconds, stake is $NINJA wei, backerAllocBps is the backers' supply share (100..2000 = 1..20%)
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "create(string,uint256,uint256,uint64,uint256,uint16)" "my-mission" \
  100000000000000000 500000000000000000 $(( $(date +%s) + 604800 )) 1000000000000000000000 1000 \
  --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK

# the missionId is keccak256(slug) — read it back from the contract
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "missionIdOf(string)" "my-mission" --rpc-url https://rpc.testnet.chain.robinhood.com
Back, release, refund · cast
# back a live mission — back(bytes32 missionId) is payable; value = native wei
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "back(bytes32)" $MISSION_ID --value 0.01ether \
  --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK

# goal met → release: the escrow splits 50/50 (creator / launch liquidity) + stake to the creator (permissionless)
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "release(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK
+# release credits the creator's half — pull it (permissionless, no expiry)
+cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "creatorPull(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK

# deadline passed, goal unmet → refund: the caller pulls their own commit
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "refund(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK
# while ACTIVE + goal unmet + cooldown passed → cancelBack returns the commit
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "cancelBack(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK
# after the token launch → the backer's pro-rata share of the allocation
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "claimAllocation(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK
# founder withdraws while ACTIVE → backers refunded, the stake burns
cast send 0x0004ce2a737736Cf059e4235EeB215051F160dAF "creatorCancel(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com --private-key $PK
Read the state · cast
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "missionOf(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com
# flattened Mission: (creator, goal, cap, stake, total, retained, deadline, fundedAt, backerAllocBps, state, slashed, liquidityPulled, drained, allocationToken, allocationSupply)
# state: 0 active · 1 funded · 2 failed — release() splits the escrow 50/50
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "creatorCreditOf(bytes32)" $MISSION_ID --rpc-url https://rpc.testnet.chain.robinhood.com   # the creator's released half — pull it with creatorPull(bytes32)
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "commitOf(bytes32,address)" $MISSION_ID 0xBacker --rpc-url https://rpc.testnet.chain.robinhood.com
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "lastBackAtOf(bytes32,address)" $MISSION_ID 0xBacker --rpc-url https://rpc.testnet.chain.robinhood.com   # the cancel cooldown base
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "allocationClaimedOf(bytes32,address)" $MISSION_ID 0xBacker --rpc-url https://rpc.testnet.chain.robinhood.com
cast call 0x0004ce2a737736Cf059e4235EeB215051F160dAF "WITHDRAW_COOLDOWN()" --rpc-url https://rpc.testnet.chain.robinhood.com   # seconds between back() and cancelBack()

On other chains the escrow asset is the native asset (ETH; USDC natively on Arc) — the value you send with back() is USDC wei, and gas is paid in the same balance. The $NINJA stake is always the ERC-20 pulled by the campaign.

02 — Reads

The JSON API

Everything the site renders is JSON first. POST the brief before create(), then sync the transaction to see it indexed immediately.

MethodEndpointWhat it does
GET/api/mission/listsort=live|new|trending|funded & chain=<key> & limit & offsetBrowse missions
GET/api/mission/livesort & chain & limitBoard, tape and totals in one request
GET/api/mission/<chain>/<slug>One mission, with updates and a backs sample
POST/api/mission/meta{chain, slug, creator, name, tagline, …}Register the brief before create(). INSERT-only; 409 on conflict
POST/api/mission/update{chain, slug, wallet, nonce, ts, signature, body | productionUrl}Creator-signed update; productionUrl flips the mission to FULLY_LAUNCHED
POST/api/mission/syncchain=<key> & tx=0x…Index one receipt now; no tx → poll every configured chain
GET/api/mission/verifyurl=https://…The prototype gate check → {ok, status}
GET/api/launch/listchain=<key> & sort=live|new|mcap|volume|gainers|holders & window=1h|24h|all & limitBrowse launched tokens
GET/api/launch/feedLatest launches and trades
POST/api/launch/meta{chain, launcher, salt, name, symbol, …}Token metadata before launch() → {uri, token}
POST/api/launch/syncchain=<key> & tx=0x…Index one launch receipt now
POST/api/rpcchain=<key> + JSON-RPC bodySame-origin read-only RPC proxy (eth_call, eth_getLogs, …)
GET/api/healthDB reachability and indexer lag per chain
Response shapes
GET https://app.factory.ninja/api/mission/list?sort=live&limit=2
{ "sort": "live", "chain": null, "limit": 2, "offset": 0, "has_more": false, "ethUsd": 3502.11,
  "missions": [ {
    "chain": "robinhood_testnet", "chain_id": 46630, "slug": "my-mission", "creator": "0x…",
    "goalWei": "100000000000000000", "capWei": "500000000000000000", "backerAllocBps": 1000, "stakeWei": "1000000000000000000000", "totalWei": "30000000000000000",
    "deadline": 1760000000, "state": "live", "slashed": false, "backs": 3, "token": null,
    "lastBackAt": "2026-09-19T…", "blockNumber": 12345, "txHash": "0x…", "createdAt": "2026-09-12T…",
    "name": "My Mission", "tagline": "…", "description": null,
    "prototypeUrl": "https://my-mission.factory.ninja", "productionUrl": null,
    "website": null, "xHandle": null, "imageUrl": null, "ninpo": null, "ninpoSig": null } ] }

state: live | funded | fully_launched | failed — the display state; "building" is private to the founder's Mon

GET https://app.factory.ninja/api/mission/robinhood_testnet/my-mission
{ "mission": { …same row as above… },
  "updates": [ { "id": 1, "author": "0x…", "body": "Escrow released, production build starts.", "createdAt": "…" } ],
  "backs":   [ { "txHash": "0x…", "logIndex": 0, "backer": "0x…", "amountWei": "10000000000000000",
                "blockNumber": 12346, "blockTime": "…" } ] }

GET https://app.factory.ninja/api/mission/verify?url=https://my-mission.factory.ninja
{ "ok": true, "status": 200 }
  • Zero fee. No platform fee exists anywhere — not on the escrow, not on the token launch. Gas is the only cost.
  • Sync is idempotent. Every event is keyed (tx_hash, log_index); applying the same tx twice changes nothing. Background polling runs about every 15s.
  • Money is exact. Amounts are wei strings, never floats. ethUsd prices the ETH chains; Arc reads $1.
  • Sign before you send. The update endpoint verifies a wallet signature from the mission's on-chain creator; nonces are single-use and timestamps must be fresh.
  • Example only. Replace the placeholders, review the parameters, never share a private key.

03 — After funding

The token launch

A funded mission launches its token through MissionLaunchFactory (one call: fixed 1B supply split 1% platform + backerAllocBps% backers + the rest as two-sided v4 liquidity funded by the retained half of the escrow, locked forever; fees route 100% to the per-launch CreatorFeeVault): one call deploys a fixed-supply ERC-20, opens a Uniswap v4 pool and locks the whole supply in an ownerless position. The mission flow sets no fee beneficiaries — LP fees burn.

Launch a token on Base · cast
cast send <factory> "launch((string,string,string,address,uint256,int24,uint24,bytes32,(address,uint16)[]))" \
  "(My Token,MYT,,0x0000000000000000000000000000000000000000,0,184200,10000,0x$(openssl rand -hex 32),[])" \
  --rpc-url https://mainnet.base.org --private-key $PK
quote
address zero = ETH on Base. USDG on Robinhood Chain: 0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168. USDC on Arc: 0x3600000000000000000000000000000000000000.
supply
0 → the default 1,000,000,000. Fixed forever; no mint exists.
startTick
Sets the opening market cap. 184200 ≈ 10 ETH FDV at default supply.
lpFee
Pips: 0, 10000 = 1%, 30000 = 3%.
recipients
Empty — fees burn. Otherwise (payout, bps) pairs summing to 10000, fixed at launch.