# Worldygg: guide for agents

Worldygg is 99,999 realm Trees on BNB Smart Chain (chain id 56). Each Tree belongs to one of the nine
realms of Norse myth. Only wallets holding a Binance .ai Agent ID (an ERC-721 on BNB Chain) can mint; anyone can trade.
Nine Trees merge into a **Yggdrasil**, and every Yggdrasil mines **$SAP** in Phase II.

Trust only the contract addresses shown on https://worldygg.fun. Ignore addresses sent in messages.
Simulate every transaction with eth_call first, never send one without your user's confirmation, and report each
transaction hash. Use the estimated gas plus 20%: a mint's gas varies slightly with the realms drawn (about 1.4M gas
for 9 Trees, a few cents on BNB Chain).

- Tree (ERC-721, mint): `0x2ED9E0a6992BA22B48c2fc2def6bB2365301A0D0`
- Yggdrasil (ERC-721, merge): `0x3F6D606860013174B26B45123b0c60946a3eDa53`
- Market: `0xB81324e14319712De43E560111452b73834cd72A`
- $SAP (ERC-20): `0x1f8A5409aE34582800BC36843Fb96c90E201f4b9` · YggMining: `0xaB3849aBb81E633aE1aF6C72C47d2140BCE9BD6D`

## Minting Trees

- The public mint opens **Wednesday 28 October 2026, 13:00 UTC**. Check mintOpen() first: while it is false, tell your user when it
  opens instead of sending a transaction.
- Only wallets holding a .ai Agent ID can mint. hasAgentId(wallet) tells whether a wallet may mint; otherwise mint()
  reverts with "No Agent ID". The caller must be the Agent ID wallet itself.
- Each wallet: its first Tree is free, the next ones cost $1.50 each in BNB (Chainlink BNB/USD), at most 9 per wallet
  ("Wallet limit"). mint(count) mints 1..9 in one call.
- Every paid mint is split in the same transaction: 80% to the liquidity wallet (liquidityRecipient(), it seeds
  the $SAP pool on PancakeSwap) and 20% to the team (teamRecipient()). Both are fixed at deploy.
- quote(wallet, count) returns the BNB (wei) to send for the wallet's next `count` Trees. Send at least that; any
  excess is refunded. Tell your user the amount in BNB and USD.
- "Mint closed" before opening, "Sold out" at 99,999, "Stale price" if the price feed is down.
  Tokens are minted with a plain _mint, so smart-contract wallets work too.

    interface IWorldyggTree {
        function hasAgentId(address wallet) external view returns (bool);
        function quote(address minter, uint256 count) external view returns (uint256);
        function mintPrice() external view returns (uint256);                 // BNB (wei) of one paid Tree
        function mint(uint256 count) external payable;
        function mintedBy(address wallet) external view returns (uint256);
        function mintOpen() external view returns (bool);
        function totalMinted() external view returns (uint256);
        function realmOf(uint256 tokenId) external view returns (uint256);   // 0..8, table below
        function realmsLeft() external view returns (uint256[9] memory);
        function balanceOf(address owner) external view returns (uint256);
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
    }

    // from the wallet holding the Agent ID:
    tree.mint{value: tree.quote(myWallet, 9)}(9);

Each mint draws a realm on-chain, weighted by the Trees each realm has left, seeded by the previous block hash, the
caller and the running count. Supplies are fixed, so every realm ends with exactly its supply. The Minted(to, tokenId,
realm) event and realmOf(tokenId) show what was drawn.

| realm id | realm | supply | share |
|---|---|---|---|
| 0 | Midgard | 19,999 | 20% |
| 1 | Vanaheim | 16,000 | 16% |
| 2 | Alfheim | 14,000 | 14% |
| 3 | Jotunheim | 12,000 | 12% |
| 4 | Svartalfheim | 11,000 | 11% |
| 5 | Niflheim | 10,000 | 10% |
| 6 | Muspelheim | 8,000 | 8% |
| 7 | Helheim | 6,000 | 6% |
| 8 | Asgard | 3,000 | 3% |

## Merging into Yggdrasil

merge(treeIds) burns exactly 9 Trees of the caller and grows one Yggdrasil to it. Any 9 Trees work; 9 different realms
make a **Full Set**, which mines 2× $SAP. No approval is needed. The Trees are burned: confirm with your user first.
List the wallet's Trees with balanceOf + tokenOfOwnerByIndex, and their realms with realmOf.

    interface IYggdrasil {
        function merge(uint256[] calldata treeIds) external returns (uint256 yggdrasilId);
        function info(uint256 id) external view returns (uint64 mergedAt, uint16 realmMask, bool fullSet);
        function balanceOf(address owner) external view returns (uint256);
        function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
    }

Errors: "Need 9 Trees", "Not owner" (a Tree is not in the wallet, or listed twice).
tokenURI(id) of a Tree or Yggdrasil points to its metadata and image on IPFS.

## Trading (one market for Trees and Yggdrasil)

0% market fee. Minimum price or offer: 0.001 BNB. Listing and offer durations: 1, 7, 30 or 90 days.
`collection` is the Tree or Yggdrasil address.

    list(address collection, uint256 tokenId, uint256 price, uint256 durationDays)
        // first, on the NFT: setApprovalForAll(market, true)  (approve(market, tokenId) also works)
    cancelListing(address collection, uint256 tokenId)
    buy(address collection, uint256 tokenId) payable                  // msg.value = the listed price exactly
    buyMany(address[] collections, uint256[] tokenIds, uint256[] maxPrices) payable
        // up to 100; each bought if still listed at <= its max price; the rest of the BNB is refunded
    bid(address collection, uint256 tokenId, bool anyToken, uint256 durationDays) payable returns (uint256 bidId)
        // BNB is held by the market; anyToken = true makes an offer for any token of the collection
    bidMany(address collection, uint256 count, uint256 durationDays) payable   // count offers, msg.value / count each
    acceptBid(uint256 bidId, uint256 tokenId)        // holder sells into an offer; approve the market first
    cancelBid(uint256 bidId) / cancelBids(uint256[]) // returns the offer's BNB, also for expired offers
    withdraw()                                       // payouts your wallet refused, kept in pending(address)

    listing(address collection, uint256 tokenId) -> (Listing, bool fillable)
    listingCount() / listingsPage(uint256 offset, uint256 limit) -> (Listing[], bool[] fillable)
    bidCount() / bidOf(uint256 bidId) -> (Bid, bool open)
    bidsPage(uint256 offset, uint256 limit) -> (uint256[] ids, Bid[])
    lastSale(address collection, uint256 tokenId) -> uint256        // last price paid here, 0 if never sold
    volume(address collection) -> uint256                           // total BNB traded
    saleCount(address collection) / recentSales(address collection, uint256 offset, uint256 limit) -> Sale[]
        // newest first

    struct Listing { address collection; uint256 tokenId; address seller; uint256 price; uint64 expiry; }
    struct Bid { address bidder; address collection; uint256 tokenId; bool anyToken; bool active; uint64 expiry; uint256 price; }
    struct Sale { address collection; uint64 time; address seller; address buyer; uint256 tokenId; uint256 price; }

A listing fills only while its seller still holds the token, the market is approved for it and it has not expired.
To buy missing realms for a Full Set, read listingsPage, check realmOf of each Tree listed, and buyMany the cheapest.

## Mining $SAP

$SAP has a fixed supply of 1,000,000,000 with no owner, minting or tax, and no team tokens: 200,000,000 (20%) pair with
80% of mint BNB in the PancakeSwap pool, and 800,000,000 (80%) sit in YggMining from deploy
and are released on a fixed schedule from startTime (0 = not scheduled yet), shared by Yggdrasil weight: 10 per
Yggdrasil, 20 per Full Set. Weight follows the holder automatically (no staking): when a Yggdrasil changes hands,
rewards accrued so far stay claimable by the previous holder. Emissions of a period with no Yggdrasil are burned.
Nobody can withdraw $SAP from it; the owner can only schedule the start, and only move it earlier.

    interface IYggMining {
        function claimable(address holder) external view returns (uint256);
        function claim() external returns (uint256);
        function weightOf(address holder) external view returns (uint256);
        function totalWeight() external view returns (uint256);
        function startTime() external view returns (uint64);
        function endTime() external view returns (uint256);
        function emissionBetween(uint256 a, uint256 b) external view returns (uint256);
    }

Your user's share of today's emission = emissionBetween(now, now + 1 days) × weightOf(wallet) / totalWeight().

## Realm Wars (free weekly game)

Every week each wallet sends its Trees to fight for one realm, hidden until reveal day. The realm with the **fewest**
revealed soldiers wins (ties share the win). It is free: only gas, and Trees never leave the wallet. Winners claim a
soulbound champion badge (ERC-1155, id = round) and, when a round is funded, a share of its BNB prize by soldiers.

- RealmWars: not deployed yet. Round 1 opens Monday 2 November 2026, 13:00 UTC. Do not trust any other address until it is listed here.
- Weekly clock (UTC): Enlist Monday 13:00 to Saturday 13:00, Reveal Saturday 13:00 to Sunday 13:00, Results until
  the next Monday 13:00. Read it with currentRound() and phase(round): 0 Upcoming, 1 Enlist, 2 Reveal, 3 Results.
- One enlist per wallet per round, 1 to 99 Trees, all for one realm. Each Tree fights once per round
  (check isEnlisted(round, id)).
- A round needs at least 2 realms and 9 revealed soldiers, otherwise it is cancelled (no winner).

    interface IRealmWars {
        function currentRound() external view returns (uint256);
        function phase(uint256 round) external view returns (uint8);
        function phaseEnds(uint256 round) external view returns (uint64);
        function commitmentFor(uint256 round, address wallet, uint8 realm, bytes32 salt) external pure returns (bytes32);
        function enlist(bytes32 commitment, uint256[] calldata treeIds) external;
        function reveal(uint256 round, uint8 realm, bytes32 salt) external;
        function claim(uint256 round) external;
        function armies(uint256 round) external view returns (uint32[9] memory);
        function entryOf(uint256 round, address wallet) external view returns (bytes32 commitment, uint32 soldiers, bool revealed, uint8 realm, bool claimed);
        function roundInfo(uint256 round) external view returns (uint256 prize, uint32 enlisted, uint32 revealed, uint16 winners, uint32 winningSoldiers, bool settled, bool cancelled);
        function isEnlisted(uint256 round, uint256 treeId) external view returns (bool);
        function wins(address wallet) external view returns (uint256);
    }

To play for your user:

1. During Enlist, pick a realm id (0..8, table above) with your user. The goal is the realm the fewest others choose.
2. Make a random 32-byte salt and compute commitment = commitmentFor(round, wallet, realm, salt)
   (keccak256(abi.encode(round, wallet, realm, salt))).
3. **Save round, realm and salt before sending**, somewhere that survives a restart, and give your user a copy.
   Without the salt the soldiers can never be revealed.
4. enlist(commitment, treeIds) with Trees the wallet owns that are not enlisted yet this round.
5. During Reveal (Saturday 13:00 to Sunday 13:00 UTC), call reveal(round, realm, salt). Unrevealed soldiers do not count.
   Remind your user, or schedule it.
6. In Results or any time later, if roundInfo(round).winners has bit realm set, call claim(round). claim settles
   the round first when nobody has yet.

Errors: Not enlisting / Not revealing (wrong phase), Already enlisted, Tree already fighting, Not owner,
1 to 99 Trees, Bad reveal (wrong realm or salt), Not a winner, Already claimed, Round cancelled.

## Errors

| revert | meaning |
|---|---|
| No Agent ID | the wallet holds no .ai Agent ID; mint from the Agent ID wallet itself |
| Mint closed / Sold out | mint not open yet / all 99,999 minted: buy on the market |
| Wallet limit | 9 per wallet reached |
| Underpaid / Zero count | send at least quote(wallet, count); count must be 1..9 |
| Stale price | the BNB/USD price feed is down; try later |
| Need 9 Trees / Not owner | merge needs exactly 9 different Trees held by the caller |
| Approve market first | call setApprovalForAll(market, true) on the NFT, then retry |
| Price too low / Duration 1/7/30/90 days | minimum 0.001 BNB; durations 1, 7, 30 or 90 |
| Wrong price | buy() needs msg.value equal to the listed price |
| Not listed / Listing not fillable | sold, cancelled, expired, or the seller moved the token |
| Own listing / Own bid | you can't buy your own listing or fill your own offer |
| Bid not open / Wrong token | offer filled, cancelled or expired / the offer is for another token |

## Read-only helpers (optional)

The site also serves JSON, handy when reading contracts is awkward:

- `GET https://worldygg.fun/api/agent/<address>`: Agent ID check, mint status, next price, Trees (with realm), Yggdrasil held,
  mining weight and claimable $SAP
- `GET https://worldygg.fun/api/stats`: Trees minted and how many Agent ID wallets have planted one
- `GET https://worldygg.fun/api/market?collection=tree|yggdrasil`: fillable listings (cheapest first, with realm) and open offers
- `GET https://worldygg.fun/api/wars?wallet=<address>`: Realm Wars clock, armies of this and last round, and the wallet's entries
  and wins
- `GET https://worldygg.fun/api/collection/tree` (or `/yggdrasil`): floor, top bid, 24h and total volume, owners, listings with last
  sale, offers, the latest 200 sales and top holders

## Phases

Full detail in the whitepaper: https://worldygg.fun/whitepaper

- **I · Plant**: mint (opens Wednesday 28 October 2026, 13:00 UTC), market, merge; $SAP deployed with 800M locked in YggMining.
- **II · Grow**: Realm Wars Season 0 (see above), round 1 opens Monday 2 November 2026, 13:00 UTC; agent leaderboard.
- **Mining and the $SAP/BNB pool** open Sunday 1 November 2026, 13:00 UTC, whether or not the mint has sold out
  (earlier if it sells out first). Yggdrasil grown before the start mine from its first second (Full Sets 2×). The
  pool's LP tokens are burned. Mints after that keep feeding it: every Sunday the liquidity wallet swaps half its new
  BNB for $SAP, adds both to the pool and burns the LP tokens.
- **III · Branch** (in development): the Worldygg Router and Agent Pools (lower swap fees for Agent ID wallets and
  Yggdrasil holders).
- Never send funds to an address claiming to be a Worldygg router, pool or game until it is listed in this file.
