Skip to main content

Web UI and verified signing

Your app’s UI is a web page — Telegram-mini-app style. The Gora mobile wallet (iOS and Android) loads it in a web view, injects window.Gora, and turns your sign requests into native approval pop-ups. When the user approves, their wallet key signs the request and the Gora network verifies the signature cryptographically before anything executes. A forged or tampered approval is rejected with HTTP 400 and never stored.

Declare the UI

In gora.app.json:
gora deploy reads html_path from your machine and embeds the file in the manifest as ui.html (up to 1 MiB), so nothing on your laptop is needed after deploy; the bridge serves it at GET /apps/{app_id}/ui. For a larger or separately hosted page use ui.url instead. Either way, the page is pure HTML/CSS/JS — no keys, no secrets, no signing logic. The wallet owns all of that.

The window.Gora API

Every method returns a Promise. Outside the Gora app, window.Gora is undefined — feature-detect and degrade gracefully.

Events

Outcomes are delivered as DOM events on window (also mirrored as gora:event with {type, payload}): gora:actionComplete — the user approved, the signature verified, and the action executed:
gora:actionCancelled — the user declined in the pop-up. Nothing was signed or submitted. Always handle this; a UI that ignores declines looks broken:
gora:actionFailed — the approval may have been signed and verified, but the follow-on execution failed (no deployed contract, chain error). The detail includes message and, when available, the verified approval. gora:ready — fired when the bridge is installed; useful if your page races the injection.

What the user actually signs

On approve, the wallet builds a canonical sorted-key JSON document and signs those exact bytes with the per-chain key:
The wallet registers it (POST /mobile/signing-requests), then posts the signature (POST /mobile/signing-requests/{id}/signature). The bridge re-verifies before storing:
  • Base / EVM — EIP-191 personal_sign; the recovered address must equal signer_address.
  • Solana — ed25519 over the payload; signer_address is the base58 public key; signature base58.
  • Algorand — ed25519 over the payload; signer_address is the standard address (checksum validated); signature base64.
Anyone can audit the result (on the live testnet, BRIDGE is https://gora-bridge.74.241.248.103.nip.io — see Connect to the testnet):
A signature that doesn’t verify — wrong key, wrong signer, edited payload — never reaches signed status. There is no structurally-trusted path.

Declared actions drive the pop-up

The pop-up’s content and the post-approval execution come from your manifest’s interface.actions. A free registration on Algorand:
An invoke_then_action (run the app, then settle on chain with the output):
The settle template maps app output into transaction arguments with $. paths ("$.round", "$.winner_1.address"). Use named output fields — the path resolver does not support array subscripts like $.winners[0].

Checklist for a good app page

  • Feature-detect window.Gora; show a “open this in the Gora app” message otherwise.
  • Handle all three outcome events; re-enable your buttons in each.
  • Show the verification result — users trust a visible ”✓ signature verified by the Gora network” with the signer address.
  • Poll your views (10–15 s) instead of assuming actions changed state.
  • Never put keys, mnemonics, or RPC tokens in the page. It’s a UI surface.
The GORA Raffle is a complete working example — gora-raffle (examples distribution)/ui/index.html is ~250 lines and exercises every event above. Operational details (hosting, systemd, demo script): docs/web-ui-signing-runbook.md in the repo.