# Pangea documentation: v2

This is the Markdown version of https://www.pangea.trade/app/docs/v2. The whole documentation, every tab, is https://www.pangea.trade/app/docs.md.

Factory v2 made every launch from 2026-10-27 until 2026-11-02, and those coins keep its rules. This tab is how a v2 launch works, and the full reference v3 builds on: the chains a creator picks, the opening market cap, degen mode, coin tokenomics, the fees, bridging, routes and the APIs. Where v3 differs, the v3 tab says so. The coins launched before factory v2, from the v1 factory, are at the end: Earlier (v1).

- Every token launches on up to five chains at once, one pool on each. No bonding curve.
- Price is automatically arbitraged using LayerZero Omnichain Fungible Tokens.
- Liquidity is locked on every chain: no contract function or program instruction withdraws it.

| Figure | Value | What it says |
| --- | --- | --- |
| Chains | 5 | Every launch deploys on all five at once, or on the ones its creator picks. |
| Supply | 5B | All of it goes into liquidity from the start: 1B per chain · 5B across 5 chains, with degen mode off. In degen mode, on by default on the launch form, 1B in all. |
| Per liquidity pool | 1B | Each chain’s own supply, all of it in its pool, with degen mode off; in degen mode, its equal share of the 1B. |
| Trading fee | 0.3-5% | Set once, per launch. |

## How it works

**Factory v2.** Factory v2 made every launch from 2026-10-27 until 2026-11-02, and this tab describes it; every launch since is made by factory v3: [Pangea v3 →](https://www.pangea.trade/app/docs/v3#v3-overview) A coin launched before factory v2 keeps the rules it launched under: [V1 (earlier launches) →](https://www.pangea.trade/app/docs/v2#v1-how)

By default a launch deploys the same token on all five chains at once. Each chain gets 1B tokens in its liquidity pool with degen mode off, or its equal share of 1B in all (200M on five chains) in degen mode, which is on by default on the launch form; all five pools open at the same US dollar price.

**Launch on selected chains.** A creator can instead pick the chains: any two to five of the five, at least one of them an EVM chain. With degen mode off, each chosen chain still gets its own 1B, so a launch on three chains is 3B in all. The token never exists on the chains left out.

**Opening market cap.** The creator picks the market cap the whole coin opens at, all of its chains together: $3,000 to $500,000 a chain times the chains (in degen mode $3,000 to $500,000 in all). Every chain opens at the same price, with its share. On factory v3 there is no locked supply: degen mode is normal mode, every chain minting its own 1B and pooling all of it, opening at $600 a chain ($3,000 for a coin on five chains). The least a v3 launch may open at is $600 a chain, which the platform may set between $600 and $3,000 a chain; standard mode still opens at $3,000 a chain unless the creator picks another cap. [Opening market cap →](https://www.pangea.trade/app/docs/v2#pools)

**Degen mode.** One switch on the launch form, on by default, makes the coin 1B in all: each chain mints and pairs an equal share, 200M (20%) on each of five chains. Turned off, each chain gets its own 1B. [Degen mode →](https://www.pangea.trade/app/docs/v2#degen-mode)

Every token a chain mints goes into its liquidity pool from the start. There is no team allocation, treasury or vesting. Each pool starts with tokens only and collects the quote asset as people buy.

Each chain operates independently, and one thing keeps the prices together: arbitrage, which is open to anyone. Buy on a cheaper chain and sell on a more expensive one.

A launch can also set [a different trading fee on each chain](https://www.pangea.trade/app/docs/v2#trading-fee), send its fees to [a fee receiver](https://www.pangea.trade/app/docs/v2#creator-wallets) and spend the creator’s share through [coin tokenomics](https://www.pangea.trade/app/docs/v2#coin-tokenomics). From a program, the same launch goes through the [Launch API](https://www.pangea.trade/app/docs/v2#launch-api).

## The five pools

Each chain gets 1 billion tokens in its liquidity pool (in degen mode, its equal share of the 1B), held in a concentrated liquidity position. As the price rises, the pool sells tokens and collects the quote asset.

| Chain | Venue | Quote |
| --- | --- | --- |
| Ethereum | Uniswap v4 | ETH |
| Base | Uniswap v4 | ETH |
| Robinhood Chain | Uniswap v4 | ETH |
| BSC | Uniswap v4 | BNB |
| Solana | Meteora DAMM v2 | SOL |

Each pool pays sellers from its own reserves. A chain with fewer buyers may have less quote asset available, regardless of how much liquidity exists elsewhere.

### Opening market cap

The creator picks the market cap the whole coin opens at, all of its chains together: **$3,000 to $500,000** a chain times the chains, in whole dollars (a total that does not divide by the chains is rounded down to a whole dollar a chain), and $3,000 a chain when left alone. The price per token is that market cap over the whole supply on every chain, and every chain opens at that one price with its share of the supply, so a $100,000 launch on four chains opens at $25,000 on each. It is signed into the launch and the contracts refuse any other, so nobody can open below $3,000 a chain.

In degen mode the market cap is likewise the whole coin's, from $3,000 to $500,000 in all: every chain reports the whole 1B, so terminals show that market cap on each chain, and each chain's pool holds its share of it. A higher opening market cap is a deeper pool: the same buy moves the price less. On factory v3 there is no locked supply: degen mode is normal mode, every chain minting its own 1B and pooling all of it, opening at $600 a chain ($3,000 for a coin on five chains). The least a v3 launch may open at is $600 a chain, which the platform may set between $600 and $3,000 a chain; standard mode still opens at $3,000 a chain unless the creator picks another cap.

## Five contracts, one token

Each chain has its own token contract and address, so buyers choose which chain to trade on.

A token’s address is different on every chain (unless its creator chose “Same contract address on all EVM chains”, which puts it at one address on the four EVM chains), and every one of them ends in `6ea`: the last three hex digits on the four EVM chains, the last three characters of the mint on Solana. The token’s page lists every one.

Each chain has 1 billion tokens in its own liquidity pool, so 5 billion across the five chains.

| Chain | In its liquidity pool at launch |
| --- | --- |
| Ethereum | 1B |
| Base | 1B |
| Robinhood | 1B |
| BNB Chain | 1B |
| Solana | 1B |

Each contract reports its own chain’s 1B supply.

A launch on selected chains has a contract on each of its chains only. In degen mode every contract reports the coin’s whole 1B instead. [Degen mode →](https://www.pangea.trade/app/docs/v2#degen-mode)

> **Note:** [How market cap is counted →](https://www.pangea.trade/app/docs/v2#market-cap)

## Degen mode

Degen mode is one switch on the launch form, on by default. The coin is **1B in all**, not 1B a chain. Each of the launch’s chains mints the same share of it and puts all of that share in its pool: **200M, 20% of the supply, on each of five chains**. On fewer chains each share is larger: 500M each on two.

| Five chains | Standard | Degen mode |
| --- | --- | --- |
| Total supply | 5B: 1B a chain | 1B |
| Minted and paired on each chain | 1B | 200M (20%) |
| What each chain’s contract reports | Its own 1B | The whole 1B |
| The rest of the supply | None | In Solana’s bridge reserve |

Every chain’s contract reports a total supply of 1B, so a terminal on any chain shows the market cap of the whole coin. Circulating is that same 1B, everything minted and paired across the chains less anything burned: the bridge reserve below backs the other chains and is not extra supply, so it is never taken off circulating, and the market cap is the price times 1B.

### The bridge reserve on Solana

On a launch with Solana, Solana’s mint holds the full 1B: Solana’s own share in its pool, and everything else in a **bridge reserve** owned by Pangea’s launch program. It can only be burned: nobody can take tokens out of it or spend them. It moves only with bridging. When tokens are bridged to Solana, the same number is burned from the bridge reserve, and when tokens leave Solana, the same number is added back to it, so Solana’s supply stays 1B.

A degen mode launch without Solana has no bridge reserve: its chains mint their shares, and every one of them reports 1B.

The opening market cap is chosen the same way as for any launch and is the whole coin's, every chain together; every chain opens at the same price, with its share. [Opening market cap →](https://www.pangea.trade/app/docs/v2#pools)

> **Note:** A coin launched in degen mode carries the degen mode mark on the explorer and on its coin page (hover or tap it to read “Degen mode”); a coin launched without it carries none. Its tokenomics shares are tags too: a small icon with the share, such as the flame with “30%” (burn) or the gift with “70% · USDT” (holder rewards); hover or tap one to read its name.

> **Note:** [How market cap is counted →](https://www.pangea.trade/app/docs/v2#market-cap)

## The market cap situation

Terminals and apps calculate market cap in different ways. A token’s full market cap is the sum of its market caps on all five chains.

The price is the same on every terminal and app. You can bridge your tokens, or buy on any of the five contracts, at the same dollar price for the same number of tokens.

To see a token’s complete market cap, use its page on this site: it is the most accurate source.

A coin launched in degen mode is the exception: every chain reports the whole 1B, so each chain’s market cap is already the whole coin’s.

The differences between platforms come from how Pangea manages supply on each chain to keep prices in line. That is what keeps the price gap between chains small, and what makes a single token across many chains possible. Every platform is consistent with itself, and many of them show the same figures.

**The key point:** the same token, at the same price, on every chain.

## Launch fee

You choose one of the launch’s chains to pay on and pay once, from your own wallet, in that chain’s native asset. That one payment covers the launch on all of its chains, so you need no funds on the others.

| Pay on | Paid in | From |
| --- | --- | --- |
| Ethereum | ETH | Your EVM wallet |
| Base | ETH | Your EVM wallet |
| Robinhood Chain | ETH | Your EVM wallet |
| BSC | BNB | Your EVM wallet |
| Solana | SOL | Your Solana wallet |

### What it pays for

- **Gas on the four EVM chains.** The launch transaction on each of the launch’s EVM chains (Ethereum, BSC, Base, Robinhood Chain), priced at 8,250,000 gas each, 5,000 more on the chain you pay on, at that chain’s gas price when you ask for the quote (on Ethereum, plus the 0.05 gwei extra tip its launch may bid to land in the next block), plus the 1,000,000 wei opening buy (0.000000000001 ETH or BNB) each launch makes in its own pool.
- **Rent and fees on Solana.** 0.0288 SOL, on a launch with Solana: the rent for the accounts the token needs there (its mint, its pool and position, its LayerZero store and escrow, its record in Pangea’s program) and the fees of the transactions that create them. In degen mode, 0.00394208 SOL more for the bridge reserve’s accounts.
- **Tokenomics, when you turn it on.** 450,000 gas on each EVM chain to set up the coin’s vault there, and on a launch with Solana a “Solana tokenomics setup” line on the form: 0.01512 SOL, or 0.027 SOL when Solana’s split has a liquidity share.
- **A platform margin.** 20% on top of that cost, for gas moving between the quote and the launch and for a chain that has to be retried.

So the amount is not fixed. It moves with gas prices on the four EVM chains and with the prices of ETH, BNB and SOL. It is quoted when you prepare the launch, and the launch page shows it in the asset you pay in, with its approximate US dollar value, before you sign. A quote is good for 10 minutes; once it runs out, you prepare again for a fresh one.

**At today’s prices a launch typically costs about $8 to $10.** For example, TEST4’s quote was $8.16: $6.80 of cost plus the 20% margin, paid as 0.0112 BNB. TEST3’s was $9.20, from $7.66 of cost.

Dev buys are off for now: the launch form does not offer one and the Launch API refuses one. Launch buys are the way to buy at launch (the V3 tab). When a dev buy is offered, it is optional. If you set one, it is your own money: it is paid on top of the fee in the same transaction, on the same chain, and shown separately. The fee then includes what the buy adds to the launch’s own cost, 150,000 gas on an EVM chain or one token account’s rent on Solana, so the platform pays no part of it.

> **Note:** The fee is held, not taken, until your launch lands. If the launch never happens it is refunded, or you can take it back yourself after 7 days.

## Trading fee

Each launch sets a trading fee between **0.3% and 5%**, once, at launch. No contract function or program instruction changes it afterwards.

By default every chain charges the same fee. A creator can instead pick **a different fee on each chain**, each between 0.3% and 5%: each chain's fee is signed into that chain's part of the launch and is just as fixed.

The platform takes **20% of the trading fee**, with a floor of 0.2% of volume. The creator receives the rest.

| Trading fee | Platform | Creator |
| --- | --- | --- |
| 0.3% | 0.2% (floor) | 0.1% |
| 1% | 0.2% | 0.8% |
| 5% | 1.0% | 4.0% |

> **Note:** The minimum fee is 0.3% because Meteora keeps 20% of trading fees on Solana. At a 0.2% fee, only 0.16% of volume would remain, less than the platform’s share. This also means creators earn less on Solana: at a 1% trading fee they receive 0.6% there against 0.8% on Base.

80% of platform fees go to PANGEA buybacks.

**Who receives the creator’s share.** The creator wallets, unless the launch names a fee receiver or turns on tokenomics. [Creator wallets and fee receiver →](https://www.pangea.trade/app/docs/v2#creator-wallets) On Solana, the fee split is enforced by Pangea's splitter program.

**Where the fee goes can change.** Only the platform can move the creator’s share to another address. A coin launched before factory v2 works differently: [V1 trading fee →](https://www.pangea.trade/app/docs/v2#v1-trading-fee)

## Factory v2 (superseded by v3)

> **Note:** **Superseded.** From 2026-11-02 launches were made by factory v3, on all five chains, and every new launch is now v4. This page is kept for coins launched by factory v2 between 2026-10-27 and 2026-11-02, whose rules do not change. [What changed in v3 →](https://www.pangea.trade/app/docs/v3#v3-changes)

From 2026-10-27 until 2026-11-02, every launch on Ethereum, Base, BSC and Robinhood Chain came from factory v2. Solana was unchanged: its launches kept going through the same splitter program. [Factory addresses →](https://www.pangea.trade/app/docs/v2#routes)

### What is new for a launch

- The coin’s own tokenomics v2 vault can be named as its fee receiver. The factory checks it, and refuses a vault made for any other launch or creator.
- A tokenomics v1 vault is accepted as the fee receiver only when every EVM chain of the launch is paired with its native coin.

### The new fee splitter

Each EVM chain of a new launch has a **FeeSplitterV2**. The trading fee and the split between the creator and the platform are fixed for good. The fee receiver can be moved, in two steps: the platform proposes a new address, and the move takes effect only when that address accepts. The creator cannot move it alone.

Coins launched earlier keep the fee splitter they launched with. Their rules do not change, and they keep trading and bridging as before.

## Creator wallets and fee receiver

**Creator wallets.** Every launch names two: one EVM address and one Solana address, because an EVM key cannot receive on Solana and a Solana key cannot receive on the EVM chains. They are the coin’s creator: they own its page, its socials and its dev buy, and they receive its share of the trading fees unless a fee receiver is set.

**Fee receiver.** Optional. It sends the trading fees to another address, such as a Safe or a partner, on the EVM chains, on Solana, or both. You stay the creator: the page, the socials and the dev buy are still yours, and only the fees go to the receiver. Left empty, the fees go to your creator wallet. Only the platform can change it later.

- An address that is a contract on one of the launch’s chains is asked about first: tick that it is your own Safe or smart wallet and that it can send on what it is paid.
- “Burn my creator fees” makes the fee receiver the burn address on every chain, for good. You stay the creator.
- With tokenomics on, the fee receiver is not used: the fees go to the coin’s vault, which pays your creator share to your creator wallet. [Coin tokenomics →](https://www.pangea.trade/app/docs/v2#coin-tokenomics)

### The connected wallet

The creator wallets start as the wallet you have connected. Switch wallets in your wallet app and a creator wallet field that came from the old wallet follows the new one; an address you typed yourself stays as it is. A quote prepared for the old wallet is dropped, since the new wallet cannot sign for it: prepare again. Nothing changes while a payment is under way.

### Creator fees on your profile

- A new launch’s creator fees show on its creator’s profile within a minute or two of launch, on every chain of the launch, Solana included, and the coin shows in the Created tab.
- Anything not read yet is said on the page, such as fees pending on a chain, instead of being shown as zero.
- When fees are paid out, the payout moves from unclaimed to claimed within about a minute.
- “Claim now” pays a launch’s fees out at once, split exactly as the automatic payout does: you send the transaction and pay its gas.

## Coin tokenomics

With “Spend my fee share automatically” on, the creator’s share of the trading fees goes to a **vault** made for that launch, not to the creator wallet. The vault spends it in the shares you set at launch. The split is fixed at launch.

| Bucket | What the vault does with it |
| --- | --- |
| Burn | Buys your coin and burns it. |
| Buy and hold (treasury) | Buys your coin and sends it to your treasury wallet. |
| Add to liquidity | Buys your coin and adds it to the pool, locked for good. |
| Creator | Paid to your creator wallet, in the chain’s coin. |
| Community wallet | Paid to a community wallet, in the chain’s coin. |

- The shares add up to 100%. The form offers presets (Balanced, All burn, Liquidity, Community) or your own split.
- “A different split on each chain” sets each chain’s shares on their own. Otherwise every chain uses the same split.
- It works on every chain of the launch, Solana included. Solana takes its own treasury and community wallets.
- With tokenomics on, the fee receiver is not used, the fees cannot also be burned, and you cannot send your own chain’s launch yourself: the vault is the coin’s fee receiver.

### When the vault runs

- At most once every 15 minutes on each chain, at the market price. There is no reference price, and each buy’s price impact is capped at the pool’s own fee, so a large balance is spent over several runs.
- A bucket waits until it holds enough to be worth a run: 0.15 ETH on Ethereum, 0.0015 ETH on Base and Robinhood Chain, 0.008 BNB on BSC and 0.025 SOL on Solana.
- A run pays its own gas from what it spends, at most 10% of it on the EVM chains and 2% on Solana. On Ethereum, where gas is dear, buys wait until the pool is deep enough for that.
- On Solana the liquidity bucket also repays the rent of the coin’s liquidity position there, at most 0.015 SOL.

> **Note:** Setting the vaults up is part of the launch fee. When the creator and community shares together are above about 62% and the split also buys, those wallets can profit by trading around the vault’s own buys; the form warns about it.

## Tokenomics v2

Tokenomics v2 is set on the launch form and works on all five chains. The creator’s share of the trading fees goes to the coin’s vault, which spends it as set at launch.

- On each chain, the creator sets shares for burn, buy and hold, liquidity, holders, and up to 10 payout wallets, adding up to 100%.
- Holders are paid in the chain’s coin, in one token, or in a basket of up to 5 tokens.
- The shares and the reward tokens are fixed at launch. Each payout wallet can hand its slot to a new address in two steps. The vault’s owner or the platform can later change who is paid: the minimum holding (never below 0.01% of supply for a coin launched on v4, on its EVM chains and on Solana (0.01%); a coin whose EVM vault was made by an earlier tokenomics factory keeps 0.1%, as its vault's code is fixed; one setting covers a launch's chains, so a new launch starts at its chains' highest floor) and whether every eligible holder, the top holders or holders picked at random are paid.

### When it runs

- Holder payouts run every 5 minutes. A holder is sent rewards once what it is due covers the payout’s cost; until then it stays owed and can claim.
- There is no fixed minimum: Pangea's runner spends a bucket, or pays the wallets, once what it would move is worth 20 times the run's gas, so the run's gas repayment (at most 10% of what it moves) always covers it. Pangea may also set a minimum per chain, 0 unless set; a vault made before this change keeps the minimum it was made with (0.15 ETH on Ethereum, 0.0015 ETH on Base and Robinhood Chain, 0.008 BNB on BSC; 0.025 SOL on Solana for now). Its buys are at least 15 minutes apart on EVM for a vault made before v4; a v4 coin's EVM vault needs no gap (Pangea's setting, 0 seconds unless set, at most a day); on Solana the gap is Pangea's setting, 3 seconds unless set (3 seconds to a day), and one buy may move the price only a bounded amount.

### What a payout costs a holder

A holder sent rewards in the chain’s coin pays for its own payout: at most 10% of what it is due, and at most the figure below for its chain. A holder who claims for itself pays no fee, only its own transaction (on Solana, plus a one-time rent for its payout record).

| Chain | Most a payout charges a holder |
| --- | --- |
| Ethereum | 0.002 ETH |
| Base | 0.0002 ETH |
| Robinhood Chain | 0.0002 ETH |
| BSC | 0.001 BNB |
| Solana | 0.002 SOL |

> **Note:** On Solana the program allows at most 0.005 SOL.

## Fee collection

Fees are paid in the quote asset: `ETH`, `BNB` or `SOL`. Never in the token that was launched.

Meteora supports collecting fees directly in the quote asset. Uniswap v4 collects fees in whichever token enters a swap, so fees from sells accrue in the launched token.

To convert them, the platform swaps those tokens for the quote asset when fees are claimed. This keeps extra code off the trading path, reducing the risk of a bug disrupting swaps. The trade-off is a small price impact from selling the collected tokens into the pool.

## Launch types

Every launch uses the same structure on each of its chains, single-sided liquidity and locked positions. The launch type determines the trading pair and any additional features.

- **Standard** (Live): A plain pool paired with each chain's native asset. No hooks means standard swap events stay compatible with DexScreener and other aggregators.
- **Stock-paired** (Live): Each chain's pool pairs with that chain's native coin or with a tokenized stock listed on that chain, chosen per chain in the launch form's "Pairs with" dropdowns. Every pool still opens at the same USD market cap.
- **Hooks** (With trade-offs): Uniswap v4 supports hooks, but Meteora has no equivalent, so a hook-based launch cannot use the same setup on all five chains. Custom swap accounting can also break the standard swap events aggregators read.

## PANGEA tokenomics

**$PANGEA is live.** It launched through Pangea on all five chains, with the same five-chain structure as every other token. [Trade $PANGEA →](https://www.pangea.trade/app/t/pangea-dadd0d)

| Chain | Official $PANGEA contract |
| --- | --- |
| Ethereum | `0x6434e57e8d12B92bb4D0b1EFb3274748B75196ea` |
| Base | `0xA551dEF8a44ea423CaA58DA45b3F5764004286Ea` |
| Robinhood Chain | `0x30de7AE4c9578D110a97367EDe9F7765Cdce06ea` |
| BSC | `0xd0cb4279e2B74c2d72EEa20Df308Fb9604E3d6Ea` |
| Solana | `JAFqCQSpy5YinrsmixWSMaD7jTn4MytJRkyWvYPnY6ea` |

> **Note:** These five are the only official $PANGEA contracts. Any launch may use the ticker PANGEA; a token at any other address is not this one.

## Team allocation

The team holds **about 19% of the total supply** of $PANGEA, which is 5B summed across the five chains. Most of it is locked: **about 14.4%** sits in vesting contracts and is released in equal monthly steps over two years. The remaining **4.18%** is a marketing treasury. Nothing is held in an anonymous wallet: every address below can be checked on chain.

| Allocation | Share of total supply | Terms |
| --- | --- | --- |
| Locked team allocation | About 14.4% (718M $PANGEA) | Released in monthly steps over 24 months, about 0.6% of the total supply per month |
| Marketing treasury | 4.18% (209M $PANGEA) | Held by the Pangea treasury, for marketing |
| Everyone else | About 81% | Pools, holders and the market |

### Locked allocation

The locked tokens are not in a team wallet. They sit in **Team Finance** lock contracts on Ethereum, Robinhood and BSC, and in a **Streamflow** vesting stream on Solana. Each releases in equal monthly steps over 24 months, about 0.6% of the total supply a month across the four. No one can withdraw ahead of that schedule: the three Team Finance locks have no owner (it was handed to 0x…dEaD on 2026-10-24, which ends their emergency withdrawal and revocation), and the Streamflow stream cannot be cancelled by its sender or its recipient.

| Chain | Lock | Locked |
| --- | --- | --- |
| Ethereum | `0xbfae7682f5550d0a9572f750906c4c4e54502ee1` | 222.9M |
| Robinhood | `0xBe17cDf528aBb0797B8DD8e3d47E43A2bAF9cb34` | 228.8M |
| BSC | `0xc362b1d31bc07712b9eba15755daeaed47a0b0c1` | 55.0M |
| Solana | `ou8M2oj3byMTRmaxispdbh82pKmew7NLDt6Yy36R6zW` | 211.0M |

### Marketing treasury

The treasury funds marketing and growth. It is one address on every EVM chain, `0x402908cd6eB5176A1B9FdE3690CCccF41d7D54D0`, named pangeatreasury.eth, pangeatreasury.base.eth and pangeatreasury.bnb.

> **Note:** Balances were read on chain when this was written; the explorers (Etherscan, Robinhood's Etherscan, BscScan and Solscan) show them live, along with each lock's release schedule.

## Bridging

The token uses LayerZero OFT. Transfers burn tokens on the source chain and mint them on the destination, keeping the total across the five chains unchanged. There are no wrapped tokens.

A token with the price-gap fee is the exception. When a transfer arrives on a chain where the token’s price is higher than on the chain it left, the destination may hold back part of it as the gap fee. The part held back is never minted, so it is burned: the total supply goes down by exactly that amount.

Each chain’s pool receives its tokens at launch (1 billion, or its share of the 1B in degen mode), so all of a launch’s pools open at once.

In degen mode Solana’s supply stays at 1B: when tokens are bridged to Solana, the same number is burned from its bridge reserve, and when tokens leave Solana, the same number is added back to it. [Degen mode →](https://www.pangea.trade/app/docs/v2#degen-mode)

### Standard or fast

A standard transfer takes LayerZero’s own path. On every route the verifiers attest once the source transaction has 1 confirmation, and a transfer typically arrives in 4 to 16 seconds. From Ethereum, whose blocks are slower, it takes longer. [How to bridge, step by step →](https://www.pangea.trade/app/docs/v2#bridge-steps)

A fast transfer arrives in seconds: a relayer pays the recipient at once from its own tokens and is repaid when the standard transfer lands. It costs the fast lane’s own 0.2% on top of the standard 0.2%, plus the relayer’s price, which is capped at 10%. The bridge screen offers it only when a relayer is quoting your transfer. [Relayers →](https://www.pangea.trade/app/docs/v2#relayers)

### What a transfer costs

- **The token’s fee.** The token keeps 0.2% of every transfer between chains. It goes to the platform. On a token from before factory v3 it is fixed in the token contract on the four EVM chains and nobody can change it; on Solana, the fee split is enforced by Pangea's splitter program. A factory v3 token's fee is its chain's registry figure instead, and a transfer to a dearer chain also pays the price-gap fee (below).
- **The message fee.** LayerZero’s price for carrying the transfer, paid in the source chain’s native asset. It is quoted for the exact transfer before you sign.
- **A Solana route’s setup.** The first transfer on a Solana route nobody has used yet also pays a one-time setup fee for that route. Every later transfer on it does not.

### Factory v3 tokens: the price-gap fee

- **The bridge fee is set per chain.** On a factory v3 token the fee is not fixed in the token: each chain's gap registry sets it, from 0 to 0.5% (0.2% unless changed), and the token reads it at every send. Always take what arrives from the token's own `quoteOFT` (or the quote), never from a fixed 0.2%.
- **The gap burn.** When the token is dearer on the destination than where it left, the destination withholds part of what arrives: the price gap above the fees and a small allowance. The part withheld is never minted, so it is burned (the supply figures take it off). A transfer from a dearer chain to a cheaper one pays no gap fee.
- **The least credited (minCredited).** Every v3 send names the least the destination may credit after the gap fee. If it would credit less, the arrival waits at the destination and lands once the price allows it. Nothing is burned while it waits, but it cannot be cancelled: it lands only once the gap narrows enough, or when Pangea turns on the token's escape, which may take as long as the gap lasts. The bridge sets it from the estimate less your tolerance (1% by default, at most 20%).
- **Held arrivals.** An arrival the destination cannot price yet (its pool unreadable, or no price on record) is held, not charged and not refused, and lands when it can. The quote says so (gap state `held`).
- **Large transfers and the price log's capacity.** Each chain's price log can take only so much in one short window. A large send out of Solana may be refused until more prices are on record (`GapNeedRecords`: record the price, then send again), and a large arrival into Solana may wait at the destination until the window holds enough records.
- **Out of an EVM chain: the price record first.** A v3 send prices on the source token's price log: one entry per second, written by `poke()` (anyone, from a plain wallet, not a contract; network fee only, with a gas limit of at least 650,000) or by an earlier send. With too few recent entries (fewer than 2 in the last 30 seconds, 384 on Ethereum) or too little capacity left, `quoteSend` and `send` revert `GapNeedRecords()` (`0x9a883013`). Call the token's `poke()` in two different seconds, then send within the window. `gapMaxSend()` returns `(maxAmountLD, perEntryLD, windowSec, minBlocks)`: the most one send carries now and what each new entry adds. A larger send goes in parts.
- **Out of Solana: the price record first.** A v3 token's Solana send prices on the store's shared price log. When it holds no recent price, the wallet first signs a small `record_price` transaction (its signature fee only, no rent), then the send a moment later.
- **A v3 store's first opening.** The first opening of a v3 token's Solana routes also creates its price log, about 0.071688 SOL of rent, paid by whoever opens it (in the setup fee shown before signing). Anyone may create it; a launch that bought "Open all bridge routes at launch" was charged for it, and if someone else creates it before the launch opens the routes, that part is not spent and is not refunded.

### Routes opened at launch

A launcher can tick “Open all bridge routes at launch” on the launch page’s review step. The launch fee then also covers the setup of all four Solana routes, to Ethereum, BNB Chain, Base and Robinhood Chain, at cost plus the launch fee’s usual margin, in the same one payment. Pangea opens the four right after the Solana launch lands, so nobody pays a setup fee on that token’s routes. The box is offered only while all four routes are configured on Solana. The launch screen shows each route as it opens; a route that cannot be opened automatically is opened by hand.

A transfer of 1,000 tokens: 2 go to the platform and 998 arrive.

> **Note:** Transfers are rounded down to six decimal places. Amounts smaller than `0.000001` token are dropped.

> **Note:** [How market cap is counted →](https://www.pangea.trade/app/docs/v2#market-cap)

## How to bridge

Bridging moves your tokens from one chain to another. It is the same token on both sides: the tokens are burned where you send them from and minted where they arrive, so nothing is wrapped and the total across the five chains does not change.

### On the Bridge page

- **Pick the token.** Open Bridge in the menu and choose the token, or open a token’s page and use its bridge tab. A link with the token in it, such as `/app/bridge?token=ptest4`, opens it ready.
- **Pick From and To.** The chain your tokens are on now, and the chain you want them on. The swap button between them turns the route around.
- **Connect a wallet.** An EVM wallet for Ethereum, Base, Robinhood Chain and BSC, a Solana wallet for Solana. A route between an EVM chain and Solana needs both: one sends, the other receives.
- **Enter the amount.** Your balance on the From chain is read from the chain. The page shows what you send, the token’s fee, what arrives, and the message fee, before you sign anything.
- **Choose Standard or Fast.** Standard is the token’s own path. Fast appears only when a relayer is quoting your transfer.
- **Sign.** One transaction on the From chain for a standard transfer. A fast transfer may ask for an approval first. The first transfer on a Solana route that nobody has used yet may ask for a setup step first; see below.
- **Wait for it to arrive.** The page follows the transfer and says when it has landed. Recent transfers are listed under the card.

### What it costs

- **The token’s fee, 0.2%.** Kept by the token for the platform, out of the amount you send. 1,000 tokens sent, 998 arrive. (A token from before factory v3. A factory v3 token's fee is its chain's registry figure, 0.2% unless changed, at most 0.5%.)
- **A factory v3 token: the price-gap fee.** A transfer from the chain where the token is cheaper to the chain where it is dearer also pays the price gap, burned at the destination. The quote shows the estimate before you sign; the other way is free.
- **LayerZero’s message fee.** Paid in the From chain’s native asset: ETH on Ethereum, Base and Robinhood Chain, BNB on BSC, SOL on Solana. It pays the verifiers and the delivery on the other side, and it is quoted for your exact transfer before you sign.
- **A factory v3 token into Ethereum: Pangea's delivery.** LayerZero's executor does not deliver there, so Pangea's delivery keys land the transfer; their cost is part of the network fee, in the From chain's coin (out of Solana, in SOL), and the quote shows it as its own line (Pangea delivery).
- **A factory v3 token into Solana: the Solana delivery.** Pangea's delivery key lands the transfer on Solana; its cost is withheld from what arrives, in the token, and shown as its own line (Solana delivery). A new wallet's first arrival also pays its token account's rent from it.
- **A transfer too large for one send: every part pays.** Sent in parts, each part is its own transfer and pays the full network fee (LayerZero, and any delivery above); the quote shows the fee per part and the total for all parts before the first signature. A co-signed transfer (one send) shows its fee and its guaranteed minimum before you sign it.
- **Gas.** The transaction itself, on the From chain, as for any transaction there.

| Route, 1,000 PTEST4 | Message fee quoted |
| --- | --- |
| Base to BSC | 0.000110 ETH |
| Ethereum to Base | 0.000205 ETH |
| Base to Solana | 0.000425 ETH |
| Solana to Base | 0.00058 SOL |

Those are live quotes from 19 October 2026, to show the size of the fee; it moves with gas prices. A transfer into Solana costs more because it also pays for the token account the tokens arrive in.

### How long it takes

A standard transfer waits for its transaction to have 1 confirmation on the From chain, then for two verifiers to attest it, and is then delivered. Measured on mainnet, transfers between Base, BSC, Robinhood Chain and Solana took 4 to 16 seconds:

| Route | Seconds, sent to delivered | Transfers measured |
| --- | --- | --- |
| Base to BSC | 5 | 2 |
| Base to Robinhood Chain | 4 to 5 | 4 |
| Base to Solana | 5 to 7 | 4 |
| BSC to Base | 8 | 1 |
| Robinhood Chain to Base | 10 to 16 | 4 |
| Robinhood Chain to BSC | 7 | 1 |
| Robinhood Chain to Solana | 5 to 13 | 2 |
| Solana to Base | 12 to 15 | 4 |

From Ethereum it takes longer. Ethereum makes a block only every 12 seconds, and the verifiers wait for the transfer’s block to be confirmed before they attest, so expect about half a minute or more. No transfer out of Ethereum has been measured on mainnet yet, and the Bridge page currently shows the same estimate for every route.

### The fast lane

Fast is an option on top of the standard transfer. When you choose it, the transfer carries a public order on Pangea’s fast bridge, PangeaFastBridge, one shared contract per chain. A relayer sees the order and pays you on the To chain within seconds from its own tokens, and is repaid when your standard transfer lands. It costs the fast lane’s 0.2% and the relayer’s price on top of the standard 0.2%, and the price is fixed in the order before you sign. If nobody fills the order, your tokens still arrive the standard way and you get the relayer’s fee too. [How relayers work →](https://www.pangea.trade/app/docs/v2#relayers)

- Fast appears only when a relayer is quoting your route and amount. Otherwise only Standard is offered.
- Into Solana it needs the token to allow a 232-byte message. Tokens launched from the current factories allow 256; some earlier launches allow 200, and for those the page offers Standard only into Solana.
- A fast transfer may need an approval first: the fast bridge moves the tokens for you, once.

### Solana

- **Into Solana, the recipient is a wallet.** Name the Solana wallet that will own the tokens, not a token account. The tokens arrive in that wallet’s token account for the token, which is created on delivery if it does not exist.
- **That account’s rent is in the message fee.** Every transfer into Solana carries 2,074,080 lamports (about 0.002 SOL), the rent of a new Token-2022 account, paid in the From chain’s asset as part of LayerZero’s fee. You need no SOL to receive. For a factory v3 coin today, the first arrival to a new wallet instead has that rent withheld from the tokens that arrive (the quote’s gap.delivery.ata), and an arrival too small to pay it waits on Solana until the account exists (gap.warning).
- **The first transfer on a route pays its setup.** A token’s route between Solana and a chain is opened the first time anyone uses it. That first transfer also pays a one-time setup fee: the rent of the accounts LayerZero keeps for the route. The page shows it as a separate line and may ask for two steps. Every later transfer on that route, by anyone, does not pay it.
- **Out of Solana, you pay in SOL.** The message fee and the transaction fee are paid from your Solana wallet, so keep a little SOL there.
- **Amounts cross at six decimals.** The odd base units below 0.000001 token stay in your wallet.

### If a transfer seems stuck

- **Look it up on LayerZero Scan.** Paste the transaction hash or signature of your send at layerzeroscan.com. It shows each verifier’s attestation and the delivery, with links to both transactions.
- **Inflight or confirming.** The verifiers are waiting for the confirmation or attesting. Wait: this is the ordinary state for the first seconds, and longer from Ethereum.
- **Verified but not delivered.** Delivery is open to anyone once a message is verified. LayerZero’s executor delivers it, and Pangea runs its own delivery service that delivers every Pangea token’s messages as soon as they are verified. If one sits verified and undelivered for more than a few minutes, anyone can deliver it by calling the destination endpoint’s `lzReceive` with the message, at their own gas cost. The exception: a factory v3 token's charged arrival into Solana, which only Pangea's appointed deliverers may land.
- **Into Solana, on a route that is not open.** A transfer sent into Solana before its route is opened waits, and is delivered once the route is opened; LayerZero Scan may show it as blocked until then. Nothing is lost in between. The Bridge page opens the route as part of the first transfer on it.
- **A held fast order.** When a fast order cannot be paid on the To chain, the fast bridge holds it for the sender. The Bridge page shows a button that sends it back; anyone may do it, paying LayerZero’s fee for the trip back.
- **Nothing on LayerZero Scan.** The send did not go through on the From chain. Check it on that chain’s explorer: if it failed, the tokens never left your wallet.

> **Note:** [How the routes are set up, and how to check them →](https://www.pangea.trade/app/docs/v2#routes)

## LayerZero routes

### One token, five contracts

- **An address per chain.** A token is a LayerZero OFT contract on each of Ethereum, Base, Robinhood Chain and BSC, and a mint on Solana, held by LayerZero’s OFT program. The five addresses are all different (or, with “Same contract address on all EVM chains”, one on the four EVM chains and the mint on Solana) and all end in `6ea`. They are on the token’s page and in `/api/pools`.
- **1B per chain.** Each chain mints its own 1 billion at launch, all of it into that chain’s pool, 5 billion across the five. On an EVM chain `launchSlice()` is that 1B and `launchTotal()` the 5B (on a launch on fewer chains, 1B for each of them). Nothing can mint more: there is no mint function and no minter. In degen mode each chain mints its equal share of 1B instead, and `launchTotal()` is the 1B.
- **Burn and mint.** A transfer burns the tokens on the chain it leaves and LayerZero’s message mints the same number on the chain it reaches. On an EVM chain the token’s fee is moved to `bridgeFeeTo()` and only the rest is burned, so what is burned on one side is exactly what is minted on the other. A factory v3 token's price-gap fee is the exception: the destination withholds it and never mints it.
- **What is on a chain, and what is reported.** On each EVM chain `localSupply()` is what is on that chain now; it moves as tokens cross. `totalSupply()` is a reported figure: it starts at the chain’s 1B, and for tokens with a SupplyReporter the platform’s reporter on that chain mirrors the token’s supply on Solana into it. A report moves no balance and mints nothing, and a factory v1 token refuses one below its own `localSupply()` or above `launchTotal()`. Earlier launches have no reporter and always report their slice. A degen mode token reports the 1B total and takes no reports.
- **Peers are sealed at launch.** A peer is the token’s counterpart on another chain, the only address it accepts a mint from. The factory registers the four peers in the transaction that creates the token and hands ownership to a LaunchOwner contract that is sealed from the start: `sealed_()` is true and it has no way to add, change or remove a peer.

| Chain | SupplyReporter |
| --- | --- |
| Ethereum | 0xD7AF0c412911DCE5F864d949abA14a3048DD87B2 |
| Base | 0xF10254C126e41F69799e781D185cc42c99EE37Ed |
| Robinhood Chain | 0x51D658a71AEa38f123d4c73e42703Fb9C949900A |
| BSC | 0xf1a98Fa0B2baB64BC4358dC0C2c13444ecCA365b |

### How every route is set up

A route is one direction between two chains, for one token. When the factory creates a token it is, for that one transaction, the token’s LayerZero delegate, and it writes every route from a table fixed in its own constructor. Nothing is left on LayerZero’s defaults, because a default can be changed by whoever administers the endpoint.

- **Libraries.** The send and the receive message library are named explicitly: LayerZero’s Ultra Light Node on every chain, the same contracts the endpoint’s defaults point at today, pinned so they stay.
- **Executor.** LayerZero’s executor on each chain is named explicitly, with a cap on the message size.
- **Verifiers.** No required verifiers. An optional set of Pangea’s own verifier plus the external ones, and 2 of the set must attest before a message counts. So no single party can deliver a message: not Pangea’s verifier, and not any one external one.
- **Confirmations.** 1 on every source chain, Solana included: the verifiers attest once the send has 1 confirmation. Both ends of a route hold the same number.
- **A gas floor.** Every transfer must give the delivery at least 100,000 gas on an EVM chain, and 200,000 compute units plus 2,074,080 lamports on Solana, for the recipient’s token account. It is an enforced option, added to every send, so a sender cannot under-pay delivery.
- **A message size.** Up to 10,000 bytes between EVM chains and from Solana. Toward Solana 256 bytes for launches from the current factories; the factory admin can move that between 200 and 800 for launches after, never for a token that exists. A Solana transaction is small, and a cap above what Solana can deliver would let a message be sent that can never land.

| Chain | Optional verifiers | Of which must attest | Confirmations |
| --- | --- | --- | --- |
| Ethereum | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |
| Base | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |
| Robinhood Chain | Pangea, LayerZero Labs, Nethermind | 2 | 1 |
| BSC | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |
| Solana | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |

That is the set on each chain, for its routes to Ethereum, Base, BSC and Solana. Robinhood Chain has two of the external verifiers, so a route with Robinhood Chain at either end uses the verifiers both chains have: Pangea, LayerZero Labs and Nethermind, 2 of 3. Every other route has 5, 2 of 5.

| Chain | Send library | Receive library | Executor |
| --- | --- | --- | --- |
| Ethereum | 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1 | 0xc02Ab410f0734EFa3F14628780e6e695156024C2 | 0x173272739Bd7Aa6e4e214714048a9fE699453059 |
| Base | 0xB5320B0B3a13cC860893E2Bd79FCd7e13484Dda2 | 0xc70AB6f32772f59fBfc23889Caf4Ba3376C84bAf | 0x2CCA08ae69E0C44b18a57Ab2A87644234dAebaE4 |
| Robinhood Chain | 0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7 | 0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043 | 0x4208D6E27538189bB48E603D6123A94b8Abe0A0b |
| BSC | 0x9F8C645f2D0b2159767Bd6E0839DE4BE49e823DE | 0xB217266c3A98C8B2709Ee26836C98cf12f6cCEC1 | 0x3ebD570ed38B1b3b4BC886999fcF507e9D584859 |
| Solana | 7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH | 7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH | AwrbHeCyniXaQhiJZkLhgWdUCteeWSGaSN1sTfLiY7xK |

| Chain | LayerZero endpoint | Launch factory (v2) | Earlier launches (v1) | Pangea’s verifier |
| --- | --- | --- | --- | --- |
| Ethereum | 0x1a44076050125825900e736c501f859c50fE728c | 0xf7052a4E38e241bc66bfAe6eB27D10B2A42cacB4 | 0x1D583e6eA4AA7c5e167a7E69B07b83DD5B813f53 | 0x5FA03555f186e53a644405817E1BeAC894Bd6562 |
| Base | 0x1a44076050125825900e736c501f859c50fE728c | 0xb405b396b607C3482A3d301b1cFF57A7de75325a | 0xba4beE425F268f7Dc0F7e5457d14320d5A64cd06 | 0x6dbFda44baB6bf0DDF3E9F88d7823eDdB48f4b25 |
| Robinhood Chain | 0x6F475642a6e85809B1c36Fa62763669b1b48DD5B | 0x9Cf5832474CBB09D2E06FF20460aff4303fAf72A | 0x8A1F3e1DB07048c007F6d9ab924494C77c0DBa65 | 0x85880e7D5315365165E97620Ba17B48A645b13F3 |
| BSC | 0x1a44076050125825900e736c501f859c50fE728c | 0x92858095Cadf9f9d60dC17226f7FF15Be4b69e45 | 0xd1454764E8D87B117a23491E77cAfcCC50430256 | 0xF61Ed4347c4D02522e96657a606f49E8381B3dCA |
| Solana | ULN 7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH | Splitter program Hb5uTppRbNxc3RjVRqTWqfdU3E3GiZyGYKccxYf9tUej | The same program | 7EgbAY7663cybirqgmFHzF8q9debeePxr8ra7wKyEmNE |

| Verifier | Ethereum | Base | Robinhood Chain | BSC | Solana |
| --- | --- | --- | --- | --- | --- |
| LayerZero Labs | 0x589dEDbD617e0CBcB916A9223F4d1300c294236b | 0x9e059a54699a285714207b43B055483E78FAac25 | 0xd01ae6905d48315f7bE10C7330aeCF8360Ef5b12 | 0xfD6865c841c2d64565562fCc7e05e619A30615f0 | 4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb |
| Nethermind | 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 | 0xcd37CA043f8479064e10635020c65FfC005d36f6 | 0x0Ffe02DF012299A370D5dd69298A5826EAcaFdF8 | 0x31F748a368a893Bdb5aBB67ec95F232507601A73 | GPjyWr8vCotGuFubDpTxDxy9Vj1ZeEN4F2dwRmFiaGab |
| Horizen | 0x380275805876Ff19055EA900CDb2B46a94ecF20D | 0xa7b5189bcA84Cd304D8553977c7C614329750d99 | not on this chain | 0x247624e2143504730aeC22912ed41F092498bEf2 | HR9NQKK1ynW9NzgdM37dU5CBtqRHTukmbMKS7qkwSkHX |
| Canary | 0xa4fE5A5B9A846458a70Cd0748228aED3bF65c2cd | 0x554833698Ae0FB22ECC90B01222903fD62CA4B47 | not on this chain | 0xfA9bA83C102283958B997Adc8B44ED3A3CdB5dDa | 7jMeX5mzXnSSKYd8DxBDP4xMnkNFZZZm5W28FWUTbwU3 |

On Solana the same is done by Pangea’s splitter program. The program holds one route setting per destination chain, and the first transfer on a token’s route applies it to that token, once: the libraries, the executor, the verifiers in both directions, the confirmations and the gas floor. The token’s LayerZero admin and delegate on Solana is an address only that program can sign for, and the program has no instruction that changes a peer, a library or a verifier afterwards.

### Why nobody can change a route later

- On an EVM chain the factory writes the routes while it is the token’s delegate, then never calls the endpoint for that token again and has no function that could. The token’s owner, the sealed LaunchOwner, has no way to name another delegate. So no key, including Pangea’s, can change a token’s libraries, executor or verifiers.
- This matters because a delegate who could name the verifiers could name itself, attest a message nobody sent, and mint. Sealing the peers alone would not close that.
- One thing stays adjustable on purpose: the gas floor. The LaunchOwner’s admin can raise or lower it, so that a chain changing its gas costs does not break bridging for good. It accepts exactly one `lzReceive` option and nothing else, under hard caps of 1,000,000 gas and 10,000,000 in value, so it cannot add a payment to anyone or set a floor nobody can pay. On Solana the gas floor is fixed when the route is opened.
- The other side of this: if LayerZero retires a verifier a route depends on, that route keeps working only while two of its verifiers still attest, and nobody can swap one in. A paused bridge can be lived with; a token anyone could mint could not.
- On Solana the admin can change the route setting for launches still to come. A token’s route is applied with the setting in force when it launched, and the program refuses a setting edited after that, unless the admin signs the opening; once a route is open nothing changes it.

### Check it yourself

Everything above can be read from the chains, with no help from Pangea. The worked example is PTEST4, a live test launch, on Base. Its address there is `0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea`; the other addresses are in the table at the end. These are read-only calls, with Foundry’s `cast` and any RPC endpoint for the chain.

The token's peers, owner and delegate, on Base:

```bash
TOKEN=0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea
ENDPOINT=0x1a44076050125825900e736c501f859c50fE728c
RPC=your-base-rpc-endpoint

# its counterpart on Ethereum, BSC, Robinhood Chain and Solana
for EID in 30101 30102 30416 30168; do
  cast call $TOKEN "peers(uint32)(bytes32)" $EID --rpc-url $RPC
done

# the owner is a LaunchOwner, sealed: true
OWNER=$(cast call $TOKEN "owner()(address)" --rpc-url $RPC)
cast call $OWNER "sealed_()(bool)" --rpc-url $RPC

# the delegate, the only key that can set verifiers, is the factory that launched it
cast call $ENDPOINT "delegates(address)(address)" $TOKEN --rpc-url $RPC
```

One route's libraries, executor, verifiers and gas floor: Base to Robinhood Chain:

```bash
EID=30416
SEND_LIB=$(cast call $ENDPOINT "getSendLibrary(address,uint32)(address)" $TOKEN $EID --rpc-url $RPC)
cast call $ENDPOINT "isDefaultSendLibrary(address,uint32)(bool)" $TOKEN $EID --rpc-url $RPC
cast call $ENDPOINT "getReceiveLibrary(address,uint32)(address,bool)" $TOKEN $EID --rpc-url $RPC

# verifiers: (confirmations, required count, optional count, threshold, required, optional)
ULN=$(cast call $ENDPOINT "getConfig(address,address,uint32,uint32)(bytes)" $TOKEN $SEND_LIB $EID 2 --rpc-url $RPC)
cast decode-abi "f()((uint64,uint8,uint8,uint8,address[],address[]))" $ULN

# executor: (max message size, executor)
EXE=$(cast call $ENDPOINT "getConfig(address,address,uint32,uint32)(bytes)" $TOKEN $SEND_LIB $EID 1 --rpc-url $RPC)
cast decode-abi "f()((uint32,address))" $EXE

# the gas floor every send carries
cast call $TOKEN "enforcedOptions(uint32,uint16)(bytes)" $EID 1 --rpc-url $RPC
```

What those return for this token, read on 19 October 2026:

| Read | Answer | What it means |
| --- | --- | --- |
| `peers(30416)` | `0x00000000000000000000000068906d8b2a22d329b853ec650fa6a29fab6056ea` | The token on Robinhood Chain, left-padded to 32 bytes. |
| `peers(30168)` | `0xbad1e830d4a3bba670d642dd253151f5564074eb91e9197e0216ec274e91df6e` | Its OFT store on Solana, `DaGUXY1qixx3Wsp7KFb7L6UtXV6QHCRXtaXtx4yfLued`, as 32 bytes. |
| `sealed_()` | `true` | No peer can ever be added, changed or removed. |
| `delegates(token)` | `0x025f893Bdcd7978232C211e3A28610FC6d8B8970` | The factory that launched it, an earlier one than today’s, with no code to touch the endpoint again. |
| `isDefaultSendLibrary` | `false` | The library is pinned, not inherited. |
| `getConfig(... 2)` | `(1, 0, 3, 2, [], [3 verifiers])` | 1 confirmation, no required verifiers, 3 optional, 2 must attest. |
| `getConfig(... 1)` | `(10000, 0x2CCA08ae69E0C44b18a57Ab2A87644234dAebaE4)` | Messages up to 10,000 bytes, LayerZero’s executor. |
| `enforcedOptions(30416, 1)` | `0x000301001101000000000000000000000000000186a0` | One `lzReceive` option: 0x186a0, which is 100,000 gas. |

The three optional verifiers it returns are Pangea’s on Base, `0x6dbFda44baB6bf0DDF3E9F88d7823eDdB48f4b25`, and LayerZero Labs’ and Nethermind’s there. Toward Solana the same read returns 5 verifiers and an executor cap of 200 bytes: this token was launched before the factories raised it to 256. The gas floor toward Solana reads `0x00030100210100000000000000000000000000030d40000000000000000000000000001fa5e0`: 0x30d40 is 200,000 compute units and 0x1fa5e0 is 2,074,080 lamports.

On LayerZero Scan, look up the token’s address, or any transfer. Its transfer from Base to Robinhood Chain on 16 October 2026, `0x5860a6920fd6fe6d97f7d88104d6ce105bb43fd4a9654d7d84cdd9229e961f65`, shows all three of the route’s verifiers attesting, and the delivery five seconds after the send. Each message lists the verifiers that attested and the `lzReceive` gas it carried.

On Solana, a token’s route to a chain is open when LayerZero’s receive config account for it exists: the ULN program’s address for the seeds `"ReceiveConfig"`, the chain’s endpoint id as four big-endian bytes, and the OFT store. Its contents are the confirmations and the verifiers, in the same order as above. For PTEST4, read on 19 October 2026, the routes from Base and Robinhood Chain are open, each with 1 confirmation and 2 of its verifiers, and the routes from Ethereum and BSC are not open yet: nobody has bridged on them. [Bridging from code →](https://www.pangea.trade/app/docs/v2#bridge-code) has a script that checks it.

| Chain | PTEST4 on that chain |
| --- | --- |
| Ethereum | 0x835e9CFB333B9E4811CA1585460442806a99d6eA |
| Base | 0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea |
| Robinhood Chain | 0x68906D8b2A22D329b853ec650Fa6A29fAb6056Ea |
| BSC | 0x90Ba4Df685A2D33c12C40F03808F290519E7F6EA |
| Solana | Mint DdJ6R9TSKdNYMZhdgATANT98BJNgScuU58baQoCfZ6ea, OFT store DaGUXY1qixx3Wsp7KFb7L6UtXV6QHCRXtaXtx4yfLued |

> **Note:** Pangea’s own checks do the same against every factory before a launch: each route’s two ends must agree, and every verifier a factory names must be on a list kept by hand, with the operator’s name. The addresses on this page are those.

## Relayers

A relayer pays the recipient on the destination chain within seconds, out of its own stock of the token, and is paid back, plus its fee, when the bridged tokens arrive over the standard path a few seconds later. Nobody has to trust it: the recipient, the amount and the fee are fixed by the order, and a relayer that pays anything else is simply never repaid. If no relayer fills an order, the transfer arrives the standard way, and the fee no relayer earned goes to the recipient.

There is one fast bridge per chain, shared by every token launched on Pangea. A new token can use it the moment its launch lands: nothing is deployed for it. An order names its launch, never a token address, and the bridge reads the token itself from Pangea’s own factories, so a relayer is only ever repaid in, and a recipient only ever paid in, that launch’s own token on that chain.

> **Note:** The fast bridges on Ethereum, Base, Robinhood Chain and BSC are deployed, at the addresses listed at the end of this section. Orders out of Solana are posted by Pangea’s fast-bridge program, in the same format. Into Solana the fast lane needs a token that allows a 232-byte message, which every launch from the current factories does.

### What a transfer costs, and who is paid

- **The token’s fee.** The token keeps 0.2% of every transfer between chains, on either lane. It goes to the platform.
- **The fast lane’s fee.** On the fast lane the fast bridge keeps 0.2% first, also for the platform, and the token’s fee is then taken from what is left. The bridge’s admin can change it, per launch or for all, but never above 1%.
- **The relayer’s fee.** This is a price, not a platform fee. Relayers publish what they charge and the app takes the lowest. The bridge refuses any order that would pay a relayer more than 10% of what arrives, or the lower cap its admin sets.
- **Your limit.** You set a slippage, 0.5% unless you change it and 10% at most. If the best relayer price is above it, no fast transfer is offered, and the bridge refuses an order that would pay the recipient less.

One transfer of 1,000 tokens, three ways, with a relayer charging 0.1% as the example price:

| Who gets | Standard | Fast, filled | Fast, unfilled |
| --- | --- | --- | --- |
| Platform | 2 | 3.996 | 3.996 |
| Relayer | 0 | 0.996004 | 0 |
| Recipient | 998 | 995.007996 | 996.004 |
| Arrives in | 4 to 16 seconds | seconds | 4 to 16 seconds |

On the fast lane the fast bridge keeps 2 tokens and the token keeps 1.996, so 996.004 are on their way. The relayer pays the recipient 995.007996 at once and receives the 996.004 when they arrive, keeping 0.996004. Both platform fees are taken on the chain the transfer leaves, when it is sent, so a fast transfer that nobody fills has still paid them.

### Becoming a relayer

Anyone can be a relayer. There is nothing to apply for and nothing to stake: you publish a price, watch for orders and fill the ones you want. Filling is never an obligation, and leaving an order unfilled takes nothing from anyone. It does count against you if the order was posted at your published price, as described under benching below.

What follows is the whole protocol, two HTTP calls and a handful of contract calls, and any program that follows it is a relayer. The HTTP calls are for the price board only: watching and filling work entirely on chain.

### 1. What you need

- **The token, on every chain you deliver to.** Fills are paid from your own wallet. The fast bridge holds no stock of its own.
- **An approval.** On each of those chains, approve that chain’s fast bridge to move your tokens: `approve(fastBridge, amount)` on the token. Without it your prices are not taken and a fill reverts.
- **Gas.** A fill is a transaction on the destination chain, paid in its native asset: ETH on Ethereum, Base and Robinhood Chain, BNB on BSC.
- **An RPC endpoint for each chain.** You read `OrderPosted` logs on the chains orders leave and send fills on the chains they go to, so the endpoint must serve `eth_getLogs`. A WebSocket endpoint makes you faster: subscribed to the fast bridge’s logs it tells you about an order the moment it is posted.
- **One key.** The address that signs your prices is the address the app checks for tokens and approval, so sign with the wallet you fill from.

Check a bridge before you approve it. `tokenOf(launchId)` returns the token it moves for a launch, read from Pangea’s factories (`isFactory(factory)` says which it accepts), and `remoteBridge(eid)` its counterpart on another chain.

### 2. Publish your price

You send a signed price sheet to the price board. The board is public: read it to see the prices you are competing with, the ceiling, and who is benched. A route is a pair of chains on one shared bridge, so one price covers every token on that route; the app only takes it for a transfer when you hold that token on the destination.

- Publish, POST: `https://www.pangea.trade/api/bridge/relayer-quotes`
- Read, GET: `https://www.pangea.trade/api/bridge/relayer-quotes`

The body you POST:

```json
{
  "sheet": {
    "v": 1,
    "relayer": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "issuedAt": 1760000000,
    "expiresAt": 1760000120,
    "routes": [
      {
        "fromEid": 30184,
        "toEid": 30102,
        "bridge": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
        "feeBps": 10,
        "minFee": "1000000",
        "maxAmount": "50000000000000"
      },
      {
        "fromEid": 30101,
        "toEid": 30102,
        "bridge": "0xcccccccccccccccccccccccccccccccccccccccc",
        "feeBps": 10,
        "minFee": "1000000",
        "maxAmount": "50000000000000"
      }
    ]
  },
  "signature": "0x<65-byte signature, 130 hex characters>"
}
```

| Field | What it is |
| --- | --- |
| `v` | Always 1. |
| `relayer` | Your address. The signature must be this address’s. |
| `issuedAt`, `expiresAt` | Unix seconds. A sheet may live at most 300 seconds, and its issue time may be at most 120 seconds ahead of the server’s clock. |
| `routes` | One to 64 routes. The same route may not appear twice. |
| `fromEid`, `toEid` | The LayerZero endpoint ids of the chain an order leaves and the chain you deliver on. |
| `bridge` | The fast bridge on the chain the order leaves. Not the one you fill on. |
| `feeBps` | Your price in basis points of what arrives, a whole number from 0 to the board’s ceiling. The ceiling is 1000, which is 10%, unless the board reports another. |
| `minFee` | The least you charge whatever the size, in base units, as a decimal string. Gas does not shrink with the amount. |
| `maxAmount` | The most you will front on that route, in base units, as a decimal string above zero. |
| `solana` | Optional, and needed for any route that delivers to Solana: your Solana address. The body then also carries `solanaSignature`, your Solana key’s Ed25519 signature over the same text, in base58 or base64. On a route from Solana, `bridge` is 32 bytes, written as 0x and 64 hex digits. |
| `launches` | Optional: up to 512 lines of `toEid`, `launchId`, `minFee` and `maxAmount`, to price each launch on its own -- every launch shares one fast bridge, and a route's amounts are base units of whichever token is sent. A destination with any launch line is quoted only for the launches it lists, at the route's `feeBps` and the line's minimum and limit (never above the route's own). Each is signed as `launch <toEid> <launchId> min <minFee> max <maxAmount>`, after the routes. |

The signature is over the text below, signed as an Ethereum personal message: EIP-191, which wallets offer as `personal_sign`. The lines are joined by a single newline with none at the end, addresses are in lower case, and the routes are sorted by `fromEid`, then `toEid`, then `bridge`, whatever order the JSON lists them in. This is the text for the body above:

The text you sign:

```text
Pangea relayer quotes v1
relayer: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
issued: 1760000000
expires: 1760000120
30101>30102 0xcccccccccccccccccccccccccccccccccccccccc 10bps min 1000000 max 50000000000000
30184>30102 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 10bps min 1000000 max 50000000000000
```

Your fee for a transfer is `feeBps` of what arrives, rounded up, and never less than `minFee`. If that comes to more than the ceiling allows on that transfer, or the transfer is larger than `maxAmount`, you have no price for it.

A sheet stops counting at its expiry, so publish again before then. Every 30 seconds is plenty, and the board accepts 120 publishes per caller in ten minutes. Each new sheet replaces your last one and must carry a later `issuedAt`. Stop publishing and you are off the board when your last sheet expires.

| Answer | What it means |
| --- | --- |
| `200` | Kept. The body is `{ ok, relayer, expiresAt }`. |
| `400` | The sheet is malformed, expired or over the ceiling. The reasons are in `error`. |
| `401` | The signature is not this relayer’s over this sheet. |
| `403` | This address may not publish on this deployment. The board is open to any address unless the deployment lists who may publish or has turned an address away. |
| `409` | A sheet issued at the same time or later is already held for this relayer. |
| `413` | The body is larger than 131,072 bytes. |
| `429` | Too many requests. The message says how many seconds to wait. |
| `424` | The board cannot be read or written right now. |

What a GET returns:

```json
{
  "now": 1760000030,
  "maxFeeBps": 1000,
  "sheets": [
    {
      "sheet": "<a sheet, as published>",
      "signature": "0x<its signature>"
    }
  ],
  "benched": [
    {
      "relayer": "0x<address>",
      "until": 1760003600
    }
  ],
  "neverBenched": [
    {
      "relayer": "0x<address>",
      "strikes": 1
    }
  ]
}
```

Reading is limited to 300 requests per caller in ten minutes. `maxFeeBps` is the ceiling in force, `sheets` every live sheet with its signature, and `benched` the relayers whose prices are not being taken, with the unix second each bench ends. `neverBenched` lists the relayers this deployment never benches that have strikes within the hour, with how many.

**How the app chooses.** For each transfer it works out every live sheet’s price and looks at the 4 lowest, lowest first. Between equal prices the sheet with the earlier `issuedAt` comes first. It takes the first whose relayer holds what the fill pays, in that launch’s token on the destination chain, and has approved the fast bridge there for it. Benched relayers are left out. If that price would leave the recipient less than the sender’s slippage allows, no fast transfer is offered. When no relayer quotes a transfer at all, the fast lane is simply not shown.

### 3. Watch for orders

Every fast transfer is an order, posted on the fast bridge of the chain it leaves. That bridge emits one event for it:

On the source chain’s fast bridge:

```solidity
event OrderPosted(
    bytes32 indexed orderId,
    bytes32 indexed launchId,
    uint32 dstEid,
    bytes32 indexed sender,
    uint64 senderNonce,
    bytes32 to,
    uint256 amount,
    uint256 relayerFee,
    uint64 lzNonce
);
```

- topic0: `0x7e20a23f868a6866f04de222fcf771b4747be0d748f6c3af50083f019a56c231`

| Field | What it is |
| --- | --- |
| `orderId` | `keccak256(abi.encode(uint32 srcEid, bytes32 srcBridge, bytes32 launchId, bytes32 sender, uint64 senderNonce, uint32 dstEid, bytes32 to, uint256 amount, uint256 relayerFee))`. |
| `launchId` | The launch the order is for. The token on either chain is `tokenOf(launchId)` on that chain’s bridge. |
| `dstEid` | The endpoint id of the chain to deliver on. |
| `sender`, `senderNonce` | Who posted the order, as 32 bytes, and that sender’s own counter on that bridge. |
| `to` | The recipient, as 32 bytes: an EVM address left-padded with zeros, or a Solana owner’s public key. An EVM destination refuses a recipient whose top 12 bytes are not zero. |
| `amount` | What arrives on the destination, in base units. Not what the sender parted with: both platform fees are already out of it. |
| `relayerFee` | What the filler keeps, out of `amount`, in base units. |
| `lzNonce` | The LayerZero nonce of the message carrying the order. A fill has to name it. |

### Orders from Solana

An order from Solana is posted by Pangea’s fast-bridge program (`post_order`) and carried by the sender’s own OFT send, in the same Solana transaction. On an EVM destination, for source endpoint `30168`, the order’s `srcBridge` is the sender’s own key (the OFT’s `composeFrom`), and the sender in the order is always taken to be that key: nobody can post in another’s name.

**Verify before you fill.** Skipping `post_order` only skips the fast lane’s fee, so an EVM-side check cannot tell you the order was paid for. Before filling an order from Solana, read the Solana transaction yourself and fill only when it holds both the `post_order` instruction and the OFT send it checks, with the same launch, recipient, amount and fee, and the transaction is finalized. A fill on anything less is a bet nobody else is making for you.

### 4. Fill

A fill is one call, made on the chain the order is going to:

On the destination chain’s fast bridge:

```solidity
function fillOrder(
    uint32 srcEid,
    bytes32 srcBridge,
    bytes32 launchId,
    bytes32 sender,
    uint64 senderNonce_,
    address to,
    uint256 amount,
    uint256 relayerFee,
    uint64 lzNonce
) external returns (bytes32 id);
```

`srcEid` is the endpoint id of the chain you saw the event on, and `srcBridge` the bridge that emitted it, as 32 bytes (the address left-padded with zeros): the destination accepts only a bridge it has named for that chain, else `NotARemoteBridge`. From Solana, `srcBridge` is the sender’s own key instead (see below). The other arguments are the event’s own fields, with `to` as the 20-byte address it pads: `senderNonce_` is its `senderNonce`. In the same transaction the bridge moves `amount - relayerFee` of `tokenOf(launchId)` from your wallet to the recipient and records you as owed `amount`.

Every one of those fields is hashed into the order’s id. Change the source bridge, the launch, the amount, the recipient or the fee and you have filled an order nobody posted: you have paid someone, and nothing will ever repay you.

Check before you send, on the destination bridge. Each check is a read and saves the gas of a fill that would revert:

- `filledBy(orderId)` is the zero address: nobody has filled it yet.
- `notYetArrived(token, srcEid, lzNonce)`, with `token` the launch’s token on this chain, is true: the order’s own message has not reached this chain. Once it has, there is nothing left to deliver early and a fill is refused.
- You hold `amount - relayerFee` of the token there and your approval covers it.
- `relayerFee` is at least your own price for that amount. You are never obliged to fill.

| Revert | What it means |
| --- | --- |
| `NoRemote(srcEid)` | This bridge has no counterpart on that source chain. |
| `NotARemoteBridge()` | The `srcBridge` you named is not a bridge this chain has ever named for that source chain. |
| `UnknownLaunch(launchId)` | None of Pangea’s factories this bridge accepts knows that launch. |
| `AlreadyFilled()` | Somebody filled it first. One fill per order. |
| `AlreadySettled()` | The order has already been paid out. |
| `FeeExceedsAmount()` | The fee is not less than the amount. No real order looks like this. |
| `TooLateToFill()` | The message you named has already reached this chain. |
| A token transfer error | Your balance or your approval is too small. |

A fill that goes through emits `OrderFilled(orderId, relayer, to, paid)`.

### 5. Get repaid

You are repaid on the chain you filled on, in the token, when the order’s own message arrives and the bridge settles it: seconds after the order was posted. The bridge checks that the message came from the launch’s own token and the source chain’s own fast bridge before it pays anyone. `settlement(orderId)` on the destination bridge says how the order settled:

| Value | What happened |
| --- | --- |
| `0` | Not settled yet. |
| `1` | Nobody filled it. The recipient was paid everything that arrived, the relayer’s fee included. Event: `SettledSlow`. |
| `2` | A fill earned the fee. The filler was sent the whole `amount`. Event: `RelayerPaid`. |
| `3` | A fill did not earn the fee. The filler was sent back `amount - relayerFee`, what it paid out, and the fee went to the recipient. Events: `RelayerPaid` and `FeeReturned`. |
| `4` | Held. The recipient could not be paid on this chain (it is not an address here), so nobody could fill it and the bridge keeps it for the sender. Event: `Refundable(orderId, guid, srcEid, sender, amount)`. It is never a strike against a relayer. |

The same order can only arrive twice from Solana, when a sender reuses a nonce. The second arrival is paid to the recipient (`ArrivedAgain` and `SettledSlow`) and the first settlement stands: each arrival brought its own tokens, so none is refused.

### Sending a held order back

Anyone may send a held amount back to its sender on the chain it came from: `quoteRefund(guid)` gives LayerZero’s fee for the message back, and `refund(guid)`, paying that fee, sends it (`Refunded`). `refunds(guid)` shows what is held. The bridge screen offers this button itself when a transfer it sent is held.

**What earns the fee.** The fee pays for delivering early with your own tokens, so a fill earns it only when both of these are true:

- The `lzNonce` your fill named is the nonce of the message that settles the order.
- The settlement lands in a later block than your fill. A fill settled in its own block is repaid what it paid out and earns nothing.

`filledBy(orderId)` and `filledLzNonce(orderId)` return the filler and the nonce it named. So fill as soon as you are willing to.

### Ceilings

| Limit | What it bounds |
| --- | --- |
| `MAX_FAST_FEE_BPS` | The fast lane’s own fee can never be set above 100 basis points, 1%. Read the fees in force for a launch with `feesOf(launchId)`. |
| `maxRelayerFeeBps` | No order may pay a relayer more than this share of what arrives: 1000 basis points, 10%, unless the bridge’s admin sets a lower one, for all launches or one. An order above it is refused with `RelayerFeeTooHigh`. |
| `maxFeeBps` | The price board’s own ceiling on a published price, reported by every read of the board. |
| `minReceived` | The sender’s floor, written into the order. An order that would pay the recipient less is refused with `ReceivesTooLittle` before any token moves. |

### What gets you benched

When the app sends an order at your published price, it remembers that the price was yours. Each order is judged once: reporting the same transaction again records nothing. It looks at the order again from 2 minutes after it was posted and judges it once it has settled, or after 30 minutes if it still has not. An order nobody filled, or one you filled without earning the fee, is a strike against you.

After 3 strikes within 60 minutes your prices are not taken for 60 minutes, however low they are. Those are the defaults, and a deployment can set its own. Your sheets stay on the board, and `benched` in the board’s answer says until when.

A deployment can name relayers it never benches, in `BRIDGE_RELAYERS_NEVER_BENCH`: EVM addresses, or Solana accounts a sheet names and has signed with. These are its own relayers, so nobody can switch its fast lane off by posting orders at its price while its stock is busy. Their unfilled orders are still struck and shown under `neverBenched`, but the strikes never add up to a bench. When the setting is empty, every relayer can be benched.

- It is not a strike if any relayer’s fill earned the fee: the sender was served.
- It is not a strike if another address filled the order without earning the fee within 90 seconds of it being posted and before the block that settled it, because that fill paid the recipient early and shut you out. A fill made later, or in the settling block itself, delivered nothing early, so it does not excuse you.
- It is not a strike if you could not have filled it. When the app gives your price it reads, at one block of the destination chain, your balance of that launch’s token and your approval of the fast bridge, and it only gives your price if they cover the fill. What you could pay out is the lower of the two. An order is matched to the last quote given before it was posted, if that was no more than a few minutes earlier (an order posted long after its quote counts against nobody), so asking for the quote again later changes nothing. Only when a strike is about to be given, the app subtracts what you have paid out since in other fills of that same launch that served somebody, until the order settled (or until it is judged, if it never did), or 90 seconds after it was posted if that is later. If what is left is less than the order pays, there is no strike. So somebody draining your stock with many small orders first does not turn the larger ones after them into strikes. A fill that would excuse you but has not settled yet is waited for, up to 30 minutes after the order was posted.
- A fill counts only if it was your own direct call of the bridge’s fillOrder for that launch (not through a contract or a batch, which cannot be read), came from an EVM chain, paid someone other than you, and earned the fee: it named the order’s own message and came before the block that settled it. A fill of an order nobody posted never settles, and one that names the wrong message or lands in the settling block is repaid at once, so neither excuses anything. Fills of orders from Solana do not count, because a plain token send from Solana can make one without paying the fast lane’s fee. Moving your stock away or revoking the approval excuses nothing either, nor does running out of native gas: both are yours to top up. Repayments that arrive in the meantime are not added back.
- Into Solana only the balance read when the price was given is used, because there is no cheap list of your fills there. Orders into Solana are not judged yet.
- If a read fails, there is no strike then. The order is checked again later, a few times, and if it still cannot be checked it is dropped without a strike, and the server logs it. A rate limit or a slow node is such a failure. Having more fills in the window than are read, or more in one block than the chain will list, is not: it is no excuse, and neither is a drain made of more than that many fills. The app makes at most a few of these checks a minute, and fewer for any one relayer, so a strike can come a little later than the order was due.
- A fill does not clear earlier strikes. Each one stops counting 60 minutes after it was given.

### The risks you take

Relaying is a business you run with your own money. Nothing here promises that it pays.

- **A reorg on the source chain.** Between your fill and your repayment, the fill is unsecured. If the source chain reorganises and the order’s transaction is gone, the message never arrives and nothing repays you. How many blocks you wait before filling is your own trade between speed and that risk. If a reorg keeps the order but moves its message to a different LayerZero nonce, you are repaid what you paid out and earn nothing.
- **Filling before the order is in a block.** Acting on pending transactions is the fastest way to fill and the riskiest: a transaction that has not been mined is likelier to vanish than a confirmed one.
- **Inventory.** You hold the token on every chain you serve, and its price moves while you hold it. Your fee is paid in the token too.
- **Gas.** A fill that reverts still costs gas, and gas is paid in the chain’s native asset while your fee is paid in the token. A price that does not cover gas loses money slowly.
- **Time.** Your tokens are out until the order settles. Settlement is run by LayerZero’s executor; if it stalls, anyone may run it, and until someone does you wait.
- **Being shut out.** Only one fill per order is accepted. Another address can fill ahead of you, even with a nonce that earns it nothing, and that order is then closed to you.

### Contract addresses

One fast bridge per chain, shared by every launch: Ethereum, Base, Robinhood Chain and BSC (deployed 16 October 2026), then Solana. The full interface is the bridge’s ABI and `INTERFACE.md`. These are the addresses, and the endpoint ids a price sheet and a fill use:

| Chain | Fast bridge | Endpoint id | Chain id |
| --- | --- | --- | --- |
| Ethereum | 0xE6feCfb8520Ce9417beB569ef95d6b8304802897 | 30101 | 1 |
| Base | 0x51D658a71AEa38f123d4c73e42703Fb9C949900A | 30184 | 8453 |
| Robinhood Chain | 0xcC87BeDb95E4ac0cf20039442A964ADB390c02c4 | 30416 | 4663 |
| BSC | 0xd7C10226327451ac768512aC4A5035a9477f862a | 30102 | 56 |

> **Note:** [How a standard transfer works →](https://www.pangea.trade/app/docs/v2#bridging)

## For integrators and AI agents

Pangea is a token launchpad. Each launch deploys its token as five contracts, a LayerZero OFT on Ethereum, Base, Robinhood Chain and BSC and a mint on Solana, and opens one liquidity pool per chain at the same US dollar price. This section is the reference for reading and using it from code.

These docs are also published as Markdown, generated from the same source as this page:

- Index for language models: `https://www.pangea.trade/llms.txt`
- Everything in one file: `https://www.pangea.trade/llms-full.txt`
- These docs as Markdown: `https://www.pangea.trade/app/docs.md`

Each section is also on its own, at `/app/docs/<section id>.md`. This section is `/app/docs/integrators.md`.

### The five chains

| Chain | Name in the API | Chain id | Endpoint id |
| --- | --- | --- | --- |
| Ethereum | ethereum | 1 | 30101 |
| Base | base | 8453 | 30184 |
| Robinhood Chain | robinhood | 4663 | 30416 |
| BSC | bsc | 56 | 30102 |
| Solana | solana | none | 30168 |

The endpoint id is LayerZero’s, and it is how the token names a chain. Solana has no EVM chain id.

### Units

- Every token has 9 decimals, on all five chains. An amount in base units is a whole number at 9 decimals: one token is 1,000,000,000 base units.
- A transfer between chains crosses at 6 decimals. It is rounded down to a multiple of 1,000 base units, and the odd base units stay in the sender’s wallet.
- Fees are in basis points. One basis point is 0.01%.
- The standard supply is 5 billion tokens, 1 billion in each chain’s liquidity pool at launch. On an EVM chain `totalSupply()` starts at that chain’s 1 billion and is a reported figure that moves no balance, so every chain shows a market cap on the same basis ([LayerZero routes](https://www.pangea.trade/app/docs/v2#routes) says how it is reported); `launchTotal()` is the sum across the launch’s chains, and `localSupply()` what is on that chain now. On Solana the mint’s supply is what is on Solana now: tokens bridged in are minted there and tokens bridged out are burned. [The market cap situation](https://www.pangea.trade/app/docs/v2#market-cap) explains why terminals show different market caps.
- A launch on selected chains is 1B on each of its chains only. A degen mode launch is 1B in all: each chain mints an equal share, every chain’s `totalSupply()` and `launchTotal()` report the 1B, and on Solana the mint holds the full 1B with the rest in its bridge reserve. [Degen mode](https://www.pangea.trade/app/docs/v2#degen-mode) has the figures.
- Times in the API are unix seconds unless a field says otherwise.

### Rules that surprise people

- Every launch is paid for by the person launching it, once, on the chain they pick, in that chain’s native asset. There are no free launches.
- Every launch opens at the same price on every chain, at the market cap its creator chose for the whole coin, every chain together: $3,000 to $500,000 a chain times the chains (in all, for a degen mode coin), signed into the launch. The price per token is that market cap over the whole supply. Launches before factory v2 all opened at $3,000 a chain, $0.000003 a token. Read a token’s price from the API; do not assume its opening one. On factory v3 there is no locked supply: degen mode is normal mode, every chain minting its own 1B and pooling all of it, opening at $600 a chain ($3,000 for a coin on five chains). The least a v3 launch may open at is $600 a chain, which the platform may set between $600 and $3,000 a chain; standard mode still opens at $3,000 a chain unless the creator picks another cap.
- A token has one address per chain it is on, all different (or one address on the EVM chains, for a token launched with “Same contract address on all EVM chains”), and every one of them ends in `6ea`: the last three hex digits on the EVM chains, the last three characters of the mint on Solana. Read the addresses from the API. Do not assume two chains share one.
- A transfer between chains of a token from before factory v3 costs 0.2% of the amount, kept by the token for the platform, plus LayerZero’s message fee in the source chain’s native asset. A factory v3 token's bridge fee is its chain's registry figure (0 to 0.5%), and a transfer from the chain where it is cheaper to the chain where it is dearer also pays the price-gap fee, burned at the destination: the quote's gap.burned, an estimate. It is attested after 1 confirmation on its source chain and typically arrives in 4 to 16 seconds.
- Each launch sets its trading fee once, between 0.3% and 5%. No contract function or program instruction changes it afterwards.
- The first transfer on a Solana route nobody has used yet also pays a one-time setup fee for that route. Every later transfer on that route does not. Any wallet can pay it, a smart-contract wallet too, except a Safe: its transactions wait for its owners, so a Safe bridges once the route is set up. Pay it from your own wallet, as the bridge page does: a payment sent through another contract (a multicall or a router) is refunded to that contract, not to you, and one made by delegatecall is not seen at all.
- On the four EVM chains nothing can mint more of a token. On Solana the mint authority is the token's store in Pangea's deployment of LayerZero's OFT program, which mints exactly what was burned on another chain. Bridging burns on one chain exactly what it mints on another, with one exception: a factory v3 token's price-gap fee is withheld at the destination and never minted, so it is burned.
- Prices on the five chains are separate markets. They are held together by arbitrage, not by a mechanism, and can differ.

### What you can and cannot do from code

- **You can read** every launch’s pools, prices, reserves and addresses, its trades and volume, recent bridge transfers and its metadata, over the HTTP endpoints below.
- **You can trade** directly against the pools: Uniswap v4 on the EVM chains, Meteora DAMM v2 on Solana. The API gives each pool’s id and venue.
- **You can bridge** by calling the token itself, or price a transfer over HTTP and sign the transaction it returns.
- **You can launch** over the Launch API: quote a launch, pay for it from your own wallet, hand it over and follow it. The price snapshot every chain launches with is still signed by the platform, after your payment is checked. [Launch API →](https://www.pangea.trade/app/docs/v2#launch-api)
- **No API key is needed and there are no accounts.** Every endpoint here is public. Where one is limited, the limit is per caller, by IP address. The Launch API has no rate limit for normal use, and takes an optional partner key: [API keys and limits →](https://www.pangea.trade/app/docs/v2#launch-api).
- **Do not crawl the API.** Each read costs reads on five chains. Call an endpoint when you need its answer; `robots.txt` asks crawlers to stay out of `/api/`.

### HTTP endpoints

All are under `https://www.pangea.trade/api`. They take and return JSON and need no key. Over a limit the answer is `429` with a message saying how many seconds to wait. An error is `{ "error": "..." }`. A value the chains could not give is `null` rather than a guess, so check for it.

| Endpoint | What it returns | Limit per caller |
| --- | --- | --- |
| `GET /pools` | Every launch, with its pool on each chain: addresses, price, reserves, supply. | None. Every caller is served one shared reading: re-read as soon as a trade moves a pool while the live page is open, and at least every 60 seconds unless the deployment sets another interval. |
| `GET /history` | Trades, volume in US dollars and price candles. | 30 uncached reads in ten minutes. An answer is reused for 45 seconds. |
| `GET /bridge/activity` | Recent transfers between chains. | None. |
| `GET /token/meta` | A token’s own metadata document: description, image, links. | None. |
| `POST /bridge/quote` | A transfer between chains, priced, with the transaction to sign. | 120 in ten minutes and 15 in any one minute. |

> **Note:** Every public endpoint, including the ones not described here, is in [API reference →](https://www.pangea.trade/app/docs/v2#api)

- Pools, GET: `https://www.pangea.trade/api/pools`

No parameters. The answer is `{ live, readAt, rates, blockers, launches }`. When nothing has launched, `live` is false and `launches` is empty. Each launch carries:

| Field | What it is |
| --- | --- |
| `slug`, `name`, `symbol` | The launch’s name in every other endpoint (`slug`), and its name and symbol. |
| `pools[]` | One entry per chain, including chains that could not be read. |
| `pools[].chain` | The chain’s name in the API. |
| `pools[].token` | The token’s address on that chain. This is where a token’s five addresses are published. |
| `pools[].poolId`, `pools[].venue` | The pool’s id and the venue it is on. |
| `pools[].price`, `pools[].priceUsd` | Quote asset per token, and the same in US dollars. `quoteSymbol` names the quote asset. |
| `pools[].reserves`, `pools[].tokenReserves` | What the pool holds: quote asset, and whole tokens. |
| `pools[].localSupply` | Whole tokens on that chain now. |
| `pools[].error` | Why that chain has no reading, when it has none. Set only when there is no price. |
| `pools[].note` | `{ kind, text }`, when present: a fact about a pool whose price was read and is good, never an outage. `kind` is `"extra-liquidity"` (others added liquidity to the pool; reserves are the launch’s position only), `"range-unknown"`, `"position-unknown"` or `"position-mismatch"` (the reserves could not be worked out). |
| `launchTotal`, `launchTotalRaw` | The token’s total supply in whole tokens, and in base units as a string. For a perChain launch, 1B on each chain it is on; for a degen mode launch, 1B. |
| `supplyModel`, `capSupply` | `"perChain"` for a launch with 1B on each chain (1B per chain · 5B across 5 chains), `"global"` for a degen mode launch and for launches before 2026-10-15, `"per-chain"` for a v4 coin (1B per opened chain; `capSupply` the supply across its open chains, `capSupplyMax` 1B times its listed chains). Market cap is a price times `capSupply`, which is `launchTotal`: the whole supply across the launch’s chains (5B for perChain on five). For a degen mode coin it is `circulating`: the 1B less what was burned; `circulating` is null for any other coin. |
| `degen` | `true` for a coin launched in degen mode: 1B in all, every chain reporting the 1B. |
| `launchChains`, `chainsTotal` | The chains the launch is on, and how many: five unless it was launched on selected chains. |
| `cheapest`, `spreadUsd` | The chain with the lowest US dollar price, and the gap between the highest and lowest price as a fraction. |
| `sold`, `burnt` | Whole tokens that have left the pools, and whole tokens sent to the burn addresses across chains: 0x…dEaD on each EVM chain, the incinerator's token account on Solana. |
| `metadataURI` | The token’s metadata document, as the contract names it. |

- History, GET: `https://www.pangea.trade/api/history`

Query: `launch`, a slug, or leave it out for every launch; `hours`, a whole number from 1 to 168, 24 unless given. The answer is `{ readAt, hours, cursor, rates, totals, launches }`. An unknown slug is a `404`. Each launch carries `volumeUsd`, `trades`, `coverage`, `chains`, `candles` and `recent`.

- `coverage` runs from 0 to 1. Below 1 the figures describe part of the window you asked for, not all of it. Show it, or check it.
- `candles` are `{ t, o, h, l, c, v, vu }`, one per bucket that traded, where `t` is the bucket's start. A bucket with no trade is left out, not filled with the last price. The prices are in US dollars. `v` is the quote volume in each chain's own asset, so across chains it adds ether, BNB and SOL together; `vu` is the bucket's volume in US dollars at the same rates as `volumeUsd`, and null when a trade in it had no rate.
- `recent` is the last trades, newest first: `{ id, chain, timestamp, txHash, side, tokenAmount, quoteAmount, quoteSymbol, priceUsd, valueUsd }`, where `side` is `buy` or `sell`.
- A coin's whole history, a page at a time: add `trades=latest` (the newest page) or `trades=before:<next>` (the page after the one that gave `next`) with `launch`, and `limit` of 50, 100 or 200. The answer is `{ slug, readAt, trades, next }`, every chain newest first, the rows as in `recent`; `next` is null on the page that reached the first trade. The first page also carries `total`, every trade since the launch, and `complete`, whether every chain is recorded back to it.

- Bridge activity, GET: `https://www.pangea.trade/api/bridge/activity`

Query: `token`, a slug, optional; `hours`, 1 to 168, 24 unless given; `limit`, 1 to 100, 25 unless given. The answer is `{ recording, hours, since, uncovered, totals, cursor, transfers }`. A transfer is `{ id, guid, slug, from, to, at, amount, party, complete, tookSeconds }`.

- `recording` false means this deployment keeps no record, and the list is empty for that reason, not because nobody bridged. `reason` says why.
- `uncovered` lists the chains whose sends are not recorded. A transfer out of such a chain appears as an arrival with no send.
- `complete` false means one side is recorded: a transfer in flight, or one that failed. The feed does not guess which.

- Token metadata, GET: `https://www.pangea.trade/api/token/meta`

Query: `launch`, a slug, required. The answer is `{ uri, resolvable, name, symbol, description, image, website, twitter, telegram }`, or `{ uri, resolvable: false, reason }` when the document cannot be opened. Every field is the token author’s own claim. The name and symbol that cannot be edited are the contract’s, in the pools endpoint.

- Bridge quote, POST: `https://www.pangea.trade/api/bridge/quote`

The body you POST:

```json
{
  "slug": "<launch slug>",
  "from": "base",
  "to": "bsc",
  "amount": "1000000000000",
  "user": "0x<sender, and the recipient unless one is given>"
}
```

| Field | What it is |
| --- | --- |
| `slug` | The launch. |
| `from`, `to` | Chain names, as in the table of chains above. They must differ. |
| `amount` | Base units, as a decimal string. |
| `user` | The EVM address sending, and the recipient on an EVM chain unless one is given. |
| `solanaUser` | The Solana address, when Solana is the source or the destination. |
| `recipient` | Who receives, when it is not the sender. Required for a transfer between an EVM chain and Solana: across the two a transfer is never sent to a wallet by default. |

The answer is `{ symbol, from, to, amount, routes }`, where `routes` holds two routes: `canonical`, the token’s own LayerZero transfer, and `fast`, the same transfer with a fast order. A route that cannot be used says why in `unavailable`. It carries:

| Field | What it is |
| --- | --- |
| `id` | `canonical` or `fast`. The fast route’s own fields are in [API reference](https://www.pangea.trade/app/docs/v2#api). |
| `amountOut` | What the transfer credits before a factory v3 token's price-gap fee, in base units. For a token from before factory v3 that is what the recipient receives. For a factory v3 token what arrives is `amountOut` less `gap.burned` (an estimate, in `gap.decimals`; every v3 token has 9 decimals on every chain) and, into Solana, less `gap.delivery.amount` too (our delivery key's cost there), and never less than `gap.minCredited`: below it the arrival waits at the destination. Never build your own minimum from `amountOut` alone. |
| `gap` | A factory v3 token only: the price-gap estimate for this transfer. `state` is `estimate`, `free` (nothing withheld), `held` (the destination cannot price it yet: it waits there) or `unavailable` (no standard route now; ask again); `burned` what the destination withholds, `minCredited` the least it may credit (`gapToleranceBps` under the estimate), and `minCreditedSource` that minimum in the source's decimals, out of Solana. |
| `nativeFee` | What it costs in the source chain’s native asset, in its smallest unit. Send it as the transaction’s value. |
| `tokenFee`, `platformFee` | Everything taken in the token, and the platform’s part of it, in base units. Both are the token’s fee. |
| `seconds` | The estimated time. |
| `call` | The transaction to sign: `{ to, data, value, chainId }`. Null when the transfer cannot be built as one contract call, as out of Solana. |
| `setupFee` | Present when this transfer opens a Solana route: the part of `nativeFee` that pays for it, once. |
| `unavailable` | Present when the route cannot be used, with the reason. |

### Bridging from code

On an EVM chain the token is a LayerZero OFT and you call it directly. Ask `quoteOFT` what a transfer debits and delivers, name the delivered amount as the minimum, ask `quoteSend` for the message fee, and send that fee as the transaction’s value. Leave `extraOptions` empty: the gas the destination needs is already enforced by the token.

On the token, on the chain the transfer leaves:

```solidity
struct SendParam {
    uint32 dstEid;       // the destination's endpoint id
    bytes32 to;          // the recipient, left-padded to 32 bytes
    uint256 amountLD;    // base units
    uint256 minAmountLD; // the least that may arrive
    bytes extraOptions;
    bytes composeMsg;
    bytes oftCmd;
}
struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; }
struct OFTReceipt { uint256 amountSentLD; uint256 amountReceivedLD; }

function quoteOFT(SendParam calldata _sendParam)
    external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);
function quoteSend(SendParam calldata _sendParam, bool _payInLzToken)
    external view returns (MessagingFee memory);
function send(SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress)
    external payable returns (MessagingReceipt memory, OFTReceipt memory);

event OFTSent(bytes32 indexed guid, uint32 dstEid, address indexed fromAddress, uint256 amountSentLD, uint256 amountReceivedLD);
event OFTReceived(bytes32 indexed guid, uint32 srcEid, address indexed toAddress, uint256 amountReceivedLD);

error AmountTooSmallToBridge(uint256 amount);
error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);
```

| Read on the token | What it returns |
| --- | --- |
| `BRIDGE_FEE_BPS()` | A token from before factory v3: its bridge fee in basis points, 20. A factory v3 token's fee is its chain's registry figure (0 to 50): take what arrives from `quoteOFT`. |
| `bridgeFeeTo()` | Where that fee goes on this chain. |
| `peers(uint32 eid)` | The token’s counterpart on another chain. Zero means there is no route to it. |
| `decimals()` | 9, on every chain. |
| `totalSupply()` | The reported supply: this chain’s 1B slice, or, for a launch with a SupplyReporter, the token’s supply on Solana as the chain’s SupplyReporter mirrors it (on a factory v1 token, never below `localSupply()` or above `launchTotal()`). For a degen mode launch, the 1B total, which never changes. |
| `launchTotal()`, `launchSlice()` | The whole launch’s supply across every chain, and this chain’s slice of it. |
| `supplyReporter()` | The only address that may report this token’s supply. Launches made before reporting existed have no such function. |
| `localSupply()` | What is on this chain now. |
| `metadataURI()` | The token’s metadata document. |

On Solana the token is an account of LayerZero’s OFT program, `SNyKyiZYU4R54SF9sYLmjCaCUsa1sC6QouNAj3x3Zzf`, and a transfer out of Solana is an instruction to that program, not a contract call. Into Solana the recipient is the wallet that will own the tokens.

### Launches on chain

On each EVM chain the launch factory emits one event per launch. The current factories’ addresses are in [LayerZero routes](https://www.pangea.trade/app/docs/v2#routes).

On each EVM chain’s launch factory:

```solidity
event Launched(
    bytes32 indexed launchId,
    address indexed token,
    address splitter,
    address launchOwner,
    address locker,
    uint128 liquidity,
    uint256 dust
);
```

### Addresses

- A token’s five addresses are in the pools endpoint, at `launches[].pools[].token`, and on the token’s page.
- The current launch factories, and each chain’s LayerZero endpoint, libraries, executor and verifiers, are in [LayerZero routes](https://www.pangea.trade/app/docs/v2#routes).

## Bridging from code

On the four EVM chains you call the token itself: it is a LayerZero OFT. Out of Solana the send is an instruction to LayerZero’s OFT program, and the simplest way to build it is to ask Pangea’s API for the unsigned transaction. The signatures and events are in [For integrators](https://www.pangea.trade/app/docs/v2#integrators); the routes they travel are in [LayerZero routes](https://www.pangea.trade/app/docs/v2#routes).

- `amountLD` is in base units, nine decimals. The token keeps 0.2% of it (a factory v3 token: its chain's registry figure, at most 0.5%), rounded down, and what crosses is rounded down to six decimals. `quoteOFT` returns both figures, exactly.
- `minAmountLD` is the least that may arrive, or the send reverts with `SlippageExceeded`. On a token from before factory v3 the fee is fixed, so the received amount from `quoteOFT` can be used as it is; on a factory v3 token the fee is read at every send, so ask `quoteOFT` right before you send.
- `extraOptions` stays empty. The token adds its own gas floor to every send; anything you add is added on top and paid for by you.
- **A factory v3 token: send the minimum.** Its `oftCmd` must carry your least credited after the price-gap fee: `abi.encode(uint256 minCreditedLD)` in the source's base units (the low 248 bits; the top byte is flags, 0). With an empty `oftCmd` the destination credits whatever the gap leaves. Take it from the quote's `gap` object (`gap.minCredited`, destination units; `gap.minCreditedSource` out of Solana), set by `gapToleranceBps` (default 100, at most 2000). Its bridge fee is its chain's registry figure, 0 to 0.5%: read what arrives from `quoteOFT`.
- `to` is 32 bytes: an EVM address left-padded with zeros, or a Solana wallet’s public key. Never a Solana token account.
- `quoteSend` gives LayerZero’s fee for that exact `SendParam`. Send it as the transaction’s value, with `payInLzToken` false. Any excess goes back to the refund address.
- No approval is needed for a standard send: the token burns from your own balance.

### EVM to EVM, with viem

Quote a transfer of 1,000 PTEST4 from Base to BSC:

```ts
import { createPublicClient, http, parseAbi, pad } from "viem";
import { base } from "viem/chains";

const OFT = parseAbi([
  "struct SendParam { uint32 dstEid; bytes32 to; uint256 amountLD; uint256 minAmountLD; bytes extraOptions; bytes composeMsg; bytes oftCmd; }",
  "struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; }",
  "struct OFTLimit { uint256 minAmountLD; uint256 maxAmountLD; }",
  "struct OFTFeeDetail { int256 feeAmountLD; string description; }",
  "struct OFTReceipt { uint256 amountSentLD; uint256 amountReceivedLD; }",
  "struct MessagingReceipt { bytes32 guid; uint64 nonce; MessagingFee fee; }",
  "function quoteOFT(SendParam _sendParam) view returns (OFTLimit, OFTFeeDetail[], OFTReceipt)",
  "function quoteSend(SendParam _sendParam, bool _payInLzToken) view returns (MessagingFee)",
  "function send(SendParam _sendParam, MessagingFee _fee, address _refundAddress) payable returns (MessagingReceipt, OFTReceipt)",
]);

const TOKEN = "0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea"; // PTEST4 on Base
const BSC = 30102;
const recipient = "0x000000000000000000000000000000000000bEEF"; // who receives on BSC
const amount = 1_000n * 10n ** 9n; // 1,000 tokens: nine decimals

const client = createPublicClient({ chain: base, transport: http(process.env.BASE_RPC) });

// What the transfer debits and what arrives: 0.2% stays with the token, the rest crosses.
const sendParam = {
  dstEid: BSC,
  to: pad(recipient), // an EVM address, left-padded to 32 bytes
  amountLD: amount,
  minAmountLD: 0n,
  extraOptions: "0x", // empty: the token already enforces the gas the destination needs
  composeMsg: "0x",
  oftCmd: "0x",
};
const [, , receipt] = await client.readContract({ address: TOKEN, abi: OFT, functionName: "quoteOFT", args: [sendParam] });
sendParam.minAmountLD = receipt.amountReceivedLD; // the least that may arrive

// LayerZero's fee for this exact transfer, in ETH.
const fee = await client.readContract({ address: TOKEN, abi: OFT, functionName: "quoteSend", args: [sendParam, false] });
console.log({ sent: receipt.amountSentLD, arrives: receipt.amountReceivedLD, nativeFee: fee.nativeFee });

// The send, from the holder's wallet, paying the fee as the value:
// await wallet.writeContract({ address: TOKEN, abi: OFT, functionName: "send",
//   args: [sendParam, fee, account.address], value: fee.nativeFee });
```

Run against Base on 19 October 2026, it printed `sent: 1000000000000n, arrives: 998000000000n, nativeFee: 109546395808232n`: 1,000 tokens sent, 998 arrive, and about 0.00011 ETH for LayerZero.

### The same, with cast

quoteOFT and quoteSend from the command line:

```bash
TOKEN=0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea
RPC=your-base-rpc-endpoint
TO=$(cast pad --len 32 0x000000000000000000000000000000000000bEEF)

# what is sent and what arrives: (limit), (fee details), (sent, received)
cast call $TOKEN "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))((uint256,uint256),(int256,string)[],(uint256,uint256))" \
  "(30102,$TO,1000000000000,0,0x,0x,0x)" --rpc-url $RPC

# LayerZero's fee in wei: (nativeFee, lzTokenFee)
cast call $TOKEN "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)((uint256,uint256))" \
  "(30102,$TO,1000000000000,998000000000,0x,0x,0x)" false --rpc-url $RPC
```

> **Note:** Pad an address with `cast pad`, which pads on the left. `cast to-bytes32` pads on the right, and a send to that would go to a different address.

### EVM to Solana

The same send, with `dstEid 30168` and the recipient’s Solana wallet as the 32 bytes. The tokens arrive in that wallet’s token account, created on delivery; its rent, 2,074,080 lamports, is in every transfer’s gas floor and so in `quoteSend`’s fee.

Quote a transfer from Base to Solana, and check the route is open:

```ts
import { createPublicClient, http, parseAbi, toHex } from "viem";
import { base } from "viem/chains";
import { Connection, PublicKey } from "@solana/web3.js";

const OFT = parseAbi([
  "struct SendParam { uint32 dstEid; bytes32 to; uint256 amountLD; uint256 minAmountLD; bytes extraOptions; bytes composeMsg; bytes oftCmd; }",
  "struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; }",
  "struct OFTLimit { uint256 minAmountLD; uint256 maxAmountLD; }",
  "struct OFTFeeDetail { int256 feeAmountLD; string description; }",
  "struct OFTReceipt { uint256 amountSentLD; uint256 amountReceivedLD; }",
  "function quoteOFT(SendParam _sendParam) view returns (OFTLimit, OFTFeeDetail[], OFTReceipt)",
  "function quoteSend(SendParam _sendParam, bool _payInLzToken) view returns (MessagingFee)",
  "function peers(uint32 eid) view returns (bytes32)",
]);

const TOKEN = "0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea"; // PTEST4 on Base
const BASE_EID = 30184;
const SOLANA = 30168;
const owner = new PublicKey("<the Solana wallet that will own the tokens>");
const amount = 1_000n * 10n ** 9n;

const evm = createPublicClient({ chain: base, transport: http(process.env.BASE_RPC) });
const solana = new Connection(process.env.SOLANA_RPC, "confirmed");

// Is this token's route from Base open on Solana? It is once LayerZero's receive config exists.
const peer = await evm.readContract({ address: TOKEN, abi: OFT, functionName: "peers", args: [SOLANA] });
const store = new PublicKey(Buffer.from(peer.slice(2), "hex")); // the OFT store
const eid = Buffer.alloc(4);
eid.writeUInt32BE(BASE_EID);
const ULN = new PublicKey("7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH");
const [receiveConfig] = PublicKey.findProgramAddressSync([Buffer.from("ReceiveConfig"), eid, store.toBuffer()], ULN);
const open = (await solana.getAccountInfo(receiveConfig)) !== null;

const sendParam = {
  dstEid: SOLANA,
  to: toHex(owner.toBytes()), // the owner's public key as 32 bytes, not a token account
  amountLD: amount,
  minAmountLD: 0n,
  extraOptions: "0x", // the token adds the gas floor and the rent of the token account
  composeMsg: "0x",
  oftCmd: "0x",
};
const [, , receipt] = await evm.readContract({ address: TOKEN, abi: OFT, functionName: "quoteOFT", args: [sendParam] });
sendParam.minAmountLD = receipt.amountReceivedLD;
const fee = await evm.readContract({ address: TOKEN, abi: OFT, functionName: "quoteSend", args: [sendParam, false] });
console.log({ store: store.toBase58(), routeOpen: open, arrives: receipt.amountReceivedLD, nativeFee: fee.nativeFee });
// Then send exactly as from EVM to EVM. With routeOpen false, the transfer waits on Solana
// until the route is opened: see the route setup below.
```

If the token’s route from that chain is not open on Solana yet, a transfer sent now waits there until it is. To open it as part of your transfer, as the Bridge page does, `POST /api/bridge/quote` returns the setup payment in `routePayment`: send it first, then report it to `POST /api/bridge/route-open`, which opens the route or refunds the payment, and then send the transfer. [API reference →](https://www.pangea.trade/app/docs/v2#api)

### Solana to EVM, with @solana/web3.js

Out of Solana the send is LayerZero’s OFT program’s own instruction, with about thirty accounts and a lookup table. `POST /api/bridge/solana-tx` builds it, unsigned, for your wallet to sign: it names the accounts, sets the minimum to what arrives, and adds the route’s opening when a route is used for the first time. You pay LayerZero’s fee and the transaction fee in SOL; the token’s 0.2% is kept in the OFT store’s escrow for the platform.

Price and build a transfer from Solana to Base, then simulate it:

```ts
import { Connection, VersionedTransaction } from "@solana/web3.js";

const API = "https://www.pangea.trade/api";
const sender = "<the Solana wallet holding the tokens>";
const recipient = "0x000000000000000000000000000000000000bEEF"; // who receives on Base
const amount = (1_000n * 10n ** 9n).toString(); // 1,000 tokens, in base units

const post = (path, body) =>
  fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then(async (r) => ({ status: r.status, body: await r.json() }));

// The price: what arrives on Base, and LayerZero's fee in lamports.
const quote = await post("/bridge/quote", { slug: "ptest4", from: "solana", to: "base", amount, user: recipient, recipient, solanaUser: sender });
const standard = quote.body.routes?.find((r) => r.id === "canonical");
if (!standard || standard.unavailable) throw new Error(standard?.unavailable ?? quote.body.error);
// A factory v3 token: what arrives is amountOut less the gap estimate, never less than gap.minCredited.
const gap = standard.gap ?? null;
console.log({ credited: standard.amountOut, gapBurned: gap?.burned, nativeFee: standard.nativeFee, setupFee: standard.setupFee ?? null });

// The unsigned transaction: the OFT send, and the route's opening if it is not open yet.
// A factory v3 token's send must name its minimum (minCredited, Solana base units): without one it is refused (400).
const ask = { slug: "ptest4", to: "base", amount, sender, recipient, ...(gap ? { minCredited: gap.minCreditedSource } : {}) };
let built = await post("/bridge/solana-tx", ask);
// 202 { pending: true }: a price record or a route opening is still landing -- ask again in a moment.
while (built.status === 202) {
  await new Promise((r) => setTimeout(r, 2_000));
  built = await post("/bridge/solana-tx", ask);
}
// stage "record" (a v3 token with no recent price on record): sign and send it, then ask again with
// { ...ask, recorded: <its signature> }; the answer is then the send.
if (built.status !== 200) throw new Error(built.body.error ?? `stage ${built.body.stage}`);
const tx = VersionedTransaction.deserialize(Buffer.from(built.body.transaction, "base64"));

// Check it before signing: a simulation sends nothing.
const connection = new Connection(process.env.SOLANA_RPC, "confirmed");
const sim = await connection.simulateTransaction(tx, { sigVerify: false });
console.log({ stage: built.body.stage, received: built.body.received, totalFee: built.body.totalFee, error: sim.value.err });

// Sign with the sender's wallet and send:
// const signed = await wallet.signTransaction(tx);
// await connection.sendRawTransaction(signed.serialize());
```

Run read-only on 19 October 2026 from a Solana holder, it built a transaction at stage `send` with `received: 998000000000` and a fee of at most 636,478 lamports, and the simulation passed. The stages are `send`, the transfer alone; `together`, the route’s opening and the transfer in one transaction; and `setup`, the opening alone, after which you ask again with `after` set to its signature. A factory v3 token adds `record`: the price record its send needs first (its signature fee only), after which you ask again with `recorded` set to its signature. A `202` with `pending: true` means the step before is still landing: ask again in a moment. A v3 token's send also needs `minCredited` (the quote's `gap.minCreditedSource`), or it is refused with a `400`.

### After the send

- The send emits `OFTSent(guid, ...)` on the source chain and the delivery `OFTReceived(guid, ...)` on the destination. The guid is the transfer’s id on LayerZero Scan.
- Look the guid or the transaction up on LayerZero Scan to follow it, or poll the destination for `OFTReceived` with that guid.
- Delivery needs nothing from you. If a message sits verified and undelivered, anyone may deliver it, except a factory v3 token's charged arrival into Solana, which only Pangea's appointed deliverers may land.

## API reference

All endpoints are under `https://www.pangea.trade/api`, take and return JSON, and need no key or account. The general rules are in [For integrators](https://www.pangea.trade/app/docs/v2#integrators), which also describes five of them in full: pools, history, bridge activity, token metadata and the bridge quote. The relayers’ price board is in [Relayers](https://www.pangea.trade/app/docs/v2#relayers). This section lists all of them and describes the rest.

- An error is `{ "error": "..." }` with a 4xx status (a 5xx is a fault, and its body may be a proxy's own). A value the chains could not give is `null`, not a guess.
- Limits are per caller, by IP address, counted on each server. Over a limit the answer is `429` and the message says how many seconds to wait; some also send `Retry-After`.
- A JSON body that is too large is `413`, one that is compressed is `415`, one that does not arrive within 10 seconds is `408`, and one that is not a JSON object of the expected fields is `400`.
- Call them from a server or a script. Only the supply endpoints (`/supply/[token]` and its two plain-number forms), `/gap-price/[id]` and `/gap-price/quote-usd` send `Access-Control-Allow-Origin: *`; the rest send no CORS header, so a web page on another site cannot read them from the visitor’s browser.
- Answers are cached briefly in front of the app (most for 5 to 60 seconds, as each endpoint says), so polling faster than that returns the same answer.
- Amounts are base units as decimal strings. Chains are named `ethereum`, `base`, `robinhood`, `bsc` and `solana`. A launch is named by its slug.
- Three endpoints answer as a stream of server-sent events: `data: <json>` lines, with `content-type: text/event-stream`. Read them with a streaming fetch; they are POST, so an `EventSource` cannot open them. The Launch API’s event stream and the launches stream are GETs, in the same format with named events.
- A coin is named by its slug, its launch id or any chain’s token address wherever the path says `[id]`. The coin, its supply, the pools, the holders and the launches feed carry `degen` and the coin’s supply: circulating, the bridge reserve and where it is held, the total, and what each chain mints and each token reports.

### Every public endpoint

| Endpoint | What it is for | Limit per caller |
| --- | --- | --- |
| `GET /pools` | Every launch and its pool on each chain. | None; one shared reading. |
| `GET /history` | Trades, volume and price candles. | 30 in ten minutes, uncached reads only. |
| `GET /holders` | Who holds a token, per chain. | 60 in ten minutes, uncached reads only. |
| `GET /gap-price/[id]` | A shared-bridge coin’s signed gap price for one transfer: the coin id or its token, src and dst (chain id or name, several dst comma-separated), and optionally amount (the send in the source token’s base units). Pass the answer unchanged in the bridge call. 503 means no signed price right now: send without one (the chain’s own readers price it, or no gap fee). Not cached. | 20 at once, then 1 a second; all callers 50 a second. |
| `GET /gap-price/quote-usd` | The signed USD price of a coin’s native quote on one EVM chain, for a buy-and-bridge that carries its own quote price (buyAndSendQuoted): chain (chain id or name) and optionally quote (0x00..00, the native coin, is the only one signed). Pass signedQuote unchanged in the call, and fetch it right before: it is valid for about 30 seconds. 503 means no price right now; on Robinhood a buy then waits, since that chain has no other quote price. 404 for an ERC-20 quote. Not cached. | Shared with the gap price: 20 at once, then 1 a second; all callers 50 a second. |
| `GET /token/meta` | A token’s metadata document. | None. |
| `GET, POST /token/socials` | Who may edit a coin’s socials (its creator, EVM and Solana); with history=1 every version so far, each with the creator’s signature, and with onchain=1 each EVM chain’s metadata authority and URI; POST stores a new version the creator signed (EIP-712, or a signed message on Solana) with a new website, X, Telegram, Discord, description or logo. No transaction and no gas: /token/meta shows the newest version, marked as edited by the creator. | GET 120 a minute, 10 with onchain=1. POST 30 attempts in ten minutes; 10 stored edits an hour, and 6 an hour and 24 a day per coin. |
| `GET /logo/[cid]/[file]` | A token’s logo from this site, as square WebP: the file is 64.webp, 128.webp or 256.webp, and the CID is the image’s own, as /token/meta names it. Cached for a year: a CID’s picture cannot change. | None. |
| `GET /platform-funds` | What is still to be spent on PANGEA buybacks on each chain: the buyback wallets’ balance less what they keep for gas, plus their share of platform fees not yet forwarded, in native units and US dollars, with the total and the buyback wallets. | None; one shared reading a minute. |
| `GET /creator-rewards` | Everything launches’ trading fees have paid to their creators, all time: per chain in native units and US dollars at today’s prices, with the total and which chains are not counted in full. | None; one shared reading a minute. |
| `GET /holder-payouts` | Creator fees paid to holders by the coins whose fees go to a tokenomics vault: all time (every vault the coin’s fees have gone to, earlier ones included) and the last 24 hours, by chain, by coin and by reward token, as each vault’s own counters on chain say, in US dollars at today’s prices. | None; one shared reading a minute. |
| `GET /revenue` | Platform and creator fee payouts over any span (from, to), in US dollars at each payout’s own time, per chain and asset. | None; held 30 seconds. |
| `GET /buyback-burns` | Every PANGEA buyback so far, with its burn transaction: totals per chain, the latest 50, or every one a page at a time. | None; held 15 seconds. |
| `GET /cto` | The coins under a community takeover, and when each chain’s fee stream moved. | 120 a minute. |
| `GET /stock-pairs` | The coins with a stock-paired chain, and the stock on each. | 120 a minute. |
| `GET, POST /token/fee-native` | Whether a coin’s creator fees in its pair token (any pair but the chain’s own coin) are paid in the chain’s native coin; POST switches it, signed by the creator. | GET 120 a minute; POST 20 in ten minutes. |
| `GET /tokenomics` | One chain’s tokenomics vault, read from the chain: its split, what waits in each bucket, and what it has burned, bought and added. | 60 a minute. |
| `GET /tokenomics/v2/burn` | A tokenomics v2.2 vault’s burn tokens: the other tokens it buys to burn, and how much of each so far. | 30 a minute. |
| `GET /tokenomics/v2/holder` | What one holder is owed and has been paid by a coin’s vault, per reward token. | 30 a minute. |
| `POST /tokenomics/v4/switch` | Where v4 is switched on: a v4 coin’s tokenomics on one chain, built for your own wallet to sign; nothing is signed or sent here. `op: "enable"` (with `vault`) turns it on or re-points it, `"disable"` turns it off, `"clear"` clears a wrongly pinned vault. The creator may only turn it on, while it is off and the role is not renounced; the platform admin may turn it on, off and re-point it at any time (EVM `adminEnableTokenomics`; Solana `admin_enable_tokenomics`, on or re-point). For a Solana-home coin, `"push"` builds the creator role push from Solana, `"role"` reads it back, and `"authorize"` builds the open authorisation from Solana (`pusher`, `opener`, `deadline`). | 20 a minute. |
| `GET /v4/abi` | The full ABI of every v4 EVM contract and the IDL of every v4 Solana program, as JSON: all of them, or one with `?id=` (and `&kind=idl` for a program), or the list with each file’s sha256 with `?manifest=1`; add `&download=1` to save it as a file. | None. |
| `GET /v4/contracts` | Every v4 address the deploy recorded, per chain: its chain id, pcid and `contracts` by name (EVM addresses checksummed, Solana programs and accounts in base58). Before the first deploy it answers `published: false`. The top level is the first v4 set (v4a); `sets.v4b` is the second set (the redeploy new coins launch on), with its own addresses, once it is deployed. | None. |
| `POST /tokenomics/v4/account` | Where v4 full scope is switched on (`NEXT_PUBLIC_GAP_V4` and `NEXT_PUBLIC_GAP_V4_FULL` both `on`; 404 otherwise): what is at one Solana tokenomics account (`address`): whether it exists, its owner program and its data, read before a creator’s tokenomics setup is built, so a retry reuses the account. Reads only. | 60 in ten minutes. |
| `GET /coin/[id]/fee-receivers/pending` | A fee receiver change waiting to be accepted, per chain. | 60 a minute. |
| `GET /bridge/health` | Whether bridging is working: ok, or the problems. | None. |
| `POST /cosign` | A v3 transfer too large for one part, co-signed by Pangea’s delivery service, with its minimum credited. | 6 a minute, 30 in ten minutes. |
| `GET /trade/tx-status` | Whether an EVM transaction succeeded, failed or is pending. | 90 a minute. |
| `GET /bridge/activity` | Recent transfers between chains. | None. |
| `GET /bridge/balance` | One address’s balance of a token or the native asset. | 120 in ten minutes |
| `POST /bridge/quote` | A transfer priced, both lanes, with the transaction to sign. | 120 in ten minutes, 15 a minute. |
| `POST /bridge/solana-tx` | The unsigned transaction for a transfer out of Solana. | 60 in ten minutes, 10 a minute. |
| `POST /bridge/solana-fast-tx` | The same, as a fast order. | 60 in ten minutes, 10 a minute. |
| `POST /bridge/route-open` | Opens a Solana route once its setup payment has landed. | 120 in ten minutes |
| `POST /bridge/arrival` | A stream that says when a transfer has arrived. | 60 in ten minutes |
| `POST /bridge/v4/arrival` | Version-4 coins, where this site has them (404 otherwise): POST { slug, from, to, tx, guid? }, and each transfer of that source transaction toward to comes back landed, held below its minimum (the recipient can accept it at the credit it would get now), accepted, waiting for a Solana price record, or on its way, with the amounts in the token’s own decimals. Reads only; nothing is signed. | 120 in ten minutes |
| `GET /bridge/v4/history` | Version-4 coins, where this site has them (404 otherwise): ?slug= (and &wallet= for one wallet’s) the coin’s recent v4 transfers, each with what was sent, what was credited and what was burned on arrival, and the accepts, from the tokens’ own events; a chain that did not answer in time is named in unread, never left out silently. | 120 in ten minutes |
| `GET /bridge/v4/path` | A version-4 coin’s stored dollar price path on one chain, where this site has version 4 (404 otherwise): ?slug=&chain=, read from the chain now: none (its pool pairs with the native coin or a dollar token), ok, weak (thin: the dollars a 2% move takes on its thinnest pool, unmeasured, or a pool keeping a single price observation), depth not measured (Solana: the route reads, its depth is not measured yet) or does not read (arrivals priced by it wait or burn until it reads again or someone replaces it), with the quote’s decimals, and whether the path was replaced since launch (anyone can put the launch path back whenever it reads). The path judged is the one the coin prices by, as stored at launch or as replaced since. | 120 in ten minutes |
| `POST /launch/v4/buy-list` | A version-4 launch’s Solana list (launch buys on Solana), from this site’s launch page only: op read (the list and its money account as on chain), op blockhash (for the list’s own creator or payer), op band (whether SOL is still within the band the list was quoted at, and whether the launcher’s pool fee and OFT version are still the ones the list sealed). Nothing is built or signed here: the creator’s wallet signs the list. | 600 in ten minutes |
| `GET /launch/v4/pair-check` | A version-4 launch’s pair token, where this site has version 4 (404 otherwise): ?chain=&address=, and what it is before payment: refused only when it truly cannot work (and why: no token code, unreadable decimals or more than the pair maximum, a token that cannot sit in a pool such as NonTransferable or an active transfer hook), not open yet on that side, or ready. Any other token is allowed, at the creator’s risk: a missing or thin pool path to a dollar price is the “weak price path” mark, never a refusal; its own decimals, its transfer tax, and that mark when the price path its launch would use is missing, thin or unmeasured (on EVM: the dollars a 2% move takes on that path’s thinnest pool). On EVM the quote itself measures the pools again and launches with the deepest path; when a pool that could be deeper cannot be measured just now it refuses (“the pools could not be measured just now, try again”) rather than pick around it, and the quote names the path it chose. A path with a pool whose price read comes within 10,000 gas of its venue’s gas cap is not used (a touch could make it stop reading). No pair list exists: nothing here allows or refuses a token by name. | 60 in ten minutes |
| `POST /launch/v4/self-open` | A version-4 launch whose pay chain you launch from your own wallet (no Solana leg), where this site has version 4 (404 otherwise): { launchId, chain }, once your turn has come. It builds, and checks, the transactions your fee wallet sends, in order: any “keep two price observations” step a pool the coin’s price rests on still needs (anyone may send it), then the launch itself through that chain’s self-launch opening router, with what the opening owes priced now (a few base units of the pair token, bought with native and paid to the router). Nothing is sent from here, and the router takes the launch only from your fee wallet, with the one signature per chain you gave before paying (it names that chain’s router). If your time runs out, our launcher sends it: same price, same fee. | 30 in ten minutes |
| `POST /launch/v4/registered` | A shared-bridge coin you opened yourself, where this site has the shared-bridge self launch (404 otherwise): { chain, txHash } of the open, once it is mined. This site reads that transaction from the chain itself and lists the coin from what the chain says (its id, home, chain list and transports, its token); nothing you send is stored. The first listing of a coin is kept. | 20 in ten minutes |
| `POST /launch/v4/sol-open` | The Solana side of a shared-bridge coin you open yourself, where this site has the shared-bridge self launch (404 otherwise): { payer }, and the answer is a recent blockhash and the shared lookup table’s addresses, so your wallet can build and sign the open. Nothing is built, signed or sent here. | 60 in ten minutes |
| `POST /launch/v4/sol-held` | Opening Solana later with a wallet the coin’s home chain authorised, where this site has the shared-bridge self launch (404 otherwise): { coinId, homePcid }, and the answer is the bridge’s authorisation slot for the coin on Solana (its address, and its data or null when no authorisation has arrived yet). Your page checks it (the opener must be your wallet, before its deadline) and builds the open itself. Nothing is built, signed or sent here. | 30 in ten minutes |
| `POST /bridge/refund` | Whether a fast order is held, and the transaction that sends it back. | 60 in ten minutes |
| `POST /bridge/orders` | Tells the board a fast order was posted at a relayer’s price. | 60 in ten minutes |
| `GET, POST /bridge/relayer-quotes` | The relayers’ price board. | 300 in ten minutes reads, 120 publishes. |
| `POST /chain/tx` | A stream that says when a transaction is in a block. | 60 in ten minutes |
| `POST /trade/quote` | A buy or sell priced through Relay, or Jupiter on Solana. | 60 a minute. |
| `POST /trade/solana-tx` | Compiles a swap’s Solana instructions into an unsigned transaction. | 60 in ten minutes |
| `GET /token/launch-buys` | Who bought in a launch’s own block, per chain, and whether that block has been read. | 60 in ten minutes, uncached reads only. |
| `GET /coin/[id]` | One coin: its addresses, degen mode, its supply, and its tokenomics config as its launch signed it. No chain is read. | 120 a minute. |
| `GET /supply/[token]` | A coin’s total, circulating and locked supply, per chain and in all, in whole tokens (locked is always 0: every launch pools all of each chain’s share). perChain.supply is the slice the launch minted on that chain, not what is on it after bridging. For a v3 coin, what its bridges burned (the gap fee: a cheap-to-dear bridge withholds part and never mints it) comes off circulating, and off the total too unless the coin's total is fixed (degen); gapBurned says how much. For CoinGecko and CoinMarketCap. A chain that cannot be read is a 503, never a smaller figure. | 120 a minute. |
| `GET /supply/[token]/total` | The total supply alone, as a bare number (text/plain). | 120 a minute, shared. |
| `GET /supply/[token]/circulating` | The circulating supply alone, as a bare number (text/plain). | 120 a minute, shared. |
| `GET /coin/[id]/supply` | A coin’s supply: what its launch made, and for a degen mode coin the same read from the chains. | 60 a minute. |
| `GET /coin/[id]/supply/total` | Only the coin’s total supply, as plain text in whole tokens (the form CoinMarketCap and CoinGecko ask for): every chain’s supply counted once, less what was burned. Held a minute; 503 when the chains cannot be read and no recent figure is held. | 60 a minute. |
| `GET /coin/[id]/supply/circulating` | Only the coin’s circulating supply, the same way: the total less anything locked or out of public hands (the Solana bridge reserve is never counted, as it backs the other chains). | 60 a minute. |
| `GET /coin/[id]/tokenomics` | A coin’s tokenomics config, and each chain’s vault read from the chain: what waits in each bucket, and what was burned, bought and added to liquidity. | 20 a minute. |
| `GET /coin/[id]/vault-summary` | What a coin’s tokenomics vault has done, all time per coin: every vault the coin’s fees have gone to on each chain, earlier ones included (each listed in previousVaults), summed per option in US dollars at today’s prices; an earlier vault not read makes the answer partial and names its chain. 404 for a coin whose fees go to no vault. | 30 a minute. |
| `GET /coin/[id]/fee-receivers` | Who a coin’s creator fees are paid to now, read from the chains: each EVM chain’s fee splitter and the Solana launch record, with what each receiver is (a wallet, a tokenomics vault, the burn address or another contract; for a community takeover whose fees are bought back and burned, “buyback”, with no address). Cached for an hour. | 60 a minute. |
| `POST /v1/launch/prepare` | A launch quoted, with the ticket every later step takes. | 120 in ten minutes per address; none with an API key. |
| `POST /v1/launch/authorization` | The permission a creator wallet signs for another launching wallet. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/pay` | The transaction that pays the launch fee. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/submit` | Hands a paid launch to Pangea’s launcher. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `GET /v1/launch/status` | Where a launch stands, chain by chain. | No rate limit; the flood ceiling. |
| `GET /v1/launch/events` | The same, as a stream of events. | 3 open at once per address. |
| `GET /v1/launches` | Every coin launched here, newest first, a page at a time. | No rate limit; the flood ceiling. |
| `GET /v1/launches/stream` | Every new launch the moment it is live, as server-sent events. | 3 open at once per address. |
| `POST /v1/launch/kit` | What you need to make your own launch-time buys. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/bundle-kit` | Where offered: your own launch call on the chain you paid on (Ethereum or BSC), to send first in your own bundle within the window. A v4 launch asks for its kit in the sponsored prepare instead (bundleKitHeld): each Ethereum or BSC chain it opens other than the one you pay on is held for your bundle. | 120 in ten minutes per address. |
| `POST /v1/launch/solana-self-call` | Your Solana launch call, when you send Solana’s launch from your own wallet (selfSolana). | 40 in ten minutes per address (90 for the landed-signature hint). |
| `POST /v1/launch/solana-cosigned` | Not available yet: answers 409. | None. |
| `POST /v1/launch/v4/sponsored/prepare` | Where v4 is switched on: a platform-paid v4 launch quoted, on the current v4 contract set, v4b (its Solana leg on the v4b bridge, whose open authorisation the creator's wallet signs as its hex text), with its v4 ticket; its launch buys are asked for here (the v4 launch kit). Payout wallets, one per chain type opened now: creatorEvm (always: the pay chain is an EVM chain) and creatorSvm when Solana opens now (required; a Solana chain only listed is asked when it is opened later). A v4 quote at /v1/launch/prepare answers 409 and names this route. | 120 in ten minutes per address, with prepare; none with an API key. |
| `POST /v1/launch/v4/sponsored/submit` | Where v4 is switched on: hands a paid v4 launch over, with every launch-buy and open signature, each checked against its ticket. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `GET /v1/launch/v4/sponsored/readiness` | Where v4 is switched on: which chains a platform-paid v4 launch can open now. | No rate limit; the flood ceiling. |
| `POST /v1/launch/v4/open` | Where v4 is switched on: the creator's own v4 leg of a queued launch, on its pay chain, priced now: `{ launchId, chain }` answers `{ pre, tx, owed, quote, via, takeoverAt }` for the creator's wallet to send before `takeoverAt`. Not an open-later: that is `POST /v1/launch/v4/open-later`. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/open-later` | Where the full-scope switch is on: opens another listed chain of a coin already launched, at any time, on the coin's own contract set (a v4a coin stays on v4a): `{ id, chain, step: "authorize" }` by the current creator on the home chain (with the payout wallet first when that chain type has none), then `{ id, chain, step: "open" }` by the authorised wallet; each answers the transactions for your own wallet. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/registered` | Where the full-scope switch is on: lists a mined v4 open from the chain itself: `{ chain, txHash }`, or `{ chain: "solana", coinId }`; answers `{ slug, coinId }`, 422 `NOT_LISTABLE` when the site will not list its name or symbol. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/tokenomics` | Where the full-scope switch is on: the same as `POST /tokenomics/v4/switch` (on, off, clear; the role push from the coin's home chain, EVM or Solana, and its read-back; for a Solana-home coin the open authorisation), built for your own wallet. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/handover` | Where the full-scope switch is on: the creator handover (`op: "transfer"` with `newEvm` and optional `newSvm`, or `"renounce"`) on an EVM-home coin's home chain, for the current creator's wallet. A Solana-home coin answers 409: its handover is signed on the coin page. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/sol-kit` | Where v4 is switched on: the Solana bundle kit of a platform-paid v4 launch. With your v4 ticket: the signed Solana launch while it is held for you; with your bundle (that launch first, then 1 to 4 signed buyer transactions, the last paying the Jito tip): checked and sent to Jito as one bundle. Refused by the relay: the launch goes alone, with its launch buys. | 300 in ten minutes per address with authorization, pay, submit and kit together, and 120 in ten minutes per IP; none with an API key. |

> **Note:** The launch endpoints under `/v1/launch` are described in [Launch API →](https://www.pangea.trade/app/docs/v2#launch-api)

### GET /coin/{id}

- Coin, GET: `https://www.pangea.trade/api/coin/{slug, launch id or token address}`

One coin as the site knows it, with no chain read: `{ slug, name, symbol, launchId, listedAt, launchChains, chains, degen, factoryGen, supplyMode, factoryV3, supply, tokenomics, tokenomicsKnown }`. `chains` is each chain’s `{ token, pool, quote }`; `quote` is the asset its pool pairs with: `"native"`, or `{ address, ticker, symbol, issuer, known }` for a coin paired with a tokenized stock on that chain, found by the stock token's address (`known: false` for an asset the stocks table does not list). A stock-paired EVM chain can show `pool: null` until its pool is listed. `factoryGen` is 1 or 2; `factoryV3: true` marks a factory v3 coin (absent before v3); `supplyMode` is factory v2’s, 0 (1B on each chain) or 1 (degen mode), and null on a factory v1 coin. `tokenomics` is the config the launch’s quote signed, or null for none; `tokenomicsKnown: false` means it could not be read, so null is then not “none”. Errors: `400` for an id that cannot be a coin’s, `404` for one not launched here.

| Field | What it is |
| --- | --- |
| `degen` | `true` for a coin launched in degen mode: 1B in all, an equal share minted and paired on each chain, every chain’s token reporting the 1B. [Degen mode →](https://www.pangea.trade/app/docs/v2#degen-mode) |
| `supply.total` | The launch’s whole supply, in whole tokens, as a string: 1B for degen mode (999,999,999 on three chains, whose equal shares are whole tokens), 1B a chain otherwise. |
| `supply.circulating` | What the chains minted and paired, in all: 1B in degen mode. The bridge reserve backs the other chains and is not extra supply, so it is never taken off circulating; `/coin/{id}/supply`’s `live` takes off what was burned and reads the bridge reserve as it is now. |
| `supply.reserve`, `supply.reserveChain` | The rest of a degen mode coin’s supply, held in Solana’s bridge reserve, and `solana`. `0` and null without one. |
| `supply.perChain` | What each chain minted and paired, by chain. |
| `supply.reportedPerChain` | What every chain’s `totalSupply()` reports in degen mode, the 1B; null otherwise, where each chain reports its own. |
| `supply.circulatingShareBps` | In degen mode, what one chain mints as a share of the total, in basis points: 2,000 on five chains. 10,000 where each chain holds a whole supply of its own; null where the supply figures are. |
| `supply.supplyModel` | `perChain` for 1B on each chain, `per-chain` for a v4 coin (1B per opened chain), `global` for one supply (degen mode, and launches before 2026-10-15, whose figures are null here: `/pools` reads them off the token). |
| `tokenomics.perChain` | Each chain’s split by bucket, in basis points: `{ burn, buyback, liquidity, creator, community }`, adding up to 10,000. |
| `tokenomics.vault`, `tokenomics.owner` | The coin’s vault on the EVM chains, the fee receiver its trading fees are paid to, and its owner, the creator, who receives the creator share. |
| `tokenomics.treasury`, `tokenomics.community` | The wallets the bought-and-held coins and the community share go to on the EVM chains; null when no chain uses that bucket. |
| `tokenomics.solana` | `{ account, owner, treasury, community }`: the coin’s tokenomics account on Solana and its wallets there; null without Solana. |
| `tokenomics.version` | `1` for the config above. `2` for tokenomics v2: `perChain` is then each chain’s `{ burn, buyHold, liquidity, holders, treasury, wallets, rewards }` (shares in basis points; `wallets` the payout wallets with theirs, `rewards` the holder rewards’ tokens, `native` for the chain’s own coin), with `vault`, `account` (Solana), `owner`, `ownerSol` and `holders` (who qualifies). Another value is a kind this API does not describe yet, and carries only `version` and `vault`. |

### GET /coin/{id}/supply

- Supply, GET: `https://www.pangea.trade/api/coin/{id}/supply`

The answer is `{ slug, name, symbol, launchId, launchChains, degen, supply, live, liveError? }`. `supply` is the same as on the coin. For a degen mode coin, `live` is read from the chains and kept a minute: `{ mode, total, reserve, circulating, burned, burnedUnread, perChain, burnedPerChain, reserveAddress, decimals, onSolana, chains }`, in base units as strings, where `total` is what the tokens report, `reserve` what Solana’s bridge reserve at `reserveAddress` holds now, and `circulating` the total less what was burned (never less the bridge reserve, which backs the other chains and is not extra supply); `perChain` is what is on each chain, Solana’s its own float with the bridge reserve apart. A figure the chains did not give is null. `live` is null for any other coin and with `?live=0`; `liveError: "busy"` means the shared budget of chain reads is spent for the minute.

### GET /coin/{id}/tokenomics

- Tokenomics, GET: `https://www.pangea.trade/api/coin/{id}/tokenomics`

The answer is `{ slug, name, symbol, launchId, launchChains, tokenomics, tokenomicsKnown, chains }`. `tokenomics` is the coin’s config, as on the coin. `chains` is each chain’s vault read from the chain, `{ tokenomics }`, or null with an `error` when that chain could not be read. A vault has its `split`, `buckets` (what waits in each, in the chain’s native unit), `spent`, and `totals`: `receivedWei`, `coinBurned`, `coinToTreasury` (bought and held), `liquidityAdded`, `gasRepaidWei`. A coin its launch’s config says has no tokenomics is read on no chain. `?live=0` answers the config alone. Each chain’s reading is shared with the coin page and kept a while: a vault five minutes, no vault 24 hours, a chain that could not be read half a minute. `404` while tokenomics is switched off on this deployment.

### GET /v1/launches

- Launches, GET: `https://www.pangea.trade/api/v1/launches?limit=25`

Every coin launched here, newest first: `{ launches, next, latest, readAt }`. Each launch is `{ id, slug, name, symbol, launchId, listedAt, launchedAt, launchChains, chains, coinId, v4, degen, factoryGen, supplyMode, factoryV3, supply, tokenomics, image, platform }`: `chains` each chain’s `{ token, pool, quote }` (`quote` as in `GET /coin/{id}`), `factoryV3` true for a factory v3 coin, `supply` is `{ total, circulating, reserve, reserveChain }`, `tokenomics` is `{ on: false }`, or `{ on: true, version, vault, buckets, chains }` naming the buckets any chain uses, or null while it is not known; `image` the coin’s logo on this site, or null until it is stored. For a v4 coin, `coinId` is its coin id and `v4` is `{ coinId, home, listed, open, notOpen, transports, role: { creatorEvm, creatorSvm, renounced } }`: its home chain, the chains it lists, which of them are open now, its transports and its creator role; each open chain carries `openedAt`, unix seconds. Both are null for any other coin. A coin is listed once it has landed and its pools are open. `id` is its cursor, `<listedAt>:<slug>`.

- `limit`: 1 to 100, 25 by default.
- `before`: the previous page’s `next`, for the page after it. `next` is null on the last page. To catch up, send the same `since` with each page.
- `since`: a cursor, for the coins listed after it, or unix seconds, for the coins listed in that second or later. A coin can be listed a little after one with a later time, so to catch up, ask from a minute or two before the newest you have and drop the ids you already hold.
- `latest`: the newest coin’s cursor. Open the stream with it as `since` and nothing is missed between the two.

The newest launches, then the page after:

```bash
curl -s "https://www.pangea.trade/api/v1/launches?limit=10"
curl -s "https://www.pangea.trade/api/v1/launches?limit=10&before=<next>"
```

### GET /v1/launches/stream

- Launches stream, GET: `https://www.pangea.trade/api/v1/launches/stream`

Every new launch as server-sent events, the moment it is live. Each is an `event: launch` with the launch, as `/v1/launches` lists it, as its data, and, for a launch newer than any sent before, its cursor as the event `id`. After the first catch-up comes one `event: ready` with `{ latest }`. To miss nothing across a reconnect, send the last id you saw as `Last-Event-ID` (an `EventSource` does this by itself) or as `?since=`: the launches listed after it come first, oldest first, up to 100 at a time. Without either, only launches from now on. The `ready` event carries the stream’s place as its id too, so a reconnect before any launch still resumes. A coin can become visible a little after one listed later, so the stream also looks two minutes back and sends such a coin with no event id, keeping your place. A resume sends those two minutes again, so a launch can come twice: drop the ones whose data’s id you have. When a v4 coin already sent opens another of its listed chains later, the stream sends an `event: chain_opened` for that same launch, never a new launch: its data is `{ key, coinId, chain, openTime, slug, id, launch }`, keyed by coin id, chain and open time, with no event id. Clients dedupe by coinId: a key you already hold is a repeat. A resume sends again each chain opened after your cursor’s time. A comment line comes every 15 seconds; the stream closes after 30 minutes, so reconnect with the last id. Send `Accept: text/event-stream`. At most 3 open per caller; it takes requests from programs and from the Pangea site, like the Launch API.

> **Note:** Server-sent events are the simplest realtime API: one long GET that any HTTP client can read, that resumes by itself, and that passes every proxy. For pools, trades and bridges as they happen, open the live WebSocket below.

Follow new launches with curl:

```bash
curl -N -H "Accept: text/event-stream" "https://www.pangea.trade/api/v1/launches/stream"
```

Follow new launches with EventSource (Node: npm install eventsource):

```js
import { EventSource } from "eventsource";

const stream = new EventSource("https://www.pangea.trade/api/v1/launches/stream");
stream.addEventListener("launch", (e) => {
  const coin = JSON.parse(e.data);
  console.log(coin.symbol, coin.degen ? "degen" : "", coin.chains);
});
// It reconnects by itself and sends the last id as Last-Event-ID, so nothing is missed.
```

### WebSocket /live

The socket the site’s own pages use, open to everyone and read-only: `wss://www.pangea.trade/live`. No key, any origin or none. You subscribe to topics and are pushed what changes: pool readings, trades, bridges and new coins. Everyone subscribed to a topic is sent the very same message, made once, so hold as many sockets as you need, up to 256 from one IP address.

It is a wake-up channel, not a second source of truth: every value on it is also the answer of a REST endpoint here. A client that misses something on the socket is right again after its next REST read, and the rules below say when one is needed.

| Topic | What it carries |
| --- | --- |
| `poolsdiff` | Log: the pool reading as small patches, about once a second while anything trades. See the pool reading, as patches, below. |
| `pools` | State: the pool reading of `GET /pools`. It is larger than one message may be, so it arrives as `reset` with `size`: follow `poolsdiff` instead. |
| `pools:<slug>` | State: one coin’s pools, the same shape with one launch in it, replaced each time. |
| `launches` | State: the coin list, `{ readAt, launches: [{ slug, name, symbol }] }`. A new coin appears here. |
| `trades`, `trades:<slug>` | Log: one event per trade, of every coin or of one. Cursor letter `s`. |
| `bridges`, `bridges:<slug>` | Log: one event per bridge transfer. Cursor letter `b`. |
| `health` | State: whether the feeds behind the socket are up. While `delayed` is true, poll REST. |
| `job:<launchId>` | State: a launch job’s status, as the launch status route answers it. It is pushed again on every step of every chain, as it happens: see A launch, step by step, below. |
| `relay:<requestId>` | The site’s own pages only: asked for from anywhere else, the socket is closed with `1008`. |
| `feed:<name>` | State: the whole answer of a site route, pushed when it changes (a trade, a bridge, a listing or a price move re-reads the routes that depend on it). Names: `alltime`, `buyback-burns`, `buyback-history`, `creator-rewards`, `creator-rewards-v4`, `holder-payouts`, `platform-funds`, `cto`, `stock-pairs`, `elysium-moves`, `swap-tape`, `usdp-reserves`, `explore-fees`, `release`; per coin: `holders.<slug>` and `cto-burns.<slug>`. An answer larger than one message arrives as `reset` with `size`: read the route. |
| `wallet:<view>.<address>` | State: one wallet’s public route answer, pushed when that wallet trades or bridges. Views: `overview`, `profile`, `fees`, `buy-lines`. An EVM address in lowercase, a Solana address as written. At most 4 on one socket. |

### A launch, step by step

- Subscribe to `job:<launchId>` (the launch id in lowercase) from the moment the fee is paid. Each message is the whole status, so replace what you hold.
- Per chain, `d.result.sponsoredLegs` while the launch runs (`d.result.legs` once it is finished) holds `{ state, step, at, ms?, tx?, why?, listed? }`.
- `step` is one of `paid` (the fee is confirmed and the chain is queued), `sending`, `sent` (`tx` is the open’s hash, or its signature on Solana), `confirmed`, `open` (the pool is live), `failed` (`why` says why) or `handover` (the creator opens that chain from their own wallet). Steps only move forward.
- `at` is when the step happened, in milliseconds since the epoch. `ms` is how long the chain took. `listed: true` means that chain is in the coin list.
- `d.result.coinId` and `d.result.coinListedAt` appear the moment the coin is listed, before the job is done. From then on `GET /coin/[id]` answers.
- The chain the fee was paid on opens first. Every other chain then opens at the same time.

### Messages

- You send JSON text of at most 4096 bytes: `{"op":"sub","topics":["trades","poolsdiff"],"cursor":"s:1200,p:1762150000000123"}`, `{"op":"unsub","topics":["trades"]}` or `{"op":"ping"}`. At most 24 topics on one socket. Anything else closes the socket with `1008`.
- The server first sends `{"t":"hello","v":1,"now":ms,"hbMs":15000,"maxTopics":24,"families":[...],"sources":{...}}`. `families` lists the topic families this server serves: do not ask for any other.
- A state topic sends `{"t":"pools","topic":"pools:abc","d":{...}}` (and likewise `launches`, `health`, `job`): replace what you hold. On subscribing you are sent the current value at once.
- A log topic sends `{"t":"trade","topic":"trades","seq":1201,"slug":"abc","d":{...}}` (and likewise `bridge`, `poolsdiff`): apply it only if its `seq` is above your cursor for that stream, then move the cursor to it.
- `{"t":"ready","topics":[...],"cursor":"s:1250"}`: the replay of those log topics is done and they are live; the cursor is where each stream now stands.
- `{"t":"reset","topics":[...],"why":"replay"}`: you are not subscribed to those topics. Read them over REST, then subscribe again with the cursor that read gives. `why` is `replay` (more than 1000 rows behind), `size`, `ahead`, `unavailable` or `busy` (wait a few seconds first).
- `{"t":"hb","now":ms}` comes every 15 seconds. If nothing at all arrives for 40 seconds, reconnect.
- `{"t":"bye","reason":"rotate","retryMs":2100}` comes before the server closes the socket: `rotate` (every socket is closed after about 30 minutes), `slow`, `shutdown` or `rate`. Reconnect after `retryMs`.

### Reconnecting without missing anything

- Keep one cursor per stream: the `seq` of the last trade (`s`), bridge (`b`) and pool patch (`p`) you applied, and subscribe with them, as `"cursor":"s:1200,b:77"`. You are sent every row after them, then `ready`, then live: no gap and no repeat. Without a cursor a log topic starts from now, and `ready` gives you the cursor to keep.
- When the socket closes without a `bye`, wait a random time between zero and a ceiling that starts at half a second and doubles up to 30 seconds, reconnect, and subscribe again with your cursors.
- Slow readers are cut off, not waited for. A socket holding 256 KiB unread is not written to; meanwhile states keep only their newest value, and past 500 queued events the socket is told `bye` with `slow`. Reconnect with your cursors and nothing is lost.
- Messages you send are counted per socket, a burst of 60 and then 10 a second, pings included. Past that the socket is told `bye` with `rate`.
- Compression (permessage-deflate) is offered, and most clients take it on their own. The messages are the same either way.

### The pool reading, as patches

- Read `https://www.pangea.trade/live/pools`: `{ gen, body }`, where `body` is a pool reading in the shape of `GET /pools`. Then subscribe to `poolsdiff` with `"cursor":"p:<gen>"`. It answers 60 reads a minute from one address: read it at the start and after a reset, not on a timer.
- Each `poolsdiff` event’s `d.base` is the gen it applies to, and its `seq` is the gen it makes. Apply it only on top of exactly the gen you hold. When `d.body` is present it is a whole new reading.
- Otherwise `d` is `{ base, top, set, order }`. `top` holds the reading’s top-level fields. Each entry of `set` is a launch by `slug`: whole as `v`, or its changed fields as `f` and its changed pools as `pools`, matched by `chain` (each whole as `v` or as changed fields `f`). `order`, when present, is the new order of the slugs.
- A patch on a gen you do not hold, a `reset` of `poolsdiff`, or a patch naming a launch or pool you do not have: read `https://www.pangea.trade/live/pools` again and subscribe with its gen.

Follow trades and pools over the live socket (Node 22 or later, or a browser):

```js
const LIVE = "wss://www.pangea.trade/live";
const BASE = "https://www.pangea.trade/live/pools";

let pools = null; // { gen, body }
let tradeCursor = null;
let attempt = 0; // reconnects since the last ready
const waits = {}; // topic -> how long to wait before asking for it again

function applyPatch(body, d) {
  const bySlug = new Map(body.launches.map((l) => [l.slug, l]));
  for (const e of d.set) {
    if (e.v) {
      bySlug.set(e.slug, e.v);
      continue;
    }
    const old = bySlug.get(e.slug);
    if (!old) return null;
    const l = { ...old, ...(e.f ?? {}) };
    if (e.pools) {
      l.pools = old.pools.slice();
      for (const pe of e.pools) {
        const i = l.pools.findIndex((p) => p.chain === pe.chain);
        if (i === -1) return null;
        l.pools[i] = pe.v ?? { ...l.pools[i], ...(pe.f ?? {}) };
      }
    }
    bySlug.set(e.slug, l);
  }
  const order = d.order ?? body.launches.map((l) => l.slug);
  return { ...d.top, launches: order.map((slug) => bySlug.get(slug)) };
}

async function readBase() {
  const res = await fetch(BASE);
  if (!res.ok) throw new Error(`GET /live/pools answered ${res.status}`); // 429 or 503: try again later
  pools = await res.json();
}

// Ask for one topic again later, waiting longer each time (2 s, doubling to 60 s).
function later(ws, topic) {
  waits[topic] = Math.min(60_000, (waits[topic] ?? 1_000) * 2);
  setTimeout(async () => {
    if (ws.readyState !== WebSocket.OPEN) return;
    try {
      if (topic === "poolsdiff" && !pools) await readBase();
      subscribe(ws, [topic]);
    } catch {
      later(ws, topic);
    }
  }, waits[topic]);
}

function subscribe(ws, topics) {
  const cursor = [];
  if (topics.includes("trades") && tradeCursor !== null) cursor.push(`s:${tradeCursor}`);
  if (topics.includes("poolsdiff")) cursor.push(`p:${pools.gen}`);
  ws.send(JSON.stringify({ op: "sub", topics, cursor: cursor.join(",") }));
}

async function connect() {
  try {
    if (!pools) await readBase();
  } catch {
    setTimeout(connect, Math.random() * Math.min(30_000, 500 * 2 ** attempt++));
    return;
  }
  const ws = new WebSocket(LIVE);
  let heard = Date.now();
  let retryMs = null; // from bye
  const watch = setInterval(() => Date.now() - heard > 40_000 && ws.close(), 5_000);
  ws.onopen = () => subscribe(ws, ["trades", "poolsdiff", "launches"]);
  ws.onmessage = (e) => {
    heard = Date.now();
    const m = JSON.parse(e.data);
    if (m.t === "trade" && (tradeCursor === null || m.seq > tradeCursor)) {
      tradeCursor = m.seq;
      console.log("trade", m.slug, m.d.side, m.d.valueUsd);
    } else if (m.t === "poolsdiff" && pools && m.seq > pools.gen) {
      const body = m.d.body ?? (m.d.base === pools.gen ? applyPatch(pools.body, m.d) : null);
      if (body) pools = { gen: m.seq, body };
      else {
        pools = null; // behind: start again from the base, on the same socket
        later(ws, "poolsdiff");
      }
    } else if (m.t === "ready") {
      attempt = 0;
      for (const t of m.topics) delete waits[t];
      const s = /s:([0-9]+)/.exec(m.cursor ?? "");
      if (s && tradeCursor === null) tradeCursor = Number(s[1]);
    } else if (m.t === "reset") {
      // Not subscribed to these any more. Keep the socket; ask again later, waiting longer each time.
      for (const topic of m.topics) {
        if (m.why === "replay" || m.why === "ahead") {
          if (topic === "trades") tradeCursor = null; // too far behind: fill the gap from GET /history
          if (topic === "poolsdiff") pools = null;
        }
        later(ws, topic);
      }
    } else if (m.t === "bye") {
      retryMs = m.retryMs;
    }
  };
  ws.onclose = () => {
    clearInterval(watch);
    setTimeout(connect, retryMs ?? Math.random() * Math.min(30_000, 500 * 2 ** attempt++));
  };
}

connect();
```

### GET /holders

- Holders, GET: `https://www.pangea.trade/api/holders`

Query: `token`, a slug, required; `chain`, optional, to answer for one chain. The answer is `{ slug, readAt, launchTotal, degen, supply, chains, all }`, where `degen` and `supply` are as on `/coin/[id]`, and each of `chains` is `{ chain, count, top10Pct, rows, complete, note?, error? }` and each row `{ chain, address, balance, pct, label }`, balances in whole tokens, at most 100 rows a chain. `all` combines the chains, `{ rows, count, partial, top10Pct }`, and is left out when `chain` is given. `label` names a known holder such as the pool or the bridge escrow. An answer is reused for a minute. Errors: `400` without a token, `404` for an unknown launch or a chain it is not on.

### GET /bridge/balance

- Balance, GET: `https://www.pangea.trade/api/bridge/balance`

Query: `token` (slug), `chain` and `address`, all required, and `asset=native` to read the chain’s native asset instead of the token. The answer is `{ slug, chain, address, balance, decimals, reason?, reserve? }`: `balance` in base units as a string, or `null` with a `reason` when the chain could not be read, never a false zero. On Solana the token balance is the sum of the owner’s token accounts for the mint. For the native asset, `reserve` is the gas the app keeps back for a trade. Errors: `400` for a missing field or an address of the wrong kind, `404` for an unknown launch or chain.

### POST /bridge/quote, in full

Beyond the fields in [For integrators](https://www.pangea.trade/app/docs/v2#integrators), the body may carry `slippageBps` for the fast lane, 50 unless given and 1000 at most, and `smartWallet`, true when the sender is a contract wallet, and `gapToleranceBps`, a factory v3 token's tolerance under the gap estimate for the minimum credited (100 unless given, 2000 at most). The answer’s `routes` holds two routes, `canonical` and `fast`, and a route that cannot be used says why in `unavailable`. Besides the fields listed there, a route may carry:

| Field | What it is |
| --- | --- |
| `label` | `Standard` or `Fast`. |
| `fastFee`, `relayerFee`, `relayer` | On the fast route: the fast lane’s fee, the relayer’s price and who it is, in base units. |
| `minReceived` | On the fast route: the least the order lets the recipient be paid. |
| `approval` | On the fast route from an EVM chain: the approval to send first, `{ to, data, value, chainId }`, or null when none is needed. |
| `solanaFast` | On the fast route out of Solana: `{ relayerFee, minReceived, nativeFeeCap }`, for `/bridge/solana-fast-tx` (`nativeFeeCap`: the most the order signs for LayerZero, also the route's `nativeFee`). |
| `fillDropFee`, `layerZeroFee`, `fillDropNote` | A fast order into Solana: the Fill drop (the instant fill's account rent, paid once to Pangea's delivery) and LayerZero's part of `nativeFee`. |
| `routePayment` | From an EVM chain into a Solana route that is not open: the one-time setup payment, `{ to, data, value, chainId }`, to send before the transfer. |
| `setupNote`, `messageFeeLater` | Out of Solana with a route to open: the setup is paid first and LayerZero’s fee is priced after it. |
| `feeIsMaximum` | `nativeFee` is the most the Solana transaction will sign; LayerZero charges what its workers quote. |
| `note` | A sentence about the route, for showing to a person. |
| `gap` | A factory v3 token's standard route: the price-gap estimate. `state` (`estimate`, `free`, `held` or `unavailable`), `burned` (what the destination withholds, in `decimals`), `minCredited` and `toleranceBps` (the least it may credit, and the tolerance it was set with), `minCreditedSource` (out of Solana, in Solana's decimals), `cover` (the gas cover, when it may apply), `source` (a Solana source's price record), `delivery` (into Solana: `{ amount, note }`, our delivery key's cost withheld from the arrival, in `decimals`; its `ata`, when present, is the part that is a new wallet's token-account rent on its first arrival) and `poke` (an EVM source's price record to send first), and `warning` (when set: a sentence to show before the send -- e.g. an arrival too small to pay a new wallet's token-account rent, which waits on Solana until that account exists; web-r4-b I-5). What arrives is `amountOut` less `burned` and, into Solana, less `delivery.amount`. |
| `keysOnlyDeliveryFee`, `keysOnlyDeliveryNote` | A factory v3 send into Ethereum: the part of `nativeFee` that pays Pangea's delivery there (in the source's wei; LayerZero's executor does not deliver there). Paid once, inside the same transaction. |
| `dstDeliveryFee` | Out of Solana toward Ethereum (a factory v3 token): the part of `nativeFee` that pays Pangea's delivery there, in lamports. |
| `deliveryFee`, `deliveryPayee` | The site's instant-delivery fee inside `nativeFee` (when on), and the payee the send's refund address names for it. |
| `needsRecord` | A factory v3 send out of an EVM chain whose price log needs records (`GapNeedRecords`): `{ pokes, poke }`. `call` is null and `gap.poke` carries the same `{ to, data, value, gas }`: send it from the sender's wallet about `pokes` times, one mined before the next, then ask the quote again. |
| `split` | A factory v3 send above what one transfer carries now: `{ text, count, minutes, parts, maxSendLD, cosign, cosignTx?, cosigner?, perPartFee, totalFeeEstimate, perPartLines, partGap? }`. `call` is null. Every part is its own transfer and pays the full per-send fees: `perPartFee` (the first part's quote, itemised in `perPartLines`) and `totalFeeEstimate` (`perPartFee` x `count`). From an EVM chain, `cosignTx` is the whole send, unsigned, for `POST /api/cosign` (`{ chain: "evm", chainId, tx, gapToleranceBps? }`): its answer is the co-signed send with its fee (`nativeFee`, `tx.value`) and the minimum it guarantees at the co-signed price (`minCreditedLD`); a 503 means send in parts. From Solana, `cosign` is true only when the co-sign service is set up and `cosigner` names the GapGlobal co-signer: `POST /api/bridge/solana-tx` with `cosign: true` (and `minCredited`) builds the unsigned co-signed send (stage `"cosign"`), `POST /api/cosign` (`{ chain: "solana", tx, gapToleranceBps? }`) co-signs it and answers `minCreditedLD` and `expectedMinCreditedLD`; the wallet signs last. Before it signs, compare the answer's message bytes with the ones you built: they must be identical, with your wallet's signature slot still empty and only the co-signer's filled (the proxy never sees what you built, so a client of `POST /api/cosign` makes this check itself, as the site's own panel does). Any 503, `ABOVE_COSIGN_MAX` or mismatch means send in parts. |

Further errors: `409` when the token has not launched on the destination, `424` when a Solana recipient could not be checked, and `424` with `retry: true` when pricing took longer than 45 seconds.

### POST /bridge/solana-tx

- Out of Solana, POST: `https://www.pangea.trade/api/bridge/solana-tx`

The body you POST:

```json
{
  "slug": "<launch slug>",
  "to": "base",
  "amount": "1000000000000",
  "sender": "<Solana wallet>",
  "recipient": "0x<EVM address>"
}
```

Builds the unsigned transaction that sends the token from Solana to an EVM chain, for the sender to sign. For a token with the price-gap fee, `minCredited` is required: the least the destination may credit after it, in Solana base units (the bridge quote’s `gap.minCreditedSource`); without it the request is refused (400). `after` and `afterValidUntil` are given only on a second call after a setup step. The answer carries the request’s `from`, `to`, `amount` and `symbol` and one of three stages:

| Stage | What to do |
| --- | --- |
| `send` | Sign `transaction`, a base64 versioned transaction, and send it. `received` is what arrives, `nativeFee` LayerZero’s fee and `nativeFeeCap` the most it will sign. |
| `together` | The same, with the route’s opening in the same transaction. `setupFee` is the opening’s part of `totalFee`. |
| `setup` | The opening alone. Sign and send it, then call again with `after` set to its signature for the send. |
| `record` | A factory v3 token whose Solana price log holds no recent price: sign `transaction` (`record_price`, its signature fee only), send it, then call again with `recorded` set to its signature. Until it has landed and a slot has passed the answer is `202` with `{ pending: true, stage: "recording" }`; then it is the send. |

On Solana a factory v3 arrival is delivered with `650,000` compute units. A charged v3 receive there runs only through LayerZero's executor (its own execute flow) or Pangea's delivery keys (R1′), so nobody can land it at a price of their choosing; an escaped, refunded or unpooled arrival stays open to anyone.

Every stage carries `blockhash` and `lastValidBlockHeight`: sign within about a minute. While a setup step is still settling the answer is `202` with `{ pending: true, stage: "waiting" }`; ask again. Errors: `400` for a bad field or a signature that is not this transfer’s setup, `404` for an unknown launch, `409` when the setup ran out or failed, the route is unusable or the wallet is short of SOL, `424` when the build takes longer than usual, with `retry: true`, and `424` after 50 seconds.

### POST /bridge/solana-fast-tx

- Fast out of Solana, POST: `https://www.pangea.trade/api/bridge/solana-fast-tx`

The fast lane out of Solana: one unsigned transaction holding Pangea’s fast-bridge order and the OFT send that carries it. Body: `{ slug, to, recipient, amount, relayerFee, minReceived, sender }`, with `relayerFee` and `minReceived` from the quote’s `solanaFast`. The answer is `{ stage: "send", transaction, orderId, amount, arriving, fastFee, relayerFee, nativeFee, nativeFeeCap, blockhash, lastValidBlockHeight }`. Errors: `409` when the fast lane is off for this launch or route, `422` when the order could not be built, nothing having been sent.

### POST /bridge/route-open

- Open a Solana route, POST: `https://www.pangea.trade/api/bridge/route-open`

The second step of the first transfer from an EVM chain into a Solana route: once the quote’s `routePayment` has been sent, this hands its transaction to Pangea’s delivery service, which checks the payment on chain and opens the route, or refunds it. Body: `{ slug, from, txHash, sender }`, `from` being the EVM chain the payment was made on. With `Accept: text/event-stream` it streams `{ state, reason?, refundTx? }` as the route is opened; otherwise it answers once. `state` is one of `verifying`, `paid`, `opening`, `open`, `refunded`, `refund-owed`, `review` and `refused`. Send the transfer once it is `open`. Errors: `404` for a launch not on Solana, `424` when routes cannot be opened from this deployment.

### POST /bridge/arrival

- Arrival stream, POST: `https://www.pangea.trade/api/bridge/arrival`

Body: `{ txHash }`, the send’s transaction hash or Solana signature. A stream of `{ state, route, guid, dstTx?, by? }`, `state` being `in-flight` and then `delivered`, with the delivery’s transaction in `dstTx`. It is fed by Pangea’s delivery service, which remembers a transfer for 30 minutes. Errors: `400` for a malformed hash, `424` when the deployment does not serve arrivals; LayerZero Scan answers the same question.

### POST /bridge/refund

- Held fast orders, POST: `https://www.pangea.trade/api/bridge/refund`

Body: `{ chain, orderId }`, the destination chain and the fast order’s id. The answer is `{ held: false }` or `{ held: true, guid, amount, srcEid, call }`, where `call` is the `refund(guid)` transaction, `{ to, data, value, chainId }`, with LayerZero’s fee for the trip back as its value. It looks back 5,000 blocks. Errors: `400` for a malformed id or a chain without a fast bridge.

### POST /bridge/orders

- Report a fast order, POST: `https://www.pangea.trade/api/bridge/orders`

Body: `{ from, hash }`, the chain a fast order was posted on and its transaction. The board reads the order from the receipt and remembers whose price it was sent at, which is how a relayer that does not fill is benched. The answer is `202` with `{ recorded: false, pending: true }` before the receipt exists, then `{ recorded, relayer, why?, orderId, to, srcBridge }`. Only orders priced from this deployment’s board are recorded; `why` says why not.

### POST /chain/tx

- Transaction stream, POST: `https://www.pangea.trade/api/chain/tx`

Body: `{ chain, hash }`. One event when the transaction is in a block: `{ state: "mined", success, block }` on an EVM chain, `{ state: "mined", success }` on Solana, or `{ state: "unseen" }` if it never appears, after up to 10 minutes on an EVM chain and 5 on Solana.

### Trading: POST /trade/quote and POST /trade/solana-tx

These are the trade card’s own and may change; read a quote for a price, and build trades from the pools directly where you can. `/trade/quote` takes `{ slug, side, chain, amount, user?, solanaUser?, slippageBps?, mode?, intent?, quoteAsset? }`: `side` is `buy` or `sell`; `chain` is the chain the wallet trades on, always a chain name (`auto` is refused, and a `fromChain` other than `chain` is refused too); `amount` is the pay asset’s base units for a buy, the token’s for a sell. `slippageBps` is the slippage, how far below the quote the trade may fill: a whole number of basis points from 1 to 9900 (0.01% to 99%), 100 unless given. `mode` is `"direct"` (the default: the coin’s own pool on that chain) or `"cheapest"` (also price the other chains’ pools and offer the route when it is better after every fee); `intent` is `"preview"` (a price, nothing to sign) or `"execute"`; `quoteAsset: "stock"` pays or receives the stock token itself on a stock-paired EVM chain. A direct trade goes through the coin’s own pool (`details.router: "pool"`), with Jupiter as a fallback on Solana. The answer is `{ mode, preview?, chain, fromChain, crossChain, cheapestNote, unroutable, details, steps, cheapest? }`; with `mode: "cheapest"`, `cheapest` is `{ offered, reason, plan, steps, comparison: { directOut, routeOut, gainBps, cost }, tried }` and, when it is offered, `directDetails` is the direct trade beside it. Cheapest mode has its own brakes (20 a minute per wallet, 2 at once per caller); past them it answers as a direct quote with the reason in `cheapestNote`. Errors: `400`, `404`, `422` with the reason when no route was found, and `424` when a chain or the router could not be reached or took too long (with `retry: true` where trying again can help). Never a 5xx: a proxy in front may replace a 5xx body.

`/trade/solana-tx` takes `{ instructions, addressLookupTableAddresses?, payer }`, a swap step’s Solana instructions as the quote gives them, at most 24 instructions and 8 lookup tables, and answers `{ transaction, blockhash, lastValidBlockHeight }`: an unsigned versioned transaction. It does not inspect what the instructions do, so read them, or let your wallet simulate them, before you sign.

## Launch API

A program can launch a token the way the launch page does: quote it, get the transaction that pays for it, hand the paid launch over, and follow it until every chain of the launch is live. The endpoints are under `https://www.pangea.trade/api/v1/launch`. Prepare, submit, status and the SOL payment are the launch page’s own steps, handed to the same code, with the same checks.

- **No API key needed, and no rate limit for normal use.** Only a flood ceiling far above any real integration applies, per IP address. A partner key takes you out of it: see API keys and limits below.
- **Programs, and the Pangea site.** A program sends no `Origin` header and is served. A browser on another website always sends one, and is refused with `403`, so another site cannot spend the reads a quote makes on five chains through its visitors. The status read is a read and takes any origin. The event stream holds a connection open, so it takes the same rule as the rest and needs `Accept: text/event-stream` (406 without it); a page's no-cors or cross-site request is refused with 403, and each caller may hold one stream per launch.
- **The platform signs the snapshot, never the API.** Every chain launches with one price snapshot, signed by Pangea’s worker after it has found your payment on chain. No endpoint here signs one or sends anything for you.
- Amounts are base units as decimal strings: wei on an EVM chain, lamports on Solana, and nine decimals for the token.

### The flow

- `POST /v1/launch/prepare` quotes the launch and returns a `ticket`: the launch’s every parameter and its fee, signed by Pangea. Every later step takes it.
- `POST /v1/launch/authorization` only when another wallet sends your own chain’s launch: the permission your creator wallet signs for it. [Launch with your own wallet →](https://www.pangea.trade/app/docs/v2#self-launch)
- `POST /v1/launch/pay` returns the one transaction that pays the fee. You send it from the payer’s wallet; Pangea sends nothing for you. Pay within the quote’s 10 minutes.
- `POST /v1/launch/submit` hands the launch over once the payment is mined, within 24 hours of the quote, with the payer’s signature (`payerAuth`) and any creator signatures the quote asked for. Pangea checks the payment on chain and queues the launch.
- `GET /v1/launch/status` and `GET /v1/launch/events` follow it: the price snapshot signed, then each chain as it goes live. Paid on an EVM chain, that chain goes first, then Solana, then the other EVM chains; paid in SOL, Solana goes first.

| Endpoint | What it is for | Limit per caller |
| --- | --- | --- |
| `POST /v1/launch/prepare` | The quote, and the ticket. | 120 in ten minutes per address; none with an API key. |
| `POST /v1/launch/authorization` | The permission your creator wallet signs for another launching wallet. | 300 in ten minutes per address, with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/pay` | The transaction that pays the fee. | 300 in ten minutes per address, with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/submit` | Hands the paid launch to Pangea’s launcher. | 300 in ten minutes per address, with authorization, pay, submit and kit together; none with an API key. |
| `GET /v1/launch/status` | Where the launch stands. | None. |
| `GET /v1/launch/events` | The same, as server-sent events. | 3 open at once per address, 100 per API key. |
| `POST /v1/launch/kit` | What you need to make your own launch-time buys. | 300 in ten minutes per address, with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/solana-self-call` | Your Solana launch call, with selfSolana. | 40 in ten minutes per address (90 for the landed-signature hint). |
| `POST /v1/launch/solana-cosigned` | Not available yet. | None. |

Every endpoint above also counts toward the flood ceiling: 1200 requests a minute from one IP address, all /v1 endpoints together.

### API keys and limits

There is no rate limit for normal use and no key is needed. Three ceilings stop floods, per IP address and counted together on every server: **1200 requests a minute** across every `/v1` endpoint, and **120 quotes in ten minutes** from `POST /v1/launch/prepare`, which reads five chains every time, and **300 in ten minutes** of authorization, pay, submit and kit together, which read the chains on every call. Each event stream holds a connection open, so at most 3 are open at once per address.

An integration that needs more, or wants a guarantee it will never meet a ceiling, can ask for a partner key: write to us through the support chat on the site with your project’s name and the IP addresses you call from (optional). Send it as the `X-Pangea-Key` header on every request. With a key there is no ceiling, at least 100 event streams may be open at once, and it can be limited to your addresses. Keep it on your server: never put it in a page or an app.

| Header | What it says |
| --- | --- |
| `X-Pangea-Key` | Your partner key, if you have one (sent by you). Leave it out to call without one. |
| `X-RateLimit-Limit` | The ceiling that applies to this request, in requests per window, or `unlimited`. |
| `X-RateLimit-Remaining` | Requests left in the window, or `unlimited`. |
| `X-RateLimit-Reset` | Seconds until the window starts again (`0` without a ceiling). |
| `Retry-After` | On a `429`: seconds to wait. |
| `X-Pangea-Key-Status` | `valid` when your key was accepted; `unverified` when it could not be checked just then and the request was served as one without a key. |

Every answer carries the three `X-RateLimit` headers. A key that is not valid or has been revoked is refused with `401` (never served as if it were absent), and a key used from an address it is not allowed from with `403`.

The whole flow, with fetch:

```js
const API = "https://www.pangea.trade/api/v1/launch";

const post = (path, body) =>
  fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then(async (r) => ({ status: r.status, body: await r.json() }));
const get = (path) => fetch(API + path).then(async (r) => ({ status: r.status, body: await r.json() }));

// 1. The quote, and the ticket: pay, submit and the launch kit all take it.
const prepared = await post("/prepare", {
  name: "Example",
  symbol: "EXMPL",
  metadataURI: "ipfs://<the CID of your metadata document>",
  feeBps: 100,
  creatorEvm: "0x<your EVM creator wallet>",
  creatorSol: "<your Solana creator wallet>",
  payChain: "base",
});
if (prepared.status !== 200) throw new Error(prepared.body.error);
const { ticket, launchId, payerProof } = prepared.body;
console.log(`fee: ${prepared.body.fee.payment.send} wei, about $${prepared.body.fee.feeUsd}`);

// 2. The transaction that pays it: send it from pay.body.from, the payer, and wait for it to be mined.
const pay = await post("/pay", { ticket });
if (pay.status !== 200) throw new Error(pay.body.error);
const { chainId, to, data, value } = pay.body.tx;
console.log({ chainId, to, data, value, sendFrom: pay.body.from, before: pay.body.validUntil });
const payer = pay.body.from;

// The payer signs that it pays for this ticket: personal_sign of payerProof.message by payerProof.signer.
const payerAuth = "0x<that signature>";

// 3. Once it is mined, hand the launch over. A 402 means the payment is not found yet: ask again.
// If prepared.body.creatorProofs lists a creator wallet, add its signature as creatorEvmAuth / creatorSolAuth.
const submitted = await post("/submit", { ticket, payer, payerAuth });
if (submitted.status !== 200) throw new Error(submitted.body.error);

// 4. Where it stands: pending, running, done or failed, and each chain as it goes live.
const status = await get(`/status?launchId=${launchId}`);
console.log(status.body.state, Object.keys(status.body.result?.live ?? {}));
```

### Errors

An error is `{ "error": "..." }` with a status, and the message says what to do. A status read or an event stream for an id the queue does not have is `404` with `{ "launchId": "0x...", "state": "unknown" }` instead.

| Status | What it means |
| --- | --- |
| `400` | The request is wrong: a field missing or malformed, a ticket that does not verify or is over a day old. |
| `402` | Submit only: the payment is not in order. Not mined yet, or short: the answer carries `paid`, `quoted`, `collector` and `chain`. Ask again once it is. |
| `401` | An `X-Pangea-Key` that is not valid, or has been revoked. |
| `403` | A browser on another website, or an API key used from an address it is not allowed from. |
| `404` | A launch id the queue does not have. |
| `409` | The request conflicts with where the launch is: the quote has run out, a creator wallet or fee receiver to confirm (with `needsConfirm`), a pair or option that cannot be had, a kit for a launch not submitted yet, a permission for a launch that needs none, a payment for a launch already paid and queued (do not pay twice: a second payment toward the same launch id is kept as fee), and the co-signed Solana launch, which is not built yet. |
| `410` | A SOL payment asked for with too little of the quote’s life left for it to land. Prepare again. |
| `424` | Something Pangea depends on could not be read or is not configured: a chain, the launch queue, the launcher’s configuration. Launches closed is a 424 too. Nothing was queued; try again. |
| `429` | Over a ceiling, or too many event streams open. The message says how many seconds to wait, and `Retry-After` says it too. |

A body that is too large is `413`, one sent compressed `415`, and one that does not arrive in time `408`.

### POST /v1/launch/prepare

- Prepare, POST: `https://www.pangea.trade/api/v1/launch/prepare`

The body you POST:

```json
{
  "name": "Example",
  "symbol": "EXMPL",
  "metadataURI": "ipfs://<the CID of your metadata document>",
  "feeBps": 100,
  "creatorEvm": "0x<your EVM creator wallet>",
  "creatorSol": "<your Solana creator wallet>",
  "payChain": "base"
}
```

| Field | What it is |
| --- | --- |
| `name`, `symbol` | The token’s name and its ticker (no space): any length, not empty. Neither has to be unique. |
| `metadataURI` | `ipfs://` and the content hash of your token’s metadata document, nothing else. It is part of every EVM token’s address. The launch page pins the document for you, but its upload endpoint answers the Pangea site only: a program pins its own. |
| `feeBps` | The trading fee, 30 to 500 basis points. Set once, for good. |
| `feeBpsByChain` | Optional, factory v2: a different trading fee per chain, as `{"base": 30, "bsc": 250}`, each 30 to 500. A chain it does not name takes `feeBps`. Each chain's fee is signed into its own leg and set once, for good. The quote then answers `legFees`, every chain's fee, in place of `feeBps`. |
| `creatorEvm`, `creatorSol` | Your creator wallets, on the EVM chains and on Solana: the coin’s creator, and where your share of the trading fee is paid unless `payeeEvm` / `payeeSol` or `tokenomics` says otherwise. A contract, or a Solana account that is not a plain wallet, is refused with `409` and `needsConfirm` unless `confirmFeeWallet` says it is yours. |
| `payChain` | `ethereum`, `base`, `robinhood`, `bsc` or `solana`; `robinhood` when left out. You pay once, on this chain, in its native asset. It must be one of the launch’s chains. |
| `chains` | Optional: the chains the launch is on, any two to five of `ethereum`, `base`, `robinhood`, `bsc` and `solana`, at least one of them an EVM chain. All five when left out. |
| `supplyMode` | `0`, the default and, on factory v3, the only one: 1B on each chain, all of it pooled. A v3 launch refuses `1` (factory v2’s 1B in all) with `409`. |
| `degen` | Optional: the launch form’s degen switch. On factory v3, `true` opens every chain at the live minimum market cap ($600 a chain today) when `marketCapUsd` is left out; the supply is 1B a chain either way. `false`, or leaving it out, is the standard opening. |
| `marketCapUsd` | Optional: the opening market cap of the whole launch, every chain together, whole US dollars: 3000 to 500000 times the number of chains (rounded down to a whole dollar a chain), or 3000 to 500000 with degen; 3000 a chain when left out. Every chain opens at the one price this over the whole supply; the quote's v2.marketCapUsd is the total it opens at. On factory v3 (no supplyMode 1): 600 (the live minimum, 600 to start, at most 3000) to 500000 a chain times the chains; with degen true and no marketCapUsd, the minimum a chain times the chains; otherwise 3000 a chain when left out. |
| `payeeEvm`, `payeeSol` | Optional: the fee receiver on the EVM chains and on Solana, when the trading fees should go somewhere other than your creator wallets. Empty is your creator wallet. An EVM receiver with code on one of the launch’s chains is refused with `409`, `needsConfirm: "payeeEvm"` and `contracts` until `confirmPayee` lists it: a list of the receivers you confirm are your own contracts. |
| `payer` | Optional: the wallet that pays the fee; your creator wallet on the pay chain when left out. Only that wallet’s own payment counts, and it signs for the launch before it is handed over (`payerAuth` at submit). |
| `sameAddress` | `true` for one token address on every EVM chain of the launch, ending in `6ea`. |
| `tokenomicsV2` | Optional: custom tokenomics, the form the launch page sends (tokenomics v2.3). `{ minHolderBps, selectMode, selectCount, legs, confirmContracts? }`: `minHolderBps` is the least a holder must hold to be paid, 1 to 10,000 basis points of supply (10 to 10,000 when an EVM chain's vault is on an earlier tokenomics factory); `selectMode` is `"all"`, `"top"` or `"random"`, with `selectCount` (0 for all, otherwise at least 1); `legs` names each chain of the launch, each `{ burnBps, buyHoldBps, liquidityBps, holdersBps, treasury?, wallets: [{ address, bps }], rewards?: [{ token, bps }], burnTargets?: [{ token, bps }], burnTargetsBps? }`. A leg’s shares add up to exactly 10,000. Up to 10 payout wallets; `treasury` when buy and hold is used; `rewards` (1 to 5 tokens adding up to 10,000) when holders is used. A reward token is `"native"` (the pool’s pair asset: ETH or BNB, SOL, or the stock token on a stock-paired chain), `"coin"` (ETH or BNB, paid as WETH or WBNB, on a stock-paired EVM chain), or any token’s address (a mint on Solana). On a stock-paired chain the stock cannot also be named as a token reward or a burn target; a Solana leg paired with an asset pays holders in that asset only; Solana takes no burn targets. A wallet with code is refused with `409` and `needsConfirm: "tokenomicsV2"` until `confirmContracts` lists it. Not with `tokenomics`, `payeeEvm`, `payeeSol`, `burnFees`, `selfLaunch` or `selfSolana`. [Custom tokenomics on v3 →](https://www.pangea.trade/app/docs/v3#v3-tokenomics) |
| `tokenomics` | Optional, the older form, still accepted: spend your fee share automatically. `{ split, perChain?, treasury?, community?, treasurySol?, communitySol?, confirmContracts? }`: `split` is five shares in basis points adding up to 10,000, in the order burn, buy and hold, add to liquidity, creator, community; `perChain` a split for each chain by name, naming only the launch’s chains (`split` covers any it leaves out). Nothing else may be in it. The wallets are needed when their share is used, the Solana ones on a launch with Solana. With it, leave `payeeEvm`, `payeeSol`, `burnFees` and `selfLaunch` out: the coin’s vault is the fee receiver. [Coin tokenomics →](https://www.pangea.trade/app/docs/v2#coin-tokenomics) |
| `devBuy` | Not available at the moment: any amount but 0 is refused with `409` (“Dev buy temporarily unavailable”) at prepare, pay and submit. `GET /api/launch/dev-buy` says whether it is offered. To buy at launch, use `buys`. |
| `buys`, `buysCap` | Optional: launch buys, first buys made inside the launch transaction on an EVM chain. `buys` is a list of `{ eid, recipient, amount }`: the chain by its LayerZero id (`30101` Ethereum, `30184` Base, `30102` BSC, `30416` Robinhood Chain), the wallet that gets the coin, and what it spends, in the smallest unit of the pool’s pair asset (wei, or the stock token’s units). Up to 10 on one chain and 32 in all, each chain within its gas limit; they run in the order listed. `buysCap` is required with them: the most they may spend, as a string. Their gas is in the launch fee. The money is deposited beforehand by `creatorEvm` into that chain’s launch-buys contract (`deposit(launchId)` with the native coin, or `approve` and `depositToken` for a stock token), and what is not spent is taken back with `reclaim(launchId, asset)`. The answer’s `launchBuys` is `{ launchId, payer, buysCap, perChain: { <chain>: { contract, factory, eid, entries, total, asset, decimals } } }`. [Launch buys →](https://www.pangea.trade/app/docs/v3#v3-launch-buys) |
| `pairs`, `pairSol` | Optional: what a chain’s pool pairs against when not its native coin. `pairs` is `{ "<chain>": "<asset address>" }` for the EVM chains, an asset that chain’s pair registry lists, inside its price band (empty or the zero address is the native coin). `pairSol` is a Solana mint on the splitter’s allowlist (empty or wrapped SOL is SOL). The stock tokens each chain offers: `GET /api/launch/stocks`. |
| `stockAck` | With a stock pair: `{ "<chain>": "<stock token address>" }` for every chain prepare warns about. Until each warned chain is named, prepare answers `409` with `needsAck: "stocks"` and the `warnings`. It is signed into the ticket. |
| `feesInNative` | `true` or `false`: “Receive fees in native” for the chains paired with a token (any pair but the chain’s own coin), `true` when left out. Stored with the launch, not signed: the creator can switch it later on their profile. [Stock fees →](https://www.pangea.trade/app/docs/v3#v3-stock-fees) |
| `openRoutes` | `true` to pay, in the same payment, for Solana’s four bridge routes to be opened right after the launch. The launch must include Solana; not with `selfSolana`. Its price is a line of the quote, `fee.openRoutes`, and `GET /api/launch/open-routes` gives it beforehand. |
| `launchHook` | Optional: the fee hook the launch names, one the factories allow, the same address on every EVM chain. Each factory’s own when left out. |
| `requote` | The ticket of a launch already paid whose quote is past its 24 hours: a new quote for the same launch id, pay chain and payer. The answer carries `requote`. |
| `selfLaunch` | `true` to send your pay chain’s launch yourself. EVM pay chains only; for Solana’s launch, see `selfSolana`. [Launch with your own wallet →](https://www.pangea.trade/app/docs/v2#self-launch) |
| `selfSolana` | Optional: send Solana’s launch from your own Solana wallet, `creatorSol`, on any pay chain. Only where the deployment offers it; elsewhere prepare refuses it with `409`. `{ nonce, launchId, oftStore, mint, mintNonce }`, made in your browser from your Solana wallet’s signature of the seed message; the seed itself is never sent. Not with `openRoutes`, and `payeeSol` must be empty or the burn address. [Launch with your own wallet →](https://www.pangea.trade/app/docs/v2#self-launch) |
| `selfLaunchFrom` | With `selfLaunch`: the wallet that sends it, when it is not `creatorEvm`. It then needs your creator wallet’s permission. |
| `confirmFeeWallet` | `{ evm: true }` or `{ sol: true }`: yes, that creator wallet is mine. |
| `burnFees` | `true` to burn your creator fees: the fee receiver becomes `0x000000000000000000000000000000000000dEaD` on the EVM chains and `1nc1nerator11111111111111111111111111111111` on Solana, for good. Leave `payeeEvm` and `payeeSol` empty with it. Your creator wallets stay yours, with the coin’s page and socials. |
| `launchTotal` | Fixed at 1B times the number of chains; anything else is refused. |
| `moon`, `poolPercent`, `poolBps`, `salt`, `salts` | Refused with `400`: moon mode is removed, and the token addresses’ salts are Pangea’s to pick. Any other field prepare does not know is ignored. |

The answer carries `launchId`, `ticket`, `creator`, `fee` (`feeUsd`, `costUsd`, `ttlSeconds` and `payment`: `chain`, `asset`, `wei`, `devBuy`, `send`, what the wallet sends in all, `collector` and, on an EVM chain, `tx`), `prediction.perChain`, each EVM chain’s token address, every one ending in `6ea`, `selfLaunch` when asked for, and `remaining`, the steps left in plain sentences.

It also carries `factoryGen`, `2` for a factory v2 launch, `degen`, and with it `v2`: `v2.degen`, the supply (circulating, bridge reserve and total), what each pool holds at the open, the fee receivers, and `legOrder`, the order the chains are sent in and by whom. `payer` is the wallet whose payment counts. `payerProof` is `{ kind, signer, message }`: the text the payer signs, `personal_sign` on an EVM chain or `signMessage` on Solana, posted to submit as `payerAuth`. `creatorProofs` lists the same for each creator wallet that is not the payer’s signing wallet, each `{ side, kind, signer, message }`, posted as `creatorEvmAuth` and `creatorSolAuth`; it is empty when the payer is your creator wallet on every side that needs one.

### POST /v1/launch/authorization

- Authorization, POST: `https://www.pangea.trade/api/v1/launch/authorization`

Body: `{ ticket }`, for a launch quoted with `selfLaunch` and a `selfLaunchFrom` other than the creator wallet. The answer is `{ launchId, chain, signer, launcher, typedData, digest, next }`: `typedData` is what `signer`, your creator wallet, signs with `eth_signTypedData_v4`, and `digest` the hash the factory recovers it from, for a signer that signs hashes. It is the same typed data prepare returned as `selfLaunch.authorization`. Post the signature to submit as `creatorAuth`. `409` for a launch that needs no permission.

### POST /v1/launch/pay

- Pay, POST: `https://www.pangea.trade/api/v1/launch/pay`

Body: `{ ticket, payer }`. Every amount comes from the ticket, never from the request. On an EVM chain, `payer` is not needed and the answer is `{ launchId, chain, tx, fee, devBuy, from, validUntil, next }`: `tx` is `{ chainId, to, data, value }`, a call of `pay(launchId)`, or `payWithDevBuy(launchId, devBuy)`, on that chain’s LaunchFeeCollector, with the fee and the dev buy as its value. Send it from `from`, the quote’s `payer`: only that wallet’s own payment counts toward the launch. `validUntil` is the end of the quote, in unix seconds. A dev buy on a chain paired with an asset is refused with `409`: send the quote’s own `fee.payment.tx` instead.

Paid in SOL, `payer` is the paying wallet in base58, and the answer is an unsigned versioned transaction naming it as the only signer: `{ launchId, programId, lamports, feeLamports, devBuyLamports, payer, transaction, feeAccount, blockhash, lastValidBlockHeight, rentLamports, quoteAccountRentLamports }`, `transaction` in base64. The payment’s own account needs `rentLamports` on top. Sign and send it within about a minute: it is good for as long as its blockhash. The paying wallet must be the quote’s `payer`.

Pay the launch fee in SOL with @solana/web3.js, then submit:

```ts
import { Connection, Keypair, VersionedTransaction } from "@solana/web3.js";

const API = "https://www.pangea.trade/api/v1/launch";
const ticket = process.env.LAUNCH_TICKET; // from prepare, with payChain solana
const payer = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.SOLANA_KEYPAIR)));
const connection = new Connection(process.env.SOLANA_RPC, "confirmed");
const post = (path, body) =>
  fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then(async (r) => ({ status: r.status, body: await r.json() }));

// The unsigned payment, naming your wallet as its only signer.
const pay = await post("/pay", { ticket, payer: payer.publicKey.toBase58() });
if (pay.status !== 200) throw new Error(pay.body.error);
const tx = VersionedTransaction.deserialize(Buffer.from(pay.body.transaction, "base64"));
console.log(`paying ${pay.body.lamports} lamports, and ${pay.body.rentLamports} of rent`);

tx.sign([payer]);
const signature = await connection.sendRawTransaction(tx.serialize());
const confirmed = await connection.confirmTransaction(
  { signature, blockhash: pay.body.blockhash, lastValidBlockHeight: pay.body.lastValidBlockHeight },
  "confirmed",
);
if (confirmed.value.err) throw new Error("the payment failed");

// The payer signs prepare's payerProof.message (signMessage, ed25519): base58 or 0x-hex.
const payerAuth = process.env.PAYER_AUTH;

// A fee paid in SOL is found by who paid it: name the payer.
const submitted = await post("/submit", { ticket, payer: payer.publicKey.toBase58(), payerAuth });
if (submitted.status !== 200) throw new Error(submitted.body.error);
console.log(submitted.body.state);
```

### POST /v1/launch/submit

- Submit, POST: `https://www.pangea.trade/api/v1/launch/submit`

Body: `{ ticket, payer, payerAuth, creatorEvmAuth, creatorSolAuth, creatorAuth }`, once the payment is mined. `payer` is required for a fee paid in SOL, because that payment is found by who paid it; on an EVM chain it is optional and only recorded. `payerAuth` is required: the payer’s signature over prepare’s `payerProof.message`. `creatorEvmAuth` and `creatorSolAuth` are each listed creator wallet’s signature over its message in prepare’s `creatorProofs`. Without them nothing is queued, and submit answers with the messages to sign. `creatorAuth` is needed when another wallet sends your own chain’s launch. Submit never refuses a paid launch over it: without one that recovers to your creator wallet for that wallet, the launch is queued all the same, the answer carries `selfLaunch: "dropped"` and a `warning`, and Pangea sends that chain like the others. So check it first: `POST /v1/launch/authorization` with `{ ticket, creatorAuth }` answers `check: { ok }`. The fee is read on chain, never taken from the request. The answer is `{ launchId, state, queued: true, enqueuedAt, creator, paid, next, selfLaunch?, warning? }` with `state: "pending"`. Submitting again queues nothing and answers `{ launchId, state, queued: false, message, result }`.

### GET /v1/launch/status

- Status, GET: `https://www.pangea.trade/api/v1/launch/status?launchId=0x...`

The answer is `{ launchId, state, message, lastError, attempts, enqueuedAt, result, selfChain, degen, tokenomics }`, each field only when it is set. `degen` is whether the launch is in degen mode, and `tokenomics` whether it has tokenomics; the coin’s config is on `/coin/[id]` once it is live. `state` is `pending`, `running`, `done` or `failed`. In `result`: `signed.at` when the price snapshot is signed, `live`, each chain as it goes live, `{ token, at, tx }`, and `selfLaunch`, your own launch call, for a launch that has one. `selfChain` names the chain you send yourself. `signed.at` and each chain’s `at` are ISO 8601 times; the call’s `takeoverAt` is in unix seconds.

### GET /v1/launch/events

- Events, GET: `https://www.pangea.trade/api/v1/launch/events?launchId=0x...`

The status as it changes, as server-sent events: each an `event: <name>` line and a `data: <json>` line. Everything so far is sent the moment the stream opens, so a late listener misses nothing. A comment line, starting with a colon, comes every 15 seconds to keep the connection open. The stream closes after `end`, after 30 minutes, or when you disconnect; open it again to keep following. An id the queue does not have is `404`: open the stream after submit has queued the launch.

| Event | Data | When |
| --- | --- | --- |
| `state` | `{ state }` | First, and whenever the state changes. |
| `signed` | `{ at }` | The price snapshot is signed. |
| `pool-live` | `{ chain, token, at, tx }` | That chain’s launch has landed and its pool is open. `tx` only when known. |
| `self-launch` | `{ chain, call }` | Your own launch call is out, once the price snapshot is signed. `call` is the same as the status’s `result.selfLaunch`. |
| `self-launched` | `{ chain, by }` | `by` is `creator` or `platform`: who sent your chain’s launch. |
| `end` | `{ state }` | `done` or `failed`. The stream closes. |

At most 3 open streams per caller, and a few hundred in all on each server; past that it is `429` with `Retry-After`, and polling the status read does the same job. A page on the Pangea site can open it with an `EventSource`; a program reads it with a streaming fetch.

Read the event stream with fetch:

```js
const API = "https://www.pangea.trade/api/v1/launch";

// One frame: its event name and its JSON. Comment lines, such as the keep-alive, are skipped.
function parseFrame(frame) {
  let event = "message";
  const data = [];
  for (const line of frame.split("\n")) {
    if (line.startsWith(":")) continue;
    if (line.startsWith("event: ")) event = line.slice(7);
    else if (line.startsWith("data: ")) data.push(line.slice(6));
  }
  return data.length ? { event, data: JSON.parse(data.join("\n")) } : null;
}

async function* launchEvents(launchId) {
  const res = await fetch(`${API}/events?launchId=${launchId}`, { headers: { Accept: "text/event-stream" } });
  if (!res.ok) throw new Error(`the stream answered ${res.status}`);
  const reader = res.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  for (;;) {
    const { value, done } = await reader.read();
    if (done) return;
    buffer += decoder.decode(value, { stream: true });
    let end;
    while ((end = buffer.indexOf("\n\n")) >= 0) {
      const frame = parseFrame(buffer.slice(0, end));
      buffer = buffer.slice(end + 2);
      if (frame) yield frame;
    }
  }
}

for await (const { event, data } of launchEvents("0x<your launch id>")) {
  if (event === "pool-live") console.log(`${data.chain} is open: token ${data.token}`);
  if (event === "self-launch") console.log(`your launch call for ${data.chain} is out`, data.call);
  if (event === "self-launched") console.log(`${data.chain} was sent by the ${data.by}`);
  if (event === "end") break;
}
```

### POST /v1/launch/kit

- Launch kit, POST: `https://www.pangea.trade/api/v1/launch/kit`

Body: `{ ticket, buys, deadline }`, once the launch is submitted; before, it is `409`. Each EVM chain’s token address and pool, ready calldata for your own buys, your launch call if you send one, and Solana’s pool once Solana has launched. The fields are under [Launch-time buys →](https://www.pangea.trade/app/docs/v2#self-launch)

### POST /v1/launch/solana-self-call

- Solana self-launch call, POST: `https://www.pangea.trade/api/v1/launch/solana-self-call`

For a launch prepared with `selfSolana`. Body: `{ launchId, ticket }`, the ticket the launch is queued under. Answers `{ launchId, call, nonce, blockhash, lastValidBlockHeight }`: `call` is the template of your `launch_self` transaction (format `pangea-solana-self-call/1`), every byte that does not depend on your seed; `nonce` is the nonce your Solana wallet signs the seed message for again. `409` until it is out (once the price snapshot is signed and, paid on an EVM chain, that chain has launched), and once Solana has landed; `202` with `retryAfterS` while a fresh call is signed. With `{ launchId, signature }` instead, you tell it the transaction your launch went out under: `{ kept }`, or `202` while it has not landed.

### POST /v1/launch/solana-cosigned

Not available. It always answers `409`, with `enabled: false` and the reason: a Solana launch transaction signed by Pangea’s launcher, that you could submit yourself through Jito with your own buys after it, needs a change to the splitter program that is still in progress.

## Launch with your own wallet

A launch is on its chains: all five, Solana, Ethereum, Base, BSC and Robinhood Chain, unless it was launched on selected chains. Paid on an EVM chain, that chain launches first, where the fee is checked; then Solana, from Pangea’s launcher, whose addresses derive from a secret seed that stays unpublished until it lands, or from your own Solana wallet if you choose that (below); then the other EVM chains. Paid in SOL, Solana launches first and the EVM chains follow, the EVM chains always sent by Pangea.

With `selfLaunch: true`, you send one EVM chain’s launch yourself: the chain you pay on, and only that one. Pangea’s launchers send the others, as for every launch.

### Why nobody can launch your chain before you

Each EVM factory has two ways in. `launch(LaunchParams p, LaunchTerms terms, Snapshot snap, bytes signature)` takes three senders only: your creator wallet (`msg.sender == p.creator`), the factory’s admin, and a launcher the admin has listed. Anyone else reverts with `NotLauncher`. A creator may send only the pay chain’s launch; any other chain reverts with `NotThePayChain`.

`launchAuthorized(LaunchParams p, LaunchTerms terms, Snapshot snap, bytes signature, bytes creatorAuth)` takes any sender that holds your creator wallet’s permission naming that sender. The permission is EIP-712 typed data, `LaunchAuth(bytes32 launchId, address launcher)`, in the domain `{ name: "PangeaLaunchAuth", version: "2" }`, with no chain id and no contract. The factory recovers it against the sender, so a permission for one wallet is useless to anyone else who sees it; a signature that recovers to anyone but your creator wallet reverts with `BadCreatorAuth`.

So nobody else can send your chain’s launch, and it cannot be front-run. The price snapshot it carries exists only after your fee is paid: Pangea’s worker signs it after checking your payment on chain, and the API never signs one.

> **Note:** A creator wallet that is a contract, such as a Safe or a smart account, gives its permission through EIP-1271: the factory hands the same digest to its `isValidSignature`, and anything but the magic value reverts with `ContractCreatorRefused`. An EIP-7702 delegated wallet counts as one with code: its delegate’s isValidSignature decides and its plain signature is never recovered, so a delegate without EIP-1271 cannot give the permission. A launch with tokenomics cannot be sent from your own wallet.

### The fee

The same as for any launch, quoted by prepare and paid once. With `selfLaunch`, the pay chain must be an EVM chain, and it is the chain you launch. That chain’s launch reverts with `LaunchUnpaid` unless the quote’s payer has paid the fee for it, and your launch transaction releases the fee to the platform in the same transaction (`LaunchFeeCollector.onLaunch`). Sending your chain yourself does not change the fee.

### Your time to send it

- Your call is handed out once the price snapshot is signed. Your chain goes first: Pangea sends Solana and the other EVM chains only once it has landed.
- The option is on only where the deployment turns it on; elsewhere prepare refuses selfLaunch. Your time is counted from when your call is handed out: 90 seconds unless the deployment sets `SELF_LAUNCH_GRACE_S` to another, held between 90 and 120. It always ends at least 300 seconds before the snapshot’s deadline, and if the launch is retried it never ends later than the first time it was given. Its end is `takeoverAt` in your call, in unix seconds.
- If your launch is not there by then, Pangea’s launcher sends it, with the same snapshot and the same parameters. Whichever of the two lands second reverts with `AlreadyLaunched`, and its sender loses only that transaction’s gas.
- If your chain’s launch cannot land, nothing else is sent: no other chain launches before the pay chain.
- The launch page stops offering the send 30 seconds before the takeover, since a transaction sent later may land after Pangea’s.

### Step by step

- `POST /v1/launch/prepare` with `selfLaunch: true`, `payChain` set to your chain, `creatorEvm` set to your creator wallet and, if another wallet will send the launch, `selfLaunchFrom` set to it. The answer’s `selfLaunch` is `{ chain, chainId, from, creator, factory, token, gasLimit, value, graceSeconds, authorization }`.
- If `authorization` is set, sign it with the creator wallet (`eth_signTypedData_v4`). You can also get it from `POST /v1/launch/authorization` with `{ ticket }`, which answers `{ launchId, chain, signer, launcher, typedData, digest }`. Sign before you pay. The typed data names its own domain type (`EIP712Domain: [{ name: "name", type: "string" }, { name: "version", type: "string" }]` in its `types`), so pass it whole: through `eth_signTypedData_v4` or viem’s `signTypedData`, a wallet hashes the domain the contract checks.
- `POST /v1/launch/pay` with `{ ticket }` answers `{ tx: { chainId, to, data, value }, from, validUntil }`. Send `tx` on your chain; with a dev buy, from `from`.
- `POST /v1/launch/submit` with `{ ticket, payer, payerAuth, creatorAuth }` once it is mined, with the payer’s signature as for any launch. `creatorAuth` is the permission, required when the launching wallet is not the creator wallet.
- Watch `GET /v1/launch/events?launchId=` or poll `GET /v1/launch/status?launchId=`. Once the price snapshot is signed, the status’s `result.selfLaunch` and the `self-launch` event carry your call: `{ chain, chainId, launchId, from, to, data, value, gas, token, deadline, takeoverAt }`, where `to` is the factory and `data` the encoded `launch`, or `launchAuthorized` with your permission, carrying the signed snapshot.
- Send exactly that transaction, from `from`, before `takeoverAt`. Then `result.selfLaunch.by` says `creator` or `platform`; the status’s top-level `selfChain` names your chain throughout.

Sign the creator wallet’s permission for another wallet, with viem:

```ts
import { privateKeyToAccount } from "viem/accounts";

const API = "https://www.pangea.trade/api/v1/launch";
const ticket = process.env.LAUNCH_TICKET; // from prepare, with selfLaunch and selfLaunchFrom
// The CREATOR WALLET signs: the creatorEvm you named. It lets the launching wallet send this launch.
const creatorWallet = privateKeyToAccount(process.env.CREATOR_WALLET_KEY);
const post = (path, body) =>
  fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then(async (r) => ({ status: r.status, body: await r.json() }));

const auth = await post("/authorization", { ticket });
if (auth.status !== 200) throw new Error(auth.body.error);
const { signer, launcher, typedData } = auth.body;
if (signer.toLowerCase() !== creatorWallet.address.toLowerCase()) throw new Error(`sign with ${signer}`);

// One signature for every EVM chain: the domain has no chain id and no contract.
const creatorAuth = await creatorWallet.signTypedData(typedData);

// Through eth_signTypedData_v4 yourself, pass it whole: it already names its domain's type (EIP712Domain).
const forWallet = typedData;
console.log("eth_signTypedData_v4 params:", [signer, JSON.stringify(forWallet)]);
console.log(`${launcher} may now launch ${auth.body.launchId}, and nobody else may use this`);

// Pay as usual, then hand it over with the payer's signature and the permission.
const submitted = await post("/submit", { ticket, payer: "0x<the wallet that paid>", payerAuth: "0x<its signature>", creatorAuth });
if (submitted.status !== 200) throw new Error(submitted.body.error);
```

Send your own chain’s launch, with viem:

```ts
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, bsc, mainnet, robinhood } from "viem/chains";

const API = "https://www.pangea.trade/api/v1/launch";
const CHAINS = { 1: mainnet, 8453: base, 56: bsc, 4663: robinhood };
const launchId = "0x<your launch id>";

// The wallet that launches: your creator wallet, or the selfLaunchFrom you named.
const account = privateKeyToAccount(process.env.LAUNCHER_KEY);

const status = await fetch(`${API}/status?launchId=${launchId}`).then((r) => r.json());
const call = status.result?.selfLaunch;
if (!call?.data) throw new Error("not out yet: it is handed out once the price snapshot is signed");
if (call.by) throw new Error(`already sent, by the ${call.by}`);
if (Date.now() / 1000 >= call.takeoverAt) throw new Error("too late: Pangea is sending it, with the same snapshot");
if (call.from.toLowerCase() !== account.address.toLowerCase()) throw new Error(`send it from ${call.from}`);

const chain = CHAINS[call.chainId];
const transport = http(process.env.RPC_URL);
const { maxFeePerGas, maxPriorityFeePerGas } = await createPublicClient({ chain, transport }).estimateFeesPerGas();
// What the wallet must hold: the whole gas limit at the max fee, and the value. Unused gas is refunded.
const mustHold = BigInt(call.gas) * maxFeePerGas + BigInt(call.value);
console.log(`hold at least ${mustHold} wei on ${chain.name}`);

const wallet = createWalletClient({ account, chain, transport });
const hash = await wallet.sendTransaction({
  to: call.to, // the factory
  data: call.data, // launch, or launchAuthorized with the permission, and the signed snapshot
  value: BigInt(call.value), // 1,000,000 wei: the opening buy
  gas: BigInt(call.gas),
  maxFeePerGas,
  maxPriorityFeePerGas,
});
console.log(`sent ${hash}; the event stream says self-launched when it lands`);
```

The examples sign with a private key to stay short. Any wallet that can sign typed data, and send a transaction with this data, value and gas limit, will do.

| Chain | Chain id | Factory | Fee collector | Universal Router |
| --- | --- | --- | --- | --- |
| Ethereum | 1 | `0xf7052a4E38e241bc66bfAe6eB27D10B2A42cacB4` | `0x79d9a01B6ADB4938761e7D4fb29a802ea709F0Ec` | `0x23617e59A5925b2A4Bf75d73ff6711cD0b29De85` |
| Base | 8453 | `0xb405b396b607C3482A3d301b1cFF57A7de75325a` | `0x7cb08218648433bbC9671f885E15954756717BCa` | `0xd6145b2D3F379919E8CdEda7B97e37c4b2Ca9c40` |
| BSC | 56 | `0x92858095Cadf9f9d60dC17226f7FF15Be4b69e45` | `0xcDB037B5B4b58e3C89d632463656D889123a7c04` | `0xDc264714F68d84CF29BC605589405E78bDBE7C9f` |
| Robinhood Chain | 4663 | `0x9Cf5832474CBB09D2E06FF20460aff4303fAf72A` | `0xcDB037B5B4b58e3C89d632463656D889123a7c04` | `0x204FAca1764B154221e35c0d20aBb3c525710498` |

- Solana splitter program: `Hb5uTppRbNxc3RjVRqTWqfdU3E3GiZyGYKccxYf9tUej`

### Gas

Your call comes with a gas limit of `11,797,500`: a launch’s measured 8,250,000 gas, with 10% and then 30% of room. A launch uses up to about 8.25 million gas and the rest is refunded, but to send it your wallet has to hold the whole gas limit at its max fee per gas, plus the value.

> **Note:** Factory v3, every launch since 2026-11-02: the call goes to that chain’s factory v3 ([Factories →](https://www.pangea.trade/app/docs/v3#v3-factories)), is handed to you once Solana’s launch has landed, and comes with prepare’s own `selfLaunch.gasLimit`: `14,300,000` (a v3 launch’s 10,000,000 with the same 10% and 30% of room), more with launch buys on that chain, at most the chain’s cap. Fund from that figure, or from `call.gas`.

The value is `1,000,000` wei: the opening buy, the pool’s first trade, made in the launch only when the launch has no dev buy. Send it as given: zero would skip the opening buy (and the token page could not tell the launch transaction's buys apart), and any other value reverts.

- **What your wallet must hold.** At least `11,797,500 × max fee per gas + 1,000,000 wei`, in ETH on Ethereum, Base and Robinhood Chain, in BNB on BSC. On Base, add its L1 data fee, which is charged on top of gas × price: a few cents is ample. The figure above is a floor: the call's `gas` is the node's estimate from your wallet with a 30% margin, never less than it, so fund from `call.gas` once the call is out.
- **What it costs you.** About `8,250,000 × the gas price paid`, plus the value. At 1 gwei that is 0.00825 of the chain’s coin. Read the chain’s gas price before you send.

### Solana: launch it from your own Solana wallet

You can also send Solana’s launch yourself, from your Solana creator wallet, on any pay chain. Its Solana addresses then come from a seed only your wallet can make, so Pangea never holds it and nobody else can send Solana’s launch. It is a separate choice from `selfLaunch`: you can tick either, or both when you pay on an EVM chain.

- **Choosing it.** On the launch page, with Solana among the launch’s chains, turn on **Launch Solana from my own wallet**. Your Solana wallet signs one message, the seed message; nothing is sent. Your browser makes the seed from that signature, finds a mint address ending in 6ea, and sends prepare only what the quote needs (`selfSolana`); the seed never leaves your browser.
- **The order.** The pay chain goes first, because your fee is checked there. Paid on an EVM chain, that chain launches (from your own EVM wallet too, with `selfLaunch`); then Solana, from your Solana wallet; then Pangea’s launchers send the other EVM chains. Paid in SOL, Solana goes first from your wallet, and Pangea sends every EVM chain once it has landed.
- **Sending it.** Once the price snapshot is signed, the launch page shows **Your launch on Solana** with a **Send your Solana launch** button. Connect the Solana creator wallet the quote named; no other wallet can send it. The wallet signs twice: the seed message again, so the page can rebuild the seed, and then one transaction, `launch_self`, which it sends. It is a v0 transaction through a shared lookup table, so your wallet must support those.
- **Your time.** The price the launch carries is good only for a while, counted down on the panel. With less than 60 seconds left the button stops offering the send and a fresh price is signed for you, so keep the page open and send it when the button is back. You have 24 hours from when your first call is handed out.
- **If you don’t send it.** Unlike your EVM chain, there is no takeover: Pangea’s launcher cannot send Solana’s launch for you, because it does not have the seed. Until you send it, nothing after Solana launches. After 24 hours no fresh price is signed and the launch stops there: paid in SOL, nothing has launched; paid on an EVM chain, that chain is already live and its fee settled, and the other chains wait for Pangea to look at it. Contact support.
- **What it costs you.** Your Solana wallet pays the launch’s rent, about 0.028 SOL (Pangea’s fee no longer includes it), and the transaction’s fee, priority fee included. Hold a little more SOL than that before you send.
- **Its fees.** A Solana launch you send yourself pays its trading fees to your Solana creator wallet, or burns them: another Solana fee receiver is refused. Its four Solana bridge routes are opened by their first bridgers, so it cannot be combined with paying for them to be opened at launch.

From a script: prepare with `selfSolana`, pay and submit as usual, then ask `POST /v1/launch/solana-self-call` with `{ launchId, ticket }` until it hands over your call, build `launch_self` from it with your seed, and sign and send it from your Solana creator wallet. The helper the example imports, [pangea-solana-self-launch.mjs](https://www.pangea.trade/sdk/pangea-solana-self-launch.mjs), is the launch page’s own code for it in one file, safe in a browser and in Node. Download it beside your script and install what it imports: `npm i @solana/web3.js viem tweetnacl`.

Launch Solana from your own Solana wallet, from a script:

```ts
import { Connection, Keypair } from "@solana/web3.js";
import nacl from "tweetnacl";
// Pangea's script helper, one file: download https://www.pangea.trade/sdk/pangea-solana-self-launch.mjs beside this script.
// It imports these, so install them: npm i @solana/web3.js viem tweetnacl
import { selfSolanaPrepareFields, seedMessageBytes, selfSolanaLaunchTx } from "./pangea-solana-self-launch.mjs";

const SITE = "https://www.pangea.trade";
const API = SITE + "/api/v1/launch";
// Your Solana creator wallet: it signs the seed message, and it sends and pays for Solana's launch.
const creator = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.SOLANA_KEYPAIR)));
const sign = (message) => nacl.sign.detached(message, creator.secretKey);
const connection = new Connection(process.env.SOLANA_RPC, "confirmed");
const post = (path, body) =>
  fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then(async (r) => ({ status: r.status, body: await r.json() }));
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// 0. Offered here? And the program ids your launch's Solana addresses derive under.
const gen = await fetch(SITE + "/api/launch/factory-gen").then((r) => r.json());
if (!gen.selfSolana) throw new Error("this deployment does not offer the Solana self-launch");

// 1. selfSolana: sign the seed message for a fresh nonce and grind a mint ending in 6ea (a few seconds).
// Only the launch id, the OFT store and the mint go to prepare: the seed never leaves this script.
const selfSolana = await selfSolanaPrepareFields({ sign, programs: gen.solanaPrograms });
const prepared = await post("/prepare", {
  name: "Example",
  symbol: "EXMPL",
  metadataURI: "ipfs://<the CID of your metadata document>",
  feeBps: 100,
  creatorEvm: "0x<your EVM creator wallet>",
  creatorSol: creator.publicKey.toBase58(),
  payChain: "base",
  selfSolana,
});
if (prepared.status !== 200) throw new Error(prepared.body.error);
const { ticket, launchId } = prepared.body;

// 2. Pay and submit, as in the whole flow above (or in SOL, with payChain solana).

// 3. Wait for Solana's turn: the call is out once the price snapshot is signed (the signed event) and,
// paid on an EVM chain, that chain has launched. 409 "not ready to send yet" means wait; 202 means a fresh
// call is being signed. Ask at most every 15 seconds: the call has its own limit per address.
let answer;
for (;;) {
  const r = await post("/solana-self-call", { launchId, ticket });
  if (r.status === 200) { answer = r.body; break; }
  if (r.status === 202) { await sleep((r.body.retryAfterS ?? 5) * 1000); continue; }
  if (r.status === 409 && /not ready/.test(r.body.error)) { await sleep(15_000); continue; }
  throw new Error(r.body.error);
}

// 4. Sign the seed message again (for answer.nonce, the same nonce), build launch_self, sign it and send it.
// Send it well before answer.call.deadline: with less than a minute left, ask again for a fresh call.
const { transaction, size } = await selfSolanaLaunchTx({ signature: sign(seedMessageBytes(answer.nonce)), answer, mint: selfSolana.mint });
transaction.sign([creator]);
const signature = await connection.sendRawTransaction(transaction.serialize());
console.log(`sent ${signature}, ${size} bytes`);
const confirmed = await connection.confirmTransaction(
  { signature, blockhash: answer.blockhash, lastValidBlockHeight: answer.lastValidBlockHeight },
  "confirmed",
);
if (confirmed.value.err) throw new Error("the launch failed: ask for a fresh call and send again");

// 5. Optional: tell Pangea which transaction it was, so the launch moves on sooner.
await post("/solana-self-call", { launchId, signature });

// 6. Follow the event stream: pool-live for solana, then the other chains as Pangea sends them.
```

### Launch-time buys

Pangea sends nobody’s buys. The launch kit gives you what you need to make your own, from your own wallets, and leaves the sending to you.

`POST /v1/launch/kit` with `{ ticket, buys, deadline }` answers once the launch is paid and submitted; before, it is `409`. `buys` is optional: at most 20 of `{ chain, amountIn, minOut }`, EVM chains only, `amountIn` in the native coin’s wei and `minOut` in the token’s base units, both positive whole numbers as strings. `deadline` is optional too: unix seconds between 60 seconds and 1 hour from now, 1 hour when left out.

| Field | What it is |
| --- | --- |
| `launchId`, `state` | The launch, and where it stands in the queue. |
| `evm[]` | Each EVM chain: `chain`, `chainId`, `token`, `quote`, `factory`, `poolKey`, `poolId`, `router`, the Universal Router, and `live`, whether its pool is open. |
| `poolKey` | `{ currency0, currency1, fee: 0, tickSpacing: 60, hooks }`, with the native coin as the zero address and `hooks` the factory’s fee hook. `poolId` is `keccak256(abi.encode(poolKey))`. |
| `buys[]` | `{ chain, chainId, to, data, value, minOut, deadline }`: a call of the Universal Router’s `execute` with one `V4_SWAP`, an exact-in single swap then `SETTLE_ALL` and `TAKE_ALL`. The tokens go to the wallet that sends it. `value` is your amountIn. |
| `validUntil` | The deadline every buy above carries, in unix seconds. Ask again for later ones. |
| `selfLaunch` | `{ chain, call }` for a launch you send yourself: `call` is your launch call once it is out, and null until then. |
| `solana` | `{ available: false, reason }` until Solana has launched, because its addresses are secret until then. Then `{ available: true, venue, programId, pool, mint, tokenDecimals, swap }`: the pool is on Meteora DAMM v2, program `cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG`, and `swap` says how to swap on it. |
| `solanaCosignedLaunch` | `{ enabled: false }` with the reason. Not available yet. |

Buy calldata is built for pools paired with the native coin only; a pool paired with an asset is refused with `409`. `minOut` is required: the pool does not exist before the launch, so there is no quote to take it from, and a buy that would get less reverts. Work it out from the opening price the quote gives, less the trading fee, your own buy’s price impact and the slippage you accept.

Ask for the launch kit and send a buy once its pool is open, with viem:

```ts
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, bsc, mainnet, robinhood } from "viem/chains";

const API = "https://www.pangea.trade/api/v1/launch";
const CHAINS = { 1: mainnet, 8453: base, 56: bsc, 4663: robinhood };

// The ticket prepare gave you, and a wallet to buy from: any wallet, and it receives the tokens.
const ticket = process.env.LAUNCH_TICKET;
const buyer = privateKeyToAccount(process.env.BUYER_KEY);

const res = await fetch(`${API}/kit`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  // 0.01 ETH on Base. At the opening price ($3,000 for a chain's 1B tokens) and ETH at $3,000 that is
  // about 10,000,000 tokens before the pool's fee and the price moving; ask for at least 9,000,000 (9 decimals).
  // Work minOut out from your own prices: it is the floor below which the buy reverts.
  body: JSON.stringify({ ticket, buys: [{ chain: "base", amountIn: "10000000000000000", minOut: "9000000000000000" }] }),
});
const kit = await res.json();
if (!res.ok) throw new Error(kit.error);

const [buy] = kit.buys;
const pool = kit.evm.find((c) => c.chain === buy.chain);
console.log(`pool ${pool.poolId}, token ${pool.token}, buy valid until ${kit.validUntil}`);
// A buy works only once its pool is open: wait for pool-live on the event stream, or ask again.
if (!pool.live) throw new Error(`the ${buy.chain} pool is not open yet`);

const wallet = createWalletClient({ account: buyer, chain: CHAINS[buy.chainId], transport: http(process.env.RPC_URL) });
const hash = await wallet.sendTransaction({ to: buy.to, data: buy.data, value: BigInt(buy.value) });
console.log("sent", hash);
```

### Making your buys land with your launch

- **Ethereum.** Sign your launch transaction and each buy, each buy from its own wallet with its own nonce and an explicit gas limit, since an estimate fails while the pool does not exist. Send them to a private relay such as Flashbots in one submission for one target block, your launch first. Nobody sees them before that block.
- **BSC.** The same, through a private submission to 48 Club or bloXroute: all of them for one block, your launch first.
- **Base and Robinhood Chain.** There is no public private relay. Send your launch, watch the event stream for `pool-live` on that chain, then send your buys. Others can buy in the same block.
- **Solana.** Pangea’s launcher sends Solana’s launch. Once it has landed, the kit gives its pool and mint, and you swap on Meteora like anyone. A launcher-signed Solana launch that you could submit through Jito with your buys after it is not available: `POST /v1/launch/solana-cosigned` answers `409`.

> **Note:** Every buy that lands in a chain’s launch block, yours or anyone’s, from any wallet, is shown publicly on the token’s page under “Bought in the launch block”: the launch block only, as the trade recorder read it, so buys in later blocks -- on Base and Robinhood Chain, where you buy after the pool opens, usually the next block or two -- are not listed there. Buys inside the launch transaction itself are shown as “In the launch tx”. Solana is not covered: its launch has no recorded slot.

### Risks

- Without a private relay, a buy can be front-run in the same block. `minOut` is what protects you: below it, the buy reverts.
- A signed snapshot expires: its deadline is never more than 1 hour away, and normally 15 minutes. After it your launch call stops working, whoever sends it.
- If your launch transaction lands after Pangea’s takeover, it reverts and you lose only its gas. The launch is made either way, with the same price and settings.
- A buy sent before its pool is open reverts, and costs its gas.

## Bundle it yourself

Pangea's launcher launches your token on every chain, as for any launch. With the bundle kit, on each chain that has private bundles you get our own signed launch transaction for a short hold (20 seconds by default): put it in a bundle of your own with your buys right after it, and the launch and your buys land together. You pay everything: the bundle's gas and tip, the gas of our held launch, your buys, and the usual launch fee. Pangea assembles no bundle, signs nothing of yours and keeps no gas of its own on the held transaction.

Two ways to bundle on Ethereum or BSC. Here: along OUR signed launch transaction, as below. Or with "Launch it yourself": you send each EVM launch from your own wallet and bundle it however you like.

| Chain | How your buys go along the launch |
| --- | --- |
| Solana | First: our signed launch_v2 transaction, first in your own Jito bundle; your own transaction after it pays the Jito tip |
| Ethereum | Once Solana is live, a Flashbots or Titan bundle of three: your transaction funding our hold key's gas, our signed launch, your buys |
| BSC | Once Solana is live, a 48 Club or BlockRazor bundle of three: your transaction funding our hold key's gas, our signed launch, your buys |
| Base | No private bundles: your buys are made by contract in our launch transaction itself (the built-in multi-wallet launch buys) |
| Robinhood Chain | No private bundles: your buys are made by contract in our launch transaction itself (the built-in multi-wallet launch buys) |

### How to use it

- Prepare the launch with `bundleKitHeld: true` (a hold key is reserved for you on Ethereum and BSC: the quote then charges each such chain the gas to clear the hold key if a hold lapses, 52,500 gas, and NOT that chain's launch transaction's gas -- its launch, launch buys, dev buy and fee check -- which your bundle pays; a chain with no free hold key, and the chain you pay on (never held), is quoted and launched un-held, and `bundleKitHeld.unheld` names it), pay the fee on any chain and submit with `bundleKit: true` (POST /api/v1/launch/submit). Base and Robinhood Chain buys: name them as launch buys when you prepare the launch.
- Poll POST /api/v1/launch/bundle-kit with `{ ticket }`. A held chain shows `state: "held"`, `holdUntil` (unix seconds) and `tx`: `tx.raw` (the raw signed transaction) on Ethereum and BSC, `tx.wire` (base64, a v1 transaction) on Solana. Only your ticket gets it, and only while it is held.
- Solana: put our transaction FIRST in your Jito bundle, your tip transaction after it, before holdUntil.
- Ethereum and BSC (once Solana is live): our transaction is signed by a hold key that holds no gas. Your bundle is three transactions, in this order: (1) yours, sending `fund.wei` (shown with it) to `fund.to`, the hold key; (2) ours, `tx.raw`; (3) your buys. Send it before holdUntil.
- Not landed by `holdUntil` on Ethereum or BSC: the kit shows `state: "lapsed"` -- that chain is NOT launched (our launcher never sends a held leg itself: its launch gas is your bundle's, not in the fee). The hold key's nonce is burnt (the 52,500 gas in your fee), and you are refunded that chain's share of the fee, and the route fee you paid for that chain if you ticked "Open all bridge routes at launch". Solana: `state: "sent"`, our launcher sends exactly the held transaction. A bundle sent after the hold may revert, at your gas.
- Live: `state: "live"`. Solana shown `state: "ours"` could not be held (its transaction could not be signed): our launcher sends it, as for every launch. An Ethereum or BSC leg quoted held is never sent by our launcher: one that could not be held at launch (for example, every hold key was in use) is `state: "lapsed"`, as above. `state: "checking"`: the chain could not be read after its hold -- it is read again, and is then live or lapsed.
- The chain you pay on is never held (owner rule): paid on Ethereum or BSC, that chain is launched by our launcher as usual, un-held, with its launch gas in the fee, and you cannot bundle on it. Instead you can bundle on your other chains, use "Launch it yourself" and send that chain's launch from your own wallet, or pay on another chain. Prepare's `bundleKitHeld.payChain` says so, and the launch page shows it before you pay.
- A lapsed chain is not launched, and nothing is bridged to it: it is left out of the coin's chains, its routes and every bridge quote.

> **Note:** Holds never slow anyone else: each held launch uses its own hold key, apart from the launcher every other launch uses, and a chain quoted with no hold key free goes out un-held, as any launch. Not with a launch whose legs you send yourself (`selfLaunch`, `selfSolana`, or "Launch it yourself"). The fee is the launch's usual fee plus the hold's worst-case gas on each Ethereum and BSC leg (a few tens of thousands of gas).

## How it works

**Before factory v2.** Every launch deployed the same token on all five chains at once. Each chain got 1B tokens in its liquidity pool, 5B in all, and all five pools opened at the same US dollar price: $3,000 of market cap a chain.

Every token went into its liquidity pool from the start. There is no team allocation, treasury or vesting. Each pool started with tokens only and collects the quote asset as people buy.

Each chain operates independently, and one thing keeps the five prices together: arbitrage, which is open to anyone. Buy on a cheaper chain and sell on a more expensive one.

### What a v1 coin does not have

These came with factory v2, and a v1 coin has none of them: a launch on selected chains, a chosen opening market cap, degen mode, a different trading fee on each chain, a fee receiver apart from the creator wallets, and coin tokenomics. [How v2 works →](https://www.pangea.trade/app/docs/v2#how)

### The v1 factory

| Chain | Launch factory (v1) |
| --- | --- |
| Ethereum | `0x1D583e6eA4AA7c5e167a7E69B07b83DD5B813f53` |
| Base | `0xba4beE425F268f7Dc0F7e5457d14320d5A64cd06` |
| BSC | `0xd1454764E8D87B117a23491E77cAfcCC50430256` |
| Robinhood Chain | `0x8A1F3e1DB07048c007F6d9ab924494C77c0DBa65` |

Nothing new launches from it. The coins it launched trade, bridge and pay their fees as this tab describes.

## The five pools

Each chain got 1 billion tokens in its liquidity pool, held in a concentrated liquidity position. As the price rises, the pool sells tokens and collects the quote asset.

| Chain | Venue | Quote |
| --- | --- | --- |
| Ethereum | Uniswap v4 | ETH |
| Base | Uniswap v4 | ETH |
| Robinhood Chain | Uniswap v4 | ETH |
| BSC | Uniswap v4 | BNB |
| Solana | Meteora DAMM v2 | SOL |

Each pool pays sellers from its own reserves. A chain with fewer buyers may have less quote asset available, regardless of how much liquidity exists elsewhere.

### Opening market cap

Every v1 coin opened at **$3,000 a chain**, $0.000003 a token, on all five chains at once. It was fixed by the contracts, not chosen.

## Five contracts, one token

Each chain has its own token contract and address, so buyers choose which chain to trade on.

A token’s address is different on every chain (unless its creator chose “Same contract address on all EVM chains”, which puts it at one address on the four EVM chains), and every one of them ends in `6ea`: the last three hex digits on the four EVM chains, the last three characters of the mint on Solana. The token’s page lists every one.

Each chain has 1 billion tokens in its own liquidity pool, so 5 billion across the five chains.

| Chain | In its liquidity pool at launch |
| --- | --- |
| Ethereum | 1B |
| Base | 1B |
| Robinhood | 1B |
| BNB Chain | 1B |
| Solana | 1B |

Each contract reports its own chain’s 1B supply.

> **Note:** [How market cap is counted →](https://www.pangea.trade/app/docs/v2#v1-market-cap)

## The market cap situation

Terminals and apps calculate market cap in different ways. A token’s full market cap is the sum of its market caps on all five chains.

The price is the same on every terminal and app. You can bridge your tokens, or buy on any of the five contracts, at the same dollar price for the same number of tokens.

To see a token’s complete market cap, use its page on this site: it is the most accurate source.

The differences between platforms come from how Pangea manages supply on each chain to keep prices in line. That is what keeps the price gap between chains small, and what makes a single token across many chains possible. Every platform is consistent with itself, and many of them show the same figures.

**The key point:** the same token, at the same price, on every chain.

## Trading fee

Each v1 launch set a trading fee between **0.3% and 5%**, once, at launch, the same on all five chains. No contract function or program instruction changes it afterwards.

The platform takes **20% of the trading fee**, with a floor of 0.2% of volume. The creator receives the rest.

| Trading fee | Platform | Creator |
| --- | --- | --- |
| 0.3% | 0.2% (floor) | 0.1% |
| 1% | 0.2% | 0.8% |
| 5% | 1.0% | 4.0% |

> **Note:** The minimum fee is 0.3% because Meteora keeps 20% of trading fees on Solana. At a 0.2% fee, only 0.16% of volume would remain, less than the platform’s share. This also means creators earn less on Solana: at a 1% trading fee they receive 0.6% there against 0.8% on Base.

80% of platform fees go to PANGEA buybacks.

**Who receives the creator’s share.** The creator wallets the launch named: one EVM address for the four EVM chains and one Solana address for Solana. On Solana, the fee split is enforced by Pangea's splitter program.

**Where the fee goes can change.** Either side may hand its stream to another address, in two steps — the new address has to accept it — so a project that changes hands does not have to relaunch to move its revenue with it.

## Fee collection

Fees are paid in the quote asset: `ETH`, `BNB` or `SOL`. Never in the token that was launched.

Meteora supports collecting fees directly in the quote asset. Uniswap v4 collects fees in whichever token enters a swap, so fees from sells accrue in the launched token.

To convert them, the platform swaps those tokens for the quote asset when fees are claimed. This keeps extra code off the trading path, reducing the risk of a bug disrupting swaps. The trade-off is a small price impact from selling the collected tokens into the pool.

## PANGEA tokenomics

**$PANGEA is live.** It launched through Pangea on all five chains, with the same five-chain structure as every other token. [Trade $PANGEA →](https://www.pangea.trade/app/t/pangea-dadd0d)

| Chain | Official $PANGEA contract |
| --- | --- |
| Ethereum | `0x6434e57e8d12B92bb4D0b1EFb3274748B75196ea` |
| Base | `0xA551dEF8a44ea423CaA58DA45b3F5764004286Ea` |
| Robinhood Chain | `0x30de7AE4c9578D110a97367EDe9F7765Cdce06ea` |
| BSC | `0xd0cb4279e2B74c2d72EEa20Df308Fb9604E3d6Ea` |
| Solana | `JAFqCQSpy5YinrsmixWSMaD7jTn4MytJRkyWvYPnY6ea` |

> **Note:** These five are the only official $PANGEA contracts. Any launch may use the ticker PANGEA; a token at any other address is not this one.

## Team allocation

The team holds **about 19% of the total supply** of $PANGEA, which is 5B summed across the five chains. Most of it is locked: **about 14.4%** sits in vesting contracts and is released in equal monthly steps over two years. The remaining **4.18%** is a marketing treasury. Nothing is held in an anonymous wallet: every address below can be checked on chain.

| Allocation | Share of total supply | Terms |
| --- | --- | --- |
| Locked team allocation | About 14.4% (718M $PANGEA) | Released in monthly steps over 24 months, about 0.6% of the total supply per month |
| Marketing treasury | 4.18% (209M $PANGEA) | Held by the Pangea treasury, for marketing |
| Everyone else | About 81% | Pools, holders and the market |

### Locked allocation

The locked tokens are not in a team wallet. They sit in **Team Finance** lock contracts on Ethereum, Robinhood and BSC, and in a **Streamflow** vesting stream on Solana. Each releases in equal monthly steps over 24 months, about 0.6% of the total supply a month across the four. No one can withdraw ahead of that schedule: the three Team Finance locks have no owner (it was handed to 0x…dEaD on 2026-10-24, which ends their emergency withdrawal and revocation), and the Streamflow stream cannot be cancelled by its sender or its recipient.

| Chain | Lock | Locked |
| --- | --- | --- |
| Ethereum | `0xbfae7682f5550d0a9572f750906c4c4e54502ee1` | 222.9M |
| Robinhood | `0xBe17cDf528aBb0797B8DD8e3d47E43A2bAF9cb34` | 228.8M |
| BSC | `0xc362b1d31bc07712b9eba15755daeaed47a0b0c1` | 55.0M |
| Solana | `ou8M2oj3byMTRmaxispdbh82pKmew7NLDt6Yy36R6zW` | 211.0M |

### Marketing treasury

The treasury funds marketing and growth. It is one address on every EVM chain, `0x402908cd6eB5176A1B9FdE3690CCccF41d7D54D0`, named pangeatreasury.eth, pangeatreasury.base.eth and pangeatreasury.bnb.

> **Note:** Balances were read on chain when this was written; the explorers (Etherscan, Robinhood's Etherscan, BscScan and Solscan) show them live, along with each lock's release schedule.

## Bridging

The token uses LayerZero OFT. Transfers burn tokens on the source chain and mint them on the destination, keeping the total across the five chains unchanged. There are no wrapped tokens.

Each chain’s pool received its 1 billion tokens at launch, so all five pools opened at once.

### Standard or fast

A standard transfer takes LayerZero’s own path. On every route the verifiers attest once the source transaction has 1 confirmation, and a transfer typically arrives in 4 to 16 seconds. From Ethereum, whose blocks are slower, it takes longer. [How to bridge, step by step →](https://www.pangea.trade/app/docs/v2#v1-bridge-steps)

A fast transfer arrives in seconds: a relayer pays the recipient at once from its own tokens and is repaid when the standard transfer lands. It costs the fast lane’s own 0.2% on top of the standard 0.2%, plus the relayer’s price, which is capped at 10%. The bridge screen offers it only when a relayer is quoting your transfer. [Relayers →](https://www.pangea.trade/app/docs/v2#v1-relayers)

### What a transfer costs

- **The token’s fee.** The token keeps 0.2% of every transfer between chains. It goes to the platform. On the four EVM chains it is fixed in the token contract and nobody can change it. On Solana, the fee split is enforced by Pangea's splitter program.
- **The message fee.** LayerZero’s price for carrying the transfer, paid in the source chain’s native asset. It is quoted for the exact transfer before you sign.
- **A Solana route’s setup.** The first transfer on a Solana route nobody has used yet also pays a one-time setup fee for that route. Every later transfer on it does not.

A transfer of 1,000 tokens: 2 go to the platform and 998 arrive.

> **Note:** Transfers are rounded down to six decimal places. Amounts smaller than `0.000001` token are dropped.

> **Note:** [How market cap is counted →](https://www.pangea.trade/app/docs/v2#v1-market-cap)

## How to bridge

Bridging moves your tokens from one chain to another. It is the same token on both sides: the tokens are burned where you send them from and minted where they arrive, so nothing is wrapped and the total across the five chains does not change.

### On the Bridge page

- **Pick the token.** Open Bridge in the menu and choose the token, or open a token’s page and use its bridge tab. A link with the token in it, such as `/app/bridge?token=ptest4`, opens it ready.
- **Pick From and To.** The chain your tokens are on now, and the chain you want them on. The swap button between them turns the route around.
- **Connect a wallet.** An EVM wallet for Ethereum, Base, Robinhood Chain and BSC, a Solana wallet for Solana. A route between an EVM chain and Solana needs both: one sends, the other receives.
- **Enter the amount.** Your balance on the From chain is read from the chain. The page shows what you send, the token’s fee, what arrives, and the message fee, before you sign anything.
- **Choose Standard or Fast.** Standard is the token’s own path. Fast appears only when a relayer is quoting your transfer.
- **Sign.** One transaction on the From chain for a standard transfer. A fast transfer may ask for an approval first. The first transfer on a Solana route that nobody has used yet may ask for a setup step first; see below.
- **Wait for it to arrive.** The page follows the transfer and says when it has landed. Recent transfers are listed under the card.

### What it costs

- **The token’s fee, 0.2%.** Kept by the token for the platform, out of the amount you send. 1,000 tokens sent, 998 arrive. (A token from before factory v3. A factory v3 token's fee is its chain's registry figure, 0.2% unless changed, at most 0.5%.)
- **A factory v3 token: the price-gap fee.** A transfer from the chain where the token is cheaper to the chain where it is dearer also pays the price gap, burned at the destination. The quote shows the estimate before you sign; the other way is free.
- **LayerZero’s message fee.** Paid in the From chain’s native asset: ETH on Ethereum, Base and Robinhood Chain, BNB on BSC, SOL on Solana. It pays the verifiers and the delivery on the other side, and it is quoted for your exact transfer before you sign.
- **A factory v3 token into Ethereum: Pangea's delivery.** LayerZero's executor does not deliver there, so Pangea's delivery keys land the transfer; their cost is part of the network fee, in the From chain's coin (out of Solana, in SOL), and the quote shows it as its own line (Pangea delivery).
- **A factory v3 token into Solana: the Solana delivery.** Pangea's delivery key lands the transfer on Solana; its cost is withheld from what arrives, in the token, and shown as its own line (Solana delivery). A new wallet's first arrival also pays its token account's rent from it.
- **A transfer too large for one send: every part pays.** Sent in parts, each part is its own transfer and pays the full network fee (LayerZero, and any delivery above); the quote shows the fee per part and the total for all parts before the first signature. A co-signed transfer (one send) shows its fee and its guaranteed minimum before you sign it.
- **Gas.** The transaction itself, on the From chain, as for any transaction there.

| Route, 1,000 PTEST4 | Message fee quoted |
| --- | --- |
| Base to BSC | 0.000110 ETH |
| Ethereum to Base | 0.000205 ETH |
| Base to Solana | 0.000425 ETH |
| Solana to Base | 0.00058 SOL |

Those are live quotes from 19 October 2026, to show the size of the fee; it moves with gas prices. A transfer into Solana costs more because it also pays for the token account the tokens arrive in.

### How long it takes

A standard transfer waits for its transaction to have 1 confirmation on the From chain, then for two verifiers to attest it, and is then delivered. Measured on mainnet, transfers between Base, BSC, Robinhood Chain and Solana took 4 to 16 seconds:

| Route | Seconds, sent to delivered | Transfers measured |
| --- | --- | --- |
| Base to BSC | 5 | 2 |
| Base to Robinhood Chain | 4 to 5 | 4 |
| Base to Solana | 5 to 7 | 4 |
| BSC to Base | 8 | 1 |
| Robinhood Chain to Base | 10 to 16 | 4 |
| Robinhood Chain to BSC | 7 | 1 |
| Robinhood Chain to Solana | 5 to 13 | 2 |
| Solana to Base | 12 to 15 | 4 |

From Ethereum it takes longer. Ethereum makes a block only every 12 seconds, and the verifiers wait for the transfer’s block to be confirmed before they attest, so expect about half a minute or more. No transfer out of Ethereum has been measured on mainnet yet, and the Bridge page currently shows the same estimate for every route.

### The fast lane

Fast is an option on top of the standard transfer. When you choose it, the transfer carries a public order on Pangea’s fast bridge, PangeaFastBridge, one shared contract per chain. A relayer sees the order and pays you on the To chain within seconds from its own tokens, and is repaid when your standard transfer lands. It costs the fast lane’s 0.2% and the relayer’s price on top of the standard 0.2%, and the price is fixed in the order before you sign. If nobody fills the order, your tokens still arrive the standard way and you get the relayer’s fee too. [How relayers work →](https://www.pangea.trade/app/docs/v2#v1-relayers)

- Fast appears only when a relayer is quoting your route and amount. Otherwise only Standard is offered.
- Into Solana it needs the token to allow a 232-byte message. Tokens launched from the current factories allow 256; some earlier launches allow 200, and for those the page offers Standard only into Solana.
- A fast transfer may need an approval first: the fast bridge moves the tokens for you, once.

### Solana

- **Into Solana, the recipient is a wallet.** Name the Solana wallet that will own the tokens, not a token account. The tokens arrive in that wallet’s token account for the token, which is created on delivery if it does not exist.
- **That account’s rent is in the message fee.** Every transfer into Solana carries 2,074,080 lamports (about 0.002 SOL), the rent of a new Token-2022 account, paid in the From chain’s asset as part of LayerZero’s fee. You need no SOL to receive. For a factory v3 coin today, the first arrival to a new wallet instead has that rent withheld from the tokens that arrive (the quote’s gap.delivery.ata), and an arrival too small to pay it waits on Solana until the account exists (gap.warning).
- **The first transfer on a route pays its setup.** A token’s route between Solana and a chain is opened the first time anyone uses it. That first transfer also pays a one-time setup fee: the rent of the accounts LayerZero keeps for the route. The page shows it as a separate line and may ask for two steps. Every later transfer on that route, by anyone, does not pay it.
- **Out of Solana, you pay in SOL.** The message fee and the transaction fee are paid from your Solana wallet, so keep a little SOL there.
- **Amounts cross at six decimals.** The odd base units below 0.000001 token stay in your wallet.

### If a transfer seems stuck

- **Look it up on LayerZero Scan.** Paste the transaction hash or signature of your send at layerzeroscan.com. It shows each verifier’s attestation and the delivery, with links to both transactions.
- **Inflight or confirming.** The verifiers are waiting for the confirmation or attesting. Wait: this is the ordinary state for the first seconds, and longer from Ethereum.
- **Verified but not delivered.** Delivery is open to anyone once a message is verified. LayerZero’s executor delivers it, and Pangea runs its own delivery service that delivers every Pangea token’s messages as soon as they are verified. If one sits verified and undelivered for more than a few minutes, anyone can deliver it by calling the destination endpoint’s `lzReceive` with the message, at their own gas cost. The exception: a factory v3 token's charged arrival into Solana, which only Pangea's appointed deliverers may land.
- **Into Solana, on a route that is not open.** A transfer sent into Solana before its route is opened waits, and is delivered once the route is opened; LayerZero Scan may show it as blocked until then. Nothing is lost in between. The Bridge page opens the route as part of the first transfer on it.
- **A held fast order.** When a fast order cannot be paid on the To chain, the fast bridge holds it for the sender. The Bridge page shows a button that sends it back; anyone may do it, paying LayerZero’s fee for the trip back.
- **Nothing on LayerZero Scan.** The send did not go through on the From chain. Check it on that chain’s explorer: if it failed, the tokens never left your wallet.

> **Note:** [How the routes are set up, and how to check them →](https://www.pangea.trade/app/docs/v2#v1-routes)

## LayerZero routes

### One token, five contracts

- **An address per chain.** A token is a LayerZero OFT contract on each of Ethereum, Base, Robinhood Chain and BSC, and a mint on Solana, held by LayerZero’s OFT program. The five addresses are all different (or, with “Same contract address on all EVM chains”, one on the four EVM chains and the mint on Solana) and all end in `6ea`. They are on the token’s page and in `/api/pools`.
- **1B per chain.** Each chain mints its own 1 billion at launch, all of it into that chain’s pool, 5 billion across the five. On an EVM chain `launchSlice()` is that 1B and `launchTotal()` the 5B (on a launch on fewer chains, 1B for each of them). Nothing can mint more: there is no mint function and no minter. In degen mode each chain mints its equal share of 1B instead, and `launchTotal()` is the 1B.
- **Burn and mint.** A transfer burns the tokens on the chain it leaves and LayerZero’s message mints the same number on the chain it reaches. On an EVM chain the token’s fee is moved to `bridgeFeeTo()` and only the rest is burned, so what is burned on one side is exactly what is minted on the other. A factory v3 token's price-gap fee is the exception: the destination withholds it and never mints it.
- **What is on a chain, and what is reported.** On each EVM chain `localSupply()` is what is on that chain now; it moves as tokens cross. `totalSupply()` is a reported figure: it starts at the chain’s 1B, and for tokens with a SupplyReporter the platform’s reporter on that chain mirrors the token’s supply on Solana into it. A report moves no balance and mints nothing, and a factory v1 token refuses one below its own `localSupply()` or above `launchTotal()`. Earlier launches have no reporter and always report their slice. A degen mode token reports the 1B total and takes no reports.
- **Peers are sealed at launch.** A peer is the token’s counterpart on another chain, the only address it accepts a mint from. The factory registers the four peers in the transaction that creates the token and hands ownership to a LaunchOwner contract that is sealed from the start: `sealed_()` is true and it has no way to add, change or remove a peer.

| Chain | SupplyReporter |
| --- | --- |
| Ethereum | 0xD7AF0c412911DCE5F864d949abA14a3048DD87B2 |
| Base | 0xF10254C126e41F69799e781D185cc42c99EE37Ed |
| Robinhood Chain | 0x51D658a71AEa38f123d4c73e42703Fb9C949900A |
| BSC | 0xf1a98Fa0B2baB64BC4358dC0C2c13444ecCA365b |

### How every route is set up

A route is one direction between two chains, for one token. When the factory creates a token it is, for that one transaction, the token’s LayerZero delegate, and it writes every route from a table fixed in its own constructor. Nothing is left on LayerZero’s defaults, because a default can be changed by whoever administers the endpoint.

- **Libraries.** The send and the receive message library are named explicitly: LayerZero’s Ultra Light Node on every chain, the same contracts the endpoint’s defaults point at today, pinned so they stay.
- **Executor.** LayerZero’s executor on each chain is named explicitly, with a cap on the message size.
- **Verifiers.** No required verifiers. An optional set of Pangea’s own verifier plus the external ones, and 2 of the set must attest before a message counts. So no single party can deliver a message: not Pangea’s verifier, and not any one external one.
- **Confirmations.** 1 on every source chain, Solana included: the verifiers attest once the send has 1 confirmation. Both ends of a route hold the same number.
- **A gas floor.** Every transfer must give the delivery at least 100,000 gas on an EVM chain, and 200,000 compute units plus 2,074,080 lamports on Solana, for the recipient’s token account. It is an enforced option, added to every send, so a sender cannot under-pay delivery.
- **A message size.** Up to 10,000 bytes between EVM chains and from Solana. Toward Solana 256 bytes for launches from the current factories; the factory admin can move that between 200 and 800 for launches after, never for a token that exists. A Solana transaction is small, and a cap above what Solana can deliver would let a message be sent that can never land.

| Chain | Optional verifiers | Of which must attest | Confirmations |
| --- | --- | --- | --- |
| Ethereum | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |
| Base | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |
| Robinhood Chain | Pangea, LayerZero Labs, Nethermind | 2 | 1 |
| BSC | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |
| Solana | Pangea, LayerZero Labs, Nethermind, Horizen, Canary | 2 | 1 |

That is the set on each chain, for its routes to Ethereum, Base, BSC and Solana. Robinhood Chain has two of the external verifiers, so a route with Robinhood Chain at either end uses the verifiers both chains have: Pangea, LayerZero Labs and Nethermind, 2 of 3. Every other route has 5, 2 of 5.

| Chain | Send library | Receive library | Executor |
| --- | --- | --- | --- |
| Ethereum | 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1 | 0xc02Ab410f0734EFa3F14628780e6e695156024C2 | 0x173272739Bd7Aa6e4e214714048a9fE699453059 |
| Base | 0xB5320B0B3a13cC860893E2Bd79FCd7e13484Dda2 | 0xc70AB6f32772f59fBfc23889Caf4Ba3376C84bAf | 0x2CCA08ae69E0C44b18a57Ab2A87644234dAebaE4 |
| Robinhood Chain | 0xC39161c743D0307EB9BCc9FEF03eeb9Dc4802de7 | 0xe1844c5D63a9543023008D332Bd3d2e6f1FE1043 | 0x4208D6E27538189bB48E603D6123A94b8Abe0A0b |
| BSC | 0x9F8C645f2D0b2159767Bd6E0839DE4BE49e823DE | 0xB217266c3A98C8B2709Ee26836C98cf12f6cCEC1 | 0x3ebD570ed38B1b3b4BC886999fcF507e9D584859 |
| Solana | 7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH | 7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH | AwrbHeCyniXaQhiJZkLhgWdUCteeWSGaSN1sTfLiY7xK |

| Chain | LayerZero endpoint | Launch factory (v2) | Earlier launches (v1) | Pangea’s verifier |
| --- | --- | --- | --- | --- |
| Ethereum | 0x1a44076050125825900e736c501f859c50fE728c | 0xf7052a4E38e241bc66bfAe6eB27D10B2A42cacB4 | 0x1D583e6eA4AA7c5e167a7E69B07b83DD5B813f53 | 0x5FA03555f186e53a644405817E1BeAC894Bd6562 |
| Base | 0x1a44076050125825900e736c501f859c50fE728c | 0xb405b396b607C3482A3d301b1cFF57A7de75325a | 0xba4beE425F268f7Dc0F7e5457d14320d5A64cd06 | 0x6dbFda44baB6bf0DDF3E9F88d7823eDdB48f4b25 |
| Robinhood Chain | 0x6F475642a6e85809B1c36Fa62763669b1b48DD5B | 0x9Cf5832474CBB09D2E06FF20460aff4303fAf72A | 0x8A1F3e1DB07048c007F6d9ab924494C77c0DBa65 | 0x85880e7D5315365165E97620Ba17B48A645b13F3 |
| BSC | 0x1a44076050125825900e736c501f859c50fE728c | 0x92858095Cadf9f9d60dC17226f7FF15Be4b69e45 | 0xd1454764E8D87B117a23491E77cAfcCC50430256 | 0xF61Ed4347c4D02522e96657a606f49E8381B3dCA |
| Solana | ULN 7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH | Splitter program Hb5uTppRbNxc3RjVRqTWqfdU3E3GiZyGYKccxYf9tUej | The same program | 7EgbAY7663cybirqgmFHzF8q9debeePxr8ra7wKyEmNE |

| Verifier | Ethereum | Base | Robinhood Chain | BSC | Solana |
| --- | --- | --- | --- | --- | --- |
| LayerZero Labs | 0x589dEDbD617e0CBcB916A9223F4d1300c294236b | 0x9e059a54699a285714207b43B055483E78FAac25 | 0xd01ae6905d48315f7bE10C7330aeCF8360Ef5b12 | 0xfD6865c841c2d64565562fCc7e05e619A30615f0 | 4VDjp6XQaxoZf5RGwiPU9NR1EXSZn2TP4ATMmiSzLfhb |
| Nethermind | 0xa59BA433ac34D2927232918Ef5B2eaAfcF130BA5 | 0xcd37CA043f8479064e10635020c65FfC005d36f6 | 0x0Ffe02DF012299A370D5dd69298A5826EAcaFdF8 | 0x31F748a368a893Bdb5aBB67ec95F232507601A73 | GPjyWr8vCotGuFubDpTxDxy9Vj1ZeEN4F2dwRmFiaGab |
| Horizen | 0x380275805876Ff19055EA900CDb2B46a94ecF20D | 0xa7b5189bcA84Cd304D8553977c7C614329750d99 | not on this chain | 0x247624e2143504730aeC22912ed41F092498bEf2 | HR9NQKK1ynW9NzgdM37dU5CBtqRHTukmbMKS7qkwSkHX |
| Canary | 0xa4fE5A5B9A846458a70Cd0748228aED3bF65c2cd | 0x554833698Ae0FB22ECC90B01222903fD62CA4B47 | not on this chain | 0xfA9bA83C102283958B997Adc8B44ED3A3CdB5dDa | 7jMeX5mzXnSSKYd8DxBDP4xMnkNFZZZm5W28FWUTbwU3 |

On Solana the same is done by Pangea’s splitter program. The program holds one route setting per destination chain, and the first transfer on a token’s route applies it to that token, once: the libraries, the executor, the verifiers in both directions, the confirmations and the gas floor. The token’s LayerZero admin and delegate on Solana is an address only that program can sign for, and the program has no instruction that changes a peer, a library or a verifier afterwards.

### Why nobody can change a route later

- On an EVM chain the factory writes the routes while it is the token’s delegate, then never calls the endpoint for that token again and has no function that could. The token’s owner, the sealed LaunchOwner, has no way to name another delegate. So no key, including Pangea’s, can change a token’s libraries, executor or verifiers.
- This matters because a delegate who could name the verifiers could name itself, attest a message nobody sent, and mint. Sealing the peers alone would not close that.
- One thing stays adjustable on purpose: the gas floor. The LaunchOwner’s admin can raise or lower it, so that a chain changing its gas costs does not break bridging for good. It accepts exactly one `lzReceive` option and nothing else, under hard caps of 1,000,000 gas and 10,000,000 in value, so it cannot add a payment to anyone or set a floor nobody can pay. On Solana the gas floor is fixed when the route is opened.
- The other side of this: if LayerZero retires a verifier a route depends on, that route keeps working only while two of its verifiers still attest, and nobody can swap one in. A paused bridge can be lived with; a token anyone could mint could not.
- On Solana the admin can change the route setting for launches still to come. A token’s route is applied with the setting in force when it launched, and the program refuses a setting edited after that, unless the admin signs the opening; once a route is open nothing changes it.

### Check it yourself

Everything above can be read from the chains, with no help from Pangea. The worked example is PTEST4, a live test launch, on Base. Its address there is `0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea`; the other addresses are in the table at the end. These are read-only calls, with Foundry’s `cast` and any RPC endpoint for the chain.

The token's peers, owner and delegate, on Base:

```bash
TOKEN=0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea
ENDPOINT=0x1a44076050125825900e736c501f859c50fE728c
RPC=your-base-rpc-endpoint

# its counterpart on Ethereum, BSC, Robinhood Chain and Solana
for EID in 30101 30102 30416 30168; do
  cast call $TOKEN "peers(uint32)(bytes32)" $EID --rpc-url $RPC
done

# the owner is a LaunchOwner, sealed: true
OWNER=$(cast call $TOKEN "owner()(address)" --rpc-url $RPC)
cast call $OWNER "sealed_()(bool)" --rpc-url $RPC

# the delegate, the only key that can set verifiers, is the factory that launched it
cast call $ENDPOINT "delegates(address)(address)" $TOKEN --rpc-url $RPC
```

One route's libraries, executor, verifiers and gas floor: Base to Robinhood Chain:

```bash
EID=30416
SEND_LIB=$(cast call $ENDPOINT "getSendLibrary(address,uint32)(address)" $TOKEN $EID --rpc-url $RPC)
cast call $ENDPOINT "isDefaultSendLibrary(address,uint32)(bool)" $TOKEN $EID --rpc-url $RPC
cast call $ENDPOINT "getReceiveLibrary(address,uint32)(address,bool)" $TOKEN $EID --rpc-url $RPC

# verifiers: (confirmations, required count, optional count, threshold, required, optional)
ULN=$(cast call $ENDPOINT "getConfig(address,address,uint32,uint32)(bytes)" $TOKEN $SEND_LIB $EID 2 --rpc-url $RPC)
cast decode-abi "f()((uint64,uint8,uint8,uint8,address[],address[]))" $ULN

# executor: (max message size, executor)
EXE=$(cast call $ENDPOINT "getConfig(address,address,uint32,uint32)(bytes)" $TOKEN $SEND_LIB $EID 1 --rpc-url $RPC)
cast decode-abi "f()((uint32,address))" $EXE

# the gas floor every send carries
cast call $TOKEN "enforcedOptions(uint32,uint16)(bytes)" $EID 1 --rpc-url $RPC
```

What those return for this token, read on 19 October 2026:

| Read | Answer | What it means |
| --- | --- | --- |
| `peers(30416)` | `0x00000000000000000000000068906d8b2a22d329b853ec650fa6a29fab6056ea` | The token on Robinhood Chain, left-padded to 32 bytes. |
| `peers(30168)` | `0xbad1e830d4a3bba670d642dd253151f5564074eb91e9197e0216ec274e91df6e` | Its OFT store on Solana, `DaGUXY1qixx3Wsp7KFb7L6UtXV6QHCRXtaXtx4yfLued`, as 32 bytes. |
| `sealed_()` | `true` | No peer can ever be added, changed or removed. |
| `delegates(token)` | `0x025f893Bdcd7978232C211e3A28610FC6d8B8970` | The factory that launched it, an earlier one than today’s, with no code to touch the endpoint again. |
| `isDefaultSendLibrary` | `false` | The library is pinned, not inherited. |
| `getConfig(... 2)` | `(1, 0, 3, 2, [], [3 verifiers])` | 1 confirmation, no required verifiers, 3 optional, 2 must attest. |
| `getConfig(... 1)` | `(10000, 0x2CCA08ae69E0C44b18a57Ab2A87644234dAebaE4)` | Messages up to 10,000 bytes, LayerZero’s executor. |
| `enforcedOptions(30416, 1)` | `0x000301001101000000000000000000000000000186a0` | One `lzReceive` option: 0x186a0, which is 100,000 gas. |

The three optional verifiers it returns are Pangea’s on Base, `0x6dbFda44baB6bf0DDF3E9F88d7823eDdB48f4b25`, and LayerZero Labs’ and Nethermind’s there. Toward Solana the same read returns 5 verifiers and an executor cap of 200 bytes: this token was launched before the factories raised it to 256. The gas floor toward Solana reads `0x00030100210100000000000000000000000000030d40000000000000000000000000001fa5e0`: 0x30d40 is 200,000 compute units and 0x1fa5e0 is 2,074,080 lamports.

On LayerZero Scan, look up the token’s address, or any transfer. Its transfer from Base to Robinhood Chain on 16 October 2026, `0x5860a6920fd6fe6d97f7d88104d6ce105bb43fd4a9654d7d84cdd9229e961f65`, shows all three of the route’s verifiers attesting, and the delivery five seconds after the send. Each message lists the verifiers that attested and the `lzReceive` gas it carried.

On Solana, a token’s route to a chain is open when LayerZero’s receive config account for it exists: the ULN program’s address for the seeds `"ReceiveConfig"`, the chain’s endpoint id as four big-endian bytes, and the OFT store. Its contents are the confirmations and the verifiers, in the same order as above. For PTEST4, read on 19 October 2026, the routes from Base and Robinhood Chain are open, each with 1 confirmation and 2 of its verifiers, and the routes from Ethereum and BSC are not open yet: nobody has bridged on them. [Bridging from code →](https://www.pangea.trade/app/docs/v2#v1-bridge-code) has a script that checks it.

| Chain | PTEST4 on that chain |
| --- | --- |
| Ethereum | 0x835e9CFB333B9E4811CA1585460442806a99d6eA |
| Base | 0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea |
| Robinhood Chain | 0x68906D8b2A22D329b853ec650Fa6A29fAb6056Ea |
| BSC | 0x90Ba4Df685A2D33c12C40F03808F290519E7F6EA |
| Solana | Mint DdJ6R9TSKdNYMZhdgATANT98BJNgScuU58baQoCfZ6ea, OFT store DaGUXY1qixx3Wsp7KFb7L6UtXV6QHCRXtaXtx4yfLued |

> **Note:** Pangea’s own checks do the same against every factory before a launch: each route’s two ends must agree, and every verifier a factory names must be on a list kept by hand, with the operator’s name. The addresses on this page are those.

## Relayers

A relayer pays the recipient on the destination chain within seconds, out of its own stock of the token, and is paid back, plus its fee, when the bridged tokens arrive over the standard path a few seconds later. Nobody has to trust it: the recipient, the amount and the fee are fixed by the order, and a relayer that pays anything else is simply never repaid. If no relayer fills an order, the transfer arrives the standard way, and the fee no relayer earned goes to the recipient.

There is one fast bridge per chain, shared by every token launched on Pangea. A new token can use it the moment its launch lands: nothing is deployed for it. An order names its launch, never a token address, and the bridge reads the token itself from Pangea’s own factories, so a relayer is only ever repaid in, and a recipient only ever paid in, that launch’s own token on that chain.

> **Note:** The fast bridges on Ethereum, Base, Robinhood Chain and BSC are deployed, at the addresses listed at the end of this section. Orders out of Solana are posted by Pangea’s fast-bridge program, in the same format. Into Solana the fast lane needs a token that allows a 232-byte message, which every launch from the current factories does.

### What a transfer costs, and who is paid

- **The token’s fee.** The token keeps 0.2% of every transfer between chains, on either lane. It goes to the platform.
- **The fast lane’s fee.** On the fast lane the fast bridge keeps 0.2% first, also for the platform, and the token’s fee is then taken from what is left. The bridge’s admin can change it, per launch or for all, but never above 1%.
- **The relayer’s fee.** This is a price, not a platform fee. Relayers publish what they charge and the app takes the lowest. The bridge refuses any order that would pay a relayer more than 10% of what arrives, or the lower cap its admin sets.
- **Your limit.** You set a slippage, 0.5% unless you change it and 10% at most. If the best relayer price is above it, no fast transfer is offered, and the bridge refuses an order that would pay the recipient less.

One transfer of 1,000 tokens, three ways, with a relayer charging 0.1% as the example price:

| Who gets | Standard | Fast, filled | Fast, unfilled |
| --- | --- | --- | --- |
| Platform | 2 | 3.996 | 3.996 |
| Relayer | 0 | 0.996004 | 0 |
| Recipient | 998 | 995.007996 | 996.004 |
| Arrives in | 4 to 16 seconds | seconds | 4 to 16 seconds |

On the fast lane the fast bridge keeps 2 tokens and the token keeps 1.996, so 996.004 are on their way. The relayer pays the recipient 995.007996 at once and receives the 996.004 when they arrive, keeping 0.996004. Both platform fees are taken on the chain the transfer leaves, when it is sent, so a fast transfer that nobody fills has still paid them.

### Becoming a relayer

Anyone can be a relayer. There is nothing to apply for and nothing to stake: you publish a price, watch for orders and fill the ones you want. Filling is never an obligation, and leaving an order unfilled takes nothing from anyone. It does count against you if the order was posted at your published price, as described under benching below.

What follows is the whole protocol, two HTTP calls and a handful of contract calls, and any program that follows it is a relayer. The HTTP calls are for the price board only: watching and filling work entirely on chain.

### 1. What you need

- **The token, on every chain you deliver to.** Fills are paid from your own wallet. The fast bridge holds no stock of its own.
- **An approval.** On each of those chains, approve that chain’s fast bridge to move your tokens: `approve(fastBridge, amount)` on the token. Without it your prices are not taken and a fill reverts.
- **Gas.** A fill is a transaction on the destination chain, paid in its native asset: ETH on Ethereum, Base and Robinhood Chain, BNB on BSC.
- **An RPC endpoint for each chain.** You read `OrderPosted` logs on the chains orders leave and send fills on the chains they go to, so the endpoint must serve `eth_getLogs`. A WebSocket endpoint makes you faster: subscribed to the fast bridge’s logs it tells you about an order the moment it is posted.
- **One key.** The address that signs your prices is the address the app checks for tokens and approval, so sign with the wallet you fill from.

Check a bridge before you approve it. `tokenOf(launchId)` returns the token it moves for a launch, read from Pangea’s factories (`isFactory(factory)` says which it accepts), and `remoteBridge(eid)` its counterpart on another chain.

### 2. Publish your price

You send a signed price sheet to the price board. The board is public: read it to see the prices you are competing with, the ceiling, and who is benched. A route is a pair of chains on one shared bridge, so one price covers every token on that route; the app only takes it for a transfer when you hold that token on the destination.

- Publish, POST: `https://www.pangea.trade/api/bridge/relayer-quotes`
- Read, GET: `https://www.pangea.trade/api/bridge/relayer-quotes`

The body you POST:

```json
{
  "sheet": {
    "v": 1,
    "relayer": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "issuedAt": 1760000000,
    "expiresAt": 1760000120,
    "routes": [
      {
        "fromEid": 30184,
        "toEid": 30102,
        "bridge": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
        "feeBps": 10,
        "minFee": "1000000",
        "maxAmount": "50000000000000"
      },
      {
        "fromEid": 30101,
        "toEid": 30102,
        "bridge": "0xcccccccccccccccccccccccccccccccccccccccc",
        "feeBps": 10,
        "minFee": "1000000",
        "maxAmount": "50000000000000"
      }
    ]
  },
  "signature": "0x<65-byte signature, 130 hex characters>"
}
```

| Field | What it is |
| --- | --- |
| `v` | Always 1. |
| `relayer` | Your address. The signature must be this address’s. |
| `issuedAt`, `expiresAt` | Unix seconds. A sheet may live at most 300 seconds, and its issue time may be at most 120 seconds ahead of the server’s clock. |
| `routes` | One to 64 routes. The same route may not appear twice. |
| `fromEid`, `toEid` | The LayerZero endpoint ids of the chain an order leaves and the chain you deliver on. |
| `bridge` | The fast bridge on the chain the order leaves. Not the one you fill on. |
| `feeBps` | Your price in basis points of what arrives, a whole number from 0 to the board’s ceiling. The ceiling is 1000, which is 10%, unless the board reports another. |
| `minFee` | The least you charge whatever the size, in base units, as a decimal string. Gas does not shrink with the amount. |
| `maxAmount` | The most you will front on that route, in base units, as a decimal string above zero. |
| `solana` | Optional, and needed for any route that delivers to Solana: your Solana address. The body then also carries `solanaSignature`, your Solana key’s Ed25519 signature over the same text, in base58 or base64. On a route from Solana, `bridge` is 32 bytes, written as 0x and 64 hex digits. |
| `launches` | Optional: up to 512 lines of `toEid`, `launchId`, `minFee` and `maxAmount`, to price each launch on its own -- every launch shares one fast bridge, and a route's amounts are base units of whichever token is sent. A destination with any launch line is quoted only for the launches it lists, at the route's `feeBps` and the line's minimum and limit (never above the route's own). Each is signed as `launch <toEid> <launchId> min <minFee> max <maxAmount>`, after the routes. |

The signature is over the text below, signed as an Ethereum personal message: EIP-191, which wallets offer as `personal_sign`. The lines are joined by a single newline with none at the end, addresses are in lower case, and the routes are sorted by `fromEid`, then `toEid`, then `bridge`, whatever order the JSON lists them in. This is the text for the body above:

The text you sign:

```text
Pangea relayer quotes v1
relayer: 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
issued: 1760000000
expires: 1760000120
30101>30102 0xcccccccccccccccccccccccccccccccccccccccc 10bps min 1000000 max 50000000000000
30184>30102 0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 10bps min 1000000 max 50000000000000
```

Your fee for a transfer is `feeBps` of what arrives, rounded up, and never less than `minFee`. If that comes to more than the ceiling allows on that transfer, or the transfer is larger than `maxAmount`, you have no price for it.

A sheet stops counting at its expiry, so publish again before then. Every 30 seconds is plenty, and the board accepts 120 publishes per caller in ten minutes. Each new sheet replaces your last one and must carry a later `issuedAt`. Stop publishing and you are off the board when your last sheet expires.

| Answer | What it means |
| --- | --- |
| `200` | Kept. The body is `{ ok, relayer, expiresAt }`. |
| `400` | The sheet is malformed, expired or over the ceiling. The reasons are in `error`. |
| `401` | The signature is not this relayer’s over this sheet. |
| `403` | This address may not publish on this deployment. The board is open to any address unless the deployment lists who may publish or has turned an address away. |
| `409` | A sheet issued at the same time or later is already held for this relayer. |
| `413` | The body is larger than 131,072 bytes. |
| `429` | Too many requests. The message says how many seconds to wait. |
| `424` | The board cannot be read or written right now. |

What a GET returns:

```json
{
  "now": 1760000030,
  "maxFeeBps": 1000,
  "sheets": [
    {
      "sheet": "<a sheet, as published>",
      "signature": "0x<its signature>"
    }
  ],
  "benched": [
    {
      "relayer": "0x<address>",
      "until": 1760003600
    }
  ],
  "neverBenched": [
    {
      "relayer": "0x<address>",
      "strikes": 1
    }
  ]
}
```

Reading is limited to 300 requests per caller in ten minutes. `maxFeeBps` is the ceiling in force, `sheets` every live sheet with its signature, and `benched` the relayers whose prices are not being taken, with the unix second each bench ends. `neverBenched` lists the relayers this deployment never benches that have strikes within the hour, with how many.

**How the app chooses.** For each transfer it works out every live sheet’s price and looks at the 4 lowest, lowest first. Between equal prices the sheet with the earlier `issuedAt` comes first. It takes the first whose relayer holds what the fill pays, in that launch’s token on the destination chain, and has approved the fast bridge there for it. Benched relayers are left out. If that price would leave the recipient less than the sender’s slippage allows, no fast transfer is offered. When no relayer quotes a transfer at all, the fast lane is simply not shown.

### 3. Watch for orders

Every fast transfer is an order, posted on the fast bridge of the chain it leaves. That bridge emits one event for it:

On the source chain’s fast bridge:

```solidity
event OrderPosted(
    bytes32 indexed orderId,
    bytes32 indexed launchId,
    uint32 dstEid,
    bytes32 indexed sender,
    uint64 senderNonce,
    bytes32 to,
    uint256 amount,
    uint256 relayerFee,
    uint64 lzNonce
);
```

- topic0: `0x7e20a23f868a6866f04de222fcf771b4747be0d748f6c3af50083f019a56c231`

| Field | What it is |
| --- | --- |
| `orderId` | `keccak256(abi.encode(uint32 srcEid, bytes32 srcBridge, bytes32 launchId, bytes32 sender, uint64 senderNonce, uint32 dstEid, bytes32 to, uint256 amount, uint256 relayerFee))`. |
| `launchId` | The launch the order is for. The token on either chain is `tokenOf(launchId)` on that chain’s bridge. |
| `dstEid` | The endpoint id of the chain to deliver on. |
| `sender`, `senderNonce` | Who posted the order, as 32 bytes, and that sender’s own counter on that bridge. |
| `to` | The recipient, as 32 bytes: an EVM address left-padded with zeros, or a Solana owner’s public key. An EVM destination refuses a recipient whose top 12 bytes are not zero. |
| `amount` | What arrives on the destination, in base units. Not what the sender parted with: both platform fees are already out of it. |
| `relayerFee` | What the filler keeps, out of `amount`, in base units. |
| `lzNonce` | The LayerZero nonce of the message carrying the order. A fill has to name it. |

### Orders from Solana

An order from Solana is posted by Pangea’s fast-bridge program (`post_order`) and carried by the sender’s own OFT send, in the same Solana transaction. On an EVM destination, for source endpoint `30168`, the order’s `srcBridge` is the sender’s own key (the OFT’s `composeFrom`), and the sender in the order is always taken to be that key: nobody can post in another’s name.

**Verify before you fill.** Skipping `post_order` only skips the fast lane’s fee, so an EVM-side check cannot tell you the order was paid for. Before filling an order from Solana, read the Solana transaction yourself and fill only when it holds both the `post_order` instruction and the OFT send it checks, with the same launch, recipient, amount and fee, and the transaction is finalized. A fill on anything less is a bet nobody else is making for you.

### 4. Fill

A fill is one call, made on the chain the order is going to:

On the destination chain’s fast bridge:

```solidity
function fillOrder(
    uint32 srcEid,
    bytes32 srcBridge,
    bytes32 launchId,
    bytes32 sender,
    uint64 senderNonce_,
    address to,
    uint256 amount,
    uint256 relayerFee,
    uint64 lzNonce
) external returns (bytes32 id);
```

`srcEid` is the endpoint id of the chain you saw the event on, and `srcBridge` the bridge that emitted it, as 32 bytes (the address left-padded with zeros): the destination accepts only a bridge it has named for that chain, else `NotARemoteBridge`. From Solana, `srcBridge` is the sender’s own key instead (see below). The other arguments are the event’s own fields, with `to` as the 20-byte address it pads: `senderNonce_` is its `senderNonce`. In the same transaction the bridge moves `amount - relayerFee` of `tokenOf(launchId)` from your wallet to the recipient and records you as owed `amount`.

Every one of those fields is hashed into the order’s id. Change the source bridge, the launch, the amount, the recipient or the fee and you have filled an order nobody posted: you have paid someone, and nothing will ever repay you.

Check before you send, on the destination bridge. Each check is a read and saves the gas of a fill that would revert:

- `filledBy(orderId)` is the zero address: nobody has filled it yet.
- `notYetArrived(token, srcEid, lzNonce)`, with `token` the launch’s token on this chain, is true: the order’s own message has not reached this chain. Once it has, there is nothing left to deliver early and a fill is refused.
- You hold `amount - relayerFee` of the token there and your approval covers it.
- `relayerFee` is at least your own price for that amount. You are never obliged to fill.

| Revert | What it means |
| --- | --- |
| `NoRemote(srcEid)` | This bridge has no counterpart on that source chain. |
| `NotARemoteBridge()` | The `srcBridge` you named is not a bridge this chain has ever named for that source chain. |
| `UnknownLaunch(launchId)` | None of Pangea’s factories this bridge accepts knows that launch. |
| `AlreadyFilled()` | Somebody filled it first. One fill per order. |
| `AlreadySettled()` | The order has already been paid out. |
| `FeeExceedsAmount()` | The fee is not less than the amount. No real order looks like this. |
| `TooLateToFill()` | The message you named has already reached this chain. |
| A token transfer error | Your balance or your approval is too small. |

A fill that goes through emits `OrderFilled(orderId, relayer, to, paid)`.

### 5. Get repaid

You are repaid on the chain you filled on, in the token, when the order’s own message arrives and the bridge settles it: seconds after the order was posted. The bridge checks that the message came from the launch’s own token and the source chain’s own fast bridge before it pays anyone. `settlement(orderId)` on the destination bridge says how the order settled:

| Value | What happened |
| --- | --- |
| `0` | Not settled yet. |
| `1` | Nobody filled it. The recipient was paid everything that arrived, the relayer’s fee included. Event: `SettledSlow`. |
| `2` | A fill earned the fee. The filler was sent the whole `amount`. Event: `RelayerPaid`. |
| `3` | A fill did not earn the fee. The filler was sent back `amount - relayerFee`, what it paid out, and the fee went to the recipient. Events: `RelayerPaid` and `FeeReturned`. |
| `4` | Held. The recipient could not be paid on this chain (it is not an address here), so nobody could fill it and the bridge keeps it for the sender. Event: `Refundable(orderId, guid, srcEid, sender, amount)`. It is never a strike against a relayer. |

The same order can only arrive twice from Solana, when a sender reuses a nonce. The second arrival is paid to the recipient (`ArrivedAgain` and `SettledSlow`) and the first settlement stands: each arrival brought its own tokens, so none is refused.

### Sending a held order back

Anyone may send a held amount back to its sender on the chain it came from: `quoteRefund(guid)` gives LayerZero’s fee for the message back, and `refund(guid)`, paying that fee, sends it (`Refunded`). `refunds(guid)` shows what is held. The bridge screen offers this button itself when a transfer it sent is held.

**What earns the fee.** The fee pays for delivering early with your own tokens, so a fill earns it only when both of these are true:

- The `lzNonce` your fill named is the nonce of the message that settles the order.
- The settlement lands in a later block than your fill. A fill settled in its own block is repaid what it paid out and earns nothing.

`filledBy(orderId)` and `filledLzNonce(orderId)` return the filler and the nonce it named. So fill as soon as you are willing to.

### Ceilings

| Limit | What it bounds |
| --- | --- |
| `MAX_FAST_FEE_BPS` | The fast lane’s own fee can never be set above 100 basis points, 1%. Read the fees in force for a launch with `feesOf(launchId)`. |
| `maxRelayerFeeBps` | No order may pay a relayer more than this share of what arrives: 1000 basis points, 10%, unless the bridge’s admin sets a lower one, for all launches or one. An order above it is refused with `RelayerFeeTooHigh`. |
| `maxFeeBps` | The price board’s own ceiling on a published price, reported by every read of the board. |
| `minReceived` | The sender’s floor, written into the order. An order that would pay the recipient less is refused with `ReceivesTooLittle` before any token moves. |

### What gets you benched

When the app sends an order at your published price, it remembers that the price was yours. Each order is judged once: reporting the same transaction again records nothing. It looks at the order again from 2 minutes after it was posted and judges it once it has settled, or after 30 minutes if it still has not. An order nobody filled, or one you filled without earning the fee, is a strike against you.

After 3 strikes within 60 minutes your prices are not taken for 60 minutes, however low they are. Those are the defaults, and a deployment can set its own. Your sheets stay on the board, and `benched` in the board’s answer says until when.

A deployment can name relayers it never benches, in `BRIDGE_RELAYERS_NEVER_BENCH`: EVM addresses, or Solana accounts a sheet names and has signed with. These are its own relayers, so nobody can switch its fast lane off by posting orders at its price while its stock is busy. Their unfilled orders are still struck and shown under `neverBenched`, but the strikes never add up to a bench. When the setting is empty, every relayer can be benched.

- It is not a strike if any relayer’s fill earned the fee: the sender was served.
- It is not a strike if another address filled the order without earning the fee within 90 seconds of it being posted and before the block that settled it, because that fill paid the recipient early and shut you out. A fill made later, or in the settling block itself, delivered nothing early, so it does not excuse you.
- It is not a strike if you could not have filled it. When the app gives your price it reads, at one block of the destination chain, your balance of that launch’s token and your approval of the fast bridge, and it only gives your price if they cover the fill. What you could pay out is the lower of the two. An order is matched to the last quote given before it was posted, if that was no more than a few minutes earlier (an order posted long after its quote counts against nobody), so asking for the quote again later changes nothing. Only when a strike is about to be given, the app subtracts what you have paid out since in other fills of that same launch that served somebody, until the order settled (or until it is judged, if it never did), or 90 seconds after it was posted if that is later. If what is left is less than the order pays, there is no strike. So somebody draining your stock with many small orders first does not turn the larger ones after them into strikes. A fill that would excuse you but has not settled yet is waited for, up to 30 minutes after the order was posted.
- A fill counts only if it was your own direct call of the bridge’s fillOrder for that launch (not through a contract or a batch, which cannot be read), came from an EVM chain, paid someone other than you, and earned the fee: it named the order’s own message and came before the block that settled it. A fill of an order nobody posted never settles, and one that names the wrong message or lands in the settling block is repaid at once, so neither excuses anything. Fills of orders from Solana do not count, because a plain token send from Solana can make one without paying the fast lane’s fee. Moving your stock away or revoking the approval excuses nothing either, nor does running out of native gas: both are yours to top up. Repayments that arrive in the meantime are not added back.
- Into Solana only the balance read when the price was given is used, because there is no cheap list of your fills there. Orders into Solana are not judged yet.
- If a read fails, there is no strike then. The order is checked again later, a few times, and if it still cannot be checked it is dropped without a strike, and the server logs it. A rate limit or a slow node is such a failure. Having more fills in the window than are read, or more in one block than the chain will list, is not: it is no excuse, and neither is a drain made of more than that many fills. The app makes at most a few of these checks a minute, and fewer for any one relayer, so a strike can come a little later than the order was due.
- A fill does not clear earlier strikes. Each one stops counting 60 minutes after it was given.

### The risks you take

Relaying is a business you run with your own money. Nothing here promises that it pays.

- **A reorg on the source chain.** Between your fill and your repayment, the fill is unsecured. If the source chain reorganises and the order’s transaction is gone, the message never arrives and nothing repays you. How many blocks you wait before filling is your own trade between speed and that risk. If a reorg keeps the order but moves its message to a different LayerZero nonce, you are repaid what you paid out and earn nothing.
- **Filling before the order is in a block.** Acting on pending transactions is the fastest way to fill and the riskiest: a transaction that has not been mined is likelier to vanish than a confirmed one.
- **Inventory.** You hold the token on every chain you serve, and its price moves while you hold it. Your fee is paid in the token too.
- **Gas.** A fill that reverts still costs gas, and gas is paid in the chain’s native asset while your fee is paid in the token. A price that does not cover gas loses money slowly.
- **Time.** Your tokens are out until the order settles. Settlement is run by LayerZero’s executor; if it stalls, anyone may run it, and until someone does you wait.
- **Being shut out.** Only one fill per order is accepted. Another address can fill ahead of you, even with a nonce that earns it nothing, and that order is then closed to you.

### Contract addresses

One fast bridge per chain, shared by every launch: Ethereum, Base, Robinhood Chain and BSC (deployed 16 October 2026), then Solana. The full interface is the bridge’s ABI and `INTERFACE.md`. These are the addresses, and the endpoint ids a price sheet and a fill use:

| Chain | Fast bridge | Endpoint id | Chain id |
| --- | --- | --- | --- |
| Ethereum | 0xE6feCfb8520Ce9417beB569ef95d6b8304802897 | 30101 | 1 |
| Base | 0x51D658a71AEa38f123d4c73e42703Fb9C949900A | 30184 | 8453 |
| Robinhood Chain | 0xcC87BeDb95E4ac0cf20039442A964ADB390c02c4 | 30416 | 4663 |
| BSC | 0xd7C10226327451ac768512aC4A5035a9477f862a | 30102 | 56 |

> **Note:** [How a standard transfer works →](https://www.pangea.trade/app/docs/v2#v1-bridging)

## For integrators and AI agents

Pangea is a token launchpad. Each launch deploys its token as five contracts, a LayerZero OFT on Ethereum, Base, Robinhood Chain and BSC and a mint on Solana, and opens one liquidity pool per chain at the same US dollar price. This section is the reference for reading and using it from code.

These docs are also published as Markdown, generated from the same source as this page:

- Index for language models: `https://www.pangea.trade/llms.txt`
- Everything in one file: `https://www.pangea.trade/llms-full.txt`
- These docs as Markdown: `https://www.pangea.trade/app/docs.md`

Each section is also on its own, at `/app/docs/<section id>.md`. This section is `/app/docs/integrators.md`.

### The five chains

| Chain | Name in the API | Chain id | Endpoint id |
| --- | --- | --- | --- |
| Ethereum | ethereum | 1 | 30101 |
| Base | base | 8453 | 30184 |
| Robinhood Chain | robinhood | 4663 | 30416 |
| BSC | bsc | 56 | 30102 |
| Solana | solana | none | 30168 |

The endpoint id is LayerZero’s, and it is how the token names a chain. Solana has no EVM chain id.

### Units

- Every token has 9 decimals, on all five chains. An amount in base units is a whole number at 9 decimals: one token is 1,000,000,000 base units.
- A transfer between chains crosses at 6 decimals. It is rounded down to a multiple of 1,000 base units, and the odd base units stay in the sender’s wallet.
- Fees are in basis points. One basis point is 0.01%.
- The standard supply is 5 billion tokens, 1 billion in each chain’s liquidity pool at launch. On an EVM chain `totalSupply()` starts at that chain’s 1 billion and is a reported figure that moves no balance, so every chain shows a market cap on the same basis ([LayerZero routes](https://www.pangea.trade/app/docs/v2#v1-routes) says how it is reported); `launchTotal()` is the sum across the launch’s chains, and `localSupply()` what is on that chain now. On Solana the mint’s supply is what is on Solana now: tokens bridged in are minted there and tokens bridged out are burned. [The market cap situation](https://www.pangea.trade/app/docs/v2#v1-market-cap) explains why terminals show different market caps.
- A launch on selected chains is 1B on each of its chains only. A degen mode launch is 1B in all: each chain mints an equal share, every chain’s `totalSupply()` and `launchTotal()` report the 1B, and on Solana the mint holds the full 1B with the rest in its bridge reserve. [Degen mode](https://www.pangea.trade/app/docs/v2#degen-mode) has the figures.
- Times in the API are unix seconds unless a field says otherwise.

### Rules that surprise people

- Every launch is paid for by the person launching it, once, on the chain they pick, in that chain’s native asset. There are no free launches.
- Every launch opens at the same price on every chain, at the market cap its creator chose for the whole coin, every chain together: $3,000 to $500,000 a chain times the chains (in all, for a degen mode coin), signed into the launch. The price per token is that market cap over the whole supply. Launches before factory v2 all opened at $3,000 a chain, $0.000003 a token. Read a token’s price from the API; do not assume its opening one. On factory v3 there is no locked supply: degen mode is normal mode, every chain minting its own 1B and pooling all of it, opening at $600 a chain ($3,000 for a coin on five chains). The least a v3 launch may open at is $600 a chain, which the platform may set between $600 and $3,000 a chain; standard mode still opens at $3,000 a chain unless the creator picks another cap.
- A token has one address per chain it is on, all different (or one address on the EVM chains, for a token launched with “Same contract address on all EVM chains”), and every one of them ends in `6ea`: the last three hex digits on the EVM chains, the last three characters of the mint on Solana. Read the addresses from the API. Do not assume two chains share one.
- A transfer between chains of a token from before factory v3 costs 0.2% of the amount, kept by the token for the platform, plus LayerZero’s message fee in the source chain’s native asset. A factory v3 token's bridge fee is its chain's registry figure (0 to 0.5%), and a transfer from the chain where it is cheaper to the chain where it is dearer also pays the price-gap fee, burned at the destination: the quote's gap.burned, an estimate. It is attested after 1 confirmation on its source chain and typically arrives in 4 to 16 seconds.
- Each launch sets its trading fee once, between 0.3% and 5%. No contract function or program instruction changes it afterwards.
- The first transfer on a Solana route nobody has used yet also pays a one-time setup fee for that route. Every later transfer on that route does not. Any wallet can pay it, a smart-contract wallet too, except a Safe: its transactions wait for its owners, so a Safe bridges once the route is set up. Pay it from your own wallet, as the bridge page does: a payment sent through another contract (a multicall or a router) is refunded to that contract, not to you, and one made by delegatecall is not seen at all.
- On the four EVM chains nothing can mint more of a token. On Solana the mint authority is the token's store in Pangea's deployment of LayerZero's OFT program, which mints exactly what was burned on another chain. Bridging burns on one chain exactly what it mints on another, with one exception: a factory v3 token's price-gap fee is withheld at the destination and never minted, so it is burned.
- Prices on the five chains are separate markets. They are held together by arbitrage, not by a mechanism, and can differ.

### What you can and cannot do from code

- **You can read** every launch’s pools, prices, reserves and addresses, its trades and volume, recent bridge transfers and its metadata, over the HTTP endpoints below.
- **You can trade** directly against the pools: Uniswap v4 on the EVM chains, Meteora DAMM v2 on Solana. The API gives each pool’s id and venue.
- **You can bridge** by calling the token itself, or price a transfer over HTTP and sign the transaction it returns.
- **You can launch** over the Launch API: quote a launch, pay for it from your own wallet, hand it over and follow it. The price snapshot every chain launches with is still signed by the platform, after your payment is checked. [Launch API →](https://www.pangea.trade/app/docs/v2#launch-api)
- **No API key is needed and there are no accounts.** Every endpoint here is public. Where one is limited, the limit is per caller, by IP address. The Launch API has no rate limit for normal use, and takes an optional partner key: [API keys and limits →](https://www.pangea.trade/app/docs/v2#launch-api).
- **Do not crawl the API.** Each read costs reads on five chains. Call an endpoint when you need its answer; `robots.txt` asks crawlers to stay out of `/api/`.

### HTTP endpoints

All are under `https://www.pangea.trade/api`. They take and return JSON and need no key. Over a limit the answer is `429` with a message saying how many seconds to wait. An error is `{ "error": "..." }`. A value the chains could not give is `null` rather than a guess, so check for it.

| Endpoint | What it returns | Limit per caller |
| --- | --- | --- |
| `GET /pools` | Every launch, with its pool on each chain: addresses, price, reserves, supply. | None. Every caller is served one shared reading: re-read as soon as a trade moves a pool while the live page is open, and at least every 60 seconds unless the deployment sets another interval. |
| `GET /history` | Trades, volume in US dollars and price candles. | 30 uncached reads in ten minutes. An answer is reused for 45 seconds. |
| `GET /bridge/activity` | Recent transfers between chains. | None. |
| `GET /token/meta` | A token’s own metadata document: description, image, links. | None. |
| `POST /bridge/quote` | A transfer between chains, priced, with the transaction to sign. | 120 in ten minutes and 15 in any one minute. |

> **Note:** Every public endpoint, including the ones not described here, is in [API reference →](https://www.pangea.trade/app/docs/v2#v1-api)

- Pools, GET: `https://www.pangea.trade/api/pools`

No parameters. The answer is `{ live, readAt, rates, blockers, launches }`. When nothing has launched, `live` is false and `launches` is empty. Each launch carries:

| Field | What it is |
| --- | --- |
| `slug`, `name`, `symbol` | The launch’s name in every other endpoint (`slug`), and its name and symbol. |
| `pools[]` | One entry per chain, including chains that could not be read. |
| `pools[].chain` | The chain’s name in the API. |
| `pools[].token` | The token’s address on that chain. This is where a token’s five addresses are published. |
| `pools[].poolId`, `pools[].venue` | The pool’s id and the venue it is on. |
| `pools[].price`, `pools[].priceUsd` | Quote asset per token, and the same in US dollars. `quoteSymbol` names the quote asset. |
| `pools[].reserves`, `pools[].tokenReserves` | What the pool holds: quote asset, and whole tokens. |
| `pools[].localSupply` | Whole tokens on that chain now. |
| `pools[].error` | Why that chain has no reading, when it has none. Set only when there is no price. |
| `pools[].note` | `{ kind, text }`, when present: a fact about a pool whose price was read and is good, never an outage. `kind` is `"extra-liquidity"` (others added liquidity to the pool; reserves are the launch’s position only), `"range-unknown"`, `"position-unknown"` or `"position-mismatch"` (the reserves could not be worked out). |
| `launchTotal`, `launchTotalRaw` | The token’s total supply in whole tokens, and in base units as a string. For a perChain launch, 1B on each chain it is on; for a degen mode launch, 1B. |
| `supplyModel`, `capSupply` | `"perChain"` for a launch with 1B on each chain (1B per chain · 5B across 5 chains), `"global"` for a degen mode launch and for launches before 2026-10-15, `"per-chain"` for a v4 coin (1B per opened chain; `capSupply` the supply across its open chains, `capSupplyMax` 1B times its listed chains). Market cap is a price times `capSupply`, which is `launchTotal`: the whole supply across the launch’s chains (5B for perChain on five). For a degen mode coin it is `circulating`: the 1B less what was burned; `circulating` is null for any other coin. |
| `degen` | `true` for a coin launched in degen mode: 1B in all, every chain reporting the 1B. |
| `launchChains`, `chainsTotal` | The chains the launch is on, and how many: five unless it was launched on selected chains. |
| `cheapest`, `spreadUsd` | The chain with the lowest US dollar price, and the gap between the highest and lowest price as a fraction. |
| `sold`, `burnt` | Whole tokens that have left the pools, and whole tokens sent to the burn addresses across chains: 0x…dEaD on each EVM chain, the incinerator's token account on Solana. |
| `metadataURI` | The token’s metadata document, as the contract names it. |

- History, GET: `https://www.pangea.trade/api/history`

Query: `launch`, a slug, or leave it out for every launch; `hours`, a whole number from 1 to 168, 24 unless given. The answer is `{ readAt, hours, cursor, rates, totals, launches }`. An unknown slug is a `404`. Each launch carries `volumeUsd`, `trades`, `coverage`, `chains`, `candles` and `recent`.

- `coverage` runs from 0 to 1. Below 1 the figures describe part of the window you asked for, not all of it. Show it, or check it.
- `candles` are `{ t, o, h, l, c, v, vu }`, one per bucket that traded, where `t` is the bucket's start. A bucket with no trade is left out, not filled with the last price. The prices are in US dollars. `v` is the quote volume in each chain's own asset, so across chains it adds ether, BNB and SOL together; `vu` is the bucket's volume in US dollars at the same rates as `volumeUsd`, and null when a trade in it had no rate.
- `recent` is the last trades, newest first: `{ id, chain, timestamp, txHash, side, tokenAmount, quoteAmount, quoteSymbol, priceUsd, valueUsd }`, where `side` is `buy` or `sell`.
- A coin's whole history, a page at a time: add `trades=latest` (the newest page) or `trades=before:<next>` (the page after the one that gave `next`) with `launch`, and `limit` of 50, 100 or 200. The answer is `{ slug, readAt, trades, next }`, every chain newest first, the rows as in `recent`; `next` is null on the page that reached the first trade. The first page also carries `total`, every trade since the launch, and `complete`, whether every chain is recorded back to it.

- Bridge activity, GET: `https://www.pangea.trade/api/bridge/activity`

Query: `token`, a slug, optional; `hours`, 1 to 168, 24 unless given; `limit`, 1 to 100, 25 unless given. The answer is `{ recording, hours, since, uncovered, totals, cursor, transfers }`. A transfer is `{ id, guid, slug, from, to, at, amount, party, complete, tookSeconds }`.

- `recording` false means this deployment keeps no record, and the list is empty for that reason, not because nobody bridged. `reason` says why.
- `uncovered` lists the chains whose sends are not recorded. A transfer out of such a chain appears as an arrival with no send.
- `complete` false means one side is recorded: a transfer in flight, or one that failed. The feed does not guess which.

- Token metadata, GET: `https://www.pangea.trade/api/token/meta`

Query: `launch`, a slug, required. The answer is `{ uri, resolvable, name, symbol, description, image, website, twitter, telegram }`, or `{ uri, resolvable: false, reason }` when the document cannot be opened. Every field is the token author’s own claim. The name and symbol that cannot be edited are the contract’s, in the pools endpoint.

- Bridge quote, POST: `https://www.pangea.trade/api/bridge/quote`

The body you POST:

```json
{
  "slug": "<launch slug>",
  "from": "base",
  "to": "bsc",
  "amount": "1000000000000",
  "user": "0x<sender, and the recipient unless one is given>"
}
```

| Field | What it is |
| --- | --- |
| `slug` | The launch. |
| `from`, `to` | Chain names, as in the table of chains above. They must differ. |
| `amount` | Base units, as a decimal string. |
| `user` | The EVM address sending, and the recipient on an EVM chain unless one is given. |
| `solanaUser` | The Solana address, when Solana is the source or the destination. |
| `recipient` | Who receives, when it is not the sender. Required for a transfer between an EVM chain and Solana: across the two a transfer is never sent to a wallet by default. |

The answer is `{ symbol, from, to, amount, routes }`, where `routes` holds two routes: `canonical`, the token’s own LayerZero transfer, and `fast`, the same transfer with a fast order. A route that cannot be used says why in `unavailable`. It carries:

| Field | What it is |
| --- | --- |
| `id` | `canonical` or `fast`. The fast route’s own fields are in [API reference](https://www.pangea.trade/app/docs/v2#v1-api). |
| `amountOut` | What the transfer credits before a factory v3 token's price-gap fee, in base units. For a token from before factory v3 that is what the recipient receives. For a factory v3 token what arrives is `amountOut` less `gap.burned` (an estimate, in `gap.decimals`; every v3 token has 9 decimals on every chain) and, into Solana, less `gap.delivery.amount` too (our delivery key's cost there), and never less than `gap.minCredited`: below it the arrival waits at the destination. Never build your own minimum from `amountOut` alone. |
| `gap` | A factory v3 token only: the price-gap estimate for this transfer. `state` is `estimate`, `free` (nothing withheld), `held` (the destination cannot price it yet: it waits there) or `unavailable` (no standard route now; ask again); `burned` what the destination withholds, `minCredited` the least it may credit (`gapToleranceBps` under the estimate), and `minCreditedSource` that minimum in the source's decimals, out of Solana. |
| `nativeFee` | What it costs in the source chain’s native asset, in its smallest unit. Send it as the transaction’s value. |
| `tokenFee`, `platformFee` | Everything taken in the token, and the platform’s part of it, in base units. Both are the token’s fee. |
| `seconds` | The estimated time. |
| `call` | The transaction to sign: `{ to, data, value, chainId }`. Null when the transfer cannot be built as one contract call, as out of Solana. |
| `setupFee` | Present when this transfer opens a Solana route: the part of `nativeFee` that pays for it, once. |
| `unavailable` | Present when the route cannot be used, with the reason. |

### Bridging from code

On an EVM chain the token is a LayerZero OFT and you call it directly. Ask `quoteOFT` what a transfer debits and delivers, name the delivered amount as the minimum, ask `quoteSend` for the message fee, and send that fee as the transaction’s value. Leave `extraOptions` empty: the gas the destination needs is already enforced by the token.

On the token, on the chain the transfer leaves:

```solidity
struct SendParam {
    uint32 dstEid;       // the destination's endpoint id
    bytes32 to;          // the recipient, left-padded to 32 bytes
    uint256 amountLD;    // base units
    uint256 minAmountLD; // the least that may arrive
    bytes extraOptions;
    bytes composeMsg;
    bytes oftCmd;
}
struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; }
struct OFTReceipt { uint256 amountSentLD; uint256 amountReceivedLD; }

function quoteOFT(SendParam calldata _sendParam)
    external view returns (OFTLimit memory, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory);
function quoteSend(SendParam calldata _sendParam, bool _payInLzToken)
    external view returns (MessagingFee memory);
function send(SendParam calldata _sendParam, MessagingFee calldata _fee, address _refundAddress)
    external payable returns (MessagingReceipt memory, OFTReceipt memory);

event OFTSent(bytes32 indexed guid, uint32 dstEid, address indexed fromAddress, uint256 amountSentLD, uint256 amountReceivedLD);
event OFTReceived(bytes32 indexed guid, uint32 srcEid, address indexed toAddress, uint256 amountReceivedLD);

error AmountTooSmallToBridge(uint256 amount);
error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);
```

| Read on the token | What it returns |
| --- | --- |
| `BRIDGE_FEE_BPS()` | A token from before factory v3: its bridge fee in basis points, 20. A factory v3 token's fee is its chain's registry figure (0 to 50): take what arrives from `quoteOFT`. |
| `bridgeFeeTo()` | Where that fee goes on this chain. |
| `peers(uint32 eid)` | The token’s counterpart on another chain. Zero means there is no route to it. |
| `decimals()` | 9, on every chain. |
| `totalSupply()` | The reported supply: this chain’s 1B slice, or, for a launch with a SupplyReporter, the token’s supply on Solana as the chain’s SupplyReporter mirrors it (on a factory v1 token, never below `localSupply()` or above `launchTotal()`). For a degen mode launch, the 1B total, which never changes. |
| `launchTotal()`, `launchSlice()` | The whole launch’s supply across every chain, and this chain’s slice of it. |
| `supplyReporter()` | The only address that may report this token’s supply. Launches made before reporting existed have no such function. |
| `localSupply()` | What is on this chain now. |
| `metadataURI()` | The token’s metadata document. |

On Solana the token is an account of LayerZero’s OFT program, `SNyKyiZYU4R54SF9sYLmjCaCUsa1sC6QouNAj3x3Zzf`, and a transfer out of Solana is an instruction to that program, not a contract call. Into Solana the recipient is the wallet that will own the tokens.

### Launches on chain

On each EVM chain the launch factory emits one event per launch. The current factories’ addresses are in [LayerZero routes](https://www.pangea.trade/app/docs/v2#v1-routes).

On each EVM chain’s launch factory:

```solidity
event Launched(
    bytes32 indexed launchId,
    address indexed token,
    address splitter,
    address launchOwner,
    address locker,
    uint128 liquidity,
    uint256 dust
);
```

### Addresses

- A token’s five addresses are in the pools endpoint, at `launches[].pools[].token`, and on the token’s page.
- The current launch factories, and each chain’s LayerZero endpoint, libraries, executor and verifiers, are in [LayerZero routes](https://www.pangea.trade/app/docs/v2#v1-routes).

## Bridging from code

On the four EVM chains you call the token itself: it is a LayerZero OFT. Out of Solana the send is an instruction to LayerZero’s OFT program, and the simplest way to build it is to ask Pangea’s API for the unsigned transaction. The signatures and events are in [For integrators](https://www.pangea.trade/app/docs/v2#v1-integrators); the routes they travel are in [LayerZero routes](https://www.pangea.trade/app/docs/v2#v1-routes).

- `amountLD` is in base units, nine decimals. The token keeps 0.2% of it (a factory v3 token: its chain's registry figure, at most 0.5%), rounded down, and what crosses is rounded down to six decimals. `quoteOFT` returns both figures, exactly.
- `minAmountLD` is the least that may arrive, or the send reverts with `SlippageExceeded`. On a token from before factory v3 the fee is fixed, so the received amount from `quoteOFT` can be used as it is; on a factory v3 token the fee is read at every send, so ask `quoteOFT` right before you send.
- `extraOptions` stays empty. The token adds its own gas floor to every send; anything you add is added on top and paid for by you.
- **A factory v3 token: send the minimum.** Its `oftCmd` must carry your least credited after the price-gap fee: `abi.encode(uint256 minCreditedLD)` in the source's base units (the low 248 bits; the top byte is flags, 0). With an empty `oftCmd` the destination credits whatever the gap leaves. Take it from the quote's `gap` object (`gap.minCredited`, destination units; `gap.minCreditedSource` out of Solana), set by `gapToleranceBps` (default 100, at most 2000). Its bridge fee is its chain's registry figure, 0 to 0.5%: read what arrives from `quoteOFT`.
- `to` is 32 bytes: an EVM address left-padded with zeros, or a Solana wallet’s public key. Never a Solana token account.
- `quoteSend` gives LayerZero’s fee for that exact `SendParam`. Send it as the transaction’s value, with `payInLzToken` false. Any excess goes back to the refund address.
- No approval is needed for a standard send: the token burns from your own balance.

### EVM to EVM, with viem

Quote a transfer of 1,000 PTEST4 from Base to BSC:

```ts
import { createPublicClient, http, parseAbi, pad } from "viem";
import { base } from "viem/chains";

const OFT = parseAbi([
  "struct SendParam { uint32 dstEid; bytes32 to; uint256 amountLD; uint256 minAmountLD; bytes extraOptions; bytes composeMsg; bytes oftCmd; }",
  "struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; }",
  "struct OFTLimit { uint256 minAmountLD; uint256 maxAmountLD; }",
  "struct OFTFeeDetail { int256 feeAmountLD; string description; }",
  "struct OFTReceipt { uint256 amountSentLD; uint256 amountReceivedLD; }",
  "struct MessagingReceipt { bytes32 guid; uint64 nonce; MessagingFee fee; }",
  "function quoteOFT(SendParam _sendParam) view returns (OFTLimit, OFTFeeDetail[], OFTReceipt)",
  "function quoteSend(SendParam _sendParam, bool _payInLzToken) view returns (MessagingFee)",
  "function send(SendParam _sendParam, MessagingFee _fee, address _refundAddress) payable returns (MessagingReceipt, OFTReceipt)",
]);

const TOKEN = "0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea"; // PTEST4 on Base
const BSC = 30102;
const recipient = "0x000000000000000000000000000000000000bEEF"; // who receives on BSC
const amount = 1_000n * 10n ** 9n; // 1,000 tokens: nine decimals

const client = createPublicClient({ chain: base, transport: http(process.env.BASE_RPC) });

// What the transfer debits and what arrives: 0.2% stays with the token, the rest crosses.
const sendParam = {
  dstEid: BSC,
  to: pad(recipient), // an EVM address, left-padded to 32 bytes
  amountLD: amount,
  minAmountLD: 0n,
  extraOptions: "0x", // empty: the token already enforces the gas the destination needs
  composeMsg: "0x",
  oftCmd: "0x",
};
const [, , receipt] = await client.readContract({ address: TOKEN, abi: OFT, functionName: "quoteOFT", args: [sendParam] });
sendParam.minAmountLD = receipt.amountReceivedLD; // the least that may arrive

// LayerZero's fee for this exact transfer, in ETH.
const fee = await client.readContract({ address: TOKEN, abi: OFT, functionName: "quoteSend", args: [sendParam, false] });
console.log({ sent: receipt.amountSentLD, arrives: receipt.amountReceivedLD, nativeFee: fee.nativeFee });

// The send, from the holder's wallet, paying the fee as the value:
// await wallet.writeContract({ address: TOKEN, abi: OFT, functionName: "send",
//   args: [sendParam, fee, account.address], value: fee.nativeFee });
```

Run against Base on 19 October 2026, it printed `sent: 1000000000000n, arrives: 998000000000n, nativeFee: 109546395808232n`: 1,000 tokens sent, 998 arrive, and about 0.00011 ETH for LayerZero.

### The same, with cast

quoteOFT and quoteSend from the command line:

```bash
TOKEN=0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea
RPC=your-base-rpc-endpoint
TO=$(cast pad --len 32 0x000000000000000000000000000000000000bEEF)

# what is sent and what arrives: (limit), (fee details), (sent, received)
cast call $TOKEN "quoteOFT((uint32,bytes32,uint256,uint256,bytes,bytes,bytes))((uint256,uint256),(int256,string)[],(uint256,uint256))" \
  "(30102,$TO,1000000000000,0,0x,0x,0x)" --rpc-url $RPC

# LayerZero's fee in wei: (nativeFee, lzTokenFee)
cast call $TOKEN "quoteSend((uint32,bytes32,uint256,uint256,bytes,bytes,bytes),bool)((uint256,uint256))" \
  "(30102,$TO,1000000000000,998000000000,0x,0x,0x)" false --rpc-url $RPC
```

> **Note:** Pad an address with `cast pad`, which pads on the left. `cast to-bytes32` pads on the right, and a send to that would go to a different address.

### EVM to Solana

The same send, with `dstEid 30168` and the recipient’s Solana wallet as the 32 bytes. The tokens arrive in that wallet’s token account, created on delivery; its rent, 2,074,080 lamports, is in every transfer’s gas floor and so in `quoteSend`’s fee.

Quote a transfer from Base to Solana, and check the route is open:

```ts
import { createPublicClient, http, parseAbi, toHex } from "viem";
import { base } from "viem/chains";
import { Connection, PublicKey } from "@solana/web3.js";

const OFT = parseAbi([
  "struct SendParam { uint32 dstEid; bytes32 to; uint256 amountLD; uint256 minAmountLD; bytes extraOptions; bytes composeMsg; bytes oftCmd; }",
  "struct MessagingFee { uint256 nativeFee; uint256 lzTokenFee; }",
  "struct OFTLimit { uint256 minAmountLD; uint256 maxAmountLD; }",
  "struct OFTFeeDetail { int256 feeAmountLD; string description; }",
  "struct OFTReceipt { uint256 amountSentLD; uint256 amountReceivedLD; }",
  "function quoteOFT(SendParam _sendParam) view returns (OFTLimit, OFTFeeDetail[], OFTReceipt)",
  "function quoteSend(SendParam _sendParam, bool _payInLzToken) view returns (MessagingFee)",
  "function peers(uint32 eid) view returns (bytes32)",
]);

const TOKEN = "0x2dEC1F0FBe66ef78a7cec00cD1FD9c58345ED6Ea"; // PTEST4 on Base
const BASE_EID = 30184;
const SOLANA = 30168;
const owner = new PublicKey("<the Solana wallet that will own the tokens>");
const amount = 1_000n * 10n ** 9n;

const evm = createPublicClient({ chain: base, transport: http(process.env.BASE_RPC) });
const solana = new Connection(process.env.SOLANA_RPC, "confirmed");

// Is this token's route from Base open on Solana? It is once LayerZero's receive config exists.
const peer = await evm.readContract({ address: TOKEN, abi: OFT, functionName: "peers", args: [SOLANA] });
const store = new PublicKey(Buffer.from(peer.slice(2), "hex")); // the OFT store
const eid = Buffer.alloc(4);
eid.writeUInt32BE(BASE_EID);
const ULN = new PublicKey("7a4WjyR8VZ7yZz5XJAKm39BUGn5iT9CKcv2pmG9tdXVH");
const [receiveConfig] = PublicKey.findProgramAddressSync([Buffer.from("ReceiveConfig"), eid, store.toBuffer()], ULN);
const open = (await solana.getAccountInfo(receiveConfig)) !== null;

const sendParam = {
  dstEid: SOLANA,
  to: toHex(owner.toBytes()), // the owner's public key as 32 bytes, not a token account
  amountLD: amount,
  minAmountLD: 0n,
  extraOptions: "0x", // the token adds the gas floor and the rent of the token account
  composeMsg: "0x",
  oftCmd: "0x",
};
const [, , receipt] = await evm.readContract({ address: TOKEN, abi: OFT, functionName: "quoteOFT", args: [sendParam] });
sendParam.minAmountLD = receipt.amountReceivedLD;
const fee = await evm.readContract({ address: TOKEN, abi: OFT, functionName: "quoteSend", args: [sendParam, false] });
console.log({ store: store.toBase58(), routeOpen: open, arrives: receipt.amountReceivedLD, nativeFee: fee.nativeFee });
// Then send exactly as from EVM to EVM. With routeOpen false, the transfer waits on Solana
// until the route is opened: see the route setup below.
```

If the token’s route from that chain is not open on Solana yet, a transfer sent now waits there until it is. To open it as part of your transfer, as the Bridge page does, `POST /api/bridge/quote` returns the setup payment in `routePayment`: send it first, then report it to `POST /api/bridge/route-open`, which opens the route or refunds the payment, and then send the transfer. [API reference →](https://www.pangea.trade/app/docs/v2#v1-api)

### Solana to EVM, with @solana/web3.js

Out of Solana the send is LayerZero’s OFT program’s own instruction, with about thirty accounts and a lookup table. `POST /api/bridge/solana-tx` builds it, unsigned, for your wallet to sign: it names the accounts, sets the minimum to what arrives, and adds the route’s opening when a route is used for the first time. You pay LayerZero’s fee and the transaction fee in SOL; the token’s 0.2% is kept in the OFT store’s escrow for the platform.

Price and build a transfer from Solana to Base, then simulate it:

```ts
import { Connection, VersionedTransaction } from "@solana/web3.js";

const API = "https://www.pangea.trade/api";
const sender = "<the Solana wallet holding the tokens>";
const recipient = "0x000000000000000000000000000000000000bEEF"; // who receives on Base
const amount = (1_000n * 10n ** 9n).toString(); // 1,000 tokens, in base units

const post = (path, body) =>
  fetch(API + path, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })
    .then(async (r) => ({ status: r.status, body: await r.json() }));

// The price: what arrives on Base, and LayerZero's fee in lamports.
const quote = await post("/bridge/quote", { slug: "ptest4", from: "solana", to: "base", amount, user: recipient, recipient, solanaUser: sender });
const standard = quote.body.routes?.find((r) => r.id === "canonical");
if (!standard || standard.unavailable) throw new Error(standard?.unavailable ?? quote.body.error);
// A factory v3 token: what arrives is amountOut less the gap estimate, never less than gap.minCredited.
const gap = standard.gap ?? null;
console.log({ credited: standard.amountOut, gapBurned: gap?.burned, nativeFee: standard.nativeFee, setupFee: standard.setupFee ?? null });

// The unsigned transaction: the OFT send, and the route's opening if it is not open yet.
// A factory v3 token's send must name its minimum (minCredited, Solana base units): without one it is refused (400).
const ask = { slug: "ptest4", to: "base", amount, sender, recipient, ...(gap ? { minCredited: gap.minCreditedSource } : {}) };
let built = await post("/bridge/solana-tx", ask);
// 202 { pending: true }: a price record or a route opening is still landing -- ask again in a moment.
while (built.status === 202) {
  await new Promise((r) => setTimeout(r, 2_000));
  built = await post("/bridge/solana-tx", ask);
}
// stage "record" (a v3 token with no recent price on record): sign and send it, then ask again with
// { ...ask, recorded: <its signature> }; the answer is then the send.
if (built.status !== 200) throw new Error(built.body.error ?? `stage ${built.body.stage}`);
const tx = VersionedTransaction.deserialize(Buffer.from(built.body.transaction, "base64"));

// Check it before signing: a simulation sends nothing.
const connection = new Connection(process.env.SOLANA_RPC, "confirmed");
const sim = await connection.simulateTransaction(tx, { sigVerify: false });
console.log({ stage: built.body.stage, received: built.body.received, totalFee: built.body.totalFee, error: sim.value.err });

// Sign with the sender's wallet and send:
// const signed = await wallet.signTransaction(tx);
// await connection.sendRawTransaction(signed.serialize());
```

Run read-only on 19 October 2026 from a Solana holder, it built a transaction at stage `send` with `received: 998000000000` and a fee of at most 636,478 lamports, and the simulation passed. The stages are `send`, the transfer alone; `together`, the route’s opening and the transfer in one transaction; and `setup`, the opening alone, after which you ask again with `after` set to its signature. A factory v3 token adds `record`: the price record its send needs first (its signature fee only), after which you ask again with `recorded` set to its signature. A `202` with `pending: true` means the step before is still landing: ask again in a moment. A v3 token's send also needs `minCredited` (the quote's `gap.minCreditedSource`), or it is refused with a `400`.

### After the send

- The send emits `OFTSent(guid, ...)` on the source chain and the delivery `OFTReceived(guid, ...)` on the destination. The guid is the transfer’s id on LayerZero Scan.
- Look the guid or the transaction up on LayerZero Scan to follow it, or poll the destination for `OFTReceived` with that guid.
- Delivery needs nothing from you. If a message sits verified and undelivered, anyone may deliver it, except a factory v3 token's charged arrival into Solana, which only Pangea's appointed deliverers may land.

## API reference

All endpoints are under `https://www.pangea.trade/api`, take and return JSON, and need no key or account. The general rules are in [For integrators](https://www.pangea.trade/app/docs/v2#v1-integrators), which also describes five of them in full: pools, history, bridge activity, token metadata and the bridge quote. The relayers’ price board is in [Relayers](https://www.pangea.trade/app/docs/v2#v1-relayers). This section lists all of them and describes the rest.

- An error is `{ "error": "..." }` with a 4xx status (a 5xx is a fault, and its body may be a proxy's own). A value the chains could not give is `null`, not a guess.
- Limits are per caller, by IP address, counted on each server. Over a limit the answer is `429` and the message says how many seconds to wait; some also send `Retry-After`.
- A JSON body that is too large is `413`, one that is compressed is `415`, one that does not arrive within 10 seconds is `408`, and one that is not a JSON object of the expected fields is `400`.
- Call them from a server or a script. Only the supply endpoints (`/supply/[token]` and its two plain-number forms), `/gap-price/[id]` and `/gap-price/quote-usd` send `Access-Control-Allow-Origin: *`; the rest send no CORS header, so a web page on another site cannot read them from the visitor’s browser.
- Answers are cached briefly in front of the app (most for 5 to 60 seconds, as each endpoint says), so polling faster than that returns the same answer.
- Amounts are base units as decimal strings. Chains are named `ethereum`, `base`, `robinhood`, `bsc` and `solana`. A launch is named by its slug.
- Three endpoints answer as a stream of server-sent events: `data: <json>` lines, with `content-type: text/event-stream`. Read them with a streaming fetch; they are POST, so an `EventSource` cannot open them. The Launch API’s event stream and the launches stream are GETs, in the same format with named events.
- A coin is named by its slug, its launch id or any chain’s token address wherever the path says `[id]`. The coin, its supply, the pools, the holders and the launches feed carry `degen` and the coin’s supply: circulating, the bridge reserve and where it is held, the total, and what each chain mints and each token reports.

### Every public endpoint

| Endpoint | What it is for | Limit per caller |
| --- | --- | --- |
| `GET /pools` | Every launch and its pool on each chain. | None; one shared reading. |
| `GET /history` | Trades, volume and price candles. | 30 in ten minutes, uncached reads only. |
| `GET /holders` | Who holds a token, per chain. | 60 in ten minutes, uncached reads only. |
| `GET /gap-price/[id]` | A shared-bridge coin’s signed gap price for one transfer: the coin id or its token, src and dst (chain id or name, several dst comma-separated), and optionally amount (the send in the source token’s base units). Pass the answer unchanged in the bridge call. 503 means no signed price right now: send without one (the chain’s own readers price it, or no gap fee). Not cached. | 20 at once, then 1 a second; all callers 50 a second. |
| `GET /gap-price/quote-usd` | The signed USD price of a coin’s native quote on one EVM chain, for a buy-and-bridge that carries its own quote price (buyAndSendQuoted): chain (chain id or name) and optionally quote (0x00..00, the native coin, is the only one signed). Pass signedQuote unchanged in the call, and fetch it right before: it is valid for about 30 seconds. 503 means no price right now; on Robinhood a buy then waits, since that chain has no other quote price. 404 for an ERC-20 quote. Not cached. | Shared with the gap price: 20 at once, then 1 a second; all callers 50 a second. |
| `GET /token/meta` | A token’s metadata document. | None. |
| `GET, POST /token/socials` | Who may edit a coin’s socials (its creator, EVM and Solana); with history=1 every version so far, each with the creator’s signature, and with onchain=1 each EVM chain’s metadata authority and URI; POST stores a new version the creator signed (EIP-712, or a signed message on Solana) with a new website, X, Telegram, Discord, description or logo. No transaction and no gas: /token/meta shows the newest version, marked as edited by the creator. | GET 120 a minute, 10 with onchain=1. POST 30 attempts in ten minutes; 10 stored edits an hour, and 6 an hour and 24 a day per coin. |
| `GET /logo/[cid]/[file]` | A token’s logo from this site, as square WebP: the file is 64.webp, 128.webp or 256.webp, and the CID is the image’s own, as /token/meta names it. Cached for a year: a CID’s picture cannot change. | None. |
| `GET /platform-funds` | What is still to be spent on PANGEA buybacks on each chain: the buyback wallets’ balance less what they keep for gas, plus their share of platform fees not yet forwarded, in native units and US dollars, with the total and the buyback wallets. | None; one shared reading a minute. |
| `GET /creator-rewards` | Everything launches’ trading fees have paid to their creators, all time: per chain in native units and US dollars at today’s prices, with the total and which chains are not counted in full. | None; one shared reading a minute. |
| `GET /holder-payouts` | Creator fees paid to holders by the coins whose fees go to a tokenomics vault: all time (every vault the coin’s fees have gone to, earlier ones included) and the last 24 hours, by chain, by coin and by reward token, as each vault’s own counters on chain say, in US dollars at today’s prices. | None; one shared reading a minute. |
| `GET /revenue` | Platform and creator fee payouts over any span (from, to), in US dollars at each payout’s own time, per chain and asset. | None; held 30 seconds. |
| `GET /buyback-burns` | Every PANGEA buyback so far, with its burn transaction: totals per chain, the latest 50, or every one a page at a time. | None; held 15 seconds. |
| `GET /cto` | The coins under a community takeover, and when each chain’s fee stream moved. | 120 a minute. |
| `GET /stock-pairs` | The coins with a stock-paired chain, and the stock on each. | 120 a minute. |
| `GET, POST /token/fee-native` | Whether a coin’s creator fees in its pair token (any pair but the chain’s own coin) are paid in the chain’s native coin; POST switches it, signed by the creator. | GET 120 a minute; POST 20 in ten minutes. |
| `GET /tokenomics` | One chain’s tokenomics vault, read from the chain: its split, what waits in each bucket, and what it has burned, bought and added. | 60 a minute. |
| `GET /tokenomics/v2/burn` | A tokenomics v2.2 vault’s burn tokens: the other tokens it buys to burn, and how much of each so far. | 30 a minute. |
| `GET /tokenomics/v2/holder` | What one holder is owed and has been paid by a coin’s vault, per reward token. | 30 a minute. |
| `POST /tokenomics/v4/switch` | Where v4 is switched on: a v4 coin’s tokenomics on one chain, built for your own wallet to sign; nothing is signed or sent here. `op: "enable"` (with `vault`) turns it on or re-points it, `"disable"` turns it off, `"clear"` clears a wrongly pinned vault. The creator may only turn it on, while it is off and the role is not renounced; the platform admin may turn it on, off and re-point it at any time (EVM `adminEnableTokenomics`; Solana `admin_enable_tokenomics`, on or re-point). For a Solana-home coin, `"push"` builds the creator role push from Solana, `"role"` reads it back, and `"authorize"` builds the open authorisation from Solana (`pusher`, `opener`, `deadline`). | 20 a minute. |
| `GET /v4/abi` | The full ABI of every v4 EVM contract and the IDL of every v4 Solana program, as JSON: all of them, or one with `?id=` (and `&kind=idl` for a program), or the list with each file’s sha256 with `?manifest=1`; add `&download=1` to save it as a file. | None. |
| `GET /v4/contracts` | Every v4 address the deploy recorded, per chain: its chain id, pcid and `contracts` by name (EVM addresses checksummed, Solana programs and accounts in base58). Before the first deploy it answers `published: false`. The top level is the first v4 set (v4a); `sets.v4b` is the second set (the redeploy new coins launch on), with its own addresses, once it is deployed. | None. |
| `POST /tokenomics/v4/account` | Where v4 full scope is switched on (`NEXT_PUBLIC_GAP_V4` and `NEXT_PUBLIC_GAP_V4_FULL` both `on`; 404 otherwise): what is at one Solana tokenomics account (`address`): whether it exists, its owner program and its data, read before a creator’s tokenomics setup is built, so a retry reuses the account. Reads only. | 60 in ten minutes. |
| `GET /coin/[id]/fee-receivers/pending` | A fee receiver change waiting to be accepted, per chain. | 60 a minute. |
| `GET /bridge/health` | Whether bridging is working: ok, or the problems. | None. |
| `POST /cosign` | A v3 transfer too large for one part, co-signed by Pangea’s delivery service, with its minimum credited. | 6 a minute, 30 in ten minutes. |
| `GET /trade/tx-status` | Whether an EVM transaction succeeded, failed or is pending. | 90 a minute. |
| `GET /bridge/activity` | Recent transfers between chains. | None. |
| `GET /bridge/balance` | One address’s balance of a token or the native asset. | 120 in ten minutes |
| `POST /bridge/quote` | A transfer priced, both lanes, with the transaction to sign. | 120 in ten minutes, 15 a minute. |
| `POST /bridge/solana-tx` | The unsigned transaction for a transfer out of Solana. | 60 in ten minutes, 10 a minute. |
| `POST /bridge/solana-fast-tx` | The same, as a fast order. | 60 in ten minutes, 10 a minute. |
| `POST /bridge/route-open` | Opens a Solana route once its setup payment has landed. | 120 in ten minutes |
| `POST /bridge/arrival` | A stream that says when a transfer has arrived. | 60 in ten minutes |
| `POST /bridge/v4/arrival` | Version-4 coins, where this site has them (404 otherwise): POST { slug, from, to, tx, guid? }, and each transfer of that source transaction toward to comes back landed, held below its minimum (the recipient can accept it at the credit it would get now), accepted, waiting for a Solana price record, or on its way, with the amounts in the token’s own decimals. Reads only; nothing is signed. | 120 in ten minutes |
| `GET /bridge/v4/history` | Version-4 coins, where this site has them (404 otherwise): ?slug= (and &wallet= for one wallet’s) the coin’s recent v4 transfers, each with what was sent, what was credited and what was burned on arrival, and the accepts, from the tokens’ own events; a chain that did not answer in time is named in unread, never left out silently. | 120 in ten minutes |
| `GET /bridge/v4/path` | A version-4 coin’s stored dollar price path on one chain, where this site has version 4 (404 otherwise): ?slug=&chain=, read from the chain now: none (its pool pairs with the native coin or a dollar token), ok, weak (thin: the dollars a 2% move takes on its thinnest pool, unmeasured, or a pool keeping a single price observation), depth not measured (Solana: the route reads, its depth is not measured yet) or does not read (arrivals priced by it wait or burn until it reads again or someone replaces it), with the quote’s decimals, and whether the path was replaced since launch (anyone can put the launch path back whenever it reads). The path judged is the one the coin prices by, as stored at launch or as replaced since. | 120 in ten minutes |
| `POST /launch/v4/buy-list` | A version-4 launch’s Solana list (launch buys on Solana), from this site’s launch page only: op read (the list and its money account as on chain), op blockhash (for the list’s own creator or payer), op band (whether SOL is still within the band the list was quoted at, and whether the launcher’s pool fee and OFT version are still the ones the list sealed). Nothing is built or signed here: the creator’s wallet signs the list. | 600 in ten minutes |
| `GET /launch/v4/pair-check` | A version-4 launch’s pair token, where this site has version 4 (404 otherwise): ?chain=&address=, and what it is before payment: refused only when it truly cannot work (and why: no token code, unreadable decimals or more than the pair maximum, a token that cannot sit in a pool such as NonTransferable or an active transfer hook), not open yet on that side, or ready. Any other token is allowed, at the creator’s risk: a missing or thin pool path to a dollar price is the “weak price path” mark, never a refusal; its own decimals, its transfer tax, and that mark when the price path its launch would use is missing, thin or unmeasured (on EVM: the dollars a 2% move takes on that path’s thinnest pool). On EVM the quote itself measures the pools again and launches with the deepest path; when a pool that could be deeper cannot be measured just now it refuses (“the pools could not be measured just now, try again”) rather than pick around it, and the quote names the path it chose. A path with a pool whose price read comes within 10,000 gas of its venue’s gas cap is not used (a touch could make it stop reading). No pair list exists: nothing here allows or refuses a token by name. | 60 in ten minutes |
| `POST /launch/v4/self-open` | A version-4 launch whose pay chain you launch from your own wallet (no Solana leg), where this site has version 4 (404 otherwise): { launchId, chain }, once your turn has come. It builds, and checks, the transactions your fee wallet sends, in order: any “keep two price observations” step a pool the coin’s price rests on still needs (anyone may send it), then the launch itself through that chain’s self-launch opening router, with what the opening owes priced now (a few base units of the pair token, bought with native and paid to the router). Nothing is sent from here, and the router takes the launch only from your fee wallet, with the one signature per chain you gave before paying (it names that chain’s router). If your time runs out, our launcher sends it: same price, same fee. | 30 in ten minutes |
| `POST /launch/v4/registered` | A shared-bridge coin you opened yourself, where this site has the shared-bridge self launch (404 otherwise): { chain, txHash } of the open, once it is mined. This site reads that transaction from the chain itself and lists the coin from what the chain says (its id, home, chain list and transports, its token); nothing you send is stored. The first listing of a coin is kept. | 20 in ten minutes |
| `POST /launch/v4/sol-open` | The Solana side of a shared-bridge coin you open yourself, where this site has the shared-bridge self launch (404 otherwise): { payer }, and the answer is a recent blockhash and the shared lookup table’s addresses, so your wallet can build and sign the open. Nothing is built, signed or sent here. | 60 in ten minutes |
| `POST /launch/v4/sol-held` | Opening Solana later with a wallet the coin’s home chain authorised, where this site has the shared-bridge self launch (404 otherwise): { coinId, homePcid }, and the answer is the bridge’s authorisation slot for the coin on Solana (its address, and its data or null when no authorisation has arrived yet). Your page checks it (the opener must be your wallet, before its deadline) and builds the open itself. Nothing is built, signed or sent here. | 30 in ten minutes |
| `POST /bridge/refund` | Whether a fast order is held, and the transaction that sends it back. | 60 in ten minutes |
| `POST /bridge/orders` | Tells the board a fast order was posted at a relayer’s price. | 60 in ten minutes |
| `GET, POST /bridge/relayer-quotes` | The relayers’ price board. | 300 in ten minutes reads, 120 publishes. |
| `POST /chain/tx` | A stream that says when a transaction is in a block. | 60 in ten minutes |
| `POST /trade/quote` | A buy or sell priced through Relay, or Jupiter on Solana. | 60 a minute. |
| `POST /trade/solana-tx` | Compiles a swap’s Solana instructions into an unsigned transaction. | 60 in ten minutes |
| `GET /token/launch-buys` | Who bought in a launch’s own block, per chain, and whether that block has been read. | 60 in ten minutes, uncached reads only. |
| `GET /coin/[id]` | One coin: its addresses, degen mode, its supply, and its tokenomics config as its launch signed it. No chain is read. | 120 a minute. |
| `GET /supply/[token]` | A coin’s total, circulating and locked supply, per chain and in all, in whole tokens (locked is always 0: every launch pools all of each chain’s share). perChain.supply is the slice the launch minted on that chain, not what is on it after bridging. For a v3 coin, what its bridges burned (the gap fee: a cheap-to-dear bridge withholds part and never mints it) comes off circulating, and off the total too unless the coin's total is fixed (degen); gapBurned says how much. For CoinGecko and CoinMarketCap. A chain that cannot be read is a 503, never a smaller figure. | 120 a minute. |
| `GET /supply/[token]/total` | The total supply alone, as a bare number (text/plain). | 120 a minute, shared. |
| `GET /supply/[token]/circulating` | The circulating supply alone, as a bare number (text/plain). | 120 a minute, shared. |
| `GET /coin/[id]/supply` | A coin’s supply: what its launch made, and for a degen mode coin the same read from the chains. | 60 a minute. |
| `GET /coin/[id]/supply/total` | Only the coin’s total supply, as plain text in whole tokens (the form CoinMarketCap and CoinGecko ask for): every chain’s supply counted once, less what was burned. Held a minute; 503 when the chains cannot be read and no recent figure is held. | 60 a minute. |
| `GET /coin/[id]/supply/circulating` | Only the coin’s circulating supply, the same way: the total less anything locked or out of public hands (the Solana bridge reserve is never counted, as it backs the other chains). | 60 a minute. |
| `GET /coin/[id]/tokenomics` | A coin’s tokenomics config, and each chain’s vault read from the chain: what waits in each bucket, and what was burned, bought and added to liquidity. | 20 a minute. |
| `GET /coin/[id]/vault-summary` | What a coin’s tokenomics vault has done, all time per coin: every vault the coin’s fees have gone to on each chain, earlier ones included (each listed in previousVaults), summed per option in US dollars at today’s prices; an earlier vault not read makes the answer partial and names its chain. 404 for a coin whose fees go to no vault. | 30 a minute. |
| `GET /coin/[id]/fee-receivers` | Who a coin’s creator fees are paid to now, read from the chains: each EVM chain’s fee splitter and the Solana launch record, with what each receiver is (a wallet, a tokenomics vault, the burn address or another contract; for a community takeover whose fees are bought back and burned, “buyback”, with no address). Cached for an hour. | 60 a minute. |
| `POST /v1/launch/prepare` | A launch quoted, with the ticket every later step takes. | 120 in ten minutes per address; none with an API key. |
| `POST /v1/launch/authorization` | The permission a creator wallet signs for another launching wallet. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/pay` | The transaction that pays the launch fee. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/submit` | Hands a paid launch to Pangea’s launcher. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `GET /v1/launch/status` | Where a launch stands, chain by chain. | No rate limit; the flood ceiling. |
| `GET /v1/launch/events` | The same, as a stream of events. | 3 open at once per address. |
| `GET /v1/launches` | Every coin launched here, newest first, a page at a time. | No rate limit; the flood ceiling. |
| `GET /v1/launches/stream` | Every new launch the moment it is live, as server-sent events. | 3 open at once per address. |
| `POST /v1/launch/kit` | What you need to make your own launch-time buys. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `POST /v1/launch/bundle-kit` | Where offered: your own launch call on the chain you paid on (Ethereum or BSC), to send first in your own bundle within the window. A v4 launch asks for its kit in the sponsored prepare instead (bundleKitHeld): each Ethereum or BSC chain it opens other than the one you pay on is held for your bundle. | 120 in ten minutes per address. |
| `POST /v1/launch/solana-self-call` | Your Solana launch call, when you send Solana’s launch from your own wallet (selfSolana). | 40 in ten minutes per address (90 for the landed-signature hint). |
| `POST /v1/launch/solana-cosigned` | Not available yet: answers 409. | None. |
| `POST /v1/launch/v4/sponsored/prepare` | Where v4 is switched on: a platform-paid v4 launch quoted, on the current v4 contract set, v4b (its Solana leg on the v4b bridge, whose open authorisation the creator's wallet signs as its hex text), with its v4 ticket; its launch buys are asked for here (the v4 launch kit). Payout wallets, one per chain type opened now: creatorEvm (always: the pay chain is an EVM chain) and creatorSvm when Solana opens now (required; a Solana chain only listed is asked when it is opened later). A v4 quote at /v1/launch/prepare answers 409 and names this route. | 120 in ten minutes per address, with prepare; none with an API key. |
| `POST /v1/launch/v4/sponsored/submit` | Where v4 is switched on: hands a paid v4 launch over, with every launch-buy and open signature, each checked against its ticket. | 300 in ten minutes per address with authorization, pay, submit and kit together; none with an API key. |
| `GET /v1/launch/v4/sponsored/readiness` | Where v4 is switched on: which chains a platform-paid v4 launch can open now. | No rate limit; the flood ceiling. |
| `POST /v1/launch/v4/open` | Where v4 is switched on: the creator's own v4 leg of a queued launch, on its pay chain, priced now: `{ launchId, chain }` answers `{ pre, tx, owed, quote, via, takeoverAt }` for the creator's wallet to send before `takeoverAt`. Not an open-later: that is `POST /v1/launch/v4/open-later`. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/open-later` | Where the full-scope switch is on: opens another listed chain of a coin already launched, at any time, on the coin's own contract set (a v4a coin stays on v4a): `{ id, chain, step: "authorize" }` by the current creator on the home chain (with the payout wallet first when that chain type has none), then `{ id, chain, step: "open" }` by the authorised wallet; each answers the transactions for your own wallet. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/registered` | Where the full-scope switch is on: lists a mined v4 open from the chain itself: `{ chain, txHash }`, or `{ chain: "solana", coinId }`; answers `{ slug, coinId }`, 422 `NOT_LISTABLE` when the site will not list its name or symbol. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/tokenomics` | Where the full-scope switch is on: the same as `POST /tokenomics/v4/switch` (on, off, clear; the role push from the coin's home chain, EVM or Solana, and its read-back; for a Solana-home coin the open authorisation), built for your own wallet. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/handover` | Where the full-scope switch is on: the creator handover (`op: "transfer"` with `newEvm` and optional `newSvm`, or `"renounce"`) on an EVM-home coin's home chain, for the current creator's wallet. A Solana-home coin answers 409: its handover is signed on the coin page. | 300 in ten minutes per address with the chain-read steps together; none with an API key. |
| `POST /v1/launch/v4/sol-kit` | Where v4 is switched on: the Solana bundle kit of a platform-paid v4 launch. With your v4 ticket: the signed Solana launch while it is held for you; with your bundle (that launch first, then 1 to 4 signed buyer transactions, the last paying the Jito tip): checked and sent to Jito as one bundle. Refused by the relay: the launch goes alone, with its launch buys. | 300 in ten minutes per address with authorization, pay, submit and kit together, and 120 in ten minutes per IP; none with an API key. |

> **Note:** The launch endpoints under `/v1/launch` are described in [Launch API →](https://www.pangea.trade/app/docs/v2#launch-api)

### GET /coin/{id}

- Coin, GET: `https://www.pangea.trade/api/coin/{slug, launch id or token address}`

One coin as the site knows it, with no chain read: `{ slug, name, symbol, launchId, listedAt, launchChains, chains, degen, factoryGen, supplyMode, factoryV3, supply, tokenomics, tokenomicsKnown }`. `chains` is each chain’s `{ token, pool, quote }`; `quote` is the asset its pool pairs with: `"native"`, or `{ address, ticker, symbol, issuer, known }` for a coin paired with a tokenized stock on that chain, found by the stock token's address (`known: false` for an asset the stocks table does not list). A stock-paired EVM chain can show `pool: null` until its pool is listed. `factoryGen` is 1 or 2; `factoryV3: true` marks a factory v3 coin (absent before v3); `supplyMode` is factory v2’s, 0 (1B on each chain) or 1 (degen mode), and null on a factory v1 coin. `tokenomics` is the config the launch’s quote signed, or null for none; `tokenomicsKnown: false` means it could not be read, so null is then not “none”. Errors: `400` for an id that cannot be a coin’s, `404` for one not launched here.

| Field | What it is |
| --- | --- |
| `degen` | `true` for a coin launched in degen mode: 1B in all, an equal share minted and paired on each chain, every chain’s token reporting the 1B. [Degen mode →](https://www.pangea.trade/app/docs/v2#degen-mode) |
| `supply.total` | The launch’s whole supply, in whole tokens, as a string: 1B for degen mode (999,999,999 on three chains, whose equal shares are whole tokens), 1B a chain otherwise. |
| `supply.circulating` | What the chains minted and paired, in all: 1B in degen mode. The bridge reserve backs the other chains and is not extra supply, so it is never taken off circulating; `/coin/{id}/supply`’s `live` takes off what was burned and reads the bridge reserve as it is now. |
| `supply.reserve`, `supply.reserveChain` | The rest of a degen mode coin’s supply, held in Solana’s bridge reserve, and `solana`. `0` and null without one. |
| `supply.perChain` | What each chain minted and paired, by chain. |
| `supply.reportedPerChain` | What every chain’s `totalSupply()` reports in degen mode, the 1B; null otherwise, where each chain reports its own. |
| `supply.circulatingShareBps` | In degen mode, what one chain mints as a share of the total, in basis points: 2,000 on five chains. 10,000 where each chain holds a whole supply of its own; null where the supply figures are. |
| `supply.supplyModel` | `perChain` for 1B on each chain, `per-chain` for a v4 coin (1B per opened chain), `global` for one supply (degen mode, and launches before 2026-10-15, whose figures are null here: `/pools` reads them off the token). |
| `tokenomics.perChain` | Each chain’s split by bucket, in basis points: `{ burn, buyback, liquidity, creator, community }`, adding up to 10,000. |
| `tokenomics.vault`, `tokenomics.owner` | The coin’s vault on the EVM chains, the fee receiver its trading fees are paid to, and its owner, the creator, who receives the creator share. |
| `tokenomics.treasury`, `tokenomics.community` | The wallets the bought-and-held coins and the community share go to on the EVM chains; null when no chain uses that bucket. |
| `tokenomics.solana` | `{ account, owner, treasury, community }`: the coin’s tokenomics account on Solana and its wallets there; null without Solana. |
| `tokenomics.version` | `1` for the config above. `2` for tokenomics v2: `perChain` is then each chain’s `{ burn, buyHold, liquidity, holders, treasury, wallets, rewards }` (shares in basis points; `wallets` the payout wallets with theirs, `rewards` the holder rewards’ tokens, `native` for the chain’s own coin), with `vault`, `account` (Solana), `owner`, `ownerSol` and `holders` (who qualifies). Another value is a kind this API does not describe yet, and carries only `version` and `vault`. |

### GET /coin/{id}/supply

- Supply, GET: `https://www.pangea.trade/api/coin/{id}/supply`

The answer is `{ slug, name, symbol, launchId, launchChains, degen, supply, live, liveError? }`. `supply` is the same as on the coin. For a degen mode coin, `live` is read from the chains and kept a minute: `{ mode, total, reserve, circulating, burned, burnedUnread, perChain, burnedPerChain, reserveAddress, decimals, onSolana, chains }`, in base units as strings, where `total` is what the tokens report, `reserve` what Solana’s bridge reserve at `reserveAddress` holds now, and `circulating` the total less what was burned (never less the bridge reserve, which backs the other chains and is not extra supply); `perChain` is what is on each chain, Solana’s its own float with the bridge reserve apart. A figure the chains did not give is null. `live` is null for any other coin and with `?live=0`; `liveError: "busy"` means the shared budget of chain reads is spent for the minute.

### GET /coin/{id}/tokenomics

- Tokenomics, GET: `https://www.pangea.trade/api/coin/{id}/tokenomics`

The answer is `{ slug, name, symbol, launchId, launchChains, tokenomics, tokenomicsKnown, chains }`. `tokenomics` is the coin’s config, as on the coin. `chains` is each chain’s vault read from the chain, `{ tokenomics }`, or null with an `error` when that chain could not be read. A vault has its `split`, `buckets` (what waits in each, in the chain’s native unit), `spent`, and `totals`: `receivedWei`, `coinBurned`, `coinToTreasury` (bought and held), `liquidityAdded`, `gasRepaidWei`. A coin its launch’s config says has no tokenomics is read on no chain. `?live=0` answers the config alone. Each chain’s reading is shared with the coin page and kept a while: a vault five minutes, no vault 24 hours, a chain that could not be read half a minute. `404` while tokenomics is switched off on this deployment.

### GET /v1/launches

- Launches, GET: `https://www.pangea.trade/api/v1/launches?limit=25`

Every coin launched here, newest first: `{ launches, next, latest, readAt }`. Each launch is `{ id, slug, name, symbol, launchId, listedAt, launchedAt, launchChains, chains, coinId, v4, degen, factoryGen, supplyMode, factoryV3, supply, tokenomics, image, platform }`: `chains` each chain’s `{ token, pool, quote }` (`quote` as in `GET /coin/{id}`), `factoryV3` true for a factory v3 coin, `supply` is `{ total, circulating, reserve, reserveChain }`, `tokenomics` is `{ on: false }`, or `{ on: true, version, vault, buckets, chains }` naming the buckets any chain uses, or null while it is not known; `image` the coin’s logo on this site, or null until it is stored. For a v4 coin, `coinId` is its coin id and `v4` is `{ coinId, home, listed, open, notOpen, transports, role: { creatorEvm, creatorSvm, renounced } }`: its home chain, the chains it lists, which of them are open now, its transports and its creator role; each open chain carries `openedAt`, unix seconds. Both are null for any other coin. A coin is listed once it has landed and its pools are open. `id` is its cursor, `<listedAt>:<slug>`.

- `limit`: 1 to 100, 25 by default.
- `before`: the previous page’s `next`, for the page after it. `next` is null on the last page. To catch up, send the same `since` with each page.
- `since`: a cursor, for the coins listed after it, or unix seconds, for the coins listed in that second or later. A coin can be listed a little after one with a later time, so to catch up, ask from a minute or two before the newest you have and drop the ids you already hold.
- `latest`: the newest coin’s cursor. Open the stream with it as `since` and nothing is missed between the two.

The newest launches, then the page after:

```bash
curl -s "https://www.pangea.trade/api/v1/launches?limit=10"
curl -s "https://www.pangea.trade/api/v1/launches?limit=10&before=<next>"
```

### GET /v1/launches/stream

- Launches stream, GET: `https://www.pangea.trade/api/v1/launches/stream`

Every new launch as server-sent events, the moment it is live. Each is an `event: launch` with the launch, as `/v1/launches` lists it, as its data, and, for a launch newer than any sent before, its cursor as the event `id`. After the first catch-up comes one `event: ready` with `{ latest }`. To miss nothing across a reconnect, send the last id you saw as `Last-Event-ID` (an `EventSource` does this by itself) or as `?since=`: the launches listed after it come first, oldest first, up to 100 at a time. Without either, only launches from now on. The `ready` event carries the stream’s place as its id too, so a reconnect before any launch still resumes. A coin can become visible a little after one listed later, so the stream also looks two minutes back and sends such a coin with no event id, keeping your place. A resume sends those two minutes again, so a launch can come twice: drop the ones whose data’s id you have. When a v4 coin already sent opens another of its listed chains later, the stream sends an `event: chain_opened` for that same launch, never a new launch: its data is `{ key, coinId, chain, openTime, slug, id, launch }`, keyed by coin id, chain and open time, with no event id. Clients dedupe by coinId: a key you already hold is a repeat. A resume sends again each chain opened after your cursor’s time. A comment line comes every 15 seconds; the stream closes after 30 minutes, so reconnect with the last id. Send `Accept: text/event-stream`. At most 3 open per caller; it takes requests from programs and from the Pangea site, like the Launch API.

> **Note:** Server-sent events are the simplest realtime API: one long GET that any HTTP client can read, that resumes by itself, and that passes every proxy. For pools, trades and bridges as they happen, open the live WebSocket below.

Follow new launches with curl:

```bash
curl -N -H "Accept: text/event-stream" "https://www.pangea.trade/api/v1/launches/stream"
```

Follow new launches with EventSource (Node: npm install eventsource):

```js
import { EventSource } from "eventsource";

const stream = new EventSource("https://www.pangea.trade/api/v1/launches/stream");
stream.addEventListener("launch", (e) => {
  const coin = JSON.parse(e.data);
  console.log(coin.symbol, coin.degen ? "degen" : "", coin.chains);
});
// It reconnects by itself and sends the last id as Last-Event-ID, so nothing is missed.
```

### WebSocket /live

The socket the site’s own pages use, open to everyone and read-only: `wss://www.pangea.trade/live`. No key, any origin or none. You subscribe to topics and are pushed what changes: pool readings, trades, bridges and new coins. Everyone subscribed to a topic is sent the very same message, made once, so hold as many sockets as you need, up to 256 from one IP address.

It is a wake-up channel, not a second source of truth: every value on it is also the answer of a REST endpoint here. A client that misses something on the socket is right again after its next REST read, and the rules below say when one is needed.

| Topic | What it carries |
| --- | --- |
| `poolsdiff` | Log: the pool reading as small patches, about once a second while anything trades. See the pool reading, as patches, below. |
| `pools` | State: the pool reading of `GET /pools`. It is larger than one message may be, so it arrives as `reset` with `size`: follow `poolsdiff` instead. |
| `pools:<slug>` | State: one coin’s pools, the same shape with one launch in it, replaced each time. |
| `launches` | State: the coin list, `{ readAt, launches: [{ slug, name, symbol }] }`. A new coin appears here. |
| `trades`, `trades:<slug>` | Log: one event per trade, of every coin or of one. Cursor letter `s`. |
| `bridges`, `bridges:<slug>` | Log: one event per bridge transfer. Cursor letter `b`. |
| `health` | State: whether the feeds behind the socket are up. While `delayed` is true, poll REST. |
| `job:<launchId>` | State: a launch job’s status, as the launch status route answers it. It is pushed again on every step of every chain, as it happens: see A launch, step by step, below. |
| `relay:<requestId>` | The site’s own pages only: asked for from anywhere else, the socket is closed with `1008`. |
| `feed:<name>` | State: the whole answer of a site route, pushed when it changes (a trade, a bridge, a listing or a price move re-reads the routes that depend on it). Names: `alltime`, `buyback-burns`, `buyback-history`, `creator-rewards`, `creator-rewards-v4`, `holder-payouts`, `platform-funds`, `cto`, `stock-pairs`, `elysium-moves`, `swap-tape`, `usdp-reserves`, `explore-fees`, `release`; per coin: `holders.<slug>` and `cto-burns.<slug>`. An answer larger than one message arrives as `reset` with `size`: read the route. |
| `wallet:<view>.<address>` | State: one wallet’s public route answer, pushed when that wallet trades or bridges. Views: `overview`, `profile`, `fees`, `buy-lines`. An EVM address in lowercase, a Solana address as written. At most 4 on one socket. |

### A launch, step by step

- Subscribe to `job:<launchId>` (the launch id in lowercase) from the moment the fee is paid. Each message is the whole status, so replace what you hold.
- Per chain, `d.result.sponsoredLegs` while the launch runs (`d.result.legs` once it is finished) holds `{ state, step, at, ms?, tx?, why?, listed? }`.
- `step` is one of `paid` (the fee is confirmed and the chain is queued), `sending`, `sent` (`tx` is the open’s hash, or its signature on Solana), `confirmed`, `open` (the pool is live), `failed` (`why` says why) or `handover` (the creator opens that chain from their own wallet). Steps only move forward.
- `at` is when the step happened, in milliseconds since the epoch. `ms` is how long the chain took. `listed: true` means that chain is in the coin list.
- `d.result.coinId` and `d.result.coinListedAt` appear the moment the coin is listed, before the job is done. From then on `GET /coin/[id]` answers.
- The chain the fee was paid on opens first. Every other chain then opens at the same time.

### Messages

- You send JSON text of at most 4096 bytes: `{"op":"sub","topics":["trades","poolsdiff"],"cursor":"s:1200,p:1762150000000123"}`, `{"op":"unsub","topics":["trades"]}` or `{"op":"ping"}`. At most 24 topics on one socket. Anything else closes the socket with `1008`.
- The server first sends `{"t":"hello","v":1,"now":ms,"hbMs":15000,"maxTopics":24,"families":[...],"sources":{...}}`. `families` lists the topic families this server serves: do not ask for any other.
- A state topic sends `{"t":"pools","topic":"pools:abc","d":{...}}` (and likewise `launches`, `health`, `job`): replace what you hold. On subscribing you are sent the current value at once.
- A log topic sends `{"t":"trade","topic":"trades","seq":1201,"slug":"abc","d":{...}}` (and likewise `bridge`, `poolsdiff`): apply it only if its `seq` is above your cursor for that stream, then move the cursor to it.
- `{"t":"ready","topics":[...],"cursor":"s:1250"}`: the replay of those log topics is done and they are live; the cursor is where each stream now stands.
- `{"t":"reset","topics":[...],"why":"replay"}`: you are not subscribed to those topics. Read them over REST, then subscribe again with the cursor that read gives. `why` is `replay` (more than 1000 rows behind), `size`, `ahead`, `unavailable` or `busy` (wait a few seconds first).
- `{"t":"hb","now":ms}` comes every 15 seconds. If nothing at all arrives for 40 seconds, reconnect.
- `{"t":"bye","reason":"rotate","retryMs":2100}` comes before the server closes the socket: `rotate` (every socket is closed after about 30 minutes), `slow`, `shutdown` or `rate`. Reconnect after `retryMs`.

### Reconnecting without missing anything

- Keep one cursor per stream: the `seq` of the last trade (`s`), bridge (`b`) and pool patch (`p`) you applied, and subscribe with them, as `"cursor":"s:1200,b:77"`. You are sent every row after them, then `ready`, then live: no gap and no repeat. Without a cursor a log topic starts from now, and `ready` gives you the cursor to keep.
- When the socket closes without a `bye`, wait a random time between zero and a ceiling that starts at half a second and doubles up to 30 seconds, reconnect, and subscribe again with your cursors.
- Slow readers are cut off, not waited for. A socket holding 256 KiB unread is not written to; meanwhile states keep only their newest value, and past 500 queued events the socket is told `bye` with `slow`. Reconnect with your cursors and nothing is lost.
- Messages you send are counted per socket, a burst of 60 and then 10 a second, pings included. Past that the socket is told `bye` with `rate`.
- Compression (permessage-deflate) is offered, and most clients take it on their own. The messages are the same either way.

### The pool reading, as patches

- Read `https://www.pangea.trade/live/pools`: `{ gen, body }`, where `body` is a pool reading in the shape of `GET /pools`. Then subscribe to `poolsdiff` with `"cursor":"p:<gen>"`. It answers 60 reads a minute from one address: read it at the start and after a reset, not on a timer.
- Each `poolsdiff` event’s `d.base` is the gen it applies to, and its `seq` is the gen it makes. Apply it only on top of exactly the gen you hold. When `d.body` is present it is a whole new reading.
- Otherwise `d` is `{ base, top, set, order }`. `top` holds the reading’s top-level fields. Each entry of `set` is a launch by `slug`: whole as `v`, or its changed fields as `f` and its changed pools as `pools`, matched by `chain` (each whole as `v` or as changed fields `f`). `order`, when present, is the new order of the slugs.
- A patch on a gen you do not hold, a `reset` of `poolsdiff`, or a patch naming a launch or pool you do not have: read `https://www.pangea.trade/live/pools` again and subscribe with its gen.

Follow trades and pools over the live socket (Node 22 or later, or a browser):

```js
const LIVE = "wss://www.pangea.trade/live";
const BASE = "https://www.pangea.trade/live/pools";

let pools = null; // { gen, body }
let tradeCursor = null;
let attempt = 0; // reconnects since the last ready
const waits = {}; // topic -> how long to wait before asking for it again

function applyPatch(body, d) {
  const bySlug = new Map(body.launches.map((l) => [l.slug, l]));
  for (const e of d.set) {
    if (e.v) {
      bySlug.set(e.slug, e.v);
      continue;
    }
    const old = bySlug.get(e.slug);
    if (!old) return null;
    const l = { ...old, ...(e.f ?? {}) };
    if (e.pools) {
      l.pools = old.pools.slice();
      for (const pe of e.pools) {
        const i = l.pools.findIndex((p) => p.chain === pe.chain);
        if (i === -1) return null;
        l.pools[i] = pe.v ?? { ...l.pools[i], ...(pe.f ?? {}) };
      }
    }
    bySlug.set(e.slug, l);
  }
  const order = d.order ?? body.launches.map((l) => l.slug);
  return { ...d.top, launches: order.map((slug) => bySlug.get(slug)) };
}

async function readBase() {
  const res = await fetch(BASE);
  if (!res.ok) throw new Error(`GET /live/pools answered ${res.status}`); // 429 or 503: try again later
  pools = await res.json();
}

// Ask for one topic again later, waiting longer each time (2 s, doubling to 60 s).
function later(ws, topic) {
  waits[topic] = Math.min(60_000, (waits[topic] ?? 1_000) * 2);
  setTimeout(async () => {
    if (ws.readyState !== WebSocket.OPEN) return;
    try {
      if (topic === "poolsdiff" && !pools) await readBase();
      subscribe(ws, [topic]);
    } catch {
      later(ws, topic);
    }
  }, waits[topic]);
}

function subscribe(ws, topics) {
  const cursor = [];
  if (topics.includes("trades") && tradeCursor !== null) cursor.push(`s:${tradeCursor}`);
  if (topics.includes("poolsdiff")) cursor.push(`p:${pools.gen}`);
  ws.send(JSON.stringify({ op: "sub", topics, cursor: cursor.join(",") }));
}

async function connect() {
  try {
    if (!pools) await readBase();
  } catch {
    setTimeout(connect, Math.random() * Math.min(30_000, 500 * 2 ** attempt++));
    return;
  }
  const ws = new WebSocket(LIVE);
  let heard = Date.now();
  let retryMs = null; // from bye
  const watch = setInterval(() => Date.now() - heard > 40_000 && ws.close(), 5_000);
  ws.onopen = () => subscribe(ws, ["trades", "poolsdiff", "launches"]);
  ws.onmessage = (e) => {
    heard = Date.now();
    const m = JSON.parse(e.data);
    if (m.t === "trade" && (tradeCursor === null || m.seq > tradeCursor)) {
      tradeCursor = m.seq;
      console.log("trade", m.slug, m.d.side, m.d.valueUsd);
    } else if (m.t === "poolsdiff" && pools && m.seq > pools.gen) {
      const body = m.d.body ?? (m.d.base === pools.gen ? applyPatch(pools.body, m.d) : null);
      if (body) pools = { gen: m.seq, body };
      else {
        pools = null; // behind: start again from the base, on the same socket
        later(ws, "poolsdiff");
      }
    } else if (m.t === "ready") {
      attempt = 0;
      for (const t of m.topics) delete waits[t];
      const s = /s:([0-9]+)/.exec(m.cursor ?? "");
      if (s && tradeCursor === null) tradeCursor = Number(s[1]);
    } else if (m.t === "reset") {
      // Not subscribed to these any more. Keep the socket; ask again later, waiting longer each time.
      for (const topic of m.topics) {
        if (m.why === "replay" || m.why === "ahead") {
          if (topic === "trades") tradeCursor = null; // too far behind: fill the gap from GET /history
          if (topic === "poolsdiff") pools = null;
        }
        later(ws, topic);
      }
    } else if (m.t === "bye") {
      retryMs = m.retryMs;
    }
  };
  ws.onclose = () => {
    clearInterval(watch);
    setTimeout(connect, retryMs ?? Math.random() * Math.min(30_000, 500 * 2 ** attempt++));
  };
}

connect();
```

### GET /holders

- Holders, GET: `https://www.pangea.trade/api/holders`

Query: `token`, a slug, required; `chain`, optional, to answer for one chain. The answer is `{ slug, readAt, launchTotal, degen, supply, chains, all }`, where `degen` and `supply` are as on `/coin/[id]`, and each of `chains` is `{ chain, count, top10Pct, rows, complete, note?, error? }` and each row `{ chain, address, balance, pct, label }`, balances in whole tokens, at most 100 rows a chain. `all` combines the chains, `{ rows, count, partial, top10Pct }`, and is left out when `chain` is given. `label` names a known holder such as the pool or the bridge escrow. An answer is reused for a minute. Errors: `400` without a token, `404` for an unknown launch or a chain it is not on.

### GET /bridge/balance

- Balance, GET: `https://www.pangea.trade/api/bridge/balance`

Query: `token` (slug), `chain` and `address`, all required, and `asset=native` to read the chain’s native asset instead of the token. The answer is `{ slug, chain, address, balance, decimals, reason?, reserve? }`: `balance` in base units as a string, or `null` with a `reason` when the chain could not be read, never a false zero. On Solana the token balance is the sum of the owner’s token accounts for the mint. For the native asset, `reserve` is the gas the app keeps back for a trade. Errors: `400` for a missing field or an address of the wrong kind, `404` for an unknown launch or chain.

### POST /bridge/quote, in full

Beyond the fields in [For integrators](https://www.pangea.trade/app/docs/v2#v1-integrators), the body may carry `slippageBps` for the fast lane, 50 unless given and 1000 at most, and `smartWallet`, true when the sender is a contract wallet, and `gapToleranceBps`, a factory v3 token's tolerance under the gap estimate for the minimum credited (100 unless given, 2000 at most). The answer’s `routes` holds two routes, `canonical` and `fast`, and a route that cannot be used says why in `unavailable`. Besides the fields listed there, a route may carry:

| Field | What it is |
| --- | --- |
| `label` | `Standard` or `Fast`. |
| `fastFee`, `relayerFee`, `relayer` | On the fast route: the fast lane’s fee, the relayer’s price and who it is, in base units. |
| `minReceived` | On the fast route: the least the order lets the recipient be paid. |
| `approval` | On the fast route from an EVM chain: the approval to send first, `{ to, data, value, chainId }`, or null when none is needed. |
| `solanaFast` | On the fast route out of Solana: `{ relayerFee, minReceived, nativeFeeCap }`, for `/bridge/solana-fast-tx` (`nativeFeeCap`: the most the order signs for LayerZero, also the route's `nativeFee`). |
| `fillDropFee`, `layerZeroFee`, `fillDropNote` | A fast order into Solana: the Fill drop (the instant fill's account rent, paid once to Pangea's delivery) and LayerZero's part of `nativeFee`. |
| `routePayment` | From an EVM chain into a Solana route that is not open: the one-time setup payment, `{ to, data, value, chainId }`, to send before the transfer. |
| `setupNote`, `messageFeeLater` | Out of Solana with a route to open: the setup is paid first and LayerZero’s fee is priced after it. |
| `feeIsMaximum` | `nativeFee` is the most the Solana transaction will sign; LayerZero charges what its workers quote. |
| `note` | A sentence about the route, for showing to a person. |
| `gap` | A factory v3 token's standard route: the price-gap estimate. `state` (`estimate`, `free`, `held` or `unavailable`), `burned` (what the destination withholds, in `decimals`), `minCredited` and `toleranceBps` (the least it may credit, and the tolerance it was set with), `minCreditedSource` (out of Solana, in Solana's decimals), `cover` (the gas cover, when it may apply), `source` (a Solana source's price record), `delivery` (into Solana: `{ amount, note }`, our delivery key's cost withheld from the arrival, in `decimals`; its `ata`, when present, is the part that is a new wallet's token-account rent on its first arrival) and `poke` (an EVM source's price record to send first), and `warning` (when set: a sentence to show before the send -- e.g. an arrival too small to pay a new wallet's token-account rent, which waits on Solana until that account exists; web-r4-b I-5). What arrives is `amountOut` less `burned` and, into Solana, less `delivery.amount`. |
| `keysOnlyDeliveryFee`, `keysOnlyDeliveryNote` | A factory v3 send into Ethereum: the part of `nativeFee` that pays Pangea's delivery there (in the source's wei; LayerZero's executor does not deliver there). Paid once, inside the same transaction. |
| `dstDeliveryFee` | Out of Solana toward Ethereum (a factory v3 token): the part of `nativeFee` that pays Pangea's delivery there, in lamports. |
| `deliveryFee`, `deliveryPayee` | The site's instant-delivery fee inside `nativeFee` (when on), and the payee the send's refund address names for it. |
| `needsRecord` | A factory v3 send out of an EVM chain whose price log needs records (`GapNeedRecords`): `{ pokes, poke }`. `call` is null and `gap.poke` carries the same `{ to, data, value, gas }`: send it from the sender's wallet about `pokes` times, one mined before the next, then ask the quote again. |
| `split` | A factory v3 send above what one transfer carries now: `{ text, count, minutes, parts, maxSendLD, cosign, cosignTx?, cosigner?, perPartFee, totalFeeEstimate, perPartLines, partGap? }`. `call` is null. Every part is its own transfer and pays the full per-send fees: `perPartFee` (the first part's quote, itemised in `perPartLines`) and `totalFeeEstimate` (`perPartFee` x `count`). From an EVM chain, `cosignTx` is the whole send, unsigned, for `POST /api/cosign` (`{ chain: "evm", chainId, tx, gapToleranceBps? }`): its answer is the co-signed send with its fee (`nativeFee`, `tx.value`) and the minimum it guarantees at the co-signed price (`minCreditedLD`); a 503 means send in parts. From Solana, `cosign` is true only when the co-sign service is set up and `cosigner` names the GapGlobal co-signer: `POST /api/bridge/solana-tx` with `cosign: true` (and `minCredited`) builds the unsigned co-signed send (stage `"cosign"`), `POST /api/cosign` (`{ chain: "solana", tx, gapToleranceBps? }`) co-signs it and answers `minCreditedLD` and `expectedMinCreditedLD`; the wallet signs last. Before it signs, compare the answer's message bytes with the ones you built: they must be identical, with your wallet's signature slot still empty and only the co-signer's filled (the proxy never sees what you built, so a client of `POST /api/cosign` makes this check itself, as the site's own panel does). Any 503, `ABOVE_COSIGN_MAX` or mismatch means send in parts. |

Further errors: `409` when the token has not launched on the destination, `424` when a Solana recipient could not be checked, and `424` with `retry: true` when pricing took longer than 45 seconds.

### POST /bridge/solana-tx

- Out of Solana, POST: `https://www.pangea.trade/api/bridge/solana-tx`

The body you POST:

```json
{
  "slug": "<launch slug>",
  "to": "base",
  "amount": "1000000000000",
  "sender": "<Solana wallet>",
  "recipient": "0x<EVM address>"
}
```

Builds the unsigned transaction that sends the token from Solana to an EVM chain, for the sender to sign. For a token with the price-gap fee, `minCredited` is required: the least the destination may credit after it, in Solana base units (the bridge quote’s `gap.minCreditedSource`); without it the request is refused (400). `after` and `afterValidUntil` are given only on a second call after a setup step. The answer carries the request’s `from`, `to`, `amount` and `symbol` and one of three stages:

| Stage | What to do |
| --- | --- |
| `send` | Sign `transaction`, a base64 versioned transaction, and send it. `received` is what arrives, `nativeFee` LayerZero’s fee and `nativeFeeCap` the most it will sign. |
| `together` | The same, with the route’s opening in the same transaction. `setupFee` is the opening’s part of `totalFee`. |
| `setup` | The opening alone. Sign and send it, then call again with `after` set to its signature for the send. |
| `record` | A factory v3 token whose Solana price log holds no recent price: sign `transaction` (`record_price`, its signature fee only), send it, then call again with `recorded` set to its signature. Until it has landed and a slot has passed the answer is `202` with `{ pending: true, stage: "recording" }`; then it is the send. |

On Solana a factory v3 arrival is delivered with `650,000` compute units. A charged v3 receive there runs only through LayerZero's executor (its own execute flow) or Pangea's delivery keys (R1′), so nobody can land it at a price of their choosing; an escaped, refunded or unpooled arrival stays open to anyone.

Every stage carries `blockhash` and `lastValidBlockHeight`: sign within about a minute. While a setup step is still settling the answer is `202` with `{ pending: true, stage: "waiting" }`; ask again. Errors: `400` for a bad field or a signature that is not this transfer’s setup, `404` for an unknown launch, `409` when the setup ran out or failed, the route is unusable or the wallet is short of SOL, `424` when the build takes longer than usual, with `retry: true`, and `424` after 50 seconds.

### POST /bridge/solana-fast-tx

- Fast out of Solana, POST: `https://www.pangea.trade/api/bridge/solana-fast-tx`

The fast lane out of Solana: one unsigned transaction holding Pangea’s fast-bridge order and the OFT send that carries it. Body: `{ slug, to, recipient, amount, relayerFee, minReceived, sender }`, with `relayerFee` and `minReceived` from the quote’s `solanaFast`. The answer is `{ stage: "send", transaction, orderId, amount, arriving, fastFee, relayerFee, nativeFee, nativeFeeCap, blockhash, lastValidBlockHeight }`. Errors: `409` when the fast lane is off for this launch or route, `422` when the order could not be built, nothing having been sent.

### POST /bridge/route-open

- Open a Solana route, POST: `https://www.pangea.trade/api/bridge/route-open`

The second step of the first transfer from an EVM chain into a Solana route: once the quote’s `routePayment` has been sent, this hands its transaction to Pangea’s delivery service, which checks the payment on chain and opens the route, or refunds it. Body: `{ slug, from, txHash, sender }`, `from` being the EVM chain the payment was made on. With `Accept: text/event-stream` it streams `{ state, reason?, refundTx? }` as the route is opened; otherwise it answers once. `state` is one of `verifying`, `paid`, `opening`, `open`, `refunded`, `refund-owed`, `review` and `refused`. Send the transfer once it is `open`. Errors: `404` for a launch not on Solana, `424` when routes cannot be opened from this deployment.

### POST /bridge/arrival

- Arrival stream, POST: `https://www.pangea.trade/api/bridge/arrival`

Body: `{ txHash }`, the send’s transaction hash or Solana signature. A stream of `{ state, route, guid, dstTx?, by? }`, `state` being `in-flight` and then `delivered`, with the delivery’s transaction in `dstTx`. It is fed by Pangea’s delivery service, which remembers a transfer for 30 minutes. Errors: `400` for a malformed hash, `424` when the deployment does not serve arrivals; LayerZero Scan answers the same question.

### POST /bridge/refund

- Held fast orders, POST: `https://www.pangea.trade/api/bridge/refund`

Body: `{ chain, orderId }`, the destination chain and the fast order’s id. The answer is `{ held: false }` or `{ held: true, guid, amount, srcEid, call }`, where `call` is the `refund(guid)` transaction, `{ to, data, value, chainId }`, with LayerZero’s fee for the trip back as its value. It looks back 5,000 blocks. Errors: `400` for a malformed id or a chain without a fast bridge.

### POST /bridge/orders

- Report a fast order, POST: `https://www.pangea.trade/api/bridge/orders`

Body: `{ from, hash }`, the chain a fast order was posted on and its transaction. The board reads the order from the receipt and remembers whose price it was sent at, which is how a relayer that does not fill is benched. The answer is `202` with `{ recorded: false, pending: true }` before the receipt exists, then `{ recorded, relayer, why?, orderId, to, srcBridge }`. Only orders priced from this deployment’s board are recorded; `why` says why not.

### POST /chain/tx

- Transaction stream, POST: `https://www.pangea.trade/api/chain/tx`

Body: `{ chain, hash }`. One event when the transaction is in a block: `{ state: "mined", success, block }` on an EVM chain, `{ state: "mined", success }` on Solana, or `{ state: "unseen" }` if it never appears, after up to 10 minutes on an EVM chain and 5 on Solana.

### Trading: POST /trade/quote and POST /trade/solana-tx

These are the trade card’s own and may change; read a quote for a price, and build trades from the pools directly where you can. `/trade/quote` takes `{ slug, side, chain, amount, user?, solanaUser?, slippageBps?, mode?, intent?, quoteAsset? }`: `side` is `buy` or `sell`; `chain` is the chain the wallet trades on, always a chain name (`auto` is refused, and a `fromChain` other than `chain` is refused too); `amount` is the pay asset’s base units for a buy, the token’s for a sell. `slippageBps` is the slippage, how far below the quote the trade may fill: a whole number of basis points from 1 to 9900 (0.01% to 99%), 100 unless given. `mode` is `"direct"` (the default: the coin’s own pool on that chain) or `"cheapest"` (also price the other chains’ pools and offer the route when it is better after every fee); `intent` is `"preview"` (a price, nothing to sign) or `"execute"`; `quoteAsset: "stock"` pays or receives the stock token itself on a stock-paired EVM chain. A direct trade goes through the coin’s own pool (`details.router: "pool"`), with Jupiter as a fallback on Solana. The answer is `{ mode, preview?, chain, fromChain, crossChain, cheapestNote, unroutable, details, steps, cheapest? }`; with `mode: "cheapest"`, `cheapest` is `{ offered, reason, plan, steps, comparison: { directOut, routeOut, gainBps, cost }, tried }` and, when it is offered, `directDetails` is the direct trade beside it. Cheapest mode has its own brakes (20 a minute per wallet, 2 at once per caller); past them it answers as a direct quote with the reason in `cheapestNote`. Errors: `400`, `404`, `422` with the reason when no route was found, and `424` when a chain or the router could not be reached or took too long (with `retry: true` where trying again can help). Never a 5xx: a proxy in front may replace a 5xx body.

`/trade/solana-tx` takes `{ instructions, addressLookupTableAddresses?, payer }`, a swap step’s Solana instructions as the quote gives them, at most 24 instructions and 8 lookup tables, and answers `{ transaction, blockhash, lastValidBlockHeight }`: an unsigned versioned transaction. It does not inspect what the instructions do, so read them, or let your wallet simulate them, before you sign.
