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 Base Sepolia; 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
Robinhood Testnet46630
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 0x9d9f4B6C0e9695E390d59ff1CE5757f96b181291 "approve(address,uint256)" 0xa0bF2a9Ae83602D159F475414F0a1f65aB3f4C3D 1000000000000000000000 \
  --rpc-url https://sepolia.base.org --private-key $PK

# 2) open the mission — create(string slug, uint256 goal, uint64 deadline, uint256 stake)
#    goal is native wei (0.1 ETH here), deadline is unix seconds, stake is $NINJA wei
cast send 0xa0bF2a9Ae83602D159F475414F0a1f65aB3f4C3D "create(string,uint256,uint64,uint256)" "my-mission" \
  100000000000000000 $(( $(date +%s) + 604800 )) 1000000000000000000000 \
  --rpc-url https://sepolia.base.org --private-key $PK

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

# goal met → release: escrow + stake to the creator (permissionless)
cast send 0xa0bF2a9Ae83602D159F475414F0a1f65aB3f4C3D "release(bytes32)" $MISSION_ID --rpc-url https://sepolia.base.org --private-key $PK

# deadline passed, goal unmet → refund: the caller pulls their own commit
cast send 0xa0bF2a9Ae83602D159F475414F0a1f65aB3f4C3D "refund(bytes32)" $MISSION_ID --rpc-url https://sepolia.base.org --private-key $PK
Read the state · cast
cast call 0xa0bF2a9Ae83602D159F475414F0a1f65aB3f4C3D "missionOf(bytes32)" $MISSION_ID --rpc-url https://sepolia.base.org
# (address creator, uint256 goal, uint256 stake, uint256 total, uint64 deadline, uint8 state, bool slashed)
# state: 0 active · 1 funded · 2 failed

cast call 0xa0bF2a9Ae83602D159F475414F0a1f65aB3f4C3D "commitOf(bytes32,address)" $MISSION_ID 0xBacker --rpc-url https://sepolia.base.org

Arc's escrow asset is native USDC — 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": "base_sepolia", "chain_id": 84532, "slug": "my-mission", "creator": "0x…",
    "goalWei": "100000000000000000", "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/base_sepolia/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 LaunchFactory: 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.