Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Executing Purchases

Approve Currency (ERC20)

Gatekeeper pulls payment tokens from the buyer. If totalPrice is greater than zero, the buyer must approve the Gatekeeper contract to spend the sale currency (the SaleKey.currency address) before calling purchase or purchaseWithApproval.

import { gatekeeperAddress } from '@sceneinfrastructure/sdk'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
 
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
})
 
const chainId = 8453 // Base
 
const currencyAddress = '0x1111111111111111111111111111111111111111'
const erc20Abi = [
  {
    type: 'function',
    name: 'approve',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'spender', type: 'address' },
      { name: 'amount', type: 'uint256' },
    ],
    outputs: [{ name: 'success', type: 'bool' }],
  },
] as const
 
// Approve at least the total price for this purchase
const amount = 10_000000n
 
// await walletClient.writeContract({
//   address: currencyAddress,
//   abi: erc20Abi,
//   functionName: 'approve',
//   args: [gatekeeperAddress[chainId], amount],
// })

Use Gatekeeper.prepareGetPrice to calculate the totalPrice you want to approve.

Open Purchases

preparePurchase is only for sales with no approvers. If the sale has approvers, use preparePurchaseWithApproval instead.

import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
 
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
})
 
const chainId = 8453 // Base
 
const saleKey = {
  token: '0x2345678901234567890123456789012345678901', // Your deployed contract
  tokenId: 1n,
  currency: '0x1111111111111111111111111111111111111111',
  fundsRecipient: '0x4567890123456789012345678901234567890123',
  vendor: '0x5678901234567890123456789012345678901234',
  vendorFeeBps: 0,
  referralFeeBps: 0,
  salesTaxBps: 0,
  nonce: 1n,
} as const
 
const { to, data } = Gatekeeper.preparePurchase({
  gatekeeper: gatekeeperAddress[chainId],
  saleKey,
  purchaseParams: {
    recipient: '0x6789012345678901234567890123456789012345',
    referrer: '0x0000000000000000000000000000000000000000',
    quantity: 2,
  },
})
 
// Execute the purchase
// await walletClient.sendTransaction({ to, data })

Approval-Gated Purchases

For sales with approvers, you need:

  1. Generate EIP-712 typed data
  2. Get a signature from an authorized approver
  3. Execute the purchase with the signature

The signature must be from one of the addresses in saleState.approvers. Signatures are single-use and bound to saleKeyId, recipient, referrer, quantity, deadline, and salt.

Step 1: Generate Typed Data

import { Approval, SaleKey, gatekeeperAddress } from '@sceneinfrastructure/sdk'
import { Hex } from 'ox'
 
const chainId = 8453 // Base
const buyerAddress = '0x2345678901234567890123456789012345678901'
 
const saleKey = {
  token: '0x3456789012345678901234567890123456789012', // Your deployed contract
  tokenId: 1n,
  currency: '0x1111111111111111111111111111111111111111',
  fundsRecipient: '0x5678901234567890123456789012345678901234',
  vendor: '0x6789012345678901234567890123456789012345',
  vendorFeeBps: 0,
  referralFeeBps: 0,
  salesTaxBps: 0,
  nonce: 1n,
} as const
 
// Generate the sale key ID
const saleKeyId = SaleKey.generateId(saleKey)
 
// Build the typed data
const typedData = Approval.getTypedData({
  chainId: BigInt(chainId),
  gatekeeper: gatekeeperAddress[chainId],
  message: {
    recipient: buyerAddress,
    referrer: '0x0000000000000000000000000000000000000000',
    quantity: 1,
    deadline: Approval.getDeadline(), // 15 minutes from now
    salt: Hex.random(32),
    saleKeyId,
  },
})

Step 2: Get Approver Signature

Have an authorized approver sign the typed data:

// This happens on the approver's side
// const signature = await approverWallet.signTypedData(typedData)

Step 3: Execute with Approval

import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
 
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
})
 
const chainId = 8453 // Base
 
const saleKey = {
  token: '0x2345678901234567890123456789012345678901', // Your deployed contract
  tokenId: 1n,
  currency: '0x1111111111111111111111111111111111111111',
  fundsRecipient: '0x4567890123456789012345678901234567890123',
  vendor: '0x5678901234567890123456789012345678901234',
  vendorFeeBps: 0,
  referralFeeBps: 0,
  salesTaxBps: 0,
  nonce: 1n,
} as const
 
// Assume we have the signature params from the approval flow
const deadline = BigInt(Math.floor(Date.now() / 1000) + 900) // 15 min
const salt = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
const signature = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12'
 
const { to, data } = Gatekeeper.preparePurchaseWithApproval({
  gatekeeper: gatekeeperAddress[chainId],
  saleKey,
  purchaseParams: {
    recipient: '0x6789012345678901234567890123456789012345',
    referrer: '0x0000000000000000000000000000000000000000',
    quantity: 1,
  },
  signatureParams: {
    deadline,
    salt,
    signature,
  },
})
 
// Execute the purchase
// await walletClient.sendTransaction({ to, data })

Custom Deadline

Configure how long an approval is valid:

import { Approval } from '@sceneinfrastructure/sdk'
 
// Default: 15 minutes from now
const defaultDeadline = Approval.getDeadline()
 
// Custom: 30 minutes from now
const thirtyMinutes = Approval.getDeadline(new Date(), 30)
 
// Custom: 1 hour from a specific time
const futureTime = new Date('2024-12-31T00:00:00Z')
const oneHour = Approval.getDeadline(futureTime, 60)

Getting Price Information

Before purchasing, get the price breakdown:

import { Gatekeeper, gatekeeperAddress, gatekeeperAbi } from '@sceneinfrastructure/sdk'
 
const chainId = 8453 // Base
 
const saleKey = {
  token: '0x2345678901234567890123456789012345678901', // Your deployed contract
  tokenId: 1n,
  currency: '0x1111111111111111111111111111111111111111',
  fundsRecipient: '0x4567890123456789012345678901234567890123',
  vendor: '0x5678901234567890123456789012345678901234',
  vendorFeeBps: 250,
  referralFeeBps: 100,
  salesTaxBps: 0,
  nonce: 1n,
} as const
 
const { to, functionName, args } = Gatekeeper.prepareGetPrice({
  gatekeeper: gatekeeperAddress[chainId],
  saleKey,
  purchaseParams: {
    recipient: '0x6789012345678901234567890123456789012345',
    referrer: '0x7890123456789012345678901234567890123456',
    quantity: 1,
  },
  as: 'args',
})
 
// Read with viem
// const result = await publicClient.readContract({
//   address: to,
//   abi: gatekeeperAbi,
//   functionName,
//   args,
// })
// Result: { totalPrice, subtotal, protocolFee, vendorFee, referrerFee, salesTax }

Checking Sale Status

Verify a sale exists and is active:

import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
 
const chainId = 8453 // Base
 
const saleKey = {
  token: '0x2345678901234567890123456789012345678901', // Your deployed contract
  tokenId: 1n,
  currency: '0x1111111111111111111111111111111111111111',
  fundsRecipient: '0x4567890123456789012345678901234567890123',
  vendor: '0x5678901234567890123456789012345678901234',
  vendorFeeBps: 0,
  referralFeeBps: 0,
  salesTaxBps: 0,
  nonce: 1n,
} as const
 
// Check if sale is registered
const { to, functionName, args } = Gatekeeper.prepareIsSaleRegistered({
  gatekeeper: gatekeeperAddress[chainId],
  saleKey,
  as: 'args',
})
 
// Get full sale state
const getSale = Gatekeeper.prepareGetSale({
  gatekeeper: gatekeeperAddress[chainId],
  saleKey,
  as: 'args',
})

Withdrawing Funds

After sales, withdraw accumulated funds:

import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
 
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
})
 
const chainId = 8453 // Base
 
const { to, data } = Gatekeeper.prepareWithdraw({
  gatekeeper: gatekeeperAddress[chainId],
  currency: '0x1111111111111111111111111111111111111111',
  amount: 1000000n, // 1.000000 in a 6-decimal currency
})
 
// Execute withdrawal
// await walletClient.sendTransaction({ to, data })

Gatekeeper tracks balances internally and does not transfer funds directly to vendors or recipients at purchase time. Each recipient calls withdraw for their own balance. Protocol fees are withdrawn by the contract owner via withdrawProtocol.

Next Steps

Common Errors

  • Approval required: the sale has approvers set, so preparePurchase will revert. Use preparePurchaseWithApproval instead.
  • Signature expired / already used: approval signatures are time‑bounded and single‑use. Regenerate with a fresh salt and a new deadline.
  • Approver not authorized: the signer isn’t in saleState.approvers.
  • Sale inactive: current time is outside startTime/endTime.
  • Insufficient quantity: requested quantity exceeds remaining sale quantity.
  • ERC20 allowance or balance: buyer hasn’t approved enough or doesn’t have enough balance in SaleKey.currency.