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, injectswindow.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
Ingora.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 onwindow (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: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 equalsigner_address. - Solana — ed25519 over the payload;
signer_addressis the base58 public key; signature base58. - Algorand — ed25519 over the payload;
signer_addressis the standard address (checksum validated); signature base64.
BRIDGE is
https://gora-bridge.74.241.248.103.nip.io — see
Connect to the testnet):
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’sinterface.actions. A free registration on Algorand:
invoke_then_action (run the app, then settle on chain with the output):
$. 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.
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.