Forest Docs
DevelopersForest Playkit

Burns

On-chain wallet burns for in-game sinks, with signed backend settlement.

A burn is an on-chain action: the player sends Project Tokens from their own wallet to a signed sink address — the dead address (0x…dead) by default, but the backend may sign any destination. Forest records the verified on-chain transfer with its reason/metadata for tracking. A transfer to 0x…dead is a real, provable supply burn; any other sink is just the app-chosen destination, not a supply reduction.

Use it for in-game sinks such as activation fees. Burns are wallet-side and never touch Game Balance.

Authorize before you transfer

The burn settlement requires an unused action authorization, so authorize first — otherwise the player can burn tokens on-chain and then fail Forest authorization/settlement, stranding the burn. The transfer is irreversible; the authorization is not.

The developer backend owns the burn intent — it decides actionId, amount, burnAddress, reason, and metadata. The client only executes that exact intent on-chain and relays the resulting txHash back; it does not choose the destination or purpose.

The burn flow

Intent and authorize

Your backend defines the intent (actionId, amount, burnAddress, reason, metadata). The iframe calls forest.game.action.authorize with that actionId and a base-unit debit limit — which is also the player-authorized max burn amount. Do not use 0.

Burn (client)

JavaScript
const { txHash, burnAddress } = await forest.game.burn({
	amount: "10", // display units
	burnAddress, // optional; defaults to 0x…dead
});

Forest performs the on-chain transfer and returns { txHash, burnAddress } after receipt confirmation — it does not call any API, and never surfaces a pending tx hash. Relay txHash and burnAddress to your backend.

Settle (backend)

Your backend HMAC-signs and submits POST /campaigns/{projectId}/html/burns with the same actionId, the base-unit amount, txHash, burnAddress, reason, metadata, and timestamp. Forest verifies on-chain that exactly amount Project Tokens moved from a linked wallet to burnAddress, then records the burn. Game Balance is never touched.

Signed burn settlement

The signed amount is the same economic amount as the client burn — display units in forest.game.burn (e.g. "10"), base-unit integer string in the signed body (e.g. "10000000000000000000").

HTTP
POST /campaigns/{projectId}/html/burns
Content-Type: application/json
X-Forest-Settlement-Signature: v1=<hex-hmac>
JavaScript
await fetch(`${FOREST_API}/campaigns/${projectId}/html/burns`, {
	method: "POST",
	headers: {
		"Content-Type": "application/json",
		"X-Forest-Settlement-Signature": `v1=${hmac}`,
	},
	body: JSON.stringify({
		actionId: "same-action-id",
		amount: "10000000000000000000", // base-unit integer string
		txHash: "0x…",
		burnAddress: "0x000000000000000000000000000000000000dead",
		timestamp: Math.floor(Date.now() / 1000),
		reason: "activation_fee",
		metadata: { item: "gold_miner_rig", level: 3 },
	}),
});

Request fields:

Prop

Type

Sign the exact request body with your Settlement Signing Secret, identical to the settlement flow. Successful responses return:

JavaScript
{
  burnId: "burn-id",
  actionId: "same-action-id",
  amount: "10",
  reason: "activation_fee",
  metadata: { item: "gold_miner_rig", level: 3 },
  txHash: "0x…",
  burnAddress: "0x000000000000000000000000000000000000dead",
  replayed: false
}

Retrying a burn settlement

Burns are idempotent by actionId. A safe retry MUST replay the same actionId, the exact same JSON body, and the same signature, and arrive within the request-freshness window (~5 min). An exact replay returns replayed: true. After the window, do not retry — confirm the burn settled by looking up its burnId in forest.game.burns.

Idempotency is keyed on actionId, not txHash: a txHash backs exactly one burn, and a txHash already recorded as a deposit is a conflict.

Burns do not change Game Balance (the tokens are burned straight from the wallet) and are not part of forest.game.transactions.

Replay and validation failures return the shared settlement error codes — SETTLEMENT_TIMESTAMP_STALE, ACTION_ID_REUSED, BURN_TX_HASH_REUSED, DEBIT_LIMIT_EXCEEDED, INVALID_SETTLEMENT_AMOUNT, and signature errors. See Errors and Security for each code's cause and resolution.

Reading burns

forest.game.burns returns the current player's recorded burns (paginated, newest first); forest.game.burns.summary returns the player's totals grouped by reason. Both are player-scoped iframe reads (like forest.game.balance).

JavaScript
const { data, meta } = await callForest("forest.game.burns", { page: 1, perPage: 20 });
// data[]: { id, amount, reason, metadata, txHash, burnAddress, createdAt }

const summary = await callForest("forest.game.burns.summary");
// { totalBurned, byReason: [{ reason, amount, count }] }

totalBurned here is "verified burn/sink settlements" — not necessarily supply reduced (only dead/zero destinations reduce supply). Burns sent without a reason are grouped under reason: null. Amounts are display token amount strings.

Token-level burn history is separate from Game Balance history. The token Burns tab is populated from synced on-chain token transfers to the burn address or zero address, alongside backend buyback burns.