Skip to content

Gated sales

Gated sales restrict minting to wallets that satisfy a gate. A gate is a reusable bundle of conditions; attach it to a sale by setting accessMode: "GATED" and gateId.

Unlike public sales, gated sales have no on-chain vector. The backend signs each claim with the platform executor key; collectors submit the signed claim to the chain. This is why gated sales can be added to Live collections — they don’t need an on-chain vector registration.

GoalUse
Allowlist of specific addressesALLOWLIST condition
Holder-only early accessTOKEN_OWNERSHIP condition
Attribute-based (e.g. holders of “Gold” traits)TOKEN_ATTRIBUTE condition
Requires minimum ETH/ERC20 balanceCURRENCY_BALANCE condition
Farcaster-based accessFARCASTER_FOLLOW condition

Combine conditions with matchMode: "ALL" (all must pass) or "ANY" (one must pass).

Ranked auctions cannot be gated. The platform rejects that combination at validation time.

Gates are owned by an account and reusable across many sales.

const gate = (await client.gate.create({
name: "Allowlist + Holders",
matchMode: "ANY",
conditions: [
{
type: "ALLOWLIST",
config: {
addresses: [
"0x1111111111111111111111111111111111111111",
"0x2222222222222222222222222222222222222222",
],
},
},
{
type: "TOKEN_OWNERSHIP",
config: {
contractAddress: "0xYourPreviousCollection",
chainId: 8453,
minAmount: 1,
},
},
],
})).data!;
const gateId = gate.id;

All on-chain conditions (TOKEN_OWNERSHIP, SPECIFIC_TOKEN, TOKEN_ATTRIBUTE, CURRENCY_BALANCE) require a chainId. The gate evaluator reads state on that chain at claim time.

await client.collection.addSale({
highlightId,
type: "FIXED_PRICE",
accessMode: "GATED",
gateId,
startAt: new Date().toISOString(),
price: "0.005",
currency: "ETH",
maxPerTransaction: 1,
maxPerWallet: 2,
maxTotal: 500,
paymentRecipient: creatorAddress,
// gasSponsored: true — REJECTED for gated sales
});

Gated sales can be added to draft or live collections. Either way, the sale is immediately usable — there’s no on-chain registration step.

The claim shape is identical to a public sale. The backend decides whether to return a public-vector claim or an executor-signed claim based on the sale’s access mode — the collector doesn’t care.

const claim = (await client.collection.claimSale({
highlightId,
saleId: gatedSale.id,
amount: 1,
})).data!;

If the caller’s wallet doesn’t satisfy the gate, the call returns SaleGateCheckFailedError (403) and the payload lists which condition failed.

Submit the returned transaction. The claim shape is flat — { chainId, to, data, value }:

const hash = await walletClient.sendTransaction({
to: (claim.to ?? undefined) as `0x${string}` | undefined,
data: claim.data as `0x${string}`,
value: BigInt(claim.value ?? "0"),
gas: 500_000n,
});

On-chain, the mint is authorized by the executor signature embedded in claim.data. The platform contracts verify the signature and the single-use nonce.

For Series collections, collectors can pick specific token IDs. Pass tokenIds alongside amount:

const claim = (await client.collection.claimSale({
highlightId,
saleId: chooseTokenSale.id,
amount: 3,
tokenIds: [7, 42, 101],
})).data!;

The support matrix dictates which mint method backs the call — see support matrix: series.

const gates = (await client.gate.list()).data!;
const detail = (await client.gate.get({ gateId })).data!;
// Replace conditions wholesale — partial updates are not supported
await client.gate.update({
gateId,
name: "Updated gate",
matchMode: "ALL",
conditions: [
{ type: "ALLOWLIST", config: { addresses: ["0x..."] } },
],
});
await client.gate.remove({ gateId });

A gate currently attached to any live sale cannot be deleted — detach first (delete the sale or replace the gate by re-adding the sale).

  • A gated sale requires gateId.
  • A public sale must not have a gateId.
  • RANKED_AUCTION + GATED is rejected.
  • gasSponsored: true is rejected on gated sales (gas sponsorship requires a public on-chain vector).
  • Gate condition evaluation happens at claim time, not sale creation. If a collector’s balance drops after you add them via an allowlist, a token-ownership condition they relied on could fail.