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:
- Generate EIP-712 typed data
- Get a signature from an authorized approver
- 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
- API Reference: Gatekeeper for complete function documentation
- API Reference: Approval for EIP-712 signature details
Common Errors
- Approval required: the sale has approvers set, so
preparePurchasewill revert. UsepreparePurchaseWithApprovalinstead. - Signature expired / already used: approval signatures are time‑bounded and single‑use. Regenerate with a fresh
saltand a newdeadline. - Approver not authorized: the signer isn’t in
saleState.approvers. - Sale inactive: current time is outside
startTime/endTime. - Insufficient quantity: requested
quantityexceeds remaining salequantity. - ERC20 allowance or balance: buyer hasn’t approved enough or doesn’t have enough balance in
SaleKey.currency.