Wallets
Connecting a Wallet

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

PathTransportWhen it is used
Browser extensionIn-page message connection, discovered by the SDK's wallet detectorDesktop browsers with Superhero Wallet installed
Wallet app or web walletDeep link with a callback URLMobile, or desktop when no extension answers
Built-in walletNo transport; the signer runs in the pageInstalled 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:

  1. Any previous session is torn down: persisted wallet info, the active account and the account list are cleared, and the SDK is disconnected.
  2. The app opens a browser message connection and starts the SDK's wallet detector, which listens for a wallet announcing itself on the page.
  3. 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).
  4. The app subscribes to the connected address and scans for accounts.
  5. 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:

SurfaceWindow
Desktop browser, user pressed Connect4 seconds
Mobile or Safari, not framed in a wallet100 ms
Silent reconnect after a page reload15 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.5s

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

ParameterMeaning
transaction, signedTransaction, signed_tx or txThe signed transaction, accepted only if it starts with tx_
signatureA message signature
statuscompleted marks the queue entry done; completed with neither result present is recorded as cancelled
returnUrlWhere 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:

FieldValue
idSuperhero Wallet
nameSuperhero
networkIdae_mainnet or ae_uat, whichever network the wallet is on
originthe wallet's own origin
typeextension in the extension, window in the web build and the mobile apps

The connect popup lists the four permissions an æpp can ask for:

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

FieldMeaningSet by the connect popup?
addressSkip the connect prompt next timeOnly if you tick "Remember me"
addressListAnswer "list all my accounts" without askingNo
messageSignAuto-sign messagesNo
dailySpendLimit / transactionSignLimitSign transactions up to a daily AE ceiling without askingNo
transactionSignSpent / transactionSignFirstAskedOnRunning total and the 24-hour window it resets inBookkeeping

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:

  1. Removes the persisted session keys from local storage: account:activeAccount, wallet:walletInfo, wallet:balance, dex:recentActivities and liquidityPositions.
  2. Stops any in-flight wallet detection and closes the message connection.
  3. Drops the cached wallet reference.
  4. Clears wallet info, the connected flag, the active account, the account list and every per-account cache.
  5. 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