> ## Documentation Index
> Fetch the complete documentation index at: https://gora.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Web UI and verified signing

> Ship a hosted web UI for your app, raise sign requests that pop up in the Gora wallet, and get back cryptographically verified approvals.

# 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.

```text theme={null}
your web page ── window.Gora.action(...) ──▶ native pop-up
                                               │ Decline ──▶ gora:actionCancelled
                                               │ Approve
                                               ▼
                              wallet key signs the approval document
                                               ▼
                       bridge verifies signature against signer address
                       (EVM personal_sign recovery · ed25519 Solana/Algorand)
                                               ▼
                  action executes ──▶ gora:actionComplete { approval: {…} }
```

## Declare the UI

In `gora.app.json`:

```json theme={null}
{
  "ui": {
    "type": "html",
    "html_path": "ui/index.html",
    "bridge": "gora_webview_v1"
  }
}
```

`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.

| Method                           | What it does                             | Resolves with                                                                                |
| -------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------- |
| `Gora.context()`                 | Wallet context                           | `{ bridge_url, chain, available_chains, wallets: {base, solana, algorand}, owner_wallets? }` |
| `Gora.view(viewId)`              | Evaluate a declared view                 | the view response (`value`, plus kind-specific fields like `participants_by_chain`)          |
| `Gora.invoke(input)`             | Run your app on the Gora node            | the full invoke record                                                                       |
| `Gora.action(actionId, {chain})` | Raise a sign request → **native pop-up** | `{ presented: true }` immediately; outcome arrives as events                                 |
| `Gora.setChain(chain)`           | Switch the active chain                  | updated context                                                                              |

```js theme={null}
const ctx = await window.Gora.context();
const participants = await window.Gora.view("participants");
await window.Gora.action("register", { chain: "algorand" });
```

### 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:

```js theme={null}
window.addEventListener("gora:actionComplete", (e) => {
  const d = e.detail;
  // d.action_id, d.chain
  // d.approval = {
  //   signing_request_id,        // audit it on the bridge
  //   signer_address,            // the wallet that approved
  //   signature,
  //   payload_base64,            // the exact signed bytes
  //   verified_by_bridge: true   // the network checked the signature
  // }
  // d.tx_hash (chain actions) or d.output (invoke_then_action results)
});
```

**`gora:actionCancelled`** — the user declined in the pop-up. Nothing was
signed or submitted. Always handle this; a UI that ignores declines looks
broken:

```js theme={null}
window.addEventListener("gora:actionCancelled", (e) => {
  showBanner(`You declined the ${e.detail.action_id} request. Nothing was signed.`);
});
```

**`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:

```json theme={null}
{
  "action_id": "register",
  "app_id": "gora_raffle",
  "chain": "algorand",
  "human_summary": "Enter the raffle (free)",
  "kind": "gora_app_action_approval_v1",
  "requested_at_unix_ms": 1781193323000,
  "schema_version": 1,
  "signer_address": "72AHB…UKDE",
  "site": "https://gora-bridge.74.241.248.103.nip.io"
}
```

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](../launch/connect-to-testnet)):

```bash theme={null}
curl $BRIDGE/mobile/signing-requests/{id}/status
# → { "status": "signed",
#     "signed_transaction": { "signature_verified": true,
#                             "verified_signer_address": "72AHB…" } }
```

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:

```json theme={null}
{
  "id": "register",
  "kind": "chain_transaction",
  "label": "Enter the raffle (free)",
  "description": "Opt into the raffle app on Algorand. No GORA required.",
  "network_fee": { "enabled": false },
  "transactions": {
    "algorand": { "type": "algorand_app_call", "contract": "algorand", "on_complete": "opt_in" }
  }
}
```

An `invoke_then_action` (run the app, then settle on chain with the output):

```json theme={null}
{
  "id": "run_round",
  "kind": "invoke_then_action",
  "input": { "round_view": "next_round", "participants_view": "participants" },
  "settle_action": "settle_winners"
}
```

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](../examples/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.
