本页目前只有英文版。

Agent ID Gate

A bonding-curve launch can be Agent ID-gated: while the token is on the curve, only wallets that hold an Agent ID can buy or sell it, and each wallet can buy at most 2% of the supply. This page explains how the gate works and how a terminal or wallet should trade a gated token.

The events and reads to index are in Build an Indexer → Agent ID gate and buy cap; how the gate is set at launch is in Extension encoding.

What is an Agent ID

An Agent ID is an ERC-721 collection used as the on-chain identity of an agent. Holding at least one NFT of the collection is the credential to trade that agent's token on the curve.

  • The gate is collection-wide: any NFT of the collection counts, not a specific tokenId.
  • Only collections whitelisted by Flap can gate a launch (nftWhitelisted(nft)); a launch with any other collection reverts NFTNotWhitelisted. Example: the Binance Agent ID collection, 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F on BNB Chain.
  • The Portal ABI keeps the older nft… names (nftGate, nftWhitelisted).

How the gate works

Where it applies

Token stategetTokenV8Safe(token).statusCurve tradeGate
On the curve1 (Tradable)swapExactInput / swapExactInputV3Enforced
Migrated to the DEX4 (DEX)Reverts TokenNotTradableNone: the DEX is open to everyone

The gate applies only to curve buys and sells, never to token transfers or DEX trades. A token is gated when nftGate(token) != address(0). An admin can remove a token's gate after launch (FlapNFTGateUpdated), but never add or change one.

The Agent ID check

Every curve buy and sell of a gated token requires nft.balanceOf(tx.origin) >= 1.

  • The check is on tx.origin, not msg.sender.
  • It runs before any payment, so a failed check reverts without moving funds (NFTGatedOnly(wallet)).
  • The balanceOf call is capped at 100,000 gas. If it reverts, runs out of gas or returns malformed data, the check is skipped and the trade goes through, so a broken collection cannot freeze the curve. Standard ERC-721 collections are not affected.

The buy cap

Gated launches get a cap of 200 bps (2%) of the max supply per tx.origin: 20,000,000 tokens for a 1B-supply token. What counts against it:

held      = max(netBought[token][origin], token.balanceOf(origin))
remaining = max(0, cap - held)          // buyQuotaOf(token, origin)
  • netBought grows with every curve buy by the origin (whoever receives the tokens) and shrinks with every curve sell, never below zero.
  • Transferring tokens out does not lower netBought, so it does not free the cap.
  • Receiving tokens raises the balance, so it uses the recipient's cap. A wallet can be pushed to its cap by a gift without buying.
  • Sells are never capped, even beyond netBought (the counter stops at zero).
  • A buy above the remaining cap is filled up to the cap and the rest of the input is refunded to the caller, in the input currency. At the cap, a buy reverts BuyCapExceeded(origin, held, cap). When only a few wei remain, a buy can also return 0 tokens with a full refund: treat that as "cap reached".
  • The creator's first buy at launch is an ordinary curve buy: it also needs the Agent ID and counts against the cap.
  • For a wallet that only buys and sells on the curve itself (no transfers in or out), netBought equals its balance, so cap - balanceOf(wallet) is exact. Otherwise read buyQuotaOf.

Non-gated tokens with a cap use the plain holding rule: held = balanceOf(origin).

Trading a gated token

1. Check whether the user can trade

const [state, nft] = await Promise.all([
  portal.getTokenV8Safe(token),
  portal.nftGate(token).catch(() => ZeroAddress), // Portals before v5.27.0 do not have it
]);
const gated = state.status === 1n && nft !== ZeroAddress;

let holdsAgentId = true;
let remaining = MaxUint256;
if (gated) {
  holdsAgentId = (await erc721(nft).balanceOf(user)) > 0n;
  [, remaining] = await portal.buyQuotaOf(token, user);
}

const canBuy = !gated || (holdsAgentId && remaining > 0n);
const canSell = !gated || holdsAgentId;

Check the Agent ID yourself: the quote does not.

2. Quote with from set to the user

The cap is read on tx.origin, so simulate as the real trader. Without from, tx.origin is the zero address and the quote assumes the full cap.

const out = await portal.quoteExactInput.staticCall(
  { inputToken: ZeroAddress, outputToken: token, inputAmount: parseEther("1") },
  { from: user },
);
  • At the cap, the quote reverts BuyCapExceeded.
  • Above the remaining cap, the quote returns the capped output (the refund is not part of the result).

3. Swap

await portal.swapExactInput(
  { inputToken: ZeroAddress, outputToken: token, inputAmount: parseEther("1"),
    minOutputAmount: (out * 99n) / 100n, permitData: "0x" },
  { value: parseEther("1") },
);

Take minOutputAmount from the quote, not from the full input: when the buy is capped the output is smaller, and a minimum computed from the full input reverts SlippageTooHigh. To show the exact refund, simulate the swap and compare the caller's balance; otherwise show "up to X refunded".

Errors

ErrorCauseSuggested message
NFTGatedOnly(address wallet)The wallet holds no Agent ID."This market requires an Agent ID: hold at least one NFT of {collection} to trade on the curve."
BuyCapExceeded(address origin, uint256 held, uint256 cap)The wallet is at its cap."Cap reached: {held} of {cap}. Sell or move tokens to buy more."
SlippageTooHigh(uint256 actual, uint256 minimum)A capped fill came in below minOutputAmount.Re-quote with from = user.
TokenNotTradable(address token)The token has migrated.Route the trade to the DEX.

Limitations

  • Relayers and smart accounts. Because the gate reads tx.origin, trades sent through a relayer, a meta-transaction or an ERC-4337 bundler are checked against that sender, not the user, and normally fail. EIP-7702 delegated wallets keep their own tx.origin and work.
  • Routed buys still count. A buy through a helper contract, or sent to another wallet, uses the origin's cap.
  • Agent ID moved away. A holder who transfers their Agent ID can still transfer tokens, but can no longer sell on the curve until they get one back or the token migrates.

Example

A 1B-supply token gated by the Binance Agent ID collection; the cap is 20,000,000 tokens.

  1. Alice holds an Agent ID and buys with 100 BNB: the fill stops at 20,000,000 tokens and the extra BNB is refunded. remaining = 0.
  2. She transfers 5,000,000 tokens to another wallet: netBought stays 20,000,000, so remaining stays 0.
  3. She sells 10,000,000 tokens on the curve: netBought drops to 10,000,000 and remaining is now 10,000,000.
  4. Bob gives Alice 20,000,000 tokens: her balance (25,000,000) is over the cap, so remaining = 0 again. Bob's own netBought is unchanged: giving tokens away did not free his cap.
  5. Alice transfers her Agent ID away: a curve sell now reverts NFTGatedOnly.
  6. The token migrates to the DEX: the gate no longer applies, and anyone can trade it there.