Starter Packs
Starter packs let you bundle game assets and distribute them to players through a purchase flow integrated with Cartridge Controller.
The Arcade starter pack registry is a permissionless onchain system — anyone can register a starter pack by deploying an implementation contract and calling register.
How It Works
The registry follows a two-contract pattern:
- Implementation contract — a contract you deploy that implements the
IStarterpackImplementationinterface. When a player purchases your starter pack, the registry calls your contract'son_issuefunction to distribute assets. - Registry contract — the Arcade registry where you register your implementation, set pricing, and configure options.
Player purchases → Registry collects payment → Registry calls on_issue → Your contract distributes assetsImplementation Contract
Your implementation contract must expose two functions:
#[starknet::interface]
trait IStarterpackImplementation<TContractState> {
/// Called by the registry when a starter pack is issued.
/// Distribute assets to the recipient here.
fn on_issue(
ref self: TContractState,
recipient: ContractAddress,
starterpack_id: u32,
quantity: u32,
);
/// Return the supply limit, or None for unlimited.
fn supply(self: @TContractState, starterpack_id: u32) -> Option<u32>;
}on_issue is where you mint tokens, transfer NFTs, or perform any onchain action to fulfill the starter pack.
The registry will only call on_issue after payment has been collected.
Example: ERC721 Starter Pack
A minimal implementation that mints an NFT to the recipient:
#[abi(embed_v0)]
impl StarterpackImpl of IStarterpackImplementation<ContractState> {
fn on_issue(
ref self: ContractState,
recipient: ContractAddress,
starterpack_id: u32,
quantity: u32,
) {
assert(self.starterpacks.read(starterpack_id), 'Invalid starterpack');
self.mint(recipient);
}
fn supply(self: @ContractState, starterpack_id: u32) -> Option<u32> {
Option::None // Unlimited supply
}
}See the full example at cartridge-gg/activations.
Registering a Starter Pack
Once your implementation contract is deployed, register it with the Arcade registry by calling register:
fn register(
ref self: TContractState,
implementation: ContractAddress, // Your deployed implementation contract
referral_percentage: u8, // Percentage of base price paid to referrers (0-50)
reissuable: bool, // Whether a player can purchase more than once
price: u256, // Price per unit in payment token
payment_token: ContractAddress, // ERC20 token used for payment
payment_receiver: Option<ContractAddress>, // Payment destination (defaults to caller)
metadata: ByteArray, // JSON metadata (see below)
conditional: bool, // Whether purchases require a voucher
) -> u32; // Returns the starter pack IDThe returned ID is what players use to purchase the starter pack via controller.openStarterPack(id).
Parameters
reissuable
Controls whether the same player can purchase the starter pack more than once.
false— each player can only purchase once, andquantityis forced to 1true— players can purchase multiple times with any quantity
referral_percentage
Percentage of the base price paid to a referrer when one is provided during purchase. Maximum is 50. Self-referrals (referrer == payer) are ignored.
payment_receiver
Where the base price (minus any referral fee) is sent.
If None, payment goes to the starter pack owner (the address that called register).
If Some(address), payment goes to that address instead — useful for treasury contracts or revenue sharing.
conditional
When true, purchases require a voucher.
Vouchers are granted by an admin via the allow function, which authorizes a specific recipient address.
The player must provide the matching voucher_key when purchasing.
Metadata
The metadata parameter is a JSON string describing the starter pack for display in the UI.
Use the MetadataTrait helper from the starterpack package to construct it:
use starterpack::types::item::ItemTrait;
use starterpack::types::metadata::MetadataTrait;
let sword = ItemTrait::new("Sword", "A mighty sword", "https://example.com/sword.png");
let metadata = MetadataTrait::new(
name: "My Starter Pack",
description: "A pack of game assets",
image_uri: "https://example.com/image.png",
items: [sword].span(),
tokens: [].span(), // Additional payment token hints for the UI
conditions: ["Must be level 5"].span(), // Display-only condition strings
).jsonify();Managing Starter Packs
After registration, the owner can manage the starter pack:
update— change implementation, pricing, referral percentage, or other parametersupdate_metadata— update the display metadatapause/resume— temporarily disable or re-enable purchases
Payment Flow
When a player purchases a starter pack, the registry handles payment distribution:
- Referral fee — if a referrer is provided, their percentage is deducted from the base price and sent to them
- Protocol fee — a fee is added on top of the base price and sent to the Arcade fee receiver
- Owner payment — the remaining base price (after referral fee) is sent to the
payment_receiveror owner - Asset distribution — the registry calls
on_issueon the implementation contract
If price is zero, all payment steps are skipped and on_issue is called directly.