Sandbox

Go to production
PackflipPartner

On-chain cards

Mint cards to a wallet on Base and buy minted cards back.

Cards are held by Packflip by default. When a user wants the NFT, you can mint the card to their wallet on Base. A minted card can still be bought back, by burning the NFT.

On-chain actions are authorized by Packflip and sent from the customer's wallet. Packflip never holds your users' keys, and your API key never signs transactions.

EnvironmentChain
ProductionBase (8453)
SandboxBase Sepolia (84532)

1. Request a mint

Minting is free: nothing is charged to your balance. The customer pays gas. walletAddress is the wallet that receives the NFTs; the customer does not need to register it first.

curl -X POST "$PACKFLIP_BASE_URL/api/v2/operations" \
  -H "Authorization: Bearer $PACKFLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: mint-user_123-0001" \
  -d '{
    "kind": "mint",
    "customerId": "cus_…",
    "cardIds": ["card_…"],
    "walletAddress": "0xabc…"
  }'

The operation returns with status: "awaiting_chain" and one entry per card in onchainOrders:

{
  "id": "oco_…",
  "cardId": "card_…",
  "chainId": 84532,
  "vendingMachineAddress": "0x…",
  "collectionAddress": "0x…",
  "tokenId": "1234",
  "buyerAddress": "0xabc…",
  "priceBaseUnits": "0",
  "deadline": "2026-09-17T08:15:00.000Z",
  "calldata": "0x…",
  "transactionHash": null,
  "confirmedAt": null
}

The cards must be revealed and held by the customer. Addresses are returned in lowercase.

2. Send the transaction from the customer's wallet

Send calldata to vendingMachineAddress with no value, before deadline (15 minutes after the authorization is issued). With viem:

import { createWalletClient, custom } from "viem";
import { baseSepolia } from "viem/chains";

const wallet = createWalletClient({ chain: baseSepolia, transport: custom(window.ethereum) });
const [account] = await wallet.requestAddresses();

const hash = await wallet.sendTransaction({
  account,
  to: order.vendingMachineAddress,
  data: order.calldata,
});

The transaction must come from buyerAddress, on chainId.

3. Wait for confirmation

Packflip watches the chain and confirms the mint on its own. When every card in the operation is minted, the operation becomes completed, each card's onchain.mintedAt is set, and operation.completed is emitted.

To speed this up, report the hash as soon as you have it. This is optional:

curl -X POST "$PACKFLIP_BASE_URL/api/v2/operations/op_…/onchain-orders/oco_…/submit" \
  -H "Authorization: Bearer $PACKFLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "transactionHash": "0x…" }'

A mint is accepted only if the transaction succeeded and either matches the signed calldata or mints that token to the wallet. Otherwise the order gets a failureReason and operation.onchain_failed is emitted.

If you think an event was missed, POST /api/v2/operations/{operationId}/refresh checks the chain immediately.

Expired authorizations

If deadline passes before the transaction lands, choose one:

  • Re-sign: POST /api/v2/operations/{operationId}/resign issues fresh signatures for the same token IDs and cards. Returns 409 if nothing has expired.
  • Cancel: POST /api/v2/operations/{operationId}/cancel cancels the mint. The cards stay held, and operation.onchain_cancelled is emitted. Returns 409 while any signature is still valid.

Both first check the chain, so a transaction that landed late is confirmed rather than cancelled.

Buy back a minted card

curl -X POST "$PACKFLIP_BASE_URL/api/v2/operations" \
  -H "Authorization: Bearer $PACKFLIP_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: buyback-onchain-user_123-0001" \
  -d '{
    "kind": "buyback",
    "mode": "onchain",
    "customerId": "cus_…",
    "cardIds": ["card_…"]
  }'

The operation returns awaiting_chain with one entry per card in onchainBurns, and the cards become buyback_pending. From the wallet that holds the NFT:

  1. Approve the handler once per collection: call setApprovalForAll(handlerAddress, true) on collectionAddress.
  2. Send calldata to handlerAddress before deadline.
import { erc721Abi } from "viem";

await wallet.writeContract({
  account,
  address: burn.collectionAddress,
  abi: erc721Abi,
  functionName: "setApprovalForAll",
  args: [burn.handlerAddress, true],
});

await wallet.sendTransaction({ account, to: burn.handlerAddress, data: burn.calldata });

When the burn confirms, the card becomes bought_back, the buyback value quoted when the operation was created is credited to your cash balance, and buyback.completed is emitted once every burn in the operation is settled. You can report the hash early with POST /api/v2/operations/{operationId}/onchain-burns/{onchainBurnId}/submit.

Expired burn authorizations can be re-signed or cancelled the same way as mints. A cancelled burn leaves the card minted and active.

Sandbox notes

The sandbox mints on Base Sepolia from a test collection. You need Base Sepolia ETH for gas, available from public faucets. If on-chain actions are not configured in an environment, these requests return 503 service_unavailable.