Example app: World Postcard
A complete, real phone app built on the Helix Phone SDK — pick or capture a photo, tag it with your current world, save a draft, unlock a premium frame via IAP, then post to Helixgram, notify, and share.
Let's build a real app end to end. World Postcard lets a player turn a moment from the world they're in into a shareable postcard. Along the way it uses media, camera, presence, storage, payments (IAP), notifications, Helixgram, and the UI affordances — a broad cross-section of the SDK.
What you'll touch
media.pickFromAlbum · camera.mockCapture · presence.getCurrentWorld · storage ·
payments.purchase · helixgram.createPost · notifications.push · ui.shareSheet · ui.setBadge
· lifecycle
1. The manifest
Declare exactly the scopes the features below use — nothing more.
{
appId: "acme.postcard",
name: "World Postcard",
version: "1.0.0",
subtitle: "Turn this moment into a postcard",
icon: "solar:postcard-bold",
accent: "#f5d90a",
entry: "/phone-apps/world-postcard/index.html",
allowedOrigins: ["self"],
permissions: [
"account.basic",
"media.pick",
"camera.capture",
"presence.world",
"storage.app",
"payments",
"notifications.push",
"social.discovery" // to post to Helixgram
],
contentRating: "Everyone",
iap: [
{ sku: "gold_frame", name: "Gold Frame", priceLix: 200, type: "non_consumable" }
]
}2. Boot the SDK and restore a draft
import { createHelixPhoneSdk } from "@helix/phone-sdk";
const phone = createHelixPhoneSdk({ appId: "acme.postcard" });
type Draft = { assetId?: string; caption: string; frame: "plain" | "gold" };
let draft: Draft = (await phone.storage.get<Draft>("draft")) ?? {
caption: "",
frame: "plain",
};
const me = await phone.account.getCurrentUser();
phone.ui.toast(`Hi ${me?.displayName ?? "traveler"} — make a postcard!`);
// Persist the draft whenever the app is backgrounded.
phone.lifecycle.on("suspend", () => phone.storage.set("draft", draft));3. Get the photo — pick or capture
Let the player choose an existing album photo, or capture a new one from the world.
async function choosePhoto() {
const ok = await phone.permissions.request(["media.pick", "camera.capture"]);
if (!ok.includes("media.pick")) {
phone.ui.toast("Photo access is needed to make a postcard.");
return;
}
const useCamera = await phone.ui.confirm({
title: "New photo?",
body: "Capture from this world, or pick from your album.",
confirmLabel: "Capture",
cancelLabel: "Album",
});
const asset = useCamera
? await phone.camera.mockCapture({ caption: "Postcard" }) // real capture proposed
: await phone.media.pickFromAlbum();
if (asset) {
draft.assetId = asset.id;
render(asset);
}
}4. Stamp it with the current world
Postcards say where you are. Read presence and use the world name as the location.
async function currentLocation(): Promise<string> {
const w = await phone.presence.getCurrentWorld();
return w?.worldName ?? "Somewhere in Helix";
}5. Sell a premium frame (IAP)
A "Gold Frame" is a one-time, non-consumable unlock. Check ownership, then offer it.
async function applyGoldFrame() {
const owned = await phone.payments.getEntitlements();
let hasGold = owned.some((e) => e.sku === "gold_frame");
if (!hasGold) {
const buy = await phone.ui.confirm({
title: "Gold Frame — 200 LIX",
body: "Unlock a premium gold frame for your postcards.",
confirmLabel: "Buy",
});
if (!buy) return;
const ent = await phone.payments.purchase("gold_frame", {
idempotencyKey: `gold_frame:${me?.userId}`,
});
hasGold = Boolean(ent);
}
if (hasGold) {
draft.frame = "gold";
render();
}
}6. Post, notify, share
Compose the postcard, post it to Helixgram, push a notification, bump the app badge, and offer the share sheet.
async function postPostcard() {
if (!draft.assetId) return phone.ui.toast("Pick a photo first.");
const asset = (await phone.media.listAlbum()).find((a) => a.id === draft.assetId);
if (!asset) return;
const location = await currentLocation();
// 1) Post to the Helixgram feed (needs social.discovery + media.pick)
const post = await phone.helixgram.createPost({
asset,
caption: draft.caption || "Wish you were here ✨",
location,
});
// 2) Notify the player it's live (deep-links back into this app)
await phone.notifications.push({
title: "Postcard posted",
body: `Your ${location} postcard is live on Helixgram.`,
deepLink: "acme.postcard/posted",
});
// 3) Badge the home-screen icon
await phone.ui.setBadge(1);
// 4) Offer to share it out
await phone.ui.shareSheet({
items: [{
title: "My World Postcard",
text: `A postcard from ${location}`,
url: post?.id ? `https://helixgame.com/g/${post.id}` : undefined,
}],
});
// 5) Clear the draft
draft = { caption: "", frame: "plain" };
await phone.storage.set("draft", draft);
}7. Handle the deep link
When the player taps the notification, the app re-opens with a launch context — route to the "posted" screen and clear the badge.
const ctx = await phone.runtime.launchContext();
if (ctx?.source === "notification" && ctx.path.endsWith("/posted")) {
showPostedScreen();
await phone.ui.setBadge(0);
await phone.notifications.markAppRead();
}What this demonstrates
| Step | SDK surface | Scope |
|---|---|---|
| Greet + toast | account, ui.toast | account.basic |
| Pick / capture | media.pickFromAlbum, camera.mockCapture, ui.confirm | media.pick, camera.capture |
| Location stamp | presence.getCurrentWorld | presence.world |
| Draft persistence | storage, lifecycle | storage.app |
| Premium frame | payments | payments |
| Publish | helixgram.createPost | social.discovery (+ media.pick) |
| Notify + badge | notifications.push, ui.setBadge | notifications.push |
| Share | ui.shareSheet | — |
| Deep link | runtime.launchContext | — |
Run it
Drop the bundle under public/phone-apps/world-postcard/ and register the manifest (see
the App Store). In a sandboxed bundle you don't pass a token — the SDK uses
the bridge automatically, so the exact code above runs unchanged.
The bundled studio.example app is a minimal reference that exercises every bridge method.
Next
In-app purchases
Sell consumables, unlocks, and subscriptions inside a phone app — priced in LIX, idempotent, and declared in your manifest.
Proposed & in-progress
Capabilities that are mocked, partial, or not yet built — plus proposed new SDK surfaces to round out the Helix Phone. Everything here is clearly labeled and not yet guaranteed.