Raffle app
The raffle app is the reference build for a Gora app with both off-chain and on-chain components. The off-chain app picks one winner from registered participants across Base, Solana, and Algorand. The winner is paid on the same chain they registered on. If the winner is on Algorand, the Algorand app treasury pays ALGO. If the winner is on Solana, the Solana treasury pays SOL. If the winner is on Base, the Base contract pays ETH.What you are building
The reference app lives inexamples/apps/raffle-app.
| Part | File or folder | Purpose |
|---|---|---|
| Off-chain app | src/index.ts | Deterministically chooses the winner from round number and participant registry. |
| Manifest | gora.app.json | Declares app id, chains, actions, contracts, and iOS display metadata. |
| HTML UI | ui/index.html | Renders the mobile app screen in a WebView and calls window.Gora. |
| App interface | gora.app.json -> interface | Declares generic views and actions so the bridge/mobile app do not hardcode raffle logic. |
| Policy | policy.json | Restricts allowed chains and actions. |
| Base contract | contracts/base/GoraBaseRaffle.sol | Stores Base participants and pays ETH from the contract treasury. |
| Solana program | contracts/solana/program | Stores Solana state and pays SOL from the treasury account. |
| Algorand app | contracts/algorand | Uses app opt-in as registration and pays ALGO from the app account. |
| Demo CLI | scripts/raffle.mjs | Deploys, registers, funds, draws, and settles for local demos. |
HTML UI
The mobile wallet does not render raffle-specific Swift UI. It loads the app’s HTML from:App interface
The Raffle app declares its app-specific surface ingora.app.json under interface.
The important views are:
| View | What it returns | How it is evaluated |
|---|---|---|
participants | participants grouped by chain | Base uses participantCount() + participants(index). Algorand uses app opt-ins from the dev wallet registry. |
next_round | the next round number | Base, Solana, and Algorand expose per-chain round counters; the bridge reduces them with max. |
| Action | Role | What it declares |
|---|---|---|
register | participant | EVM register(), Solana register instruction, Algorand app opt-in. |
run_round | operator | Build off-chain input from participants + next_round, invoke Gora, then settle. |
settle_winner | operator | Map app output fields into the winner-chain settlement transaction. |
on_complete, app args, foreign accounts, and the minimum fee needed for inner transactions.
Prerequisites
You need:- a checkout of
gora-v2 - Node and npm
- Rust/Cargo for the Gora CLI
- the VM Devnet running Base, Solana, Algorand, Gora API, Dev Bridge, and ngrok
- the iOS app only if you want to test the mobile flow after the CLI flow works
raffle-app example. To point the same flow at a different example later, set GORA_DEV_EXAMPLE_APP=<folder-name> or GORA_DEV_EXAMPLE_APP_DIR=/absolute/path/to/app.
Expected public endpoints:
Create the app workspace
For the reference demo, copy or open the existing example:setup:wallets writes:
wallets.json contains public dev addresses. secrets.json contains dev-only private keys and mnemonics. Do not use these outside local/dev demos.
For a remote VM demo, the VM only needs wallets.json to pre-fund generated Solana and Algorand accounts. Your local machine needs secrets.json if you want to import those wallets into the iOS simulator.
Deploy the chain components
The raffle has one chain component per chain. Each deploy writes a generic receipt to:Base
Algorand
Solana
Build and deploy the Solana program where the Solana toolchain is installed:Deploy the off-chain Gora app
Point the CLI at Devnet and deploy:Register participants
Register one or more participants per chain:register().
Solana registration sends the raffle register instruction.
Algorand registration is an application opt-in. If an Algorand address cannot register twice, that is expected; opt-in is the registration.
Fund treasuries
Each chain has its own treasury. The winner is paid by the treasury on the winner’s chain.app_address value if you want to fund manually from the mobile wallet.
Draw and settle
Run one round:- winner chain
- winner address
- registry hash
- attestation hash
- settlement transaction
Test from iOS
Use the iOS app after the CLI path works.- Open the app and set the bridge URL to
https://gora-bridge-dev.ngrok.app. - Import the operator wallet for the chain you want to operate from.
- Import participant wallets in other simulators.
- Pull to refresh balances.
- Open Apps.
- Open Raffle.
- As participants, register on Base, Solana, or Algorand.
- As operator, run the next round.
owner_wallets in the app manifest.
Troubleshooting
Apps fail with cannot reach gora node
The bridge is running but points at a stale Gora API port.
Restart Devnet or kill the old bridge process on port 8787, then start Devnet again:
Algorand balance does not update
Pull to refresh in the iOS wallet. The app should query the bridge dynamically. If the balance is still stale, check the bridge Algorand RPC URL and token:Algorand settle says app does not exist
The mobile app or bridge is using an old Algorand app id. Refresh Apps and verify:Algorand settle says would result negative
The Algorand app account does not have enough ALGO to pay the 5% payout and fees. Fund the app treasury:
Algorand settle says unavailable Account
The winner address must be included in the Algorand transaction’s foreign account list. This is handled by the current iOS and raffle script code. Rebuild the iOS app if you see this from mobile.
Second round fails with assert failed
The round submitted to the chain is stale. Rebuild the iOS app and refresh Apps so it queries the live next round from the bridge before invoking the off-chain app.
Solana airdrops hang
The Devnet script funds Solana accounts through JSON-RPC. If funding is slow, restart the Solana local validator and rerun Devnet. The funding log is:Demo checklist
Before livestreaming:- Start Devnet cleanly.
- Confirm bridge health.
- Deploy Base and Algorand components.
- Deploy Solana only if the VM toolchain is ready.
- Fund all treasuries.
- Register at least one participant per chain.
- Run one CLI draw.
- Rebuild iOS.
- Repeat the same flow from mobile.