HELIX 3 Docs
Helix Phone

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

app.ts
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);
}

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

StepSDK surfaceScope
Greet + toastaccount, ui.toastaccount.basic
Pick / capturemedia.pickFromAlbum, camera.mockCapture, ui.confirmmedia.pick, camera.capture
Location stamppresence.getCurrentWorldpresence.world
Draft persistencestorage, lifecyclestorage.app
Premium framepaymentspayments
Publishhelixgram.createPostsocial.discovery (+ media.pick)
Notify + badgenotifications.push, ui.setBadgenotifications.push
Shareui.shareSheet
Deep linkruntime.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

On this page