Add the Forest bridge to your HTML app and make your first call.
This gets a Forest HTML template talking to the parent page: request the wallet, handle Forest's messages, and fire your first quote. For the concepts behind it, see the Overview.
Drop this into your HTML app. It sends RPC requests with callForest(...) and routes Forest's messages back to your code.
let wallet = null;
let projectId = null;
let autoSwapsEnabled = false;
let latestSnapshot = null;
function requestId() {
if (crypto.randomUUID) return crypto.randomUUID();
return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
function callForest(method, params = {}) {
const id = requestId();
window.parent.postMessage(
{
type: "FOREST_RPC_REQUEST",
version: 1,
id,
method,
params,
},
"*"
);
return id;
}
window.addEventListener("message", (event) => {
if (event.source !== window.parent) return;
const data = event.data || {};
if (data.type === "FOREST_WALLET_CONNECTED") {
// Display-only. Never send this to your backend as identity — see Player Identity.
wallet = data.walletAddress;
callForest("forest.game.balance");
callForest("forest.swap.quote", { amount: "0.05", direction: "buy" });
return;
}
if (data.type === "FOREST_WALLET_DISCONNECTED") {
wallet = null;
latestSnapshot = null;
return;
}
if (data.type === "FOREST_PROJECT_CONTEXT" && typeof data.projectId === "string") {
projectId = data.projectId;
autoSwapsEnabled = data.autoSwapsEnabled === true;
return;
}
if (data.type !== "FOREST_RPC_RESPONSE") return;
if (data.result && data.result.snapshot) {
latestSnapshot = data.result.snapshot;
}
if (data.result && data.result.balance) {
console.log("Game balance:", data.result.balance);
}
if (data.status === "hash") {
console.log("Transaction submitted:", data.result && data.result.txHash);
}
if (data.status === "success") {
console.log("Forest request complete:", data.result && data.result.txHash);
}
if (data.status === "error") {
console.error(data.error && data.error.code, data.error && data.error.message);
}
});
window.parent.postMessage({ type: "FOREST_REQUEST_WALLET" }, "*");The wallet address is display-only
FOREST_WALLET_CONNECTED carries a walletAddress for display only. Never send it to your
backend as player identity — use the Player Identity
flow instead. Error responses (status: "error") carry an error.code; see the error
reference for the codes.
On load, Forest sends the current project context to your iframe:
{
type: "FOREST_PROJECT_CONTEXT",
projectId: "project-id",
gameEntity: "html",
autoSwapsEnabled: false
}Prop
Type
Store projectId, never hardcode it
projectId is not a secret, but do not hardcode it. The same HTML file can be uploaded to
different projects, and Forest provides the active id at runtime — store the id Forest sends.
Gate session UI on autoSwapsEnabled
autoSwapsEnabled tells the iframe whether the owner enabled user-approved auto-swap sessions.
If it is false, do not render session controls.
Most templates follow the same swap loop. Game, session, burn, and identity flows layer on top — each is documented on its own page.
Send FOREST_REQUEST_WALLET and store the incoming FOREST_PROJECT_CONTEXT.
Call forest.swap.quote once the wallet connects, and re-quote whenever amount, direction, or slippage changes.
Call forest.swap.buy or forest.swap.sell when the user confirms.
On success, request another quote so wallet balances update:
if (response.status === "success") {
setTimeout(() => {
callForest("forest.swap.quote", {
amount: currentAmount,
direction: currentDirection,
});
}, 750);
}From here: