# Gatekeeper SDK
> Type-safe utilities for the Gatekeeper ticketing protocol
## Getting Started
### Installation
:::code-group
```bash [pnpm]
pnpm add @sceneinfrastructure/sdk
```
```bash [npm]
npm install @sceneinfrastructure/sdk
```
```bash [yarn]
yarn add @sceneinfrastructure/sdk
```
:::
#### Peer Dependencies
```bash
pnpm add ox zod viem @ponder/client
```
:::info
Only install `@ponder/client` if you plan to use the indexed data client. `viem` is only required if you intend to send transactions or read onchain state; the SDK itself just prepares calldata.
:::
### SDK Surface Area
* **Onchain helpers**: `Factory`, `Gatekeeper`, `Gatekeeper1155` for deployment, sales, purchases, and ticket ops.
* **Indexer client**: `Client` for Ponder SQL queries and live subscriptions.
### Basic Usage
#### Import the SDK
```ts twoslash
///
// ---cut---
import {
Factory,
Gatekeeper,
Approval,
Sale,
SaleKey,
MINTER_ROLE,
gatekeeperAddress,
gatekeeper1155FactoryAddress,
} from '@sceneinfrastructure/sdk'
```
#### Contract Addresses
The SDK exports deployed contract addresses for supported chains:
```ts twoslash
///
// ---cut---
import {
gatekeeperAddress,
gatekeeper1155FactoryAddress,
gatekeeper1155Address,
} from '@sceneinfrastructure/sdk'
// Access addresses by chain ID
const gatekeeperOnBase = gatekeeperAddress[8453]
const factoryOnBase = gatekeeper1155FactoryAddress[8453]
```
#### Role Constants
Common role constants for access control:
```ts twoslash
///
// ---cut---
import { ADMIN_ROLE, HOST_ROLE, DOOR_ROLE, MINTER_ROLE } from '@sceneinfrastructure/sdk'
// Combine roles with bitwise OR
const combinedRoles = ADMIN_ROLE | MINTER_ROLE
```
### Output Formats
SDK functions support two output formats via the `as` parameter:
| Format | Returns | Use With |
| ------------------ | ---------------------------- | ----------------- |
| `'data'` (default) | `{ to, data }` | `sendTransaction` |
| `'args'` | `{ to, functionName, args }` | `writeContract` |
```ts twoslash
///
// ---cut---
import { Gatekeeper } from '@sceneinfrastructure/sdk'
const saleKey = {
token: '0x1234567890123456789012345678901234567890',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x3456789012345678901234567890123456789012',
fundsRecipient: '0x4567890123456789012345678901234567890123',
tokenId: 1n,
} as const
// Default: raw calldata
const { to, data } = Gatekeeper.preparePurchase({
gatekeeper: '0x5678901234567890123456789012345678901234',
saleKey,
purchaseParams: {
recipient: '0x6789012345678901234567890123456789012345',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
},
})
// With as: 'args' for viem's writeContract
const { to: addr, functionName, args } = Gatekeeper.preparePurchase({
gatekeeper: '0x5678901234567890123456789012345678901234',
saleKey,
purchaseParams: {
recipient: '0x6789012345678901234567890123456789012345',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
},
as: 'args',
})
```
### Querying Indexed Data
```ts twoslash
///
// ---cut---
import { Client, ADMIN_ROLE } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
// Query contracts
const contract = await client.eventContract.get({
contractAddress: '0x1234567890123456789012345678901234567890'
})
// Check permissions
const isAdmin = await client.permission.hasRole({
contractAddress: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
role: ADMIN_ROLE,
})
```
See [Querying Guide](/guides/querying) for live subscriptions and raw queries.
### Next Steps
Now that you have the SDK installed, learn how to:
* [Quickstart](/guides/quickstart)
* [Deploy a Gatekeeper1155 contract](/guides/deploying)
* [Register and manage sales](/guides/sales)
* [Execute purchases](/guides/purchasing)
* [Query indexed data](/guides/querying)
## ABIs
Contract ABIs and config objects exported from the SDK.
```ts twoslash
///
// ---cut---
import {
gatekeeperAbi,
gatekeeper1155Abi,
gatekeeper1155FactoryAbi,
} from '@sceneinfrastructure/sdk'
```
### Available ABIs
#### gatekeeperAbi
The Gatekeeper contract ABI for sales and purchases.
```ts twoslash
///
// ---cut---
import { gatekeeperAbi } from '@sceneinfrastructure/sdk'
// Use with viem
// const hash = await walletClient.writeContract({
// address: gatekeeperAddress[chainId],
// abi: gatekeeperAbi,
// functionName: 'purchase',
// args: [saleKey, purchaseParams],
// })
```
Key functions:
* `registerSale` - Register a new sale configuration
* `updateSale` - Update an existing sale
* `purchase` - Execute a purchase
* `purchaseWithApproval` - Execute a purchase with EIP-712 signature
* `getPrice` - Calculate purchase price with fees
* `withdraw` - Withdraw funds
#### gatekeeper1155Abi
The Gatekeeper1155 ERC1155 contract ABI for ticket operations.
```ts twoslash
///
// ---cut---
import { gatekeeper1155Abi } from '@sceneinfrastructure/sdk'
// Use with viem
// const balance = await publicClient.readContract({
// address: contractAddress,
// abi: gatekeeper1155Abi,
// functionName: 'balanceOf',
// args: [userAddress, tokenId],
// })
```
Key functions:
* `adminMint` - Mint tickets (requires MINTER\_ROLE, HOST\_ROLE, or ADMIN\_ROLE)
* `adminStubTickets` - Check-in attendees (requires DOOR\_ROLE, HOST\_ROLE, or ADMIN\_ROLE)
* `voidTickets` - Burn tickets (requires ADMIN\_ROLE, HOST\_ROLE, or MINTER\_ROLE)
* `setupNewTier` - Create a new token tier
* `updateToken` - Update tier metadata
* `grantRoles` / `revokeRoles` - Role management
* `balanceOf` - Check ticket balance
* `getTierInfo` - Get tier metadata
#### gatekeeper1155FactoryAbi
The factory contract ABI for deploying ticket contracts.
```ts twoslash
///
// ---cut---
import { gatekeeper1155FactoryAbi } from '@sceneinfrastructure/sdk'
// Use with viem
// const { request } = await publicClient.simulateContract({
// address: gatekeeper1155FactoryAddress[chainId],
// abi: gatekeeper1155FactoryAbi,
// functionName: 'deploy',
// args: [implementation, salt, owner, contractURI, calls],
// })
```
Key functions:
* `deploy` - Deploy a new Gatekeeper1155 proxy
* `predictDeterministicAddress` - Calculate CREATE2 address
### Config Objects
For convenience, the SDK also exports config objects that combine addresses and ABIs:
```ts twoslash
///
// ---cut---
import {
gatekeeperConfig,
gatekeeper1155Config,
gatekeeper1155FactoryConfig,
} from '@sceneinfrastructure/sdk'
// Each config contains address and abi
console.log(gatekeeperConfig.address[8453])
console.log(gatekeeperConfig.abi)
```
### Usage with SDK Modules
The SDK modules handle ABI encoding internally, so you typically don't need to use ABIs directly:
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
// SDK encodes the ABI call for you
const { to, data } = Gatekeeper1155.prepareAdminMint({
contract: '0x...',
recipient: '0x...',
tokenId: 1n,
quantity: 1n,
})
// Just send the transaction
// await walletClient.sendTransaction({ to, data })
```
However, ABIs are useful for:
* Reading contract state with `publicClient.readContract`
* Using viem's `simulateContract` for gas estimation
* Building custom integrations
### Usage Example
```ts twoslash
///
// ---cut---
import {
gatekeeperAbi,
gatekeeperAddress,
gatekeeper1155Abi,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
// Read sale state
// const sale = await publicClient.readContract({
// address: gatekeeperAddress[chainId],
// abi: gatekeeperAbi,
// functionName: 'getSale',
// args: [saleKeyId],
// })
// Read ticket balance
// const balance = await publicClient.readContract({
// address: contractAddress,
// abi: gatekeeper1155Abi,
// functionName: 'balanceOf',
// args: [userAddress, 1n],
// })
```
## Contract Addresses
Deployed contract addresses for the Gatekeeper protocol.
```ts twoslash
///
// ---cut---
import {
gatekeeperAddress,
gatekeeper1155Address,
gatekeeper1155FactoryAddress,
} from '@sceneinfrastructure/sdk'
```
### Supported Networks
| Network | Chain ID |
| ------------ | -------- |
| Base Mainnet | `8453` |
| Base Sepolia | `84532` |
### Gatekeeper
The main Gatekeeper contract handles sales, purchases, and fund distribution.
```ts twoslash
///
// ---cut---
import { gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const address = gatekeeperAddress[chainId]
// '0xC937fb52d0dF60ecba04eb5b1186aA3A9d267910'
```
| Network | Address |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Base Mainnet | [`0xC937fb52d0dF60ecba04eb5b1186aA3A9d267910`](https://basescan.org/address/0xC937fb52d0dF60ecba04eb5b1186aA3A9d267910) |
| Base Sepolia | [`0x34b3E31A9a48b1254f221F065c6840C4c869ED6A`](https://sepolia.basescan.org/address/0x34b3E31A9a48b1254f221F065c6840C4c869ED6A) |
### Gatekeeper1155
The ERC1155 implementation contract used as the template for ticket contracts.
```ts twoslash
///
// ---cut---
import { gatekeeper1155Address } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const implementation = gatekeeper1155Address[chainId]
// '0x3740Eb562aff5eda722C5530555f98669F9F8489'
```
| Network | Address |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Base Mainnet | [`0x3740Eb562aff5eda722C5530555f98669F9F8489`](https://basescan.org/address/0x3740Eb562aff5eda722C5530555f98669F9F8489) |
| Base Sepolia | [`0x9aF5185589bDA188de8FA5Fc3319540042a881e8`](https://sepolia.basescan.org/address/0x9aF5185589bDA188de8FA5Fc3319540042a881e8) |
### Gatekeeper1155Factory
The factory contract for deploying new ticket contracts.
```ts twoslash
///
// ---cut---
import { gatekeeper1155FactoryAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const factory = gatekeeper1155FactoryAddress[chainId]
// '0x36d9641Aa964061CA21253e23d7BbfEBc33d1Bae'
```
| Network | Address |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| Base Mainnet | [`0x36d9641Aa964061CA21253e23d7BbfEBc33d1Bae`](https://basescan.org/address/0x36d9641Aa964061CA21253e23d7BbfEBc33d1Bae) |
| Base Sepolia | [`0xe6f9A5E7868C5daBCa486f8cd75a47b50333D8bb`](https://sepolia.basescan.org/address/0xe6f9A5E7868C5daBCa486f8cd75a47b50333D8bb) |
### Type-Safe Access
All address objects are typed with chain IDs:
```ts twoslash
///
// ---cut---
import { gatekeeperAddress } from '@sceneinfrastructure/sdk'
// TypeScript knows valid chain IDs
gatekeeperAddress[8453] // OK
gatekeeperAddress[84532] // OK
// gatekeeperAddress[1] // Type error - chain not supported
```
### Usage Example
```ts twoslash
///
// ---cut---
import {
Factory,
Gatekeeper,
gatekeeperAddress,
gatekeeper1155Address,
gatekeeper1155FactoryAddress,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const walletAddress = '0x3456789012345678901234567890123456789012'
// Deploy a new ticket contract
const { to, data, predictedAddress } = Factory.prepareDeploy({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
nonce: BigInt(Date.now()),
creator: walletAddress,
owner: walletAddress,
contractURI: 'ipfs://...',
})
// Create a sale key with a currency address
const saleKey = {
token: predictedAddress,
currency: '0x1111111111111111111111111111111111111111',
// ... other fields
} as const
```
## Constants
Protocol constants exported from the SDK.
```ts twoslash
///
// ---cut---
import {
ADMIN_ROLE,
HOST_ROLE,
DOOR_ROLE,
MINTER_ROLE,
PROTOCOL_FEE_BPS,
} from '@sceneinfrastructure/sdk'
```
### Role Constants
Bitmap-based role values for access control on Gatekeeper1155 contracts.
#### ADMIN\_ROLE
```ts twoslash
///
// ---cut---
const ADMIN_ROLE = 1n // 1 << 0
```
Full contract control:
* Setup new tiers
* Mint tickets
* Stub tickets (check-in)
* Void tickets
* Update token metadata
* Grant and revoke roles
* Call gatekeeper contract
#### HOST\_ROLE
```ts twoslash
///
// ---cut---
const HOST_ROLE = 2n // 1 << 1
```
Event management capabilities:
* Mint tickets
* Stub tickets (check-in)
* Void tickets
#### DOOR\_ROLE
```ts twoslash
///
// ---cut---
const DOOR_ROLE = 4n // 1 << 2
```
Check-in only:
* Stub tickets (convert to used state)
#### MINTER\_ROLE
```ts twoslash
///
// ---cut---
const MINTER_ROLE = 8n // 1 << 3
```
Issuance permissions:
* Mint tickets
* Void tickets
* Required by Gatekeeper contract for purchases
### Combining Roles
Roles can be combined with bitwise OR when granting multiple roles to staff:
```ts twoslash
///
// ---cut---
import { HOST_ROLE, DOOR_ROLE } from '@sceneinfrastructure/sdk'
// Grant HOST + DOOR to event staff
const staffRoles = HOST_ROLE | DOOR_ROLE // 6n
```
### Protocol Constants
#### PROTOCOL\_FEE\_BPS
```ts twoslash
///
// ---cut---
const PROTOCOL_FEE_BPS = 100 // 1%
```
The protocol fee in basis points (1/100th of a percent).
* 100 BPS = 1% fee on all sales
* Fee is collected by the protocol on each purchase
### Indexer Endpoints
Docs-only placeholders for Ponder SQL endpoints:
```ts
const PONDER_ENDPOINTS = {
dev: 'https://ponder-dev.example.com/sql',
prod: 'https://ponder.example.com/sql',
} as const
```
### Role Values Reference
| Constant | Value | Binary | Hex |
| ------------- | ----- | ------ | ----- |
| `ADMIN_ROLE` | `1n` | `0001` | `0x1` |
| `HOST_ROLE` | `2n` | `0010` | `0x2` |
| `DOOR_ROLE` | `4n` | `0100` | `0x4` |
| `MINTER_ROLE` | `8n` | `1000` | `0x8` |
### Usage Example
```ts twoslash
///
// ---cut---
import {
Gatekeeper1155,
Factory,
MINTER_ROLE,
HOST_ROLE,
DOOR_ROLE,
gatekeeperAddress,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
// Grant MINTER to Gatekeeper for sales
const grantMinter = Factory.encodeGrantRoles({
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
})
// Grant HOST + DOOR to staff
const grantStaff = Factory.encodeGrantRoles({
user: '0x2345678901234567890123456789012345678901',
roles: HOST_ROLE | DOOR_ROLE,
})
```
## Deploying Contracts
Deploy Gatekeeper1155 contracts via the factory using CREATE2 for deterministic addresses.
### Deploy with Setup Calls
```ts twoslash
///
// ---cut---
import {
Factory,
MINTER_ROLE,
gatekeeper1155FactoryAddress,
gatekeeper1155Address,
gatekeeperAddress,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const walletAddress = '0x3456789012345678901234567890123456789012'
// Prepare setup calls
const setupCalls = [
// Grant MINTER role to the Gatekeeper contract
Factory.encodeGrantRoles({
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
}),
// Create a token tier
Factory.encodeSetupNewTier({
tokenURI: 'ipfs://QmTokenMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 1000n,
}),
]
const { to, data, predictedAddress } = Factory.prepareDeploy({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
nonce: BigInt(Date.now()),
creator: walletAddress,
owner: walletAddress,
contractURI: 'ipfs://QmContractMetadata...',
calls: setupCalls,
})
```
### Setup Call Helpers
#### Grant Roles
Grant access control roles to addresses:
```ts twoslash
///
// ---cut---
import {
Factory,
ADMIN_ROLE,
HOST_ROLE,
DOOR_ROLE,
MINTER_ROLE,
gatekeeperAddress,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
// Grant single role (e.g., to the Gatekeeper contract)
const grantMinter = Factory.encodeGrantRoles({
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
})
// Grant multiple roles to an admin address
const grantMultiple = Factory.encodeGrantRoles({
user: '0x2345678901234567890123456789012345678901',
roles: ADMIN_ROLE | HOST_ROLE,
})
```
#### Setup Token Tiers
Create token tiers with metadata and supply limits:
```ts twoslash
///
// ---cut---
import { Factory } from '@sceneinfrastructure/sdk'
// Limited supply tier
const limitedTier = Factory.encodeSetupNewTier({
tokenURI: 'ipfs://QmTokenMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 500n,
})
// Unlimited supply tier (maxSupply = 0)
const unlimitedTier = Factory.encodeSetupNewTier({
tokenURI: 'ipfs://QmTokenMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 0n,
})
```
#### Call Gatekeeper
Execute calls to the Gatekeeper contract during deployment:
```ts twoslash
///
// ---cut---
import { Factory, Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
// Token contract will be the predictedAddress from prepareDeploy
const tokenContractAddress = '0x2345678901234567890123456789012345678901'
const saleKey = {
token: tokenContractAddress,
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x3456789012345678901234567890123456789012',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x5678901234567890123456789012345678901234',
tokenId: 1n,
} as const
// Encode a registerSale call
const registerData = Gatekeeper.encodeRegisterSale({
saleKey,
saleState: {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1000000n,
approvers: [],
},
})
// Wrap in callGatekeeper
const gatekeeperCall = Factory.encodeCallGatekeeper({
gatekeeper: gatekeeperAddress[chainId],
data: registerData,
})
```
### Next Steps
* [Register a sale](/guides/sales) on your deployed contract
* [API Reference: Factory](/api/factory) for complete function documentation
## 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`.
```ts twoslash
///
// ---cut---
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.
```ts twoslash
///
// ---cut---
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
```ts twoslash
///
// ---cut---
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:
```ts twoslash
///
// ---cut---
// This happens on the approver's side
// const signature = await approverWallet.signTypedData(typedData)
```
#### Step 3: Execute with Approval
```ts twoslash
///
// ---cut---
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:
```ts twoslash
///
// ---cut---
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:
```ts twoslash
///
// ---cut---
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:
```ts twoslash
///
// ---cut---
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:
```ts twoslash
///
// ---cut---
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](/api/gatekeeper) for complete function documentation
* [API Reference: Approval](/api/approval) for EIP-712 signature details
### 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`.
import { PonderReadDemo } from '../../components/PonderReadDemo'
## Querying Indexed Data
### Creating a Client
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
```
The `url` should point to your Ponder SQL endpoint.
### Try It
Point this at your Ponder SQL endpoint to run a live read query in the browser.
### Domain Namespaces
The client provides domain-specific namespaces for common query patterns:
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
const walletAddress = '0x3456789012345678901234567890123456789012'
// Query deployed contracts
const contract = await client.eventContract.get({
contractAddress: '0x1234567890123456789012345678901234567890'
})
// List contracts by creator
const myContracts = await client.eventContract.listByCreator({
creator: walletAddress
})
// Check token balances
const balance = await client.balance.get({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
address: walletAddress
})
// Query sale configurations
const sales = await client.sale.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890'
})
```
#### Available Namespaces
| Namespace | Description |
| --------------- | ------------------------------------------ |
| `eventContract` | Deployed Gatekeeper1155 contracts |
| `sale` | Sale configurations |
| `token` | ERC1155 token data |
| `tier` | Ticket tier configurations |
| `balance` | Token holder balances |
| `transfer` | Transfer history |
| `permission` | Role grants and permissions |
| `stub` | Check-in events (ticket to stub exchanges) |
| `void` | Voiding events (burns without stub) |
### Pagination
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
const page1 = await client.eventContract.list({ limit: 10, offset: 0 })
const page2 = await client.eventContract.list({ limit: 10, offset: 10 })
const sales = await client.sale.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
orderBy: 'createdAt',
order: 'desc'
})
```
### Raw Drizzle Queries
For complex queries not covered by the namespaces, use the raw Drizzle query builder:
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
import { eq, and, gt } from '@ponder/client'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
// Access the query builder
const results = await client.db
.select()
.from(client.schema.onchainTicketBalance)
.where(
and(
eq(client.schema.onchainTicketBalance.contractAddress, '0x1234567890123456789012345678901234567890'),
gt(client.schema.onchainTicketBalance.balance, 0n)
)
)
.limit(100)
```
#### Available Schema Tables
Access tables via `client.schema`:
* `onchainEventContract` - Deployed contracts
* `onchainToken` - Token configurations
* `onchainTicketTier` - Tier configurations
* `onchainSaleConfig` - Sale configurations
* `onchainOrder` - Purchase orders
* `onchainOrderItem` - Order line items
* `onchainTicketTransfer` - Transfer events
* `onchainTicketBalance` - Current balances
* `onchainRoleGrant` - Role assignments
* `onchainSwap` - Check-in swaps
* `onchainVoid` - Void events
### Live Subscriptions
Subscribe to real-time updates using Server-Sent Events:
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
const { unsubscribe } = client.live(
// Query function
(db) => db.select().from(client.schema.onchainOrder).limit(10),
// On result callback
(result) => {
console.log('Orders updated:', result)
},
// On error callback
(error) => {
console.error('Subscription error:', error)
}
)
// Later: clean up the subscription
unsubscribe()
```
#### Cleanup Pattern
Always unsubscribe when the component unmounts or the subscription is no longer needed:
```ts
import { useEffect, useState } from 'react'
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
// React example
const [balances, setBalances] = useState([])
useEffect(() => {
const { unsubscribe } = client.live(
(db) => db.select().from(client.schema.onchainTicketBalance),
setBalances,
console.error
)
return () => unsubscribe()
}, [])
```
### Indexer Status
Check the indexer sync status for all chains:
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
const status = await client.getStatus()
console.log('Indexer status:', status)
```
### Next Steps
* [Client API Reference](/api/client) - Full client API documentation
* [EventContract](/api/event-contract) - Query deployed contracts
* [Balance](/api/balance) - Query token balances
* [Permission](/api/permission) - Query role assignments
## Quickstart
A minimal, end-to-end path to deploy a contract, register a sale, sell tickets, and withdraw funds.
:::info
This guide assumes you have a funded wallet on your target chain and a Gatekeeper indexer running (optional for reads).
:::
### 1) Deploy a ticket contract
You will:
* Grant `MINTER_ROLE` to the Gatekeeper contract (required for purchases to mint).
* Create the first ticket tier.
```ts twoslash
///
// ---cut---
import {
Factory,
MINTER_ROLE,
gatekeeperAddress,
gatekeeper1155Address,
gatekeeper1155FactoryAddress,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const walletAddress = '0x3456789012345678901234567890123456789012'
const setupCalls = [
// Allow the Gatekeeper contract to mint tickets on purchase
Factory.encodeGrantRoles({
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
}),
// Create the first tier (tokenId 1 on a new contract)
Factory.encodeSetupNewTier({
tokenURI: 'ipfs://QmTicketMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 1000n,
}),
]
const { to, data, predictedAddress } = Factory.prepareDeploy({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
nonce: BigInt(Date.now()),
creator: walletAddress,
owner: walletAddress,
contractURI: 'ipfs://QmContractMetadata...',
calls: setupCalls,
})
console.log('Predicted contract address:', predictedAddress)
// await walletClient.sendTransaction({ to, data })
```
### 2) Register a sale
`registerSale` must be executed via `Gatekeeper1155.callGatekeeper` on your token contract (requires `ADMIN_ROLE` or owner).
```ts twoslash
///
// ---cut---
import {
Gatekeeper,
gatekeeper1155Abi,
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 tokenContract = '0x2345678901234567890123456789012345678901'
const saleKey = {
token: tokenContract,
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x4567890123456789012345678901234567890123',
vendor: '0x5678901234567890123456789012345678901234',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const saleState = {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1_000000n, // 1.000000 in a 6-decimal currency
approvers: [],
}
const registerData = Gatekeeper.encodeRegisterSale({ saleKey, saleState })
// await walletClient.writeContract({
// address: tokenContract,
// abi: gatekeeper1155Abi,
// functionName: 'callGatekeeper',
// args: [gatekeeperAddress[chainId], registerData],
// })
```
### 3) Approve currency + purchase
If `totalPrice` is greater than zero, the buyer must approve the Gatekeeper contract to spend `saleKey.currency` before purchasing.
```ts twoslash
///
// ---cut---
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
// await walletClient.writeContract({
// address: currencyAddress,
// abi: erc20Abi,
// functionName: 'approve',
// args: [gatekeeperAddress[chainId], 1_000000n],
// })
```
Then execute the purchase:
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x2345678901234567890123456789012345678901',
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: 1,
},
})
// await walletClient.sendTransaction({ to, data })
```
If the sale has approvers, use [Approval](/api/approval) + `preparePurchaseWithApproval` instead.
### 4) (Optional) Read data from the indexer
If you have a Ponder SQL endpoint, you can read back the contract, sale, and ticket status.
```ts twoslash
///
// ---cut---
import { Client, SaleKey } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const chainId = 8453 // Base
const tokenContract = '0x2345678901234567890123456789012345678901'
// Contract metadata
const contract = await client.eventContract.get({
contractAddress: tokenContract,
})
// Sale config
const saleKey = {
token: tokenContract,
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x4567890123456789012345678901234567890123',
vendor: '0x5678901234567890123456789012345678901234',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const saleKeyId = SaleKey.generateId(saleKey)
const sale = await client.sale.get({ saleKeyId })
// Buyer ticket status
const status = await client.balance.getTokenStatus({
contractAddress: tokenContract,
tokenId: 1n,
address: '0x6789012345678901234567890123456789012345',
})
```
### 5) Withdraw funds
Gatekeeper tracks balances internally. Each recipient withdraws their own balance.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const { to, data } = Gatekeeper.prepareWithdraw({
gatekeeper: gatekeeperAddress[chainId],
currency: '0x1111111111111111111111111111111111111111',
amount: 1_000000n,
})
// await walletClient.sendTransaction({ to, data })
```
### 6) (Optional) Check-in or void tickets
Use `Gatekeeper1155.prepareAdminStubTickets` to check-in (stub) tickets, or `Gatekeeper1155.prepareVoidTickets` to void them. See [Ticket Management](/guides/tickets).
### Next Steps
* [Role Management](/guides/roles)
* [Managing Sales](/guides/sales)
* [Executing Purchases](/guides/purchasing)
* [Ticket Management](/guides/tickets)
## Role Management
### Available Roles
```ts twoslash
///
// ---cut---
import {
ADMIN_ROLE,
HOST_ROLE,
DOOR_ROLE,
MINTER_ROLE,
} from '@sceneinfrastructure/sdk'
// Role values (bitmask)
console.log(ADMIN_ROLE) // 1n (1 << 0)
console.log(HOST_ROLE) // 2n (1 << 1)
console.log(DOOR_ROLE) // 4n (1 << 2)
console.log(MINTER_ROLE) // 8n (1 << 3)
```
#### Role Capabilities
| Role | Value | Capabilities |
| ------------- | ----- | ------------------------------------------------------------------------------------------- |
| `ADMIN_ROLE` | `1n` | Full control: setup tiers, mint, stub, void, update metadata, manage roles, call gatekeeper |
| `HOST_ROLE` | `2n` | Event management: mint tickets, stub tickets (check-in), void tickets |
| `DOOR_ROLE` | `4n` | Check-in only: stub tickets |
| `MINTER_ROLE` | `8n` | Issuance: mint tickets, void tickets |
#### Permission Matrix
| Function | ADMIN | HOST | DOOR | MINTER | Owner |
| ------------------ | :---: | :--: | :--: | :----: | :---: |
| `setupNewTier` | ✓ | | | | ✓ |
| `adminMint` | ✓ | ✓ | | ✓ | ✓ |
| `adminStubTickets` | ✓ | ✓ | ✓ | | ✓ |
| `voidTickets` | ✓ | ✓ | | ✓ | ✓ |
| `updateToken` | ✓ | | | | ✓ |
| `callGatekeeper` | ✓ | | | | ✓ |
| `grantRoles` | ✓ | | | | ✓ |
| `revokeRoles` | ✓ | | | | ✓ |
### Granting Roles
Grant roles to addresses. Requires `ADMIN_ROLE` (the owner is also permitted).
#### Single Role
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155, MINTER_ROLE, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const chainId = 8453 // Base
// Grant MINTER to Gatekeeper contract (required for sales)
const { to, data } = Gatekeeper1155.prepareGrantRoles({
contract: '0x1234567890123456789012345678901234567890',
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
})
// await walletClient.sendTransaction({ to, data })
```
#### Multiple Roles
Combine roles with bitwise OR:
```ts twoslash
///
// ---cut---
import { Gatekeeper1155, ADMIN_ROLE, HOST_ROLE, DOOR_ROLE } from '@sceneinfrastructure/sdk'
// Grant multiple roles to an operator
const { to, data } = Gatekeeper1155.prepareGrantRoles({
contract: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
roles: HOST_ROLE | DOOR_ROLE, // Combine with |
})
```
### Revoking Roles
Remove roles from addresses. Requires `ADMIN_ROLE` (the owner is also permitted).
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155, MINTER_ROLE } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const { to, data } = Gatekeeper1155.prepareRevokeRoles({
contract: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
roles: MINTER_ROLE,
})
// await walletClient.sendTransaction({ to, data })
```
### Roles During Deployment
Grant roles as part of contract deployment:
```ts twoslash
///
// ---cut---
import {
Factory,
MINTER_ROLE,
HOST_ROLE,
DOOR_ROLE,
gatekeeperAddress,
gatekeeper1155FactoryAddress,
gatekeeper1155Address,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const walletAddress = '0x3456789012345678901234567890123456789012'
const operatorAddress = '0x4567890123456789012345678901234567890123'
const setupCalls = [
// Grant MINTER to Gatekeeper (required for sales)
Factory.encodeGrantRoles({
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
}),
// Grant HOST + DOOR to operator
Factory.encodeGrantRoles({
user: operatorAddress,
roles: HOST_ROLE | DOOR_ROLE,
}),
]
const { to, data, predictedAddress } = Factory.prepareDeploy({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
nonce: BigInt(Date.now()),
creator: walletAddress,
owner: walletAddress,
contractURI: 'ipfs://...',
calls: setupCalls,
})
```
### Typical Role Assignments
#### Event Setup
For a typical event, assign roles as follows:
| Address | Roles | Purpose |
| ------------------- | ------------------------ | ------------------------------------- |
| Gatekeeper Contract | `MINTER_ROLE` | Enable ticket purchases through sales |
| Event Owner | `ADMIN_ROLE` | Full contract control |
| Event Staff | `HOST_ROLE \| DOOR_ROLE` | Manage event and check-in attendees |
#### Checking Roles
Read role assignments using viem directly:
```ts twoslash
///
// ---cut---
import { gatekeeper1155Abi } from '@sceneinfrastructure/sdk'
// Check if address has role
// const hasRole = await publicClient.readContract({
// address: contractAddress,
// abi: gatekeeper1155Abi,
// functionName: 'hasAllRoles',
// args: [userAddress, DOOR_ROLE],
// })
```
### Next Steps
* [Deploying Contracts](/guides/deploying) with role setup
* [Ticket Management](/guides/tickets) for operations requiring roles
* [Reference: Constants](/reference/constants) for role values
## Managing Sales
Sales are identified by a `SaleKey` and configured with a `SaleState`.
Key details:
* The `SaleKey` is the immutable identity of a sale. Changing any field creates a new `saleKeyId`. Use `updateSale` only when the `SaleKey` stays the same, and use `nonce` to create multiple sales for the same token.
* `startTime` and `endTime` are Unix timestamps (seconds). `startTime` must be non-zero; `0n` is treated as "uninitialized" onchain.
* `price` is expressed in the smallest unit of the sale currency (use the ERC20's decimals).
### Registering a Sale
```ts twoslash
///
// ---cut---
import { Gatekeeper, Factory, 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
const saleState = {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1000000n,
approvers: [],
}
// Encode the registerSale call
const registerData = Gatekeeper.encodeRegisterSale({
saleKey,
saleState,
})
// Wrap in callGatekeeper (for use during deployment)
const callData = Factory.encodeCallGatekeeper({
gatekeeper: gatekeeperAddress[chainId],
data: registerData,
})
```
If you're registering or updating sales after deployment, call `Gatekeeper1155.callGatekeeper` directly (requires `ADMIN_ROLE` or owner on the token contract):
```ts twoslash
///
// ---cut---
import { gatekeeper1155Abi, 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 tokenContract = '0x2345678901234567890123456789012345678901'
const saleKey = {
token: tokenContract,
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x4567890123456789012345678901234567890123',
vendor: '0x5678901234567890123456789012345678901234',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const saleState = {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1000000n,
approvers: [],
}
const registerData = Gatekeeper.encodeRegisterSale({ saleKey, saleState })
// await walletClient.writeContract({
// address: tokenContract,
// abi: gatekeeper1155Abi,
// functionName: 'callGatekeeper',
// args: [gatekeeperAddress[chainId], registerData],
// })
```
### Approval-Gated Sales
For sales requiring approval signatures, add approver addresses:
```ts twoslash
///
// ---cut---
import { Gatekeeper } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
vendor: '0x4567890123456789012345678901234567890123',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const registerData = Gatekeeper.encodeRegisterSale({
saleKey,
saleState: {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1000000n,
approvers: [
'0x5678901234567890123456789012345678901234',
'0x6789012345678901234567890123456789012345',
],
},
})
```
When approvers are set, purchases require a valid EIP-712 signature from one of the approvers. See [Executing Purchases](/guides/purchasing) for details.
### Updating a Sale
Update an existing sale's configuration:
```ts twoslash
///
// ---cut---
import { Gatekeeper, Factory, 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
// End the sale by setting endTime to now
const updateData = Gatekeeper.encodeUpdateSale({
saleKey,
saleState: {
startTime: 1n,
endTime: BigInt(Math.floor(Date.now() / 1000)),
quantity: 0,
price: 0n,
approvers: [],
},
})
// Wrap in callGatekeeper
const callData = Factory.encodeCallGatekeeper({
gatekeeper: gatekeeperAddress[chainId],
data: updateData,
})
```
### Generating Sale Key IDs
The `SaleKey` is hashed to create a unique identifier:
```ts twoslash
///
// ---cut---
import { SaleKey } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
vendor: '0x4567890123456789012345678901234567890123',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const saleKeyId = SaleKey.generateId(saleKey)
console.log('Sale Key ID:', saleKeyId)
```
### Converting Sale to SaleKey
If you have a full `Sale` object from the indexer, convert it to a `SaleKey`:
```ts twoslash
///
// ---cut---
import { Client, Sale } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
// Get a sale from the indexer
const saleKeyId = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as const
const sale = await client.sale.get({ saleKeyId })
if (sale) {
// Convert to SaleKey for contract calls
const saleKey = Sale.toSaleKey(sale)
console.log('SaleKey:', saleKey)
}
```
### Next Steps
* [Execute purchases](/guides/purchasing) from your registered sales
* [API Reference: Gatekeeper](/api/gatekeeper) for complete function documentation
* [API Reference: Sale](/api/sale) for type definitions
## Ticket Management
### Minting Tickets
Admin mint tickets to recipients. Requires `MINTER_ROLE`, `HOST_ROLE`, or `ADMIN_ROLE` (the owner is also permitted).
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } 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 contractAddress = '0x1234567890123456789012345678901234567890'
const recipientAddress = '0x2345678901234567890123456789012345678901'
const { to, data } = Gatekeeper1155.prepareAdminMint({
contract: contractAddress,
recipient: recipientAddress,
tokenId: 1n, // Tier ID
quantity: 2n,
})
// Execute with your wallet client
// await walletClient.sendTransaction({ to, data })
```
#### Mint with Custom Data
Pass custom data to the recipient's `onERC1155Received` hook:
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const { to, data } = Gatekeeper1155.prepareAdminMint({
contract: '0x1234567890123456789012345678901234567890',
recipient: '0x2345678901234567890123456789012345678901',
tokenId: 1n,
quantity: 1n,
data: '0xdeadbeef', // Custom data
})
```
### Check-in (Stubbing Tickets)
Convert tickets to stubs when attendees check in. Requires `DOOR_ROLE`, `HOST_ROLE`, or `ADMIN_ROLE` (the owner is also permitted).
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } 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 { to, data } = Gatekeeper1155.prepareAdminStubTickets({
contract: '0x1234567890123456789012345678901234567890',
attendee: '0x2345678901234567890123456789012345678901',
tierId: 1n,
amount: 1n,
})
// await walletClient.sendTransaction({ to, data })
```
The stub process:
1. Burns the ticket token (ID = tierId)
2. Mints a stub token (ID = tierId + 1)
3. Updates the stub URI metadata
### Voiding Tickets
Burn tickets without creating stubs. Requires `ADMIN_ROLE`, `HOST_ROLE`, or `MINTER_ROLE` (the owner is also permitted).
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } 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 { to, data } = Gatekeeper1155.prepareVoidTickets({
contract: '0x1234567890123456789012345678901234567890',
attendee: '0x2345678901234567890123456789012345678901',
tierId: 1n,
amount: 1n,
})
// await walletClient.sendTransaction({ to, data })
```
### Creating Token Tiers
Set up new ticket tiers with metadata and supply limits:
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
// Limited supply tier
const { to, data } = Gatekeeper1155.prepareSetupNewTier({
contract: '0x1234567890123456789012345678901234567890',
tokenURI: 'ipfs://QmTokenMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 500n,
})
// Unlimited supply tier (maxSupply = 0)
const unlimitedTier = Gatekeeper1155.prepareSetupNewTier({
contract: '0x1234567890123456789012345678901234567890',
tokenURI: 'ipfs://QmTokenMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 0n,
})
```
### Updating Token Metadata
Update tier metadata after creation. Requires `ADMIN_ROLE` (the owner is also permitted).
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const { to, data } = Gatekeeper1155.prepareUpdateToken({
contract: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
uri: 'ipfs://QmNewMetadata...',
maxSupply: 1000n, // Can increase, but not decrease below minted
})
```
### Reading Tier Information
Query tier metadata and supply info:
```ts twoslash
///
// ---cut---
import { Gatekeeper1155, gatekeeper1155Abi } from '@sceneinfrastructure/sdk'
const { to, functionName, args } = Gatekeeper1155.prepareGetTierInfo({
contract: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
as: 'args',
})
// const [uri, maxSupply, totalMinted] = await publicClient.readContract({
// address: to,
// abi: gatekeeper1155Abi,
// functionName,
// args,
// })
```
### Token ID Structure
Gatekeeper1155 uses a specific token ID scheme:
| Token ID | Type | Description |
| ------------ | ------ | ------------------------------- |
| 1, 3, 5, ... | Ticket | Odd IDs are active tickets |
| 2, 4, 6, ... | Stub | Even IDs are stubs (tierId + 1) |
When you call `setupNewTier`, it creates a tier with the next odd ID. Stubbing burns the ticket and mints the next ID (stub).
### Next Steps
* [Role Management](/guides/roles) for access control
* [API Reference: Gatekeeper1155](/api/gatekeeper-1155) for complete function documentation
## Approval
EIP-712 typed data utilities for purchase approval signatures.
```ts twoslash
///
// ---cut---
import { Approval } from '@sceneinfrastructure/sdk'
```
### Functions
#### getTypedData
Returns complete EIP-712 typed data for signing a purchase approval.
```ts twoslash
///
// ---cut---
import { Approval, SaleKey, gatekeeperAddress } from '@sceneinfrastructure/sdk'
import { Hex } from 'ox'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const typedData = Approval.getTypedData({
chainId: BigInt(chainId),
gatekeeper: gatekeeperAddress[chainId],
message: {
recipient: '0x4567890123456789012345678901234567890123',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
deadline: Approval.getDeadline(),
salt: Hex.random(32),
saleKeyId: SaleKey.generateId(saleKey),
},
})
// Sign with viem
// const signature = await walletClient.signTypedData(typedData)
```
Each approval signature is single-use. Gatekeeper rejects replays using the `(recipient, referrer, quantity, deadline, salt, saleKeyId)` hash, so generate a fresh `salt` for every approval and keep deadlines short.
##### Parameters
| Parameter | Type | Description |
| ------------ | ----------------- | ----------------------------- |
| `chainId` | `bigint` | Chain ID for domain separator |
| `gatekeeper` | `Address` | Gatekeeper contract address |
| `message` | `ApprovalMessage` | Message to sign |
##### Returns
```ts
{
domain: {
name: 'Gatekeeper'
version: '1'
chainId: bigint
verifyingContract: Address
}
types: typeof purchaseApprovalTypes
primaryType: 'PurchaseApproval'
message: ApprovalMessage
}
```
***
#### getDeadline
Calculates a Unix timestamp deadline for approval signatures.
```ts twoslash
///
// ---cut---
import { Approval } from '@sceneinfrastructure/sdk'
// Default: 15 minutes from now
const deadline = Approval.getDeadline()
// Custom: 30 minutes from now
const thirtyMin = Approval.getDeadline(new Date(), 30)
// From specific date
const future = Approval.getDeadline(new Date('2024-12-31'), 60)
```
##### Parameters
| Parameter | Type | Default | Description |
| --------- | -------- | ------------ | ------------------------ |
| `date` | `Date` | `new Date()` | Base date |
| `minutes` | `number` | `15` | Minutes until expiration |
##### Returns
`bigint` - Unix timestamp
***
### Constants
#### types
EIP-712 type definitions for PurchaseApproval signatures.
```ts twoslash
///
// ---cut---
import { Approval } from '@sceneinfrastructure/sdk'
console.log(Approval.types)
// {
// EIP712Domain: [...],
// PurchaseApproval: [
// { name: 'recipient', type: 'address' },
// { name: 'referrer', type: 'address' },
// { name: 'quantity', type: 'uint16' },
// { name: 'deadline', type: 'uint256' },
// { name: 'salt', type: 'bytes32' },
// { name: 'saleKeyId', type: 'bytes32' },
// ]
// }
```
***
#### domain
Base EIP-712 domain (name and version only).
```ts twoslash
///
// ---cut---
import { Approval, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
console.log(Approval.domain)
// { name: 'Gatekeeper', version: '1' }
// Complete domain for signing:
const fullDomain = {
...Approval.domain,
chainId: BigInt(chainId),
verifyingContract: gatekeeperAddress[chainId],
}
```
***
#### primaryType
The primary type identifier.
```ts twoslash
///
// ---cut---
import { Approval } from '@sceneinfrastructure/sdk'
console.log(Approval.primaryType)
// 'PurchaseApproval'
```
***
### Types
#### ApprovalMessage
The message payload for purchase approval signatures.
```ts twoslash
///
// ---cut---
import type { Approval } from '@sceneinfrastructure/sdk'
const message: Approval.ApprovalMessage = {
recipient: '0x1234567890123456789012345678901234567890', // Ticket recipient
referrer: '0x0000000000000000000000000000000000000000', // Referrer address (or zero)
quantity: 1, // Number of tickets
deadline: 1700000000n, // Expiration timestamp
salt: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', // Random bytes32
saleKeyId: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab', // Sale key hash
}
```
| Field | Type | Description |
| ----------- | --------- | -------------------------- |
| `recipient` | `Address` | Address receiving tickets |
| `referrer` | `Address` | Referrer address (or zero) |
| `quantity` | `number` | Number of tickets |
| `deadline` | `bigint` | Unix timestamp expiration |
| `salt` | `Hex` | Random bytes32 nonce |
| `saleKeyId` | `Hex` | Hash of SaleKey struct |
***
### Schema
#### messageSchema
Zod schema for validating approval messages.
```ts twoslash
///
// ---cut---
import { Approval } from '@sceneinfrastructure/sdk'
const result = Approval.messageSchema.safeParse({
recipient: '0x...',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
deadline: 1700000000n,
salt: '0x...',
saleKeyId: '0x...',
})
if (result.success) {
console.log('Valid:', result.data)
}
```
import { BalanceDemo } from '../../components/BalanceDemo'
## Balance
Query token holder balances.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Methods
#### get
Get a specific balance for a token and holder.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const balance = await client.balance.get({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
address: '0x2345678901234567890123456789012345678901'
})
if (balance) {
console.log('Balance:', balance.balance)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The token ID |
| `address` | `Address` | The holder address |
##### Returns
`Balance | null` - The balance record or null if not found.
***
#### getTokenStatus
Get detailed status for a holder's tokens and stubs.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const status = await client.balance.getTokenStatus({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n, // Ticket token ID (odd)
address: '0x2345678901234567890123456789012345678901'
})
console.log('Ticket count:', status.tokenCount)
console.log('Stub count:', status.stubCount)
console.log('Has tickets:', status.hasTokens)
console.log('Has stubs:', status.hasStubs)
console.log('Fully stubbed:', status.isFullyStubbed)
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The ticket token ID (odd number) |
| `address` | `Address` | The holder address |
##### Returns
`TokenStatus` - Status object with token and stub counts.
***
#### getHoldings
Get all token holdings for an address.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const holdings = await client.balance.getHoldings({
address: '0x2345678901234567890123456789012345678901'
})
console.log('Tickets:', holdings.tokens.length)
console.log('Stubs:', holdings.stubs.length)
// Filter by contract
const contractHoldings = await client.balance.getHoldings({
address: '0x2345678901234567890123456789012345678901',
contractAddress: '0x1234567890123456789012345678901234567890'
})
// Filter by minimum balance
const significantHoldings = await client.balance.getHoldings({
address: '0x2345678901234567890123456789012345678901',
minBalance: 5n
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | --------------------------- |
| `address` | `Address` | **required** | The holder address |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `minBalance` | `bigint` | `1n` | Minimum balance to include |
##### Returns
`TokenHoldings` - Object with `tokens` and `stubs` arrays.
***
#### isStubbed
Check if a holder has converted their tickets to stubs.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const stubbed = await client.balance.isStubbed({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
address: '0x2345678901234567890123456789012345678901'
})
if (stubbed) {
console.log('User has checked in')
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The ticket token ID |
| `address` | `Address` | The holder address |
##### Returns
`boolean` - True if the holder has any stubs for this tier.
***
#### listByToken
List all holders of a specific token.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const holders = await client.balance.listByToken({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
limit: 100
})
for (const balance of holders) {
console.log(`${balance.address}: ${balance.balance}`)
}
// Filter by minimum balance
const whales = await client.balance.listByToken({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
minBalance: 10n
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | -------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tokenId` | `bigint` | **required** | The token ID |
| `minBalance` | `bigint` | - | Minimum balance to include |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Balance[]` - Array of balance records.
***
#### listByHolder
List all balances for a specific holder.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const balances = await client.balance.listByHolder({
address: '0x2345678901234567890123456789012345678901',
limit: 100
})
for (const balance of balances) {
console.log(`Token ${balance.tokenId}: ${balance.balance}`)
}
// Filter by contract
const contractBalances = await client.balance.listByHolder({
address: '0x2345678901234567890123456789012345678901',
contractAddress: '0x1234567890123456789012345678901234567890'
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | --------------------------- |
| `address` | `Address` | **required** | The holder address |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `minBalance` | `bigint` | - | Minimum balance to include |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Balance[]` - Array of balance records.
***
#### hasBalance
Check if a holder has any balance of a specific token.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const hasTicket = await client.balance.hasBalance({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
address: '0x2345678901234567890123456789012345678901'
})
if (hasTicket) {
console.log('User has tickets')
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The token ID |
| `address` | `Address` | The holder address |
##### Returns
`boolean` - True if balance > 0.
***
### Types
#### Balance
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type Balance = {
contractAddress: Address
tokenId: bigint
address: Address
balance: bigint
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The token ID |
| `address` | `Address` | The holder address |
| `balance` | `bigint` | Current balance |
***
#### TokenStatus
```ts twoslash
///
// ---cut---
type TokenStatus = {
tokenCount: bigint // Balance of ticket token (odd ID)
stubCount: bigint // Balance of stub token (even ID)
hasTokens: boolean // tokenCount > 0
hasStubs: boolean // stubCount > 0
isFullyStubbed: boolean // hasStubs && !hasTokens
}
```
***
#### TokenHoldings
```ts twoslash
///
// ---cut---
import type { Balance } from '@sceneinfrastructure/sdk'
type TokenHoldings = {
tokens: Balance.Balance[] // Balances with odd token IDs (tickets)
stubs: Balance.Balance[] // Balances with even token IDs (stubs)
}
```
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { Balance } from '@sceneinfrastructure/sdk'
const data = {} // Data from API or external source
const result = Balance.schema.safeParse(data)
if (result.success) {
console.log('Valid balance:', result.data)
}
```
## Client
Query indexed blockchain data via Ponder.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
```
:::info
The Client requires the `@ponder/client` peer dependency. Install it with:
```bash
pnpm add @ponder/client
```
:::
### Functions
#### create
Creates a configured Gatekeeper client instance.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({
url: 'https://dev.ponder.mesh.ing/sql'
})
```
##### Parameters
| Parameter | Type | Description |
| ------------- | -------- | --------------------------- |
| `options.url` | `string` | The Ponder SQL endpoint URL |
##### Returns
A `Client` instance with:
* Domain namespaces for type-safe queries
* `db` - Drizzle query builder for custom queries
* `live` - SSE subscription method
* `getStatus` - Indexer status query
* `schema` - Ponder schema tables
***
### Client Interface
#### db
Drizzle query builder for custom SQL queries.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
import { eq } from '@ponder/client'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const results = await client.db
.select()
.from(client.schema.onchainSaleConfig)
.where(eq(client.schema.onchainSaleConfig.contractAddress, '0x1234567890123456789012345678901234567890'))
```
***
#### live
Subscribe to live query updates via Server-Sent Events.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const { unsubscribe } = client.live(
// Query function
(db) => db.select().from(client.schema.onchainOrder),
// On result callback
(result) => console.log('Updated:', result),
// On error callback
(error) => console.error('Error:', error)
)
// Later: clean up subscription
unsubscribe()
```
##### Parameters
| Parameter | Type | Description |
| ---------- | ------------------------ | ------------------------------ |
| `queryFn` | `(db: DB) => Query` | Function that builds the query |
| `onResult` | `(result: T[]) => void` | Callback for query results |
| `onError` | `(error: Error) => void` | Callback for errors |
##### Returns
```ts twoslash
///
// ---cut---
type LiveResult = {
unsubscribe: () => void // Function to stop the subscription
}
```
***
#### getStatus
Get indexer status for all indexed chains.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const status = await client.getStatus()
console.log('Indexer status:', status)
```
Returns an array of chain status objects with sync progress information.
***
#### schema
Ponder schema tables for building custom queries.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
// Access schema tables
client.schema.onchainEventContract
client.schema.onchainToken
client.schema.onchainTicketTier
client.schema.onchainSaleConfig
client.schema.onchainOrder
client.schema.onchainOrderItem
client.schema.onchainTicketTransfer
client.schema.onchainTicketBalance
client.schema.onchainRoleGrant
client.schema.onchainSwap
client.schema.onchainVoid
```
***
### Domain Namespaces
The client provides domain-specific namespaces for common queries:
| Namespace | Description | Documentation |
| --------------- | --------------------------------- | ------------------------------------ |
| `eventContract` | Deployed Gatekeeper1155 contracts | [EventContract](/api/event-contract) |
| `sale` | Sale configurations | [Sale](/api/sale) |
| `token` | ERC1155 token data | [Token](/api/token) |
| `tier` | Ticket tier configurations | [Tier](/api/tier) |
| `balance` | Token holder balances | [Balance](/api/balance) |
| `transfer` | Transfer history | [Transfer](/api/transfer) |
| `permission` | Role grants and permissions | [Permission](/api/permission) |
| `stub` | Check-in events | [Stub](/api/stub) |
| `void` | Voiding events | [Void](/api/void) |
#### Example Usage
```ts twoslash
///
// ---cut---
import { Client, ADMIN_ROLE } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
// Get a deployed contract
const contract = await client.eventContract.get({
contractAddress: '0x1234567890123456789012345678901234567890'
})
// List sales for a contract
const sales = await client.sale.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890'
})
// Check a user's balance
const balance = await client.balance.get({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
address: '0x2345678901234567890123456789012345678901'
})
// Check if user has admin role
const isAdmin = await client.permission.hasRole({
contractAddress: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
role: ADMIN_ROLE
})
```
***
### Static Exports
#### schema
The Ponder schema is also available as a static export:
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
// Access schema without creating a client
Client.schema.onchainEventContract
Client.schema.onchainSaleConfig
// ... etc
```
This is useful for type definitions and building queries before client initialization.
## Errors
Error handling utilities with structured error hierarchy and error chaining.
```ts twoslash
///
// ---cut---
import { Errors } from '@sceneinfrastructure/sdk'
```
Based on [ox](https://github.com/wevm/ox) error patterns.
### Types
#### GlobalErrorType
Catch-all error type for function error type unions.
```ts twoslash
///
// ---cut---
import { Errors } from '@sceneinfrastructure/sdk'
// Define a custom error type for your function
type SpecificError = Error & { code: 'SPECIFIC_ERROR' }
export declare namespace myFunction {
type ErrorType = SpecificError | Errors.GlobalErrorType
}
```
***
### Classes
#### BaseError
Base error class for all Gatekeeper SDK errors. Provides structured error handling with:
* Short message for quick identification
* Detailed information for debugging
* Error chaining via `cause` property
```ts twoslash
///
// ---cut---
import { Errors } from '@sceneinfrastructure/sdk'
throw new Errors.BaseError('Something went wrong.', {
details: 'The operation failed due to invalid input.',
})
```
##### Constructor
```ts
new Errors.BaseError(shortMessage: string, options?: BaseError.Options)
```
| Parameter | Type | Description |
| ----------------- | -------- | --------------------------------------- |
| `shortMessage` | `string` | Primary error message |
| `options.cause` | `Error` | Underlying error that caused this error |
| `options.details` | `string` | Extended debugging information |
##### Properties
| Property | Type | Description |
| -------------- | -------------------- | ------------------------------------- |
| `name` | `string` | Error name (`'BaseError'` by default) |
| `shortMessage` | `string` | The primary error message |
| `details` | `string` | Extended error information |
| `cause` | `Error \| undefined` | The underlying error |
##### Methods
##### walk
Traverses the error chain, calling the provided function for each error.
```ts twoslash
///
// ---cut---
import { Errors } from '@sceneinfrastructure/sdk'
const error = new Errors.BaseError('Outer error', {
cause: new Errors.BaseError('Inner error', {
cause: new TypeError('Root cause'),
}),
})
// Find a specific error type in the chain
const typeError = error.walk((err) => err instanceof TypeError)
```
***
### Usage Patterns
#### Creating Custom Errors
```ts
import { Errors } from '@sceneinfrastructure/sdk'
class InvalidSaleKeyError extends Errors.BaseError {
override name = 'InvalidSaleKeyError'
constructor(options: Errors.BaseError.Options = {}) {
super('Invalid sale key configuration.', options)
}
}
```
#### Error Chaining
Wrap lower-level errors with context:
```ts twoslash
///
// ---cut---
import { Errors } from '@sceneinfrastructure/sdk'
async function sendTransaction(): Promise {
// Example function that might fail
throw new Error('Network error')
}
async function processTransaction() {
try {
await sendTransaction()
} catch (error) {
throw new Errors.BaseError('Failed to process transaction.', {
cause: error as Error,
details: 'Transaction was rejected by the network.',
})
}
}
```
#### Error Inspection
Walk the error chain to find root causes:
```ts twoslash
///
// ---cut---
import { Errors } from '@sceneinfrastructure/sdk'
function getRootCause(error: Error): Error {
if (error instanceof Errors.BaseError) {
return error.walk() ?? error
}
return error
}
function hasSpecificCause(error: Error, predicate: (e: Error) => boolean): boolean {
if (error instanceof Errors.BaseError) {
return error.walk(predicate) !== undefined
}
return predicate(error)
}
```
import { EventContractDemo } from '../../components/EventContractDemo'
## EventContract
Query deployed Gatekeeper1155 contracts.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Methods
#### get
Get a single contract by address.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const contract = await client.eventContract.get({
contractAddress: '0x1234567890123456789012345678901234567890'
})
if (contract) {
console.log('Owner:', contract.ownerAddress)
console.log('Creator:', contract.creatorAddress)
console.log('Contract URI:', contract.contractURI)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | ------------------------------- |
| `contractAddress` | `Address` | The contract address to look up |
##### Returns
`EventContract | null` - The contract data or null if not found.
***
#### list
List contracts with optional filters.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
// List all contracts
const contracts = await client.eventContract.list({
limit: 20,
offset: 0
})
// List contracts by creator
const myContracts = await client.eventContract.list({
creator: '0x2345678901234567890123456789012345678901',
limit: 10
})
// List contracts by owner
const ownedContracts = await client.eventContract.list({
owner: '0x2345678901234567890123456789012345678901',
orderBy: 'createdAt',
order: 'desc'
})
```
##### Parameters
| Parameter | Type | Default | Description |
| --------- | ---------------------------- | ------------- | ------------------------- |
| `creator` | `Address` | - | Filter by creator address |
| `owner` | `Address` | - | Filter by owner address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
| `orderBy` | `'createdAt' \| 'updatedAt'` | `'createdAt'` | Sort field |
| `order` | `'asc' \| 'desc'` | `'desc'` | Sort direction |
##### Returns
`EventContract[]` - Array of matching contracts.
***
#### listByCreator
List contracts created by a specific address.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const myContracts = await client.eventContract.listByCreator({
creator: '0x2345678901234567890123456789012345678901',
limit: 10
})
for (const contract of myContracts) {
console.log(contract.contractAddress)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| --------- | --------- | ------------ | ------------------------- |
| `creator` | `Address` | **required** | The creator address |
| `limit` | `number` | `50` | Maximum results to return |
##### Returns
`EventContract[]` - Array of contracts created by the address.
***
#### listByOwner
List contracts owned by a specific address.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const ownedContracts = await client.eventContract.listByOwner({
owner: '0x2345678901234567890123456789012345678901',
limit: 10
})
```
##### Parameters
| Parameter | Type | Default | Description |
| --------- | --------- | ------------ | ------------------------- |
| `owner` | `Address` | **required** | The owner address |
| `limit` | `number` | `50` | Maximum results to return |
##### Returns
`EventContract[]` - Array of contracts owned by the address.
***
### Types
#### EventContract
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type EventContract = {
contractAddress: Address
contractURI: string
creatorAddress: Address
implementationAddress: Address
ownerAddress: Address
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ----------------------- | --------- | ---------------------------------- |
| `contractAddress` | `Address` | The deployed contract address |
| `contractURI` | `string` | Contract metadata URI |
| `creatorAddress` | `Address` | Address that deployed the contract |
| `implementationAddress` | `Address` | Implementation contract address |
| `ownerAddress` | `Address` | Current owner address |
| `createdAt` | `bigint` | Creation timestamp |
| `createdAtBlock` | `bigint` | Block number of creation |
| `creationTxHash` | `Hex` | Transaction hash of creation |
| `updatedAt` | `bigint` | Last update timestamp |
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { EventContract } from '@sceneinfrastructure/sdk'
const data = {
contractAddress: '0x1234567890123456789012345678901234567890',
contractURI: 'https://example.com/metadata.json',
creatorAddress: '0x2345678901234567890123456789012345678901',
implementationAddress: '0x3456789012345678901234567890123456789012',
ownerAddress: '0x2345678901234567890123456789012345678901',
createdAt: 1700000000n,
createdAtBlock: 100000n,
createdAtLogIndex: 0,
creationTxHash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
updatedAt: 1700000000n,
updatedAtBlock: 100000n,
updatedAtLogIndex: 0,
updatedTxHash: null,
}
const result = EventContract.schema.safeParse(data)
if (result.success) {
console.log('Valid contract:', result.data)
}
```
## Factory
Utilities for deploying Gatekeeper1155 contracts via the factory pattern.
```ts twoslash
///
// ---cut---
import { Factory } from '@sceneinfrastructure/sdk'
```
### Functions
#### prepareDeploy
Prepares a factory deploy transaction with optional setup calls.
```ts twoslash
///
// ---cut---
import {
Factory,
gatekeeper1155FactoryAddress,
gatekeeper1155Address,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const walletAddress = '0x3456789012345678901234567890123456789012'
// With raw calldata (default)
const { to, data, salt, predictedAddress } = Factory.prepareDeploy({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
nonce: BigInt(Date.now()),
creator: walletAddress,
owner: walletAddress,
contractURI: 'ipfs://...',
})
// With viem args
const result = Factory.prepareDeploy({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
nonce: BigInt(Date.now()),
creator: walletAddress,
owner: walletAddress,
contractURI: 'ipfs://...',
as: 'args',
})
```
##### Parameters
| Parameter | Type | Description |
| ---------------- | ------------------ | -------------------------------------- |
| `factory` | `Address` | Factory contract address |
| `implementation` | `Address` | Implementation contract address |
| `nonce` | `bigint` | Deployment nonce (typically timestamp) |
| `creator` | `Address` | Address calling deploy |
| `owner` | `Address` | Owner of deployed contract |
| `contractURI` | `string` | Contract metadata URI |
| `calls` | `Hex[]` | Optional setup calls |
| `as` | `'data' \| 'args'` | Output format (default: `'data'`) |
##### Returns
| Field | Type | Description |
| ------------------ | --------- | ------------------------------------ |
| `to` | `Address` | Factory address |
| `data` | `Hex` | Encoded calldata (when `as: 'data'`) |
| `args` | `tuple` | Function args (when `as: 'args'`) |
| `salt` | `Hex` | CREATE2 salt |
| `predictedAddress` | `Address` | Predicted deployment address |
***
#### generateSalt
Generates the CREATE2 salt from deployment parameters.
```ts twoslash
///
// ---cut---
import { Factory } from '@sceneinfrastructure/sdk'
const creatorAddress = '0x3456789012345678901234567890123456789012'
const salt = Factory.generateSalt({
nonce: BigInt(Date.now()),
creator: creatorAddress,
contractURI: 'ipfs://...',
})
```
##### Parameters
| Parameter | Type | Description |
| ------------- | --------- | --------------------- |
| `nonce` | `bigint` | Deployment nonce |
| `creator` | `Address` | Creator address |
| `contractURI` | `string` | Contract metadata URI |
##### Returns
`Hex` - The keccak256 hash (bytes32)
***
#### predictAddress
Predicts the deployment address using CREATE2.
```ts twoslash
///
// ---cut---
import {
Factory,
gatekeeper1155FactoryAddress,
gatekeeper1155Address,
} from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const creatorAddress = '0x3456789012345678901234567890123456789012'
const salt = Factory.generateSalt({
nonce: 1n,
creator: creatorAddress,
contractURI: 'ipfs://...',
})
const address = Factory.predictAddress({
factory: gatekeeper1155FactoryAddress[chainId],
implementation: gatekeeper1155Address[chainId],
salt,
})
```
##### Parameters
| Parameter | Type | Description |
| ---------------- | --------- | ------------------------ |
| `factory` | `Address` | Factory contract address |
| `implementation` | `Address` | Implementation address |
| `salt` | `Hex` | CREATE2 salt |
##### Returns
`Address` - The predicted contract address
***
#### encodeGrantRoles
Encodes a `grantRoles` call for contract initialization.
```ts twoslash
///
// ---cut---
import { Factory, MINTER_ROLE, ADMIN_ROLE, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
// Single role (e.g., grant minter to Gatekeeper)
const data = Factory.encodeGrantRoles({
user: gatekeeperAddress[chainId],
roles: MINTER_ROLE,
})
// Multiple roles (bitwise OR)
const multiData = Factory.encodeGrantRoles({
user: '0x2345678901234567890123456789012345678901',
roles: ADMIN_ROLE | MINTER_ROLE,
})
```
##### Parameters
| Parameter | Type | Description |
| --------- | --------- | ------------------------- |
| `user` | `Address` | Address to grant roles to |
| `roles` | `bigint` | Role bitmap |
##### Returns
`Hex` - Encoded function calldata
***
#### encodeSetupNewTier
Encodes a `setupNewTier` call for creating token tiers.
```ts twoslash
///
// ---cut---
import { Factory } from '@sceneinfrastructure/sdk'
const data = Factory.encodeSetupNewTier({
tokenURI: 'ipfs://QmToken...',
stubURI: 'ipfs://QmStub...',
maxSupply: 1000n, // 0n for unlimited
})
```
##### Parameters
| Parameter | Type | Description |
| ----------- | -------- | -------------------------- |
| `tokenURI` | `string` | Token metadata URI |
| `stubURI` | `string` | Stub metadata URI |
| `maxSupply` | `bigint` | Max supply (0 = unlimited) |
##### Returns
`Hex` - Encoded function calldata
***
#### encodeCallGatekeeper
Encodes a `callGatekeeper` call for executing Gatekeeper operations.
```ts twoslash
///
// ---cut---
import { Factory, Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x2345678901234567890123456789012345678901', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x3456789012345678901234567890123456789012',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x4567890123456789012345678901234567890123',
tokenId: 1n,
} as const
const innerCall = Gatekeeper.encodeRegisterSale({
saleKey,
saleState: {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1000000n,
approvers: [],
},
})
const data = Factory.encodeCallGatekeeper({
gatekeeper: gatekeeperAddress[chainId],
data: innerCall,
})
```
##### Parameters
| Parameter | Type | Description |
| ------------ | --------- | --------------------------- |
| `gatekeeper` | `Address` | Gatekeeper contract address |
| `data` | `Hex` | Encoded Gatekeeper call |
##### Returns
`Hex` - Encoded function calldata
### Errors
#### InvalidDeployOptionsError
Thrown when deploy options are invalid.
```ts twoslash
///
// ---cut---
import { Factory } from '@sceneinfrastructure/sdk'
try {
// ...
} catch (error) {
if (error instanceof Factory.InvalidDeployOptionsError) {
console.error('Invalid options:', error.message)
}
}
```
## Gatekeeper1155
Utilities for ERC1155 token operations on Gatekeeper1155 contracts.
```ts twoslash
///
// ---cut---
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
```
### Token Operations
#### prepareSetupNewTier
Creates a new ticket tier with metadata and supply configuration. Requires admin permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const { to, data } = Gatekeeper1155.prepareSetupNewTier({
contract: '0x1234567890123456789012345678901234567890',
tokenURI: 'ipfs://QmTokenMetadata...',
stubURI: 'ipfs://QmStubMetadata...',
maxSupply: 1000n, // 0n for unlimited
})
await walletClient.sendTransaction({ to, data })
```
##### Parameters
| Parameter | Type | Description |
| ----------- | ------------------ | --------------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `tokenURI` | `string` | Metadata URI for the ticket tier |
| `stubURI` | `string` | Metadata URI for stubbed (used) tickets |
| `maxSupply` | `bigint` | Maximum supply (0 for unlimited) |
| `as` | `'data' \| 'args'` | Output format |
***
#### prepareAdminMint
Mints tickets to a recipient. Requires minting permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const { to, data } = Gatekeeper1155.prepareAdminMint({
contract: '0x1234567890123456789012345678901234567890',
recipient: '0x2345678901234567890123456789012345678901',
tokenId: 1n,
quantity: 2n,
})
await walletClient.sendTransaction({ to, data })
```
##### Parameters
| Parameter | Type | Description |
| ----------- | ------------------ | --------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `recipient` | `Address` | Address to receive the tickets |
| `tokenId` | `bigint` | Token tier ID |
| `quantity` | `bigint` | Number of tickets to mint |
| `data` | `Hex` | Optional data to pass to receiver |
| `as` | `'data' \| 'args'` | Output format |
***
#### prepareAdminStubTickets
Checks in an attendee by converting their tickets to stubs. Requires check-in permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const { to, data } = Gatekeeper1155.prepareAdminStubTickets({
contract: '0x1234567890123456789012345678901234567890',
attendee: '0x2345678901234567890123456789012345678901',
tierId: 1n,
amount: 1n,
})
await walletClient.sendTransaction({ to, data })
```
##### Parameters
| Parameter | Type | Description |
| ---------- | ------------------ | ------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `attendee` | `Address` | Attendee address to check-in |
| `tierId` | `bigint` | Token tier ID |
| `amount` | `bigint` | Number of tickets to stub |
| `as` | `'data' \| 'args'` | Output format |
***
#### prepareVoidTickets
Burns tickets from an attendee. Requires void permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const { to, data } = Gatekeeper1155.prepareVoidTickets({
contract: '0x1234567890123456789012345678901234567890',
attendee: '0x2345678901234567890123456789012345678901',
tierId: 1n,
amount: 1n,
})
await walletClient.sendTransaction({ to, data })
```
##### Parameters
| Parameter | Type | Description |
| ---------- | ------------------ | ------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `attendee` | `Address` | Attendee address |
| `tierId` | `bigint` | Token tier ID |
| `amount` | `bigint` | Number of tickets to void |
| `as` | `'data' \| 'args'` | Output format |
***
#### prepareUpdateToken
Updates tier metadata. Requires admin permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { Gatekeeper1155 } from '@sceneinfrastructure/sdk'
const account = privateKeyToAccount('0x...' as `0x${string}`)
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const { to, data } = Gatekeeper1155.prepareUpdateToken({
contract: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
uri: 'ipfs://new-metadata-uri',
maxSupply: 500n,
})
await walletClient.sendTransaction({ to, data })
```
##### Parameters
| Parameter | Type | Description |
| ----------- | ------------------ | -------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `tokenId` | `bigint` | Token tier ID to update |
| `uri` | `string` | New metadata URI |
| `maxSupply` | `bigint` | New max supply (0 for unlimited) |
| `as` | `'data' \| 'args'` | Output format |
***
### Role Management
#### prepareGrantRoles
Grants roles to an address. Requires admin permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { Gatekeeper1155, MINTER_ROLE, DOOR_ROLE } from '@sceneinfrastructure/sdk'
// Grant single role
const { to, data } = Gatekeeper1155.prepareGrantRoles({
contract: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
roles: MINTER_ROLE,
})
// Grant multiple roles with bitwise OR
const { to: to2, data: data2 } = Gatekeeper1155.prepareGrantRoles({
contract: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
roles: MINTER_ROLE | DOOR_ROLE,
})
```
##### Parameters
| Parameter | Type | Description |
| ---------- | ------------------ | -------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `user` | `Address` | Address to grant roles to |
| `roles` | `bigint` | Role bitmask (combine with `\|`) |
| `as` | `'data' \| 'args'` | Output format |
***
#### prepareRevokeRoles
Revokes roles from an address. Requires admin permissions (see [Role Management](/guides/roles)).
```ts twoslash
///
// ---cut---
import { Gatekeeper1155, MINTER_ROLE } from '@sceneinfrastructure/sdk'
const { to, data } = Gatekeeper1155.prepareRevokeRoles({
contract: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
roles: MINTER_ROLE,
})
```
##### Parameters
| Parameter | Type | Description |
| ---------- | ------------------ | ------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `user` | `Address` | Address to revoke roles from |
| `roles` | `bigint` | Role bitmask to revoke |
| `as` | `'data' \| 'args'` | Output format |
***
### View Functions
#### prepareContractURI
Gets the contract-level metadata URI.
```ts twoslash
///
// ---cut---
import { Gatekeeper1155, gatekeeper1155Abi } from '@sceneinfrastructure/sdk'
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'
const publicClient = createPublicClient({
chain: base,
transport: http(),
})
const { to, functionName, args } = Gatekeeper1155.prepareContractURI({
contract: '0x1234567890123456789012345678901234567890',
as: 'args',
})
const uri = await publicClient.readContract({
address: to,
abi: gatekeeper1155Abi,
functionName,
args,
})
```
***
#### prepareGetTierInfo
Gets tier metadata including URI, max supply, and total minted.
```ts twoslash
///
// ---cut---
import { Gatekeeper1155, gatekeeper1155Abi } from '@sceneinfrastructure/sdk'
import { createPublicClient, http } from 'viem'
import { base } from 'viem/chains'
const publicClient = createPublicClient({
chain: base,
transport: http(),
})
const { to, functionName, args } = Gatekeeper1155.prepareGetTierInfo({
contract: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
as: 'args',
})
const result = await publicClient.readContract({
address: to,
abi: gatekeeper1155Abi,
functionName,
args,
})
console.log('Tier info:', result)
```
##### Parameters
| Parameter | Type | Description |
| ---------- | ------------------ | ------------------------------- |
| `contract` | `Address` | Gatekeeper1155 contract address |
| `tokenId` | `bigint` | Token tier ID |
| `as` | `'data' \| 'args'` | Output format |
## Gatekeeper
Utilities for managing sales and executing purchases on the Gatekeeper protocol.
```ts twoslash
///
// ---cut---
import { Gatekeeper } from '@sceneinfrastructure/sdk'
```
### Encode Functions
Functions for encoding calls to be executed via `callGatekeeper`.
`registerSale` and `updateSale` can only be called by the token contract. Use `Gatekeeper1155.callGatekeeper` (requires `ADMIN_ROLE` or owner) with the encoded data. During deployment, include the call via `Factory.encodeCallGatekeeper`.
#### encodeRegisterSale
Encodes a `registerSale` call for registering new sales.
```ts twoslash
///
// ---cut---
import { Gatekeeper } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const data = Gatekeeper.encodeRegisterSale({
saleKey,
saleState: {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 1000000n,
approvers: [],
},
})
```
##### Parameters
| Parameter | Type | Description |
| ----------- | ----------- | -------------------------- |
| `saleKey` | `SaleKey` | Sale identifier struct |
| `saleState` | `SaleState` | Initial sale configuration |
***
#### encodeUpdateSale
Encodes an `updateSale` call for modifying existing sales.
```ts twoslash
///
// ---cut---
import { Gatekeeper } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const data = Gatekeeper.encodeUpdateSale({
saleKey,
saleState: {
startTime: 1n,
endTime: BigInt(Math.floor(Date.now() / 1000)), // End now
quantity: 0,
price: 0n,
approvers: [],
},
})
```
***
### Purchase Functions
#### preparePurchase
Prepares a purchase transaction for open sales (no approval required).
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
// Raw calldata
const { to, data } = Gatekeeper.preparePurchase({
gatekeeper: gatekeeperAddress[chainId],
saleKey,
purchaseParams: {
recipient: '0x4567890123456789012345678901234567890123',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
},
})
// Viem args
const result = Gatekeeper.preparePurchase({
gatekeeper: gatekeeperAddress[chainId],
saleKey,
purchaseParams: {
recipient: '0x4567890123456789012345678901234567890123',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
},
as: 'args',
})
```
##### Parameters
| Parameter | Type | Description |
| ---------------- | ------------------ | --------------------------- |
| `gatekeeper` | `Address` | Gatekeeper contract address |
| `saleKey` | `SaleKey` | Sale identifier |
| `purchaseParams` | `PurchaseParams` | Purchase configuration |
| `as` | `'data' \| 'args'` | Output format |
***
#### preparePurchaseWithApproval
Prepares a purchase with EIP-712 approval signature.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const { to, data } = Gatekeeper.preparePurchaseWithApproval({
gatekeeper: gatekeeperAddress[chainId],
saleKey,
purchaseParams: {
recipient: '0x4567890123456789012345678901234567890123',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 1,
},
signatureParams: {
deadline: BigInt(Math.floor(Date.now() / 1000) + 900),
salt: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
signature: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12',
},
})
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | ------------------------- | --------------------------- |
| `gatekeeper` | `Address` | Gatekeeper contract address |
| `saleKey` | `SaleKey` | Sale identifier |
| `purchaseParams` | `PurchaseParams` | Purchase configuration |
| `signatureParams` | `ApprovalSignatureParams` | EIP-712 signature data |
| `as` | `'data' \| 'args'` | Output format |
***
### Fund Functions
#### prepareWithdraw
Prepares a withdrawal of accumulated funds.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const { to, data } = Gatekeeper.prepareWithdraw({
gatekeeper: gatekeeperAddress[chainId],
currency: '0x1111111111111111111111111111111111111111',
amount: 1000000n,
})
```
##### Parameters
| Parameter | Type | Description |
| ------------ | --------- | --------------------------- |
| `gatekeeper` | `Address` | Gatekeeper contract address |
| `currency` | `Address` | Currency token address |
| `amount` | `bigint` | Amount to withdraw |
***
#### prepareWithdrawProtocol
Prepares withdrawal of protocol fees (owner only).
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const { to, data } = Gatekeeper.prepareWithdrawProtocol({
gatekeeper: gatekeeperAddress[chainId],
currency: '0x1111111111111111111111111111111111111111',
})
```
***
### View Functions
#### prepareGetPrice
Prepares a price calculation query.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const { to, functionName, args } = Gatekeeper.prepareGetPrice({
gatekeeper: gatekeeperAddress[chainId],
saleKey,
purchaseParams: {
recipient: '0x4567890123456789012345678901234567890123',
referrer: '0x5678901234567890123456789012345678901234',
quantity: 1,
},
as: 'args',
})
```
**Returns** (from contract):
* `totalPrice` - Total amount due
* `subtotal` - Base price
* `protocolFee` - Protocol fee amount
* `vendorFee` - Vendor fee amount
* `referrerFee` - Referrer fee amount
* `salesTax` - Sales tax amount
***
#### prepareGetSale
Prepares a sale state query.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const { to, functionName, args } = Gatekeeper.prepareGetSale({
gatekeeper: gatekeeperAddress[chainId],
saleKey,
as: 'args',
})
```
***
#### prepareIsSaleRegistered
Checks if a sale is registered.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
vendor: '0x2345678901234567890123456789012345678901',
nonce: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x3456789012345678901234567890123456789012',
tokenId: 1n,
} as const
const { to, functionName, args } = Gatekeeper.prepareIsSaleRegistered({
gatekeeper: gatekeeperAddress[chainId],
saleKey,
as: 'args',
})
```
***
#### prepareDomainSeparator
Gets the EIP-712 domain separator.
```ts twoslash
///
// ---cut---
import { Gatekeeper, gatekeeperAddress } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const { to, functionName, args } = Gatekeeper.prepareDomainSeparator({
gatekeeper: gatekeeperAddress[chainId],
as: 'args',
})
```
## API Reference
Complete API documentation for the Gatekeeper SDK.
The SDK is organized into two main categories:
* **Writes** - Transaction preparation utilities for state-changing operations (viem-based)
* **Reads** - Query methods for indexed blockchain data (Ponder/Drizzle-based)
***
### Writes
Transaction preparation utilities for deploying contracts, managing sales, and executing purchases.
#### [Factory](/api/factory)
Deploy Gatekeeper1155 contracts via the factory pattern.
* `prepareDeploy()` - Prepare a deployment transaction
* `generateSalt()` - Generate CREATE2 salt
* `predictAddress()` - Predict deployment address
* `encodeGrantRoles()` - Encode role grants
* `encodeSetupNewTier()` - Encode tier creation
* `encodeCallGatekeeper()` - Encode Gatekeeper calls
#### [Gatekeeper](/api/gatekeeper)
Manage sales and execute purchases.
* `encodeRegisterSale()` - Register a new sale
* `encodeUpdateSale()` - Update sale configuration
* `preparePurchase()` - Prepare purchase transaction
* `preparePurchaseWithApproval()` - Prepare approval-gated purchase
* `prepareWithdraw()` - Withdraw funds
* `prepareGetPrice()` - Get price breakdown
* `prepareGetSale()` - Get sale state
#### [Gatekeeper1155](/api/gatekeeper-1155)
Token operations and role management on deployed contracts.
* `prepareSetupNewTier()` - Create a new ticket tier
* `prepareAdminMint()` - Mint tickets
* `prepareAdminStubTickets()` - Check in attendees
* `prepareVoidTickets()` - Void tickets
* `prepareGrantRoles()` / `prepareRevokeRoles()` - Manage roles
***
### Signing
EIP-712 utilities for approvals and ticket intent signatures.
#### [Approval](/api/approval)
Typed data for purchase approvals.
* `getTypedData()` - Generate typed data for signing
* `getDeadline()` - Calculate deadline timestamp
* `types` - EIP-712 type definitions
* `domain` - EIP-712 domain separator
#### [TicketIntent](/api/ticket-intent)
Typed data for proving ticket ownership.
* `types` - EIP-712 type definitions
* `domain` - EIP-712 domain separator
* `primaryType` - Primary type identifier
* `messageSchema` - Zod schema for validation
***
### Reads
Query indexed blockchain data via Ponder. All read operations require a configured client.
#### [Client](/api/client)
Create and configure the query client.
* `Client.create()` - Create a client instance
* `client.db` - Raw Drizzle query builder
* `client.live()` - Subscribe to live updates via SSE
* `client.getStatus()` - Get indexer status
* `client.schema` - Access Ponder schema tables
#### [EventContract](/api/event-contract)
Query deployed Gatekeeper1155 contracts.
* `get()` - Get a contract by address
* `list()` - List contracts with filters
* `listByCreator()` - List contracts by creator
* `listByOwner()` - List contracts by owner
#### [Sale](/api/sale)
Query sale configurations.
* `get()` - Get a sale by ID
* `getByKey()` - Get a sale by composite key
* `listByContract()` - List sales for a contract
* `listByVendor()` - List sales by vendor
* `listActive()` - List currently active sales
#### [Tier](/api/tier)
Query ticket tier configurations with supply statistics.
* `get()` - Get a tier by token ID
* `getWithSupply()` - Get tier with supply info
* `getStats()` - Get attendance statistics
* `listByContract()` - List tiers for a contract
* `listByContractWithSupply()` - List tiers with supply data
#### [Token](/api/token)
Query ERC1155 token data.
* `get()` - Get a token by ID
* `listByContract()` - List tokens for a contract
#### [Balance](/api/balance)
Query token holder balances with ticket/stub status.
* `get()` - Get a specific balance
* `getTokenStatus()` - Get holder's token/stub status
* `getHoldings()` - Get all holdings for an address
* `isStubbed()` - Check if holder has stubs
* `listByToken()` - List holders of a token
* `listByHolder()` - List balances for a holder
* `hasBalance()` - Check if holder has balance
#### [Transfer](/api/transfer)
Query transfer history including mints and burns.
* `listByToken()` - List transfers for a token
* `listByFrom()` - List transfers from an address
* `listByTo()` - List transfers to an address
* `listMints()` - List mint events
* `listBurns()` - List burn events
#### [Permission](/api/permission)
Query user permissions and role grants.
* `get()` - Get permission for a user
* `hasRole()` - Check if user has a role
* `listByContract()` - List permissions for a contract
* `listByUser()` - List permissions for a user
* `listByRole()` - List users with a specific role
* **Utilities**: `decodeRoles()`, `hasRoleInMask()`, `combineRoles()`
#### [Stub](/api/stub)
Query stubbing events (check-ins where tickets become stubs).
* `listByContract()` - List stubs for a contract
* `listByHolder()` - List stubs for a holder
* `listByTier()` - List stubs for a tier
#### [Void](/api/void)
Query voiding events (burns without stub conversion).
* `listByContract()` - List voids for a contract
* `listByHolder()` - List voids for a holder
* `listByTier()` - List voids for a tier
***
### Utilities
#### [Errors](/api/errors)
Custom error types for SDK operations.
#### [Validators](/api/validators)
Input validation utilities.
***
### Exported Constants
#### Contract Addresses
```ts twoslash
///
// ---cut---
import {
gatekeeperAddress,
gatekeeper1155Address,
gatekeeper1155FactoryAddress,
} from '@sceneinfrastructure/sdk'
```
#### Role Constants
```ts twoslash
///
// ---cut---
import {
ADMIN_ROLE,
HOST_ROLE,
DOOR_ROLE,
MINTER_ROLE,
} from '@sceneinfrastructure/sdk'
```
#### ABIs
```ts twoslash
///
// ---cut---
import {
gatekeeperAbi,
gatekeeper1155Abi,
gatekeeper1155FactoryAbi,
} from '@sceneinfrastructure/sdk'
```
#### Protocol Fee
```ts twoslash
///
// ---cut---
import { PROTOCOL_FEE_BPS } from '@sceneinfrastructure/sdk'
// 100 = 1%
```
import { PermissionDemo } from '../../components/PermissionDemo'
## Permission
Query user permissions and role grants on contracts.
```ts twoslash
///
// ---cut---
import { Client, Permission, ADMIN_ROLE, HOST_ROLE, DOOR_ROLE, MINTER_ROLE } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Methods
#### get
Get a permission record for a specific user on a contract.
```ts twoslash
///
// ---cut---
import { Client, Permission } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const permission = await client.permission.get({
contractAddress: '0x1234567890123456789012345678901234567890' as const,
user: '0x2345678901234567890123456789012345678901' as const
})
if (permission) {
// Decode the role bitmask
const roles = Permission.decodeRoles(permission.roles)
console.log('Admin:', roles.admin)
console.log('Host:', roles.host)
console.log('Door:', roles.door)
console.log('Minter:', roles.minter)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `user` | `Address` | The user address |
##### Returns
`Permission | null` - The permission record or null if not found.
***
#### hasRole
Check if a user has a specific role on a contract.
```ts twoslash
///
// ---cut---
import { Client, ADMIN_ROLE, HOST_ROLE, DOOR_ROLE, MINTER_ROLE } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
// Check admin role
const isAdmin = await client.permission.hasRole({
contractAddress: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
role: ADMIN_ROLE
})
// Check door role (check-in capability)
const canCheckIn = await client.permission.hasRole({
contractAddress: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901',
role: DOOR_ROLE
})
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------------------------- |
| `contractAddress` | `Address` | The contract address |
| `user` | `Address` | The user address |
| `role` | `bigint` | The role to check (e.g., `ADMIN_ROLE`) |
##### Returns
`boolean` - True if the user has the role.
***
#### listByContract
List all permission records for a contract.
```ts twoslash
///
// ---cut---
import { Client, Permission } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const permissions = await client.permission.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 50
})
for (const perm of permissions) {
const roles = Permission.decodeRoles(perm.roles)
console.log(`${perm.userAddress}:`, roles)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Permission[]` - Array of permission records.
***
#### listByUser
List all contracts where a user has roles.
```ts twoslash
///
// ---cut---
import { Client, Permission } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const permissions = await client.permission.listByUser({
user: '0x2345678901234567890123456789012345678901',
limit: 50
})
for (const perm of permissions) {
const roles = Permission.decodeRoles(perm.roles)
console.log(`Contract ${perm.contractAddress}:`, roles)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| --------- | --------- | ------------ | ------------------------- |
| `user` | `Address` | **required** | The user address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Permission[]` - Array of permission records.
***
#### listByRole
List users with a specific role on a contract.
```ts twoslash
///
// ---cut---
import { Client, ADMIN_ROLE, DOOR_ROLE } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
// Find all admins
const admins = await client.permission.listByRole({
contractAddress: '0x1234567890123456789012345678901234567890',
role: ADMIN_ROLE
})
for (const perm of admins) {
console.log('Admin:', perm.userAddress)
}
// Find all door staff
const doorStaff = await client.permission.listByRole({
contractAddress: '0x1234567890123456789012345678901234567890',
role: DOOR_ROLE
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `role` | `bigint` | **required** | The role to filter by |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Permission[]` - Array of permission records for users with the role.
***
### Utility Functions
#### Permission.decodeRoles
Decode a role bitmask into individual role flags.
```ts twoslash
///
// ---cut---
import { Client, Permission } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const contractAddress = '0x1234567890123456789012345678901234567890'
const user = '0x2345678901234567890123456789012345678901'
const permission = await client.permission.get({ contractAddress, user })
if (permission) {
const roles = Permission.decodeRoles(permission.roles)
// Returns:
// {
// admin: boolean,
// host: boolean,
// door: boolean,
// minter: boolean
// }
if (roles.admin) {
console.log('User is an admin')
}
}
```
***
#### Permission.hasRoleInMask
Check if a bitmask contains a specific role.
```ts twoslash
///
// ---cut---
import { Client, Permission, ADMIN_ROLE, HOST_ROLE } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const permission = await client.permission.get({
contractAddress: '0x1234567890123456789012345678901234567890',
user: '0x2345678901234567890123456789012345678901'
})
if (permission) {
const hasAdmin = Permission.hasRoleInMask(permission.roles, ADMIN_ROLE)
const hasHost = Permission.hasRoleInMask(permission.roles, HOST_ROLE)
}
```
***
#### Permission.combineRoles
Combine multiple roles into a single bitmask.
```ts twoslash
///
// ---cut---
import { Permission, ADMIN_ROLE, HOST_ROLE, MINTER_ROLE } from '@sceneinfrastructure/sdk'
// Grant admin and minter roles
const roles = Permission.combineRoles([ADMIN_ROLE, MINTER_ROLE])
// roles = 9n (ADMIN=1 | MINTER=8)
```
***
### Types
#### Permission
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type Permission = {
contractAddress: Address
userAddress: Address
roles: bigint // Role bitmask
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ----------------- | --------- | ------------------------ |
| `contractAddress` | `Address` | The contract address |
| `userAddress` | `Address` | The user address |
| `roles` | `bigint` | Bitmask of granted roles |
***
#### DecodedRoles
```ts twoslash
///
// ---cut---
type DecodedRoles = {
admin: boolean // ADMIN_ROLE = 1
host: boolean // HOST_ROLE = 2
door: boolean // DOOR_ROLE = 4
minter: boolean // MINTER_ROLE = 8
}
```
***
### Role Constants
```ts twoslash
///
// ---cut---
import { ADMIN_ROLE, HOST_ROLE, DOOR_ROLE, MINTER_ROLE } from '@sceneinfrastructure/sdk'
// Values:
// ADMIN_ROLE = 1n (0x01) - Full contract control
// HOST_ROLE = 2n (0x02) - Event management
// DOOR_ROLE = 4n (0x04) - Check-in capability
// MINTER_ROLE = 8n (0x08) - Token minting
```
#### Role Capabilities
| Role | Value | Capabilities |
| ------------- | ----- | --------------------------------- |
| `ADMIN_ROLE` | `1n` | Full control, grant/revoke roles |
| `HOST_ROLE` | `2n` | Manage event settings |
| `DOOR_ROLE` | `4n` | Check in attendees (stub tickets) |
| `MINTER_ROLE` | `8n` | Mint new tokens |
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { Permission } from '@sceneinfrastructure/sdk'
const data = {} // Data from API or external source
const result = Permission.schema.safeParse(data)
if (result.success) {
console.log('Valid permission:', result.data)
}
```
import { SaleDemo } from '../../components/SaleDemo'
## Sale
Sale types, schemas, and utilities for the Gatekeeper protocol.
```ts twoslash
///
// ---cut---
import { Sale, SaleKey } from '@sceneinfrastructure/sdk'
```
### Try It
### Types
#### Sale
Complete sale configuration returned from the indexer.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
// Sale objects are returned from indexer queries
const saleKeyId = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as const
const sale = await client.sale.get({ saleKeyId })
if (sale) {
console.log('Price:', sale.price)
console.log('Quantity:', sale.quantity)
console.log('Vendor:', sale.vendorAddress)
}
```
| Field | Type | Description |
| ----------------------- | ----------- | --------------------------- |
| `contractAddress` | `Address` | Gatekeeper1155 contract |
| `tokenId` | `bigint` | Token tier ID |
| `currencyAddress` | `Address` | Payment currency |
| `fundsRecipientAddress` | `Address` | Funds destination |
| `vendorAddress` | `Address` | Vendor address |
| `vendorFeeBps` | `number` | Vendor fee (basis points) |
| `referralFeeBps` | `number` | Referral fee (basis points) |
| `salesTaxBps` | `number` | Sales tax (basis points) |
| `nonce` | `bigint` | Unique nonce |
| `price` | `bigint` | Price per unit |
| `quantity` | `number` | Available quantity |
| `startTime` | `bigint` | Sale start timestamp |
| `endTime` | `bigint` | Sale end timestamp |
| `approvers` | `Address[]` | Authorized approvers |
Additional indexer metadata:
* `saleKeyId` - keccak256 hash of the `SaleKey`
* `gatekeeperAddress` - Gatekeeper contract that registered the sale
* `saleType` - Currency label emitted by the indexer
* `createdAt`, `updatedAt` (plus block/log/tx fields) - indexer timestamps
***
#### SaleKey
The struct that uniquely identifies a sale in the Gatekeeper contract.
```ts twoslash
///
// ---cut---
import type { Sale } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x2345678901234567890123456789012345678901',
vendor: '0x3456789012345678901234567890123456789012',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const satisfies Sale.SaleKey
```
| Field | Type | Description |
| ---------------- | --------- | ---------------- |
| `token` | `Address` | Contract address |
| `tokenId` | `bigint` | Token ID |
| `currency` | `Address` | Currency address |
| `fundsRecipient` | `Address` | Funds recipient |
| `vendor` | `Address` | Vendor address |
| `vendorFeeBps` | `number` | Vendor fee BPS |
| `referralFeeBps` | `number` | Referral fee BPS |
| `salesTaxBps` | `number` | Sales tax BPS |
| `nonce` | `bigint` | Unique nonce |
`SaleKey` is the immutable identity of a sale. Any field change produces a new `saleKeyId`, so keep the same key when calling `updateSale`. Use `nonce` to create multiple sales for the same token.
***
#### SaleState
Mutable sale configuration stored in the contract.
```ts twoslash
///
// ---cut---
import type { Sale } from '@sceneinfrastructure/sdk'
const saleState: Sale.SaleState = {
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 10_000000n,
approvers: ['0x1234567890123456789012345678901234567890'],
}
```
| Field | Type | Description |
| ----------- | ----------- | -------------------- |
| `startTime` | `bigint` | Start timestamp |
| `endTime` | `bigint` | End timestamp |
| `quantity` | `number` | Available quantity |
| `price` | `bigint` | Price per unit |
| `approvers` | `Address[]` | Authorized approvers |
`startTime` must be non-zero; `0n` is treated as "uninitialized" onchain.
***
#### PurchaseParams
Parameters for executing a purchase.
```ts twoslash
///
// ---cut---
import type { Sale } from '@sceneinfrastructure/sdk'
const purchaseParams: Sale.PurchaseParams = {
recipient: '0x1234567890123456789012345678901234567890',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 2,
}
```
| Field | Type | Description |
| ----------- | --------- | ------------------ |
| `recipient` | `Address` | Token recipient |
| `referrer` | `Address` | Referrer (or zero) |
| `quantity` | `number` | Number to purchase |
***
#### ApprovalSignatureParams
EIP-712 signature parameters for approved purchases.
```ts twoslash
///
// ---cut---
import type { Sale } from '@sceneinfrastructure/sdk'
const signatureParams: Sale.ApprovalSignatureParams = {
deadline: BigInt(Math.floor(Date.now() / 1000) + 900),
salt: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
signature: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12',
}
```
| Field | Type | Description |
| ----------- | -------- | -------------------- |
| `deadline` | `bigint` | Signature expiration |
| `salt` | `Hex` | Random bytes32 |
| `signature` | `Hex` | ECDSA signature |
***
#### PurchaseResult
Price breakdown returned by `getPrice`.
```ts twoslash
///
// ---cut---
import type { Sale } from '@sceneinfrastructure/sdk'
const result: Sale.PurchaseResult = {
totalPrice: 10_200000n,
subtotal: 10_000000n,
protocolFee: 100000n,
vendorFee: 50000n,
referrerFee: 25000n,
salesTax: 25000n,
}
```
***
### Functions
#### toSaleKey
Converts a full `Sale` object to a `SaleKey` struct.
```ts twoslash
///
// ---cut---
import { Client, Sale, SaleKey } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
// Get a sale from the indexer using its saleKeyId
const saleKeyId = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef' as const
const sale = await client.sale.get({ saleKeyId })
if (sale) {
// Convert to SaleKey for contract interactions
const saleKey = Sale.toSaleKey(sale)
console.log('SaleKey:', saleKey)
}
```
***
### SaleKey Module
#### generateId
Generates the keccak256 hash of a SaleKey struct.
```ts twoslash
///
// ---cut---
import { SaleKey } from '@sceneinfrastructure/sdk'
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890', // Your deployed contract
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x2345678901234567890123456789012345678901',
vendor: '0x3456789012345678901234567890123456789012',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const id = SaleKey.generateId(saleKey)
console.log('Sale Key ID:', id)
```
##### Returns
`Hex` - keccak256 hash (bytes32)
***
### Schemas
Zod schemas for runtime validation of sale data.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
```
#### schema
The main schema for validating complete sale configurations.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
const data = {
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
currencyAddress: '0xded683C101b0171aA42d90F5388b7A44a7D7c355',
fundsRecipientAddress: '0x2345678901234567890123456789012345678901',
vendorAddress: '0x3456789012345678901234567890123456789012',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
price: 10_000000n,
quantity: 100,
startTime: 1n,
endTime: 9999999999n,
approvers: [],
}
const result = Sale.schema.safeParse(data)
if (result.success) {
console.log('Valid sale:', result.data.price)
} else {
console.error('Validation errors:', result.error.issues)
}
```
***
#### saleKeySchema
Validates `SaleKey` structs for contract interactions.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
const saleKey = Sale.saleKeySchema.parse({
token: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
currency: '0xded683C101b0171aA42d90F5388b7A44a7D7c355',
fundsRecipient: '0x2345678901234567890123456789012345678901',
vendor: '0x3456789012345678901234567890123456789012',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
})
```
| Field | Type | Validation |
| ---------------- | --------- | ---------------------- |
| `token` | `Address` | Valid Ethereum address |
| `tokenId` | `bigint` | BigInt |
| `currency` | `Address` | Valid Ethereum address |
| `fundsRecipient` | `Address` | Valid Ethereum address |
| `vendor` | `Address` | Valid Ethereum address |
| `vendorFeeBps` | `number` | 0 to 4,294,967,295 |
| `referralFeeBps` | `number` | 0 to 4,294,967,295 |
| `salesTaxBps` | `number` | 0 to 4,294,967,295 |
| `nonce` | `bigint` | BigInt |
***
#### saleStateSchema
Validates mutable sale state for registration and updates.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
const saleState = Sale.saleStateSchema.parse({
startTime: 1n,
endTime: 9999999999n,
quantity: 100,
price: 10_000000n,
approvers: ['0x1234567890123456789012345678901234567890'],
})
```
| Field | Type | Validation |
| ----------- | ----------- | ------------------------ |
| `startTime` | `bigint` | BigInt |
| `endTime` | `bigint` | BigInt |
| `quantity` | `number` | 0 to 4,294,967,295 |
| `price` | `bigint` | BigInt |
| `approvers` | `Address[]` | Array of valid addresses |
***
#### purchaseParamsSchema
Validates purchase parameters.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
const params = Sale.purchaseParamsSchema.parse({
recipient: '0x1234567890123456789012345678901234567890',
referrer: '0x0000000000000000000000000000000000000000',
quantity: 2,
})
```
| Field | Type | Validation |
| ----------- | --------- | ---------------------- |
| `recipient` | `Address` | Valid Ethereum address |
| `referrer` | `Address` | Valid Ethereum address |
| `quantity` | `number` | 1 to 65,535 |
***
#### approvalSignatureParamsSchema
Validates EIP-712 signature parameters for approved purchases.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
const sigParams = Sale.approvalSignatureParamsSchema.parse({
deadline: BigInt(Math.floor(Date.now() / 1000) + 900),
salt: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
signature: '0x...',
})
```
| Field | Type | Validation |
| ----------- | -------- | -------------------------- |
| `deadline` | `bigint` | BigInt |
| `salt` | `Hex` | Valid hex string (bytes32) |
| `signature` | `Hex` | Valid hex string |
***
#### purchaseResultSchema
Validates price breakdown results from `getPrice`.
```ts twoslash
///
// ---cut---
import { Sale } from '@sceneinfrastructure/sdk'
const result = Sale.purchaseResultSchema.parse({
totalPrice: 10_200000n,
subtotal: 10_000000n,
protocolFee: 100000n,
vendorFee: 50000n,
referrerFee: 25000n,
salesTax: 25000n,
})
```
| Field | Type | Validation |
| ------------- | -------- | ---------- |
| `totalPrice` | `bigint` | BigInt |
| `subtotal` | `bigint` | BigInt |
| `protocolFee` | `bigint` | BigInt |
| `vendorFee` | `bigint` | BigInt |
| `referrerFee` | `bigint` | BigInt |
| `salesTax` | `bigint` | BigInt |
***
### Read Operations
Query sale configurations from the indexer.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
#### get
Get a sale by its key ID.
```ts twoslash
///
// ---cut---
import { Client, SaleKey } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const chainId = 8453 // Base
const saleKey = {
token: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x2345678901234567890123456789012345678901',
vendor: '0x3456789012345678901234567890123456789012',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
} as const
const saleKeyId = SaleKey.generateId(saleKey)
const sale = await client.sale.get({
saleKeyId
})
if (sale) {
console.log('Price:', sale.price)
console.log('Quantity:', sale.quantity)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------- | ----- | --------------------------------- |
| `saleKeyId` | `Hex` | The keccak256 hash of the SaleKey |
##### Returns
`Sale | null` - The sale configuration or null if not found.
***
#### getByKey
Get a sale by its SaleKey struct (convenience method).
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const chainId = 8453 // Base
const sale = await client.sale.getByKey({
token: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
currency: '0x1111111111111111111111111111111111111111',
fundsRecipient: '0x2345678901234567890123456789012345678901',
vendor: '0x3456789012345678901234567890123456789012',
vendorFeeBps: 0,
referralFeeBps: 0,
salesTaxBps: 0,
nonce: 1n,
})
```
##### Parameters
Takes a `SaleKey` struct directly instead of the hash.
##### Returns
`Sale | null` - The sale configuration or null if not found.
***
#### listByContract
List all sales for a contract.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const sales = await client.sale.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 20
})
for (const sale of sales) {
console.log(`Token ${sale.tokenId}: ${sale.price} per ticket`)
}
// Filter to only active sales
const activeSales = await client.sale.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
active: true
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | ------------------------ | ------------- | --------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `active` | `boolean` | - | Filter to only active sales |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
| `orderBy` | `'createdAt' \| 'price'` | `'createdAt'` | Sort field |
| `order` | `'asc' \| 'desc'` | `'desc'` | Sort direction |
##### Returns
`Sale[]` - Array of sale configurations.
***
#### listByVendor
List all sales where an address is the vendor.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const sales = await client.sale.listByVendor({
vendor: '0x2345678901234567890123456789012345678901',
limit: 20
})
for (const sale of sales) {
console.log(`Contract ${sale.contractAddress}: ${sale.vendorFeeBps} bps fee`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| --------- | --------- | ------------ | ------------------------- |
| `vendor` | `Address` | **required** | The vendor address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Sale[]` - Array of sale configurations.
***
#### listActive
List currently active sales.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
// All active sales across all contracts
const activeSales = await client.sale.listActive({
limit: 50
})
// Active sales for a specific contract
const contractActiveSales = await client.sale.listActive({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 20
})
for (const sale of contractActiveSales) {
const now = BigInt(Math.floor(Date.now() / 1000))
const endsIn = sale.endTime - now
console.log(`Sale ends in ${endsIn} seconds`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------- | --------------------------- |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Sale[]` - Array of currently active sales (within start/end time window).
import { StubDemo } from '../../components/StubDemo'
## Stub
Query stubbing events (check-ins where tickets are exchanged for stubs).
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Overview
When an attendee checks in, their ticket tokens (odd IDs like 1, 3, 5) are exchanged for stub tokens (even IDs like 2, 4, 6). This is called "stubbing" - the ticket is consumed and a stub is minted as proof of attendance.
The stub namespace queries these exchange events.
### Methods
#### listByContract
List all stubbing events for a contract.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const stubs = await client.stub.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 50
})
for (const stub of stubs) {
console.log(`${stub.fromAddress} stubbed ${stub.amount} tickets`)
console.log(` Ticket ID: ${stub.burnTokenId} -> Stub ID: ${stub.mintTokenId}`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Stub[]` - Array of stubbing events.
***
#### listByHolder
List stubbing events for a specific holder.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const stubs = await client.stub.listByHolder({
holderAddress: '0x2345678901234567890123456789012345678901',
limit: 50
})
for (const stub of stubs) {
console.log(`Checked in ${stub.amount} tickets at ${stub.contractAddress}`)
}
// Filter by contract
const contractStubs = await client.stub.listByHolder({
holderAddress: '0x2345678901234567890123456789012345678901',
contractAddress: '0x1234567890123456789012345678901234567890'
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | --------------------------- |
| `holderAddress` | `Address` | **required** | The holder address |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Stub[]` - Array of stubbing events.
***
#### listByTier
List stubbing events for a specific tier.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const stubs = await client.stub.listByTier({
contractAddress: '0x1234567890123456789012345678901234567890',
tierId: 1n, // The ticket token ID
limit: 50
})
console.log(`${stubs.length} check-ins for tier 1`)
let totalStubbed = 0n
for (const stub of stubs) {
totalStubbed += stub.amount
}
console.log(`Total stubbed: ${totalStubbed}`)
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ----------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tierId` | `bigint` | **required** | The tier ID (ticket token ID) |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Stub[]` - Array of stubbing events for the tier.
***
### Types
#### Stub
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type Stub = {
id: string
contractAddress: Address
fromAddress: Address
burnTokenId: bigint // Ticket token ID (odd: 1, 3, 5...)
mintTokenId: bigint // Stub token ID (even: 2, 4, 6...)
amount: bigint // Number of tokens stubbed
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ----------------- | --------- | -------------------------- |
| `id` | `string` | Unique event identifier |
| `contractAddress` | `Address` | The contract address |
| `fromAddress` | `Address` | The holder who checked in |
| `burnTokenId` | `bigint` | The ticket token ID burned |
| `mintTokenId` | `bigint` | The stub token ID minted |
| `amount` | `bigint` | Number of tokens exchanged |
| `creationTxHash` | `Hex` | Transaction hash |
***
### Token ID Relationship
Tickets and stubs have a predictable ID relationship:
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
// Ticket IDs are odd: 1, 3, 5, 7...
// Stub IDs are even: 2, 4, 6, 8...
// Stub ID = Ticket ID + 1
const ticketId = 1n
const stubId = ticketId + 1n // 2n
// Or from a stub event:
const stubs = await client.stub.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 1
})
if (stubs.length > 0) {
const stub = stubs[0]
const tierId = stub.burnTokenId // The original ticket tier
}
```
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { Stub } from '@sceneinfrastructure/sdk'
const data = {
id: 'stub_123',
contractAddress: '0x1234567890123456789012345678901234567890',
fromAddress: '0x2345678901234567890123456789012345678901',
burnTokenId: 1n,
mintTokenId: 2n,
amount: 1n,
createdAt: 1700000000n,
createdAtBlock: 100000n,
createdAtLogIndex: 0,
creationTxHash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
updatedAt: 1700000000n,
updatedAtBlock: 100000n,
updatedAtLogIndex: 0,
updatedTxHash: null,
}
const result = Stub.schema.safeParse(data)
if (result.success) {
console.log('Valid stub event:', result.data)
}
```
***
### Related
* [Balance](/api/balance) - Check if users have stubs via `getTokenStatus()` or `isStubbed()`
* [Tier](/api/tier) - Get stub counts via `getStats()`
* [Void](/api/void) - For burns without stub conversion
## TicketIntent
EIP-712 typed data for ticket intent signatures.
```ts twoslash
///
// ---cut---
import { TicketIntent } from '@sceneinfrastructure/sdk'
```
### Overview
Ticket intents are signed messages that cryptographically prove a user owns a specific ticket. The ticket holder signs an EIP-712 message containing their token ID, quantity, and an expiry deadline. This signature can then be verified to confirm ownership without requiring a transaction.
Common use cases:
* Generating QR codes for event check-in
* Proving ticket ownership to third parties
* Time-limited verification of ticket holdings
### Types
#### IntentMessage
The message payload for ticket intent signatures.
```ts twoslash
///
// ---cut---
import { TicketIntent } from '@sceneinfrastructure/sdk'
const message: TicketIntent.IntentMessage = {
tokenId: 1n,
quantity: 2n,
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour
}
```
| Field | Type | Description |
| ---------- | -------- | --------------------- |
| `tokenId` | `bigint` | The token tier ID |
| `quantity` | `bigint` | Number of tickets |
| `deadline` | `bigint` | Unix timestamp expiry |
***
### Functions
#### Signing a Ticket Intent
Use the exported constants with viem's `signTypedData`:
```ts
import { TicketIntent } from '@sceneinfrastructure/sdk'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
const account = privateKeyToAccount('0x...')
const walletClient = createWalletClient({
account,
chain: base,
transport: http(),
})
const message: TicketIntent.IntentMessage = {
tokenId: 1n,
quantity: 2n,
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
}
const signature = await walletClient.signTypedData({
domain: {
...TicketIntent.domain,
chainId: 8453n, // Base
verifyingContract: '0x1234567890123456789012345678901234567890',
},
types: TicketIntent.types,
primaryType: TicketIntent.primaryType,
message,
})
```
***
### Constants
#### types
EIP-712 type definitions for TicketIntent signatures.
```ts twoslash
///
// ---cut---
import { TicketIntent } from '@sceneinfrastructure/sdk'
// TicketIntent.types includes:
// - EIP712Domain type
// - TicketIntent struct type
```
***
#### domain
Base domain separator for TicketIntent signatures. Add `chainId` and `verifyingContract` at runtime.
```ts twoslash
///
// ---cut---
import { TicketIntent } from '@sceneinfrastructure/sdk'
const contractAddress = '0x1234567890123456789012345678901234567890' as const
const fullDomain = {
...TicketIntent.domain,
chainId: 8453n,
verifyingContract: contractAddress,
}
```
| Field | Value |
| --------- | ---------------- |
| `name` | `'TicketIntent'` |
| `version` | `'1.0.0'` |
***
#### primaryType
The primary type identifier for TicketIntent signatures.
```ts twoslash
///
// ---cut---
import { TicketIntent } from '@sceneinfrastructure/sdk'
console.log(TicketIntent.primaryType) // 'TicketIntent'
```
***
### Schemas
#### messageSchema
Zod schema for validating TicketIntent messages. Coerces string/number inputs to bigint.
```ts twoslash
///
// ---cut---
import { TicketIntent } from '@sceneinfrastructure/sdk'
const message = TicketIntent.messageSchema.parse({
tokenId: '1',
quantity: 2,
deadline: Date.now() + 3600000,
})
```
import { TierDemo } from '../../components/TierDemo'
## Tier
Query ticket tier configurations.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Methods
#### get
Get a tier by contract address and token ID.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const tier = await client.tier.get({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n
})
if (tier) {
console.log('Token URI:', tier.tokenURI)
console.log('Stub URI:', tier.stubURI)
console.log('Max supply:', tier.quantity)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | ------------------------------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The tier token ID (odd numbers are tickets) |
##### Returns
`Tier | null` - The tier data or null if not found.
***
#### getWithSupply
Get a tier with current supply information.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const tier = await client.tier.getWithSupply({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n
})
if (tier) {
console.log('Max supply:', tier.supply.maxSupply)
console.log('Minted:', tier.supply.minted)
console.log('Remaining:', tier.supply.remaining)
console.log('Stubbed:', tier.supply.stubbed)
console.log('Active (unstubbed):', tier.supply.active)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The tier token ID |
##### Returns
`TierWithSupply | null` - Tier with supply data or null if not found.
***
#### getStats
Get statistics for a tier.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const stats = await client.tier.getStats({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n
})
if (stats) {
console.log('Total sold:', stats.sold)
console.log('Total stubbed:', stats.stubbed)
console.log('Stub rate:', stats.stubRate) // e.g., 75 = 75%
console.log('Remaining:', stats.remaining)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The tier token ID |
##### Returns
`TierStats | null` - Statistics or null if tier not found.
***
#### listByContract
List all tiers for a contract.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const tiers = await client.tier.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 20,
orderBy: 'tokenId',
order: 'asc'
})
for (const tier of tiers) {
console.log(`Tier ${tier.tokenId}: ${tier.tokenURI}`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | -------------------------- | ------------- | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
| `orderBy` | `'createdAt' \| 'tokenId'` | `'createdAt'` | Sort field |
| `order` | `'asc' \| 'desc'` | `'desc'` | Sort direction |
##### Returns
`Tier[]` - Array of tiers.
***
#### listByContractWithSupply
List all tiers for a contract with supply information.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const tiers = await client.tier.listByContractWithSupply({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 20
})
for (const tier of tiers) {
console.log(`Tier ${tier.tokenId}:`)
console.log(` Minted: ${tier.supply.minted}/${tier.supply.maxSupply}`)
console.log(` Active: ${tier.supply.active}`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`TierWithSupply[]` - Array of tiers with supply data.
***
### Types
#### Tier
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type Tier = {
contractAddress: Address
tokenId: bigint
tokenURI: string
stubTokenId: bigint
stubURI: string
quantity: bigint // Max supply (0 = unlimited)
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ------------- | -------- | --------------------------------- |
| `tokenId` | `bigint` | Ticket token ID (odd: 1, 3, 5...) |
| `stubTokenId` | `bigint` | Stub token ID (even: 2, 4, 6...) |
| `tokenURI` | `string` | Metadata URI for tickets |
| `stubURI` | `string` | Metadata URI for stubs |
| `quantity` | `bigint` | Max supply (0 = unlimited) |
***
#### TierSupply
```ts twoslash
///
// ---cut---
type TierSupply = {
maxSupply: bigint // From tier.quantity
minted: bigint // Total minted
remaining: bigint | null // maxSupply - minted (null if unlimited)
stubbed: bigint // Number converted to stubs
voided: bigint // Number voided (refunds, cancellations)
active: bigint // minted - stubbed - voided
}
```
***
#### TierStats
```ts twoslash
///
// ---cut---
type TierStats = {
sold: bigint // Total sold (minted)
stubbed: bigint // Total stubbed (checked in)
stubRate: number // Percentage stubbed (0-100)
remaining: bigint | null // Remaining supply (null if unlimited)
}
```
***
#### TierWithSupply
```ts
type TierWithSupply = Tier & {
supply: TierSupply
}
```
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { Tier } from '@sceneinfrastructure/sdk'
const data = {} // Data from API or external source
const result = Tier.schema.safeParse(data)
if (result.success) {
console.log('Valid tier:', result.data)
}
```
import { TokenDemo } from '../../components/TokenDemo'
## Token
ERC1155 Token types and utilities.
```ts twoslash
///
// ---cut---
import { Token } from '@sceneinfrastructure/sdk'
```
### Try It
### Types
#### Token
Represents the state of a token tier in a Gatekeeper1155 contract.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
// Token objects are returned from indexer queries
const token = await client.token.get({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
})
if (token) {
console.log('Token URI:', token.tokenURI)
console.log('Max supply:', token.maxSupply)
console.log('Total minted:', token.totalMinted)
}
```
| Field | Type | Description |
| ----------------- | --------- | ----------------------------------- |
| `contractAddress` | `Address` | The Gatekeeper1155 contract address |
| `tokenId` | `bigint` | The token tier ID |
| `tokenURI` | `string` | Metadata URI for the token |
| `maxSupply` | `bigint` | Maximum supply (0 = unlimited) |
| `totalMinted` | `bigint` | Number of tokens minted |
***
### Schemas
#### schema
Zod schema for validating and parsing Token data.
```ts twoslash
///
// ---cut---
import { Token } from '@sceneinfrastructure/sdk'
const data = {} // Data from API
// Parse token data from API
const result = Token.schema.safeParse(data)
if (result.success) {
const available = result.data.maxSupply - result.data.totalMinted
console.log('Available tickets:', available)
}
```
##### Example: Calculate Availability
```ts twoslash
///
// ---cut---
import { Token } from '@sceneinfrastructure/sdk'
function getAvailability(token: Token.Token): bigint {
if (token.maxSupply === 0n) {
return BigInt(Number.MAX_SAFE_INTEGER) // Unlimited
}
return token.maxSupply - token.totalMinted
}
function isSoldOut(token: Token.Token): boolean {
if (token.maxSupply === 0n) return false
return token.totalMinted >= token.maxSupply
}
```
***
### Read Operations
Query token data from the indexer.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
#### get
Get a token by contract address and token ID.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const token = await client.token.get({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n
})
if (token) {
console.log('Token URI:', token.tokenURI)
console.log('Max supply:', token.maxSupply)
console.log('Total minted:', token.totalMinted)
}
```
##### Parameters
| Parameter | Type | Description |
| ----------------- | --------- | -------------------- |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The token ID |
##### Returns
`Token | null` - The token data or null if not found.
***
#### listByContract
List all tokens for a contract.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
const tokens = await client.token.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 50
})
for (const token of tokens) {
console.log(`Token ${token.tokenId}: ${token.tokenURI}`)
console.log(` Supply: ${token.totalMinted}/${token.maxSupply || 'unlimited'}`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | -------------------------- | ------------- | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
| `orderBy` | `'createdAt' \| 'tokenId'` | `'createdAt'` | Sort field |
| `order` | `'asc' \| 'desc'` | `'desc'` | Sort direction |
##### Returns
`Token[]` - Array of token records.
import { TransferDemo } from '../../components/TransferDemo'
## Transfer
Query ERC1155 transfer history.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Methods
#### listByToken
List transfers for a specific token.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const transfers = await client.transfer.listByToken({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
limit: 50
})
for (const transfer of transfers) {
const type = transfer.fromAddress === null ? 'MINT' :
transfer.toAddress === null ? 'BURN' : 'TRANSFER'
console.log(`${type}: ${transfer.quantity} tokens`)
}
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | ----------------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tokenId` | `bigint` | **required** | The token ID |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
| `order` | `'asc' \| 'desc'` | `'desc'` | Sort by creation time |
##### Returns
`Transfer[]` - Array of transfer records.
***
#### listByFrom
List transfers sent from a specific address.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const sent = await client.transfer.listByFrom({
from: '0x2345678901234567890123456789012345678901',
limit: 50
})
for (const transfer of sent) {
console.log(`Sent ${transfer.quantity} of token ${transfer.tokenId}`)
}
// Filter by contract
const sentFromContract = await client.transfer.listByFrom({
from: '0x2345678901234567890123456789012345678901',
contractAddress: '0x1234567890123456789012345678901234567890'
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | --------------------------- |
| `from` | `Address` | **required** | The sender address |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Transfer[]` - Array of transfer records.
***
#### listByTo
List transfers received by a specific address.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const received = await client.transfer.listByTo({
to: '0x2345678901234567890123456789012345678901',
limit: 50
})
for (const transfer of received) {
console.log(`Received ${transfer.quantity} of token ${transfer.tokenId}`)
}
// Filter by contract
const receivedFromContract = await client.transfer.listByTo({
to: '0x2345678901234567890123456789012345678901',
contractAddress: '0x1234567890123456789012345678901234567890'
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | --------------------------- |
| `to` | `Address` | **required** | The recipient address |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Transfer[]` - Array of transfer records.
***
#### listMints
List mint events (transfers from the zero address).
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const mints = await client.transfer.listMints({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 50
})
for (const mint of mints) {
console.log(`Minted ${mint.quantity} of token ${mint.tokenId} to ${mint.toAddress}`)
}
// Filter by token ID
const tierMints = await client.transfer.listMints({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tokenId` | `bigint` | - | Filter to specific token |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Transfer[]` - Array of mint records (where `fromAddress` is null).
***
#### listBurns
List burn events (transfers to the zero address).
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://ponder.mesh.xyz' })
const burns = await client.transfer.listBurns({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 50
})
for (const burn of burns) {
console.log(`Burned ${burn.quantity} of token ${burn.tokenId} from ${burn.fromAddress}`)
}
// Filter by token ID
const tierBurns = await client.transfer.listBurns({
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tokenId` | `bigint` | - | Filter to specific token |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Transfer[]` - Array of burn records (where `toAddress` is null).
***
### Types
#### Transfer
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type Transfer = {
id: string
contractAddress: Address
tokenId: bigint
fromAddress: Address | null // null for mints
toAddress: Address | null // null for burns
quantity: bigint
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ----------------- | ----------------- | ---------------------------- |
| `id` | `string` | Unique transfer identifier |
| `contractAddress` | `Address` | The contract address |
| `tokenId` | `bigint` | The token ID |
| `fromAddress` | `Address \| null` | Sender (null for mints) |
| `toAddress` | `Address \| null` | Recipient (null for burns) |
| `quantity` | `bigint` | Number of tokens transferred |
| `creationTxHash` | `Hex` | Transaction hash |
#### Transfer Types
* **Mint**: `fromAddress === null` - New tokens created
* **Burn**: `toAddress === null` - Tokens destroyed
* **Transfer**: Both addresses present - Standard transfer
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { Transfer } from '@sceneinfrastructure/sdk'
const data = {
id: 'transfer_123',
contractAddress: '0x1234567890123456789012345678901234567890',
tokenId: 1n,
fromAddress: null,
toAddress: '0x2345678901234567890123456789012345678901',
quantity: 1n,
createdAt: 1700000000n,
createdAtBlock: 100000n,
createdAtLogIndex: 0,
creationTxHash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
updatedAt: 1700000000n,
updatedAtBlock: 100000n,
updatedAtLogIndex: 0,
updatedTxHash: null,
}
const result = Transfer.schema.safeParse(data)
if (result.success) {
console.log('Valid transfer:', result.data)
}
```
## Validators
Zod schemas for validating Ethereum primitives.
```ts twoslash
///
// ---cut---
import { Validators } from '@sceneinfrastructure/sdk'
```
### Schemas
#### ethAddress
Validates and transforms Ethereum addresses. Uses [ox](https://github.com/wevm/ox) for validation.
```ts twoslash
///
// ---cut---
import { Validators } from '@sceneinfrastructure/sdk'
const result = Validators.ethAddress.safeParse('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045')
if (result.success) {
console.log('Valid address:', result.data)
}
// Invalid address
const invalid = Validators.ethAddress.safeParse('0xinvalid')
// invalid.success === false
```
***
#### ethTxHash
Validates Ethereum transaction hashes (66-character hex strings).
```ts twoslash
///
// ---cut---
import { Validators } from '@sceneinfrastructure/sdk'
const txHash = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
const result = Validators.ethTxHash.safeParse(txHash)
if (result.success) {
console.log('Valid transaction hash')
}
```
***
#### hash
Validates 66-character hash strings (bytes32).
```ts twoslash
///
// ---cut---
import { Validators } from '@sceneinfrastructure/sdk'
const bytes32 = '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef'
const result = Validators.hash.safeParse(bytes32)
```
***
#### hex
Validates and transforms hex strings with `0x` prefix.
```ts twoslash
///
// ---cut---
import { Validators } from '@sceneinfrastructure/sdk'
const result = Validators.hex.safeParse('0xdeadbeef')
if (result.success) {
console.log('Valid hex:', result.data) // '0xdeadbeef'
}
```
***
#### uint256
Coerces values to bigint for uint256 fields.
```ts twoslash
///
// ---cut---
import { Validators } from '@sceneinfrastructure/sdk'
// Accepts strings, numbers, or bigints
const fromString = Validators.uint256.parse('1000000')
const fromNumber = Validators.uint256.parse(1000000)
const fromBigint = Validators.uint256.parse(1000000n)
// All result in: 1000000n
```
***
### Usage with Zod
Combine with other Zod schemas for complex validation:
```ts twoslash
///
// ---cut---
import { z } from 'zod'
import { Validators } from '@sceneinfrastructure/sdk'
const transferSchema = z.object({
from: Validators.ethAddress,
to: Validators.ethAddress,
amount: Validators.uint256,
txHash: Validators.ethTxHash.optional(),
})
const transfer = transferSchema.parse({
from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
to: '0x1234567890123456789012345678901234567890',
amount: '1000000000000000000',
})
```
import { VoidDemo } from '../../components/VoidDemo'
## Void
Query voiding events (ticket burns without stub conversion).
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: '...' })
```
### Try It
### Overview
Voiding is when tickets are burned directly without creating stubs. This is different from stubbing (check-in), where tickets are exchanged for stub tokens.
Use cases for voiding:
* Canceling tickets without marking as attended
* Refund scenarios
* Administrative removal of tickets
For check-in events (ticket to stub exchange), use the [Stub](/api/stub) namespace instead.
### Methods
#### listByContract
List all voiding events for a contract.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const voids = await client.void.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
limit: 50
})
for (const v of voids) {
console.log(`${v.holderAddress} had ${v.amount} tickets voided`)
console.log(` Tier: ${v.tierId}`)
}
// Filter by tier
const tier1Voids = await client.void.listByContract({
contractAddress: '0x1234567890123456789012345678901234567890',
tierId: 1n
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tierId` | `bigint` | - | Filter to specific tier |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Void[]` - Array of voiding events.
***
#### listByHolder
List voiding events for a specific holder.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const voids = await client.void.listByHolder({
holderAddress: '0x2345678901234567890123456789012345678901',
limit: 50
})
for (const v of voids) {
console.log(`${v.amount} tickets voided from ${v.contractAddress}`)
}
// Filter by contract
const contractVoids = await client.void.listByHolder({
holderAddress: '0x2345678901234567890123456789012345678901',
contractAddress: '0x1234567890123456789012345678901234567890'
})
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | --------------------------- |
| `holderAddress` | `Address` | **required** | The holder address |
| `contractAddress` | `Address` | - | Filter to specific contract |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Void[]` - Array of voiding events.
***
#### listByTier
List voiding events for a specific tier.
```ts twoslash
///
// ---cut---
import { Client } from '@sceneinfrastructure/sdk'
const client = Client.create({ url: 'https://dev.ponder.mesh.ing/sql' })
const voids = await client.void.listByTier({
contractAddress: '0x1234567890123456789012345678901234567890',
tierId: 1n,
limit: 50
})
let totalVoided = 0n
for (const v of voids) {
totalVoided += v.amount
}
console.log(`Total voided from tier 1: ${totalVoided}`)
```
##### Parameters
| Parameter | Type | Default | Description |
| ----------------- | --------- | ------------ | ------------------------- |
| `contractAddress` | `Address` | **required** | The contract address |
| `tierId` | `bigint` | **required** | The tier ID |
| `limit` | `number` | `50` | Maximum results to return |
| `offset` | `number` | `0` | Number of results to skip |
##### Returns
`Void[]` - Array of voiding events for the tier.
***
### Types
#### Void
```ts twoslash
///
// ---cut---
import type { Address, Hex } from 'viem'
type Void = {
id: string
contractAddress: Address
holderAddress: Address
tierId: bigint
amount: bigint
createdAt: bigint
createdAtBlock: bigint
createdAtLogIndex: number
creationTxHash: Hex
updatedAt: bigint
updatedAtBlock: bigint
updatedAtLogIndex: number
updatedTxHash: Hex | null
}
```
| Field | Type | Description |
| ----------------- | --------- | ------------------------------------ |
| `id` | `string` | Unique event identifier |
| `contractAddress` | `Address` | The contract address |
| `holderAddress` | `Address` | The holder whose tickets were voided |
| `tierId` | `bigint` | The tier ID |
| `amount` | `bigint` | Number of tickets voided |
| `creationTxHash` | `Hex` | Transaction hash |
***
### Schema
#### Zod Validation
```ts twoslash
///
// ---cut---
import { Void } from '@sceneinfrastructure/sdk'
const data = {} // Data to validate
const result = Void.schema.safeParse(data)
if (result.success) {
console.log('Valid void event:', result.data)
}
```
***
### Void vs Stub
| Event | Action | Result |
| ------------------- | -------------------------- | ---------------------- |
| **Stub** (Check-in) | Ticket burned, stub minted | Holder gets stub token |
| **Void** | Ticket burned directly | No stub token |
Use stubs for attendance tracking (check-ins). Use voids for administrative actions like cancellations or refunds.
***
### Related
* [Stub](/api/stub) - For check-in events (ticket to stub exchange)
* [Transfer](/api/transfer) - For all transfer events including burns
* [Balance](/api/balance) - Check current token balances