![Pera Connect Cover Image](https://user-images.githubusercontent.com/54077855/179966121-bd9295c3-5f61-4203-b13f-851434e72d35.png)

## @perawallet/connect

JavaScript SDK for integrating [Pera Wallet](https://perawallet.app) to web applications. For more detailed information, please check our [Pera Connect Docs](https://docs.perawallet.app/references/pera-connect/). Using [use-wallet](https://github.com/TxnLab/use-wallet) to support several wallets? See [Using with use-wallet](#using-with-use-wallet).

[![](https://img.shields.io/npm/v/@perawallet/connect?style=flat-square)](https://www.npmjs.com/package/@perawallet/connect) [![](https://img.shields.io/bundlephobia/min/@perawallet/connect?style=flat-square)](https://www.npmjs.com/package/@perawallet/connect)

## Getting Started

[Learn how to integrate with your JavaScript application](#guide)

[Learn how to Sign Transactions](#sign-transaction)

[Try it out using CodeSandbox](#example-applications)

## Example Applications

<details>
  <summary>Expand details</summary>
  
- [Using React Hooks](https://codesandbox.io/s/perawallet-connect-react-demo-zlvokc)

- [Using React Hooks with React@18](https://codesandbox.io/s/perawallet-connect-react-18-demo-tig2md)

- [Using Vue3](https://codesandbox.io/s/perawallet-connect-vue3-demo-yiyw4b)

- [Using Svelte](https://codesandbox.io/s/perawallet-connect-svelte-demo-ys1m4x)

- [Using Next.js](https://codesandbox.io/s/perawallet-connect-next-js-demo-ryhbdb)

- [Using Nuxt.js](https://codesandbox.io/s/perawallet-connect-nuxt-js-demo-s65z58)

- [Vanilla JS](https://codesandbox.io/s/perawallet-connect-vanillajs-demo-s5pjeo)
</details>

## Quick Start

Let's start with installing `@perawallet/connect`

```sh
# pnpm
pnpm add @perawallet/connect

# npm
npm install @perawallet/connect

# yarn
yarn add @perawallet/connect
```

Subscribe to `disconnect` once, next to the instance rather than inside a connect
callback: `on()` keeps every handler you pass it until you unsubscribe, so
registering a fresh one per connect leaves the old ones running too.

```jsx
// Once, alongside the peraWallet instance — not inside connect()/reconnectSession()
useEffect(() => {
  // Fires for every transport (mobile, extension) when the wallet ends the
  // session on its side. Returns the unsubscribe function.
  const unsubscribe = peraWallet.on("disconnect", handleDisconnectWalletClick);

  // Releases the `window.pera` subscriptions this instance holds; the wallet
  // session itself is untouched.
  return () => {
    unsubscribe();
    peraWallet.dispose();
  };
}, []);
```

```jsx
// Connect handler
peraWallet
  .connect()
  .then((newAccounts) => {
    setAccountAddress(newAccounts[0]);
  })
  .catch((error) => {
    // You MUST handle the reject because once the user closes the modal, peraWallet.connect() promise will be rejected.
    // For the async/await syntax you MUST use try/catch
    if (error?.data?.type !== "CONNECT_MODAL_CLOSED") {
      // log the necessary errors
    }
  });
```

If you don't want the user's account information to be lost by the dApp when the user closes the browser with user’s wallet connected to the dApp, you need to handle the reconnect session status. You can do this in the following way.

```jsx
// On the every page refresh
peraWallet.reconnectSession().then((accounts) => {
  if (accounts.length) {
    setAccountAddress(accounts[0]);
  }
});
```

After that you can sign transaction with this way

```jsx
// Single Transaction
try {
  const signedTxn = await peraWallet.signTransaction([singleTxnGroups]);
} catch (error) {
  console.log("Couldn't sign Opt-in txns", error);
}
```

## Using with use-wallet

If your dApp uses [use-wallet](https://github.com/TxnLab/use-wallet) v5, add Pera with [`@perawallet/use-wallet-pera`](https://github.com/perawallet/use-wallet-pera). Pera maintains that adapter, and it pins the version of this SDK it was tested against.

```sh
pnpm add @perawallet/use-wallet-pera
```

```ts
import {WalletManager} from "@txnlab/use-wallet";
import {pera} from "@perawallet/use-wallet-pera";

const manager = new WalletManager({wallets: [pera()]});
```

`pera()` accepts the same [options](#options) as `new PeraWalletConnect()`. Moving from TxnLab's `@txnlab/use-wallet-pera` only means changing the import: the wallet id is still `"pera"`, so persisted sessions carry over.

[`getEmptySignatures()`](#perawalletconnectgetemptysignaturesnetwork-peranetwork-promiserecordstring-string) is what the adapter will use to report account types for use-wallet 5.1.

## Options

| option                   | default | value                                 |          |
| ------------------------ | ------- | ------------------------------------- | -------- |
| `chainId`                | `4160`  | `416001`, `416002`, `416003` , `4160` | optional |
| `shouldShowSignTxnToast` | `true`  | `boolean`                             | optional |
| `compactMode`            | `false` | `boolean`                             | optional |
| `shouldPreferExtension`  | `true`  | `boolean`                             | optional |
| `algod`                  | public nodes | `{mainnet?, testnet?: algosdk.Algodv2}` | optional |

#### **`chainId`**

Determines which Algorand network your dApp uses.

**MainNet**: 416001

**TestNet**: 416002

**BetaNet**: 416003

**All Networks**: 4160

#### **`shouldShowSignTxnToast`**

<img width="422" alt="Group 48096937" src="https://user-images.githubusercontent.com/54077855/202682828-9ac57b62-58c1-4a83-af3b-e1b7ffad2d89.png">

It's enabled by default but in some cases, you may not need the toast message (e.g. you already have signing guidance for users). To disable it, use the `shouldShowSignTxnToast` option.

#### **`compactMode`**

It offers a compact UI optimized for smaller screens, with a minimum resolution of 400x400 pixels.

#### **`shouldPreferExtension`**

When the Pera browser extension is installed, the connect modal lists "Connect with Pera Extension" first and pre-selects it. Set this to `false` to leave the extension out of the modal and only offer the QR code and Pera Web options. See [Browser extension](#browser-extension-windowpera) below.

#### **`algod`**

Connect reads account state from an Algorand node in two places: `resolveArc60Signer`, and the auth-address lookup behind `signData` verification. By default those reads go to the public [AlgoNode](https://algonode.io) endpoints (`mainnet-api.algonode.cloud`, `testnet-api.algonode.cloud`), which need no API token.

That default is a third party on a shared free tier. **If your dApp reads often, supply your own client** — otherwise a rate limit surfaces as `SIGN_DATA_AUTH_ADDR_LOOKUP_FAILED`. Pass `algosdk.Algodv2` instances to keep these reads on your own infrastructure, with whatever token, headers, or HTTP client your provider needs. A network you leave out keeps the public default.

```javascript
import algosdk from "algosdk";
import {PeraWalletConnect} from "@perawallet/connect";

const peraWallet = new PeraWalletConnect({
  chainId: 416001,
  algod: {
    mainnet: new algosdk.Algodv2(process.env.ALGOD_TOKEN, "https://my-node.example.com", 443)
  }
});
```

The client is used as you built it, so anything `algosdk.Algodv2` supports works — a tokenless node (`new algosdk.Algodv2("", "http://localhost", 4001)`), a provider that wants its own header (`new algosdk.Algodv2({"X-API-Key": key}, server, "")`), or a custom `BaseHTTPClient`.

## Methods

#### `PeraWalletConnect.connect(): Promise<string[]>`

Starts the initial connection flow and returns the array of account addresses.

#### `PeraWalletConnect.reconnectSession(): Promise<string[]>`

Reconnects to the wallet if there is any active connection and returns the array of account addresses.

#### `PeraWalletConnect.disconnect(): Promise<void | undefined>`

Disconnects from the wallet and resets the related storage items. Pera's session state lives only under its own `localStorage` keys (`PeraWallet.Wallet` and `PeraWallet.WalletConnect`), so disconnecting never removes another wallet's WalletConnect session. A session stored under the shared `walletconnect` key by earlier versions is migrated to `PeraWallet.WalletConnect` automatically the first time `PeraWalletConnect` is instantiated.


#### `PeraWalletConnect.platform: PeraWalletPlatformType`

Returns the platform of the active session. Possible responses: _`mobile | web | extension | null`_

#### `PeraWalletConnect.isExtensionAvailable(): Promise<boolean>`

Resolves with whether the Pera browser extension's provider (`window.pera`) is present on the page. See [Browser extension](#browser-extension-windowpera).

#### `PeraWalletConnect.on(event, handler): () => void`

Subscribes to session events and returns the unsubscribe function.

- `"disconnect"`: the wallet ended the session on its side. With the extension this happens when the user revokes your site from the extension's Connections screen; with Pera mobile when the WalletConnect session is killed from the app. The SDK has already cleared its session state when the handler runs. It does not fire for teardown the SDK itself starts — your own `disconnect()` call, or the session `connect()` replaces.
- `"networkChanged"`: the extension wallet switched network, on a session owned by the extension. The handler receives `{network: "mainnet" | "testnet" | "betanet"}`.

Handlers stay subscribed until you call the returned function, so register them once per instance rather than on every connect.

```typescript
const unsubscribe = peraWallet.on("disconnect", () => setAccountAddress(null));
```

#### `PeraWalletConnect.dispose(): void`

Releases everything the instance holds — the `window.pera` subscriptions and any WalletConnect connector — without touching the wallet session. Call it when the component owning the instance unmounts; under React StrictMode or hot reload, an undisposed instance keeps listening and swallows events the live one should handle. Use `disconnect()` to end the session itself.

#### `PeraWalletConnect.isConnected: boolean`

Checks if there's any active session regardless of platform. Possible responses: _`true | false`_

#### `PeraWalletConnect.isPeraDiscoverBrowser: boolean`

Checks if it is on Pera Discover Browser. Possible responses: _`true | false`_

#### `PeraWalletConnect.signTransaction(txGroups: SignerTransaction[][], signerAddress?: string): Promise<Uint8Array[]>`

Starts the sign process and returns the signed transaction in `Uint8Array`

#### `PeraWalletConnect.transactionSigner: TransactionSigner`

An algosdk [`TransactionSigner`](https://github.com/algorand/js-algorand-sdk/blob/develop/src/signer.ts) backed by the current wallet connection, so Pera can be plugged straight into `AtomicTransactionComposer` (or anything else that accepts a signer). The whole group is sent to the wallet in a single request; transactions not assigned to Pera are shown to the user but not signed. The same function instance is returned on every access, which is what lets the composer batch every Pera-signed transaction into one prompt. The signer carries only the transactions themselves; if you need `authAddr`, `msig` or a per-transaction `message`, call `signTransaction` directly.

<details>
  <summary>See example</summary>

```typescript
import algosdk from "algosdk";

const peraWallet = new PeraWalletConnect();
const [sender] = await peraWallet.reconnectSession();

const algod = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
const suggestedParams = await algod.getTransactionParams().do();
const atc = new algosdk.AtomicTransactionComposer();

atc.addTransaction({
  signer: peraWallet.transactionSigner,
  txn: algosdk.makePaymentTxnWithSuggestedParamsFromObject({
    sender,
    receiver: sender,
    amount: 0,
    suggestedParams
  })
});

const result = await atc.execute(algod, 4);
```
</details>

#### `PeraWalletConnect.signData(data: PeraWalletArbitraryData[], signer: string, verifySignature?: boolean): Promise<Uint8Array[]>`

Starts the signing process for arbitrary data signing and returns the signed data in `Uint8Array`. The wallet signs each item with the `"MX"` prefix, exactly like `algosdk.signBytes`, so each signature verifies with `algosdk.verifyBytes` against the account that signed it, or with `verifySignature` below. `signer` should be a valid Algorand address that exists in the user's wallet.

**Parameters:**
- `data`: Array of arbitrary data to sign
- `signer`: Algorand address that will sign the data
- `verifySignature` (optional): If `true`, automatically detects if the account is rekeyed (has `authAddr`) and uses the `authAddr` as the signer. After signing, verifies each signature against the original data. Defaults to `false`.

**Note:** When `verifySignature` is `true`, the function will:
1. Fetch account information from the Algorand network
2. Check if the account has an `authAddr` (rekeyed account)
3. Automatically use the `authAddr` as the signer if it exists, otherwise use the provided `signer` address
4. Verify each signature after signing using the `verifySignature` method (see below)

<details>
  <summary>See example</summary>
  
```typescript
// Basic usage
const signedData: Uint8Array[] = await peraWallet.signData([
  {
    data: new Uint8Array(Buffer.from(`timestamp//${Date.now()}`)),
    message: "Timestamp confirmation"
  },
  {
    data: new Uint8Array(Buffer.from(`agent//${navigator.userAgent}`)),
    message: "User agent confirmation"
  }
], "SAHBJDRHHRR72JHTWSXZR5VHQQUVC7S757TJZI656FWSDO3TZZWV3IGJV4");

// With signature verification (automatically handles rekeyed accounts)
const verifiedSignedData: Uint8Array[] = await peraWallet.signData([
  {
    data: new Uint8Array(Buffer.from(`timestamp//${Date.now()}`)),
    message: "Timestamp confirmation"
  }
], "SAHBJDRHHRR72JHTWSXZR5VHQQUVC7S757TJZI656FWSDO3TZZWV3IGJV4", true);
```
</details>

#### `PeraWalletConnect.verifySignature(data: Uint8Array, signature: Uint8Array, signerAddress: string): boolean`

Verifies a signature against the provided data and signer address. This method can be used independently to verify signatures returned from `signData` or other sources. When `signData` is called with `verifySignature: true`, it uses this method internally to verify the signatures.

**Parameters:**
- `data`: The original data that was signed (as `Uint8Array`)
- `signature`: The signature to verify (as `Uint8Array`)
- `signerAddress`: The Algorand address that should have signed the data

**Returns:** `true` if the signature is valid, `false` otherwise.

**Note:** This method automatically prefixes the data with "MX" (bytes `[77, 88]`) before verification to be consistent with `algosdk.verifyBytes` function. This ensures compatibility with Algorand's standard signature verification format. The data passed to this method should be the original data without the "MX" prefix, as the prefix is added internally.

<details>
  <summary>See example</summary>
  
```typescript
// Verify a signature independently
const isValid = peraWallet.verifySignature(
  originalData,
  signature,
  "SAHBJDRHHRR72JHTWSXZR5VHQQUVC7S757TJZI656FWSDO3TZZWV3IGJV4"
);

if (isValid) {
  console.log("Signature is valid!");
} else {
  console.log("Signature verification failed!");
}
```
</details>

#### `PeraWalletConnect.signArc60Data(payload: PeraWalletArc60SignData, metadata: SignMetadata, verifySignature?: boolean): Promise<PeraWalletArc60SignDataResponse>`

Signs an ARC-60 payload (for example a Sign-In With Algorand request) with the Pera mobile wallet or the Pera extension. `payload.signer` is the public key that must sign, `payload.domain` must match your page origin, and `metadata` is `{scope: ScopeType.AUTH, encoding: "base64"}`. With `verifySignature: true` the returned signature is checked against `payload.signer` before resolving. For rekeyed accounts see `resolveArc60Signer` below.

`payload.scheme` names the signing scheme, as proposed for ARC-60 in [algorandfoundation/ARCs#494](https://github.com/algorandfoundation/ARCs/pull/494). Leave it out for ed25519, which is what every request used before schemes existed. Those requests go to the wallet unchanged.

##### Falcon-1024 accounts

Pera's quantum accounts sign with `scheme: "falcon-1024-det"`, the same way as [use-wallet](https://github.com/TxnLab/use-wallet/pull/473) and [Lute](https://github.com/GalaxyPay/lute-connect#falcon-1024-accounts):

- A Falcon address is a hash of its public key, so `signer` must be the key itself. Take it from the account's [empty signature](#perawalletconnectgetemptysignaturesnetwork-peranetwork-promiserecordstring-string) (`pqsig.pk`).
- The SIWA payload's `type` must also be `"falcon-1024-det"`. The wallet rejects a scheme that doesn't match it, or that the account can't sign with.
- connect sends the key as base64, with the scheme beside it. Pera versions that can't sign Falcon ARC-60 yet reject the request.
- The response keeps your `signer` and `scheme`, and `signature` is a compressed Falcon signature.
- connect can only verify ed25519 signatures. With `verifySignature: true`, a Falcon request throws `SIGN_DATA_VERIFICATION_UNSUPPORTED` before the wallet is asked. Verify it in your app instead.

```typescript
import algosdk from "algosdk"; // 3.7 or later, for post-quantum accounts
import {verifyCompressed} from "falcon-1024";

const emptySignatures = await peraWallet.getEmptySignatures("testnet");
const signer = algosdk
  .msgpackRawDecodeAsMap(Buffer.from(emptySignatures[accountAddress], "base64"))
  .get("pqsig")
  ?.get("pk"); // undefined for an account that isn't Falcon

// The other SIWA fields as in the resolveArc60Signer example below.
const siwa = {...siwaFields, account_address: accountAddress, type: "falcon-1024-det"};
const data = new TextEncoder().encode(JSON.stringify(siwa, Object.keys(siwa).sort()));
const sha256 = async (bytes: Uint8Array) =>
  new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
const authenticatorData = await sha256(new TextEncoder().encode(siwa.domain));

const response = await peraWallet.signArc60Data(
  {
    data: Buffer.from(data).toString("base64"),
    signer,
    domain: siwa.domain,
    authenticatorData,
    scheme: "falcon-1024-det"
  },
  {scope: ScopeType.AUTH, encoding: "base64"}
);

// The key must hash to the account (or its auth address), and the signature
// must cover sha256(data) || sha256(authenticatorData).
const {address} = algosdk.addressFromPQKey(algosdk.FALCON_1024_SCHEME, response.signer);
const signedBytes = new Uint8Array([...(await sha256(data)), ...(await sha256(authenticatorData))]);
const ok =
  address.toString() === accountAddress &&
  verifyCompressed(response.signer, response.signature, signedBytes);
```

#### `PeraWalletConnect.resolveArc60Signer(accountAddress: string, network?: PeraWalletNetwork): Promise<PeraWalletArc60SignerResolution>`

Resolves who has to sign an ARC-60 request for `accountAddress`. An ARC-60 signature is verified against the `signer` public key, and Pera never substitutes another key. If the account is rekeyed, its own key no longer controls it, so the request must name the on-chain **auth address** as `signer` while the SIWA payload keeps the account as `account_address`. Pera refuses a rekeyed account that names itself as `signer`, and it can only sign when the user's wallet holds the auth address as a key-bearing or Ledger account (a watch-only or multisig auth address cannot sign ARC-60).

Rekeys are per network, and the wallet checks them on the network it is currently connected to, which connect cannot observe:

- With `chainId: 416001` or `416002` the wallet only serves the session while it is on that network, so `network` can be omitted; passing a different one throws `SIGN_DATA_NETWORK_MISMATCH`. This is the recommended setup.
- With an all-networks session (`4160`, the default) `network` is required (`SIGN_DATA_NETWORK_REQUIRED` otherwise) and must be the network the user's wallet is on; if it is not, the wallet rejects the sign-in.
- Betanet sessions (`416003`) and values other than `"mainnet"` / `"testnet"` throw `SIGN_DATA_NETWORK_UNSUPPORTED`.

**Returns:** `{accountAddress, signerAddress, signer, isRekeyed, network}`. `signer` is `signerAddress` as a public key, ready for `PeraWalletArc60SignData.signer`.

It resolves ed25519 signers only. A Falcon account's key can't be read from its address, so see [Falcon-1024 accounts](#falcon-1024-accounts) instead.

**Throws:** `SIGN_DATA_INVALID_ADDRESS` for a malformed address, the network errors above, and `SIGN_DATA_AUTH_ADDR_LOOKUP_FAILED` when the account lookup fails (the cause is in `error.data.detail`), instead of assuming the account is not rekeyed.

The auth address is read from a public node by default. Use the [`algod`](#algod) option to read it from your own node instead — recommended for anything beyond light traffic.

<details>
  <summary>See example</summary>

```typescript
import {PeraWalletConnect, ScopeType} from "@perawallet/connect";

// Pin the session to one network so the wallet and this lookup agree.
const peraWallet = new PeraWalletConnect({chainId: 416002});
const [accountAddress] = await peraWallet.connect();

const {signer, signerAddress} = await peraWallet.resolveArc60Signer(accountAddress);

const siwa = {
  account_address: accountAddress, // the account being authenticated
  chain_id: "283",
  domain: window.location.host,
  nonce: crypto.randomUUID(),
  type: "ed25519",
  uri: window.location.origin,
  version: "1"
};

const response = await peraWallet.signArc60Data(
  {
    data: Buffer.from(JSON.stringify(siwa, Object.keys(siwa).sort())).toString("base64"),
    signer, // the auth address when rekeyed, the account itself otherwise
    domain: siwa.domain,
    authenticatorData: new Uint8Array(
      await crypto.subtle.digest("SHA-256", new TextEncoder().encode(siwa.domain))
    )
  },
  {scope: ScopeType.AUTH, encoding: "base64"},
  true
);

// Send `response` and `signerAddress` to your backend. A verifier checks the
// signature against `signerAddress`, then checks on chain that `signerAddress`
// is the current auth address of `account_address`, or, when the two are equal,
// that `account_address` has no auth address (it is not rekeyed).
```
</details>

#### `PeraWalletConnect.getEmptySignatures(network?: PeraNetwork): Promise<Record<string, string>>`

Returns each connected account's **empty signature**, as defined by [use-wallet](https://github.com/TxnLab/use-wallet/pull/465): base64 of a `SignedTransaction` without its `txn` field. It tells you the account type (ed25519, multisig, logic sig or post-quantum) and lets you simulate with correctly shaped signatures. Accounts missing from the result are unknown.

```js
const accounts = await peraWallet.connect();
const emptySignatures = await peraWallet.getEmptySignatures("testnet");
// {"<address>": "<base64>"}
```

- Pera Mobile and the Pera extension answer it without prompting the user. Pera Web, and extension versions that don't support it yet, resolve `{}`.
- Call it right after a fresh `connect()`, not after `reconnectSession()`: a value must come from the wallet just now.
- `network` (`"mainnet"`, `"testnet"` or `"betanet"`) defaults to the network your `chainId` option pins. With the default `chainId` (`4160`, any network), Pera Mobile needs it; the extension answers for the network it is on.
- If Pera Mobile doesn't answer within 30 seconds it rejects with `EMPTY_SIGNATURES_TIMEOUT`. That happens with Pera versions that don't support it yet, or when iOS suspends Pera after the user returns to the browser. Treat it as "unknown" and don't block your UI on it.

It rejects with these `error.data.type` values:

| `error.data.type`                      | when                                                                                      |
| -------------------------------------- | ----------------------------------------------------------------------------------------- |
| `SESSION_DISCONNECTED`                 | no Pera session is connected                                                              |
| `EMPTY_SIGNATURES_NETWORK_REQUIRED`    | Pera Mobile only: `network` is missing and the session allows any network (`chainId` `4160`) |
| `EMPTY_SIGNATURES_NETWORK_UNSUPPORTED` | `network` isn't `"mainnet"`, `"testnet"` or `"betanet"`                                   |
| `EMPTY_SIGNATURES_NETWORK_MISMATCH`    | extension only: the wallet is on a different network than `network`                       |
| `EMPTY_SIGNATURES_TIMEOUT`             | Pera didn't answer within 30 seconds                                                      |
| `EMPTY_SIGNATURES`                     | `error.data.detail.reason` is `"wallet-error"`, `"session-changed"` or `"invalid-result"` |

## Customizing Style

You can override the z-index using the `.pera-wallet-modal` class so that the modal does not conflict with another component on your application.

```scss
.pera-wallet-modal {
  // The default value of z-index is 10. You can lower and raise it as much as you want.
  z-index: 11;
}
```

## Your app name on Pera Wallet

By default, the connect wallet drawer on Pera Wallet gets the app name from `document.title`.

In some cases, you may want to customize it. You can achieve this by adding a meta tag to your HTML between the `head` tag.

```html
<meta name="name" content="My dApp" />
```

## WalletConnect

`@perawallet/connect` talks to the Pera mobile app over **WalletConnect v1**, through [`@perawallet/walletconnect`](https://github.com/perawallet/pera-walletconnect-ts), a v1 client that Pera maintains. WalletConnect v2 is not supported, and the Pera mobile app only accepts v1 pairing URIs. Sessions run through Pera-hosted bridge servers, so no WalletConnect Cloud project ID is needed.

## Browser extension (`window.pera`)

The Pera browser extension injects a provider at `window.pera` on every `https` page (and `http://localhost`) before any page script runs. When it is present, `@perawallet/connect` uses it directly instead of WalletConnect: the connect modal lists "Connect with Pera Extension" first and pre-selects it, and `connect()`, `reconnectSession()`, `signTransaction()`, `signData()`, `signArc60Data()` and `disconnect()` all go through the provider. Pages without the extension behave exactly as before, and users can still pick the QR code or Pera Web options from the same modal.

The SDK detects the provider synchronously when it is constructed, so no probe or handshake delays the connect modal. Call `peraWallet.isExtensionAvailable()` if you want to know yourself, or `getPeraProvider()` to reach the provider directly. Pass `shouldPreferExtension: false` to keep the extension out of the modal.

#### `connect()` needs a user gesture

The extension only opens its approval window for a first-time connection while the page has transient user activation (a click or key press within the last few seconds). The SDK's extension button calls `window.pera.connect()` synchronously from its click handler, so the normal flow works out of the box. If you build your own UI on top of `window.pera`, call `connect()` directly inside your click handler, before any `await`; a connect without activation fails with code `-32001` and no approval window.

Once your origin is approved, `reconnectSession()` resolves silently with the wallet's current accounts on every page load. If the user has revoked the site, it resolves with `[]` just like any other missing session. Any other failure — a wallet on a network your `chainId` does not allow, or the extension's service worker restarting mid page-load — rejects with the cause at `error.data.type` and leaves the approval in place, so a later reload picks the session back up.

#### Networks

With a specific `chainId` (`416001`, `416002`, `416003`) the SDK asks the extension for that network, and `connect()` rejects with `CONNECT_NETWORK_MISMATCH` when the wallet is on a different one. With the default `4160` the wallet connects on whatever network it is on. Subscribe to `peraWallet.on("networkChanged", ...)` to follow later switches.

#### Errors

Provider errors are mapped onto the SDK's `PeraWalletConnectError` types:

| extension code | meaning                          | `error.data.type`                                                                         |
| -------------- | -------------------------------- | ----------------------------------------------------------------------------------------- |
| `-32002`       | user rejected                    | `CONNECT_CANCELLED`, `SIGN_TXN_CANCELLED`, `SIGN_DATA_CANCELLED`                          |
| `-32003`       | network mismatch                 | `CONNECT_NETWORK_MISMATCH`, `SIGN_TXN_NETWORK_MISMATCH`, `SIGN_DATA_NETWORK_MISMATCH`     |
| `-32001`       | not connected / no user gesture  | `SESSION_CONNECT` on connect, `SESSION_DISCONNECTED` when signing, `[]` from reconnect    |
| `-32004`       | the wallet did not answer in time| `MESSAGE_NOT_RECEIVED`                                                                    |

The wallet applies its own approval timeout (about five minutes); the SDK adds none, so users can take their time. The original provider error is attached as `error.data.detail`.

#### Using `window.pera` directly

The provider's types are exported for dApps that talk to it themselves:

```typescript
import {getPeraProvider, PERA_PROVIDER_ERROR_CODES, type PeraProvider} from "@perawallet/connect";

const pera = getPeraProvider(); // PeraProvider | null, also typed as window.pera
```

## Contributing

All contributions are welcomed! To get more information about the details, please read the [contribution](./CONTRIBUTING.md) guide first.
