Connecting a Wallet
superhero.com never holds your keys. To act on your behalf it finds a wallet, opens an RPC session with it, and sends every transaction there for you to approve.
The three connection paths
| Path | Transport | When it is used |
|---|---|---|
| Browser extension | In-page message connection, discovered by the SDK's wallet detector | Desktop browsers with Superhero Wallet installed |
| Wallet app or web wallet | Deep link with a callback URL | Mobile, or desktop when no extension answers |
| Built-in wallet | No transport; the signer runs in the page | Installed PWA with an on-device vault |
The first two are external custody: the wallet holds the key and Superhero only ever receives a signed result. The third is documented in The Built-In Wallet.
None of them is WalletConnect. The æternity connection path is the AEX-2 postMessage RPC from
@aeternity/aepp-sdk, with no project id, pairing URI or relay. If you are writing a wallet to
connect to superhero.com, implement the AEX-2 side.
The extension handshake
Pressing Connect runs this sequence:
- Any previous session is torn down: persisted wallet info, the active account and the account list are cleared, and the SDK is disconnected.
- The app opens a browser message connection and starts the SDK's wallet detector, which listens for a wallet announcing itself on the page.
- On detection, the app connects to the wallet over that connection and stores the returned wallet info (name, network id, and the rest of the wallet's self-description).
- The app subscribes to the connected address and scans for accounts.
- If at least one account comes back, the session is marked connected and that address becomes active.
Detection is time-boxed, and the window depends on the surface:
| Surface | Window |
|---|---|
| Desktop browser, user pressed Connect | 4 seconds |
| Mobile or Safari, not framed in a wallet | 100 ms |
| Silent reconnect after a page reload | 15 seconds |
The mobile window is short because extensions do not exist there, so the app fails fast to the deep-link path. The reconnect window is long because the extension's content script can be injected several seconds after the app has booted; the extension re-announces itself to every connected page every three seconds, so it only has to be there within the window, not at boot.
The deep-link path
When no wallet answers the scan and the page is not itself framed inside a wallet, the app opens a deep link instead:
superhero://<action>?… ← tried first on mobile
https://wallet.superhero.com/<action>?… ← desktop, and the mobile fallback after 1.5sOn mobile the app tries the custom scheme and starts a 1.5-second timer; if the page loses focus (meaning the native app took over), the timer is cancelled. If it fires, the hosted web wallet opens instead. On desktop the web wallet URL is opened directly in a new window.
For the connect action, the app passes x-success and x-cancel callbacks pointing back at the
current page, with address and networkId placeholders the wallet fills in. On return, the app
validates the address and installs it as a static account, a read-only account that lets the
app show balances and portfolio data.
A static account is not a signing session. Signing still routes through the wallet, and address-change events are not delivered, because those only exist over a live RPC connection.
The transaction queue at /tx-queue/:id
Deep-link signing needs a place for the wallet to come back to. /tx-queue/:id is a landing route
whose only job is to accept the wallet's result and hand it to whichever tab asked for it.
| Parameter | Meaning |
|---|---|
transaction, signedTransaction, signed_tx or tx | The signed transaction, accepted only if it starts with tx_ |
signature | A message signature |
status | completed marks the queue entry done; completed with neither result present is recorded as cancelled |
returnUrl | Where to send the user afterwards. Same-origin only; anything else falls back to / |
The route writes the result into the shared transaction queue, then hands off over a broadcast channel to the originating tab. If that tab acknowledges within 1.5 seconds, this window closes (or navigates to the return path). If nothing acknowledges (Android Chrome sometimes opens the callback in a temporary tab), the queue page completes message-signing requests itself and refreshes the account and profile caches.
Message-signing requests are the ones that travel this way: profile updates, .chain name claims,
and the identity-link proofs for X, bio, website and preferred AENS name. Each is verified against
the claimed address before submission; a signature that does not verify is rejected.
What the connection grants
Connecting grants the app the ability to ask. It does not grant spending authority:
- The app can read your address, your account list, and your balances.
- The app can request a signature. The wallet decides what to show you and whether to sign.
- Every value-moving action is a separate request you approve separately.
There is no blanket allowance and no session token. With the built-in wallet, user verification runs on every signature, with no cached unlock.
The same handshake, from Superhero Wallet's side
Connecting is not the step that prompts you. The wallet records the æpp's self-description against its client id and returns. The dialog you see is triggered one step later, when the æpp asks to subscribe to an address.
What the wallet sends back about itself:
| Field | Value |
|---|---|
id | Superhero Wallet |
name | Superhero |
networkId | ae_mainnet or ae_uat, whichever network the wallet is on |
origin | the wallet's own origin |
type | extension in the extension, window in the web build and the mobile apps |
The connect popup lists the four permissions an æpp can ask for:
| Permission | What the popup says |
|---|---|
address | "View your address": your current account address |
addressList | "View your address list": every account address |
networks | "Use available networks": Mainnet and Testnet |
transactions | "Propose transactions. You are in charge of confirming them." |
A connection lets an æpp propose. Nothing in the set grants spending.
The permission record, and what "Remember me" remembers
Approving the popup writes one record per host (test.com, not a full URL) into the wallet's
storage. Every field starts false or 0:
| Field | Meaning | Set by the connect popup? |
|---|---|---|
address | Skip the connect prompt next time | Only if you tick "Remember me" |
addressList | Answer "list all my accounts" without asking | No |
messageSign | Auto-sign messages | No |
dailySpendLimit / transactionSignLimit | Sign transactions up to a daily AE ceiling without asking | No |
transactionSignSpent / transactionSignFirstAskedOn | Running total and the 24-hour window it resets in | Bookkeeping |
The checkbox grants exactly one standing permission, a silent reconnect, and leaves everything else prompting. The other switches live in More → Settings → Permissions, where you can edit a host's record or add one by hand; the wallet's own copy there reads "Use this feature with caution and only for platforms you trust."
The standing record maps onto RPC methods one-for-one: connect and subscribeAddress read
address; address (list accounts) reads addressList; signMessage reads messageSign; and
sign runs the daily-limit check. Anything else (signTypedData, signDelegation,
unsafeSign) has no standing-permission path and always opens a confirmation.
Two more guards:
- A standing spend limit never applies off æternity. An EVM or Solana transaction always prompts.
- A transaction the wallet cannot decode gets a louder dialog. If the payload is not a transaction type the wallet knows how to render, the modal switches to the raw-sign warning screen rather than a friendly summary.
What an æpp cannot ask for
An æpp cannot make Superhero Wallet switch networks. A network mismatch is only fixable by you, in the wallet.
Switching accounts
With a live RPC session, the wallet pushes address changes to the app and the active account follows your wallet's selection. Without the RPC connection, as in a restored read-only session, switching accounts in the extension goes unnoticed by the app; reconnect to fix it.
Per-account caches (balance, AEX-9 balances, recent DEX activity, liquidity positions) are keyed by address and pruned to the addresses in the current session, so switching accounts never shows you another account's numbers.
Network mismatch
Superhero builds are pinned to one network at build time through VITE_NETWORK, and the app
offers exactly that network. There is no in-app switcher; change the network in the wallet. See
Using Testnet.
Disconnecting
Disconnect does all of the following, in order, so that a refresh does not silently reconnect:
- Removes the persisted session keys from local storage:
account:activeAccount,wallet:walletInfo,wallet:balance,dex:recentActivitiesandliquidityPositions. - Stops any in-flight wallet detection and closes the message connection.
- Drops the cached wallet reference.
- Clears wallet info, the connected flag, the active account, the account list and every per-account cache.
- Disconnects the SDK.
Disconnecting is a local action. It does not revoke anything on chain, and it does not remove a built-in wallet's vault from the device. That is a separate, deliberate erase.
Related
- Choosing a Wallet: which of these paths you should be on.
- The Built-In Wallet: the case where there is no transport at all.
- Troubleshooting: detection windows, stale sessions and read-only reconnects, as symptoms.
- Navigating the App: which parts of superhero.com need a connected account.
- Web Wallet: the two transports a hosted wallet has, and the deep-link routes.
- Connect an æpp to a wallet (opens in a new tab): the SDK guide for the RPC protocol underneath all of this.