import WalletConnect from "@perawallet/walletconnect"; import { type TransactionSigner } from "algosdk"; import { PeraWalletArbitraryData, PeraWalletArc60SignData, PeraWalletArc60SignDataResponse, PeraWalletArc60SignerResolution, PeraWalletNetwork, SignerTransaction, SignMetadata } from "./util/model/peraWalletModels"; import { AlgorandChainIDs } from "./util/peraWalletTypes"; import { AlgodClients } from "./util/algod/algodTypes"; import { PeraNetwork } from "./transport/extension/peraProviderTypes"; import { ConnectOptions } from "./transport/WalletTransport"; interface PeraWalletConnectOptions { bridge?: string; shouldShowSignTxnToast?: boolean; chainId?: AlgorandChainIDs; compactMode?: boolean; singleAccount?: boolean; /** * When the Pera browser extension is installed (`window.pera` is present), * the connect modal offers "Connect with Pera Extension" and pre-selects it. * Set to `false` to leave the extension out of the modal and only offer the * QR / Pera Web options. Defaults to `true`. */ shouldPreferExtension?: boolean; /** * Opts in to experimental features. Nothing is gated behind it at the * moment: browser-extension support used to be, and is now on by default * whenever `window.pera` is present. Kept as the opt-in for whatever is * experimental next. */ experimental?: boolean; /** * Algod clients connect reads account state with — the auth-address lookups * behind `resolveArc60Signer` and `signData` verification. Supply your own to * keep those reads on your infrastructure and off Pera's rate limits; a * network left out keeps Pera's node. * * ```ts * new PeraWalletConnect({ * algod: { * mainnet: new algosdk.Algodv2(MY_TOKEN, "https://my-node.example.com", "") * } * }) * ``` */ algod?: AlgodClients; } type PeraWalletConnectEventMap = { /** The session ended from the wallet side (for example the user revoked the site). */ disconnect: undefined; /** The wallet switched network (extension only). */ networkChanged: { network: PeraNetwork; }; }; type PeraWalletConnectEvent = keyof PeraWalletConnectEventMap; type PeraWalletConnectEventHandler = (payload: PeraWalletConnectEventMap[E]) => void; declare class PeraWalletConnect { bridge: string; connector: WalletConnect | null; shouldShowSignTxnToast: boolean; isInWebview: boolean; chainId?: AlgorandChainIDs; compactMode?: boolean; singleAccount?: boolean; shouldPreferExtension: boolean; experimental: boolean; private extensionTransport; /** Connectors the SDK has retired; see `abandonConnector()`. */ private abandonedConnectors; private eventHandlers; private algodClients; private suppliedAlgodClients?; private _configPromise; private _webviewCheckPromise; private _transactionSigner; constructor(options?: PeraWalletConnectOptions); get platform(): import("./util/peraWalletTypes").PeraWalletPlatformType; /** * Whether the Pera browser extension's provider (`window.pera`) is present * on this page. The detection itself is synchronous (the provider is * installed before any page script runs, so there is nothing to poll for or * wait on); the promise is kept for backwards compatibility. */ isExtensionAvailable(): Promise; /** * Subscribe to session events. Returns the unsubscribe function. * * `disconnect` fires when the wallet ends the session on its side: the user * revoked the site from the extension's Connections screen, or the Pera * mobile wallet killed the WalletConnect session. `networkChanged` fires * when the extension wallet switches network. */ on(event: E, handler: PeraWalletConnectEventHandler): () => void; private emit; private createExtensionTransport; /** * The WalletConnect connector reports a wallet-side session end through its * own `disconnect` event; mirror it on the SDK so dApps can listen in one * place regardless of transport. * * The event is also how WalletConnect v1 reports *our* `killSession()`, so * connectors we have abandoned are filtered out — a dApp must not be told * the wallet ended a session that the SDK itself is tearing down. */ private forwardConnectorDisconnect; /** * Retires a WalletConnect connector the SDK is done with. WalletConnect v1 * keeps its listeners for the connector's lifetime and its `off()` drops * every listener for an event, so the connector is marked instead and its * handlers bail. * * Without this a QR pairing left in flight by a connect that finished * through another transport still fires `connect` when the user finally * scans, overwriting the stored session with a mobile one. */ private abandonConnector; get isConnected(): boolean; get isPeraDiscoverBrowser(): boolean; private checkIsInWebview; connect(options?: ConnectOptions): Promise; reconnectSession(): Promise; disconnect(): Promise; /** * Releases everything this instance holds: the `window.pera` subscriptions * (the provider outlives the page's components, so an undisposed instance is * retained for the page's life) and any WalletConnect connector. The wallet * session itself is untouched — call `disconnect()` for that. * * Call it when the component owning the instance unmounts. Under React * StrictMode or hot reload, skipping it leaves the discarded instance * listening and swallowing the events the live one should handle. */ dispose(): void; verifySignature(data: Uint8Array, signature: Uint8Array, signerAddress: string): boolean; verifyArc60Signature(data: Uint8Array, authenticatorData: Uint8Array, signature: Uint8Array, signerAddress: string): Promise; private checkIsPeraDiscoverBrowser; private getAlgodClient; private getAccountAuthAddr; private getTransport; signTransaction(txGroups: SignerTransaction[][], signerAddress?: string): Promise; /** * An algosdk `TransactionSigner` backed by this wallet connection, for use * with `AtomicTransactionComposer` and any API that accepts a signer: * * atc.addTransaction({txn, signer: peraWallet.transactionSigner}); * * The whole group is sent to the wallet in one request; slots not listed in * `indexesToSign` are marked `signers: []` (ARC-0001) so the wallet shows * but does not sign them. The same function instance is returned on every * access: `AtomicTransactionComposer` batches transactions by signer * identity, so a fresh function per access would mean one wallet prompt per * transaction instead of one per group. */ get transactionSigner(): TransactionSigner; private signTransactionGroupForIndexes; signData(data: PeraWalletArbitraryData[], signer: string, verifySignature?: boolean): Promise; /** * Resolves who has to sign an ARC-60 (SIWA) request for `accountAddress`. * ed25519 only: a Falcon account's key isn't in its address, so its `signer` * comes from its empty signature (`pqsig.pk`) instead. * * An ARC-60 signature verifies against `signer`'s own key and the wallet * never substitutes another key, so a rekeyed account must be signed for by * its on-chain auth address: put the returned `signer` into * `PeraWalletArc60SignData` and keep `accountAddress` as the SIWA * `account_address`. The wallet refuses a rekeyed account signing for itself, * and it can only sign when it holds the auth address as a key-bearing or * Ledger account. * * Rekeys are per network and the wallet checks them on the network it is * connected to, which connect cannot observe. A session pinned to `416001` * or `416002` is only served while the wallet is on that network, so the * lookup uses it and an explicit `network` may only agree with it * (`SIGN_DATA_NETWORK_MISMATCH` otherwise). An all-networks session (`4160`, * the default) needs `network` (`SIGN_DATA_NETWORK_REQUIRED`) and the caller * has to know the wallet's network by other means. Betanet has no algod here * (`SIGN_DATA_NETWORK_UNSUPPORTED`). A failed lookup throws * `SIGN_DATA_AUTH_ADDR_LOOKUP_FAILED` rather than assuming "not rekeyed". */ resolveArc60Signer(accountAddress: string, network?: PeraWalletNetwork): Promise; /** * Each connected account's empty signature, for use-wallet's * `WalletAccount.emptySignature` (TxnLab/use-wallet#465): base64 of a * `SignedTransaction` without `txn`, which tells the account type and feeds * fee simulation. Accounts missing from the result are unknown. * * Pera Mobile answers over WalletConnect (`algo_getEmptySignatures`) and the * Pera extension through `window.pera`, both without prompting the user. * Pera Web, and extensions that predate the method, resolve `{}`. Call it * right after a fresh `connect()`; a value must come from the wallet just * now, never be replayed after `reconnectSession()`. * * `network` defaults to the one the `chainId` option pins. On Pera Mobile an * all-networks session (`4160`, the default) needs it * (`EMPTY_SIGNATURES_NETWORK_REQUIRED`); the extension answers for the * network it is on and refuses another (`EMPTY_SIGNATURES_NETWORK_MISMATCH`). * Rejects with `SESSION_DISCONNECTED`, `EMPTY_SIGNATURES_TIMEOUT` after 30 s * (older Pera apps, or iOS suspending Pera), or `EMPTY_SIGNATURES` * (`detail.reason`: `"wallet-error"`, `"session-changed"`, `"invalid-result"`). */ getEmptySignatures(network?: PeraNetwork): Promise>; /** * Sign an ARC-60 payload (e.g. an auth request). * * Sends `algo_signData` with a single object as `params` so the Pera mobile * wallet routes it to the ARC-60 handler instead of the legacy * arbitrary-data handler. The signature returned is * `sign(sha256(data) || sha256(authenticatorData))` per ARC-60, not a raw * signature over `data`, with the key and scheme of `payload`: ed25519 when * `payload.scheme` is omitted. `verifySignature` handles ed25519 only, and * throws `SIGN_DATA_VERIFICATION_UNSUPPORTED` for any other scheme before * the wallet is asked. * * Resolves with the full ARC-60 `SignDataResponse` shape (signed payload, * signer public key, domain, authenticatorData and signature) so responses * are interchangeable with use-wallet / lute-connect. * * Mirrors ARC-60's `signData(signingData, metadata)` signature: `payload` * is the spec's `StdSigData` and `metadata` (scope + encoding) is passed * separately; the two are unified into one object on the wire. * * `payload.domain` MUST match the dApp's page origin (SIWA origin binding) * on every transport. Connect pre-validates this and throws * `SIGN_DATA_DOMAIN_MISMATCH` before contacting the wallet; the extension * additionally enforces the same rule independently. */ /** * SIWA origin binding: reject early on every transport when the requested * domain does not match the page origin. This is a client-side guard for * honest integrations — the extension enforces the rule independently. */ private assertArc60DomainMatchesOrigin; signArc60Data(payload: PeraWalletArc60SignData, metadata: SignMetadata, verifySignature?: boolean): Promise; } export default PeraWalletConnect;