> For the complete documentation index, see [llms.txt](https://docs.qie.digital/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.qie.digital/qie-wallet-integration-guide-for-websites.md).

# 👛 QIE Wallet Integration Guide for Websites

Connect your website or dApp to QIE Wallet using the standard EIP-1193 injected provider.

### Overview

QIE Wallet integrates with any website through the standard injected provider interface. The browser extension injects an [EIP-1193](https://eips.ethereum.org/EIPS/eip-1193) compatible provider into every page. Your site talks to this provider directly, or through a library such as ethers.js, viem, or wagmi.

Any dApp that already supports injected EVM wallets works with QIE Wallet with little or no change. This guide covers the full flow: detecting the wallet, connecting accounts, switching to the QIE network, and performing transactions and contract calls.

> **Scope:** EVM integration only (QIE Blockchain and other EVM networks). Bitcoin and Solana accounts in QIE Wallet are not exposed through the EIP-1193 provider and are out of scope here.

### Prerequisites

* QIE Wallet extension installed from the Chrome Web Store, with at least one wallet created or imported
* A website served over HTTPS (or `localhost` for development). Extensions do not inject providers into pages opened from `file://`
* ethers.js v6 for the code samples: `npm install ethers`
* Basic familiarity with async JavaScript and EVM concepts (accounts, chain IDs, gas)

No API keys or app registration are needed. The same code also works with other injected EVM wallets, which keeps your dApp wallet-agnostic.

### Network Parameters

#### Mainnet

| Parameter       | Value                                                                       |
| --------------- | --------------------------------------------------------------------------- |
| Network name    | QIE Blockchain Mainnet                                                      |
| Chain ID        | `1990` (hex: `0x7C6`)                                                       |
| RPC URL         | `https://rpc1mainnet.qie.digital` through `https://rpc5mainnet.qie.digital` |
| Currency symbol | QIE                                                                         |
| Decimals        | 18                                                                          |
| Block explorer  | <https://mainnet.qie.digital>                                               |

> **Caution:** Older aggregator listings still show the pre-V3 chain ID `5656` or the legacy chain ID `9731`. Do not use those. V3 (chain ID `1990`) is current.

#### Testnet

| Parameter       | Value                             |
| --------------- | --------------------------------- |
| Network name    | QIE Testnet                       |
| Chain ID        | `1983` (hex: `0x7BF`)             |
| RPC URL         | `https://rpc1testnet.qie.digital` |
| Currency symbol | QIE                               |
| Decimals        | 18                                |
| Block explorer  | <https://testnet.qie.digital>     |
| Faucet          | <https://www.qie.digital/faucet>  |

> **Testnet prerequisite:** QIE Testnet is hidden until the user enables the testnet toggle in QIE Wallet settings. Before that, `wallet_switchEthereumChain` to chain `1983` returns error `4902` even though the wallet knows the chain, and `wallet_addEthereumChain` will not help. Tell testers to switch the toggle on first.

### Step 1: Detect QIE Wallet

Use [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963) (Multi Injected Provider Discovery) as the primary detection method. It finds QIE Wallet reliably even when the user has several wallet extensions installed, without fighting over `window.ethereum`.

```javascript
// Collect all announced wallet providers
const providers = [];
window.addEventListener('eip6963:announceProvider', (event) => {
  providers.push(event.detail); // { info: { uuid, name, icon, rdns }, provider }
});

// Ask installed wallets to announce themselves
window.dispatchEvent(new Event('eip6963:requestProvider'));

// Pick QIE Wallet from the announced providers
function getQIEProvider() {
  const match = providers.find((p) => p.info.rdns === 'me.qiewallet');
  return match ? match.provider : null;
}
```

Fallback for older setups:

```javascript
function getInjectedProvider() {
  // Preferred: EIP-6963 result
  const qie = getQIEProvider();
  if (qie) return qie;

  // Fallback: injected globals (window.qie is QIE Wallet's own)
  if (typeof window.qie !== 'undefined') return window.qie;
  if (typeof window.ethereum !== 'undefined') return window.ethereum;

  return null; // No wallet installed
}
```

If no provider is found, show an install prompt linking to the Chrome Web Store listing instead of failing silently.

**How QIE Wallet identifies itself**

* EIP-6963: `rdns: me.qiewallet`, `name: QIE Wallet`
* `window.qie`: always set, QIE Wallet only
* `window.ethereum`: claimed only if no other wallet already holds it. If MetaMask or another wallet injected first, it may point elsewhere

Prefer EIP-6963 or `window.qie` for unambiguous detection.

> **Warning:** The provider also reports `isMetaMask = true` for compatibility with dApps that only check for MetaMask. Any MetaMask-specific branch in your code will therefore run against QIE Wallet. To tell the wallets apart, use the EIP-6963 `rdns` value or `window.qie`, not `isMetaMask`.

### Step 2: Connect the User's Wallet

Request account access only in response to a user action, such as a **Connect Wallet** button. Unsolicited connection popups are treated as hostile by browsers and users.

```javascript
async function connectWallet() {
  const provider = getInjectedProvider();
  if (!provider) {
    alert('QIE Wallet is not installed.');
    return null;
  }

  try {
    // Opens the QIE Wallet popup asking the user to approve
    const accounts = await provider.request({ method: 'eth_requestAccounts' });
    return accounts[0]; // The active address
  } catch (error) {
    if (error.code === 4001) {
      console.log('Connection request was rejected');
    }
    return null;
  }
}
```

To check for an existing connection on page load **without** a popup, use `eth_accounts`. It returns connected addresses silently, or an empty array if the site is not connected.

```javascript
const accounts = await provider.request({ method: 'eth_accounts' });
const isConnected = accounts.length > 0;
```

### Step 3: Ensure the QIE Network Is Selected

After connecting, check the active chain and switch if needed. If the chain is unknown to the wallet (error `4902`), add it first.

```javascript
const QIE_CHAIN_ID = '0x7c6'; // 1990 (lowercase, matching what the wallet returns)
const QIE_CHAIN_ID_DECIMAL = 1990;

const QIE_NETWORK_PARAMS = {
  chainId: QIE_CHAIN_ID,
  chainName: 'QIE Blockchain Mainnet',
  nativeCurrency: { name: 'QIE', symbol: 'QIE', decimals: 18 },
  rpcUrls: [
    'https://rpc1mainnet.qie.digital',
    'https://rpc5mainnet.qie.digital',
  ],
  blockExplorerUrls: ['https://mainnet.qie.digital'],
};

async function ensureQIENetwork(provider) {
  const currentChainId = await provider.request({ method: 'eth_chainId' });

  // Compare numerically: the wallet returns lowercase hex ('0x7c6'),
  // so a string comparison against '0x7C6' would never match
  if (parseInt(currentChainId, 16) === QIE_CHAIN_ID_DECIMAL) return;

  try {
    await provider.request({
      method: 'wallet_switchEthereumChain',
      params: [{ chainId: QIE_CHAIN_ID }],
    });
  } catch (error) {
    if (error.code === 4902) {
      // Chain not added to the wallet yet
      await provider.request({
        method: 'wallet_addEthereumChain',
        params: [QIE_NETWORK_PARAMS],
      });
    } else {
      throw error;
    }
  }
}
```

> **Note:** Always compare chain IDs numerically. QIE Wallet returns `eth_chainId` as lowercase `0x7c6`; a strict string check against `0x7C6` fails and triggers a needless switch prompt on every load. The `chainId` param of `wallet_switchEthereumChain` is case-insensitive, so either casing works there.
>
> QIE Wallet ships with QIE network support built in, so for most users the switch call succeeds directly. The add-chain fallback matters when your dApp is also used from other injected wallets.

For a **testnet** build, swap in chain ID `0x7bf`, the testnet RPC, and the testnet explorer in `QIE_NETWORK_PARAMS`.

### Step 4: Core Operations with ethers.js

Wrap the injected provider in an ethers `BrowserProvider`. Reads go through the provider; anything that needs a signature (transactions, message signing) goes through the signer, which triggers the QIE Wallet approval popup.

```javascript
import { BrowserProvider, formatEther, parseEther } from 'ethers';

const injected = getInjectedProvider();
const ethersProvider = new BrowserProvider(injected);
const signer = await ethersProvider.getSigner();
```

**Read the native QIE balance**

```javascript
const address = await signer.getAddress();
const balanceWei = await ethersProvider.getBalance(address);
console.log(`Balance: ${formatEther(balanceWei)} QIE`);
```

**Send a native QIE transfer**

```javascript
const tx = await signer.sendTransaction({
  to: '0xRecipientAddressHere',
  value: parseEther('1.5'), // 1.5 QIE
});
console.log('Submitted:', tx.hash);

const receipt = await tx.wait(); // Wait for confirmation
console.log('Confirmed in block', receipt.blockNumber);
```

**Sign a message** (login, ownership verification)

```javascript
const signature = await signer.signMessage(
  'Sign in to ExampleDApp at 2026-09-17T10:00Z'
);
// Verify server-side with ethers.verifyMessage(message, signature)
```

> **Warning:** Always include a nonce or timestamp in login messages so signatures cannot be replayed.

**Sign EIP-712 typed data** (`eth_signTypedData_v4`)

Used for token permits, off-chain orders, and structured login payloads.

```javascript
const domain = {
  name: 'ExampleDApp',
  version: '1',
  chainId: 1990,
  verifyingContract: '0xYourContract',
};

const types = {
  Order: [
    { name: 'maker', type: 'address' },
    { name: 'amount', type: 'uint256' },
    { name: 'expiry', type: 'uint256' },
  ],
};

const value = {
  maker: address,
  amount: parseEther('10'),
  expiry: Math.floor(Date.now() / 1000) + 3600,
};

const signature = await signer.signTypedData(domain, types, value);
// Verify server-side with ethers.verifyTypedData(domain, types, value, signature)
```

Typed data shows as readable structured fields in the approval popup, so users can review exactly what they are signing.

### Step 5: Interact with Smart Contracts

Use the contract's address and ABI. Connect read-only calls to the provider and state-changing calls to the signer.

```javascript
import { Contract } from 'ethers';

const TOKEN_ADDRESS = '0xYourTokenContract';
const TOKEN_ABI = [
  'function balanceOf(address owner) view returns (uint256)',
  'function decimals() view returns (uint8)',
  'function symbol() view returns (string)',
  'function transfer(address to, uint256 amount) returns (bool)',
];

// Read (no popup, no gas)
const readContract = new Contract(TOKEN_ADDRESS, TOKEN_ABI, ethersProvider);
const decimals = await readContract.decimals();
const balance = await readContract.balanceOf(address);

// Write (opens QIE Wallet for approval, costs gas)
const writeContract = new Contract(TOKEN_ADDRESS, TOKEN_ABI, signer);
const tx = await writeContract.transfer('0xRecipient', 1000000n);
await tx.wait();
```

The same pattern covers any contract on the QIE network: DEX routers, NFT contracts, or the QIE Domains registry. Only the ABI and address change.

### Step 6: Handle Wallet Events

Users switch accounts and networks inside the wallet at any time. Subscribe to provider events and keep your app state in sync, otherwise you will show stale balances or send transactions from the wrong account.

```javascript
const injected = getInjectedProvider();

injected.on('accountsChanged', (accounts) => {
  if (accounts.length === 0) {
    // User disconnected the site from the wallet.
    // QIE Wallet signals site disconnection here, not through a 'disconnect' event.
    resetAppState();
  } else {
    // Active account changed
    setActiveAccount(accounts[0]);
    refreshBalances();
  }
});

injected.on('chainChanged', (chainId) => {
  // Simplest safe handling: reload so all providers and contracts reinitialize
  window.location.reload();
});

injected.on('connect', ({ chainId }) => {
  // Provider is ready; chainId is the active chain as a hex string
  console.log('Provider connected on chain', parseInt(chainId, 16));
});
```

> **Warning:** Do not rely on a `disconnect` listener with QIE Wallet: the provider never emits that event. Site disconnection (the user removing the site from the wallet's connected sites) arrives as `accountsChanged` with an empty array, which the first listener above already handles.

In single-page apps, remove listeners on component unmount with `injected.removeListener(eventName, handler)` to avoid duplicate handlers.

### Other Supported Provider Methods

Beyond the standard calls above, QIE Wallet handles the following methods. They are not listed in the extension's public docs but work as of v4.2.2.

| Method                                      | What it does                                                                                       |
| ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `wallet_watchAsset`                         | Prompts the user to add a token to their wallet's asset list                                       |
| `wallet_requestPermissions`                 | Re-opens the account selection prompt; this is how a user changes which accounts a site is granted |
| `wallet_revokePermissions`                  | Removes the site's account grant from the dApp side                                                |
| `net_version`                               | Returns the active chain ID as a decimal string (`"1990"`)                                         |
| `eth_sign`                                  | Legacy raw-hash signing; prefer `personal_sign`                                                    |
| `eth_signTypedData`, `eth_signTypedData_v3` | Older typed-data variants; prefer `eth_signTypedData_v4`                                           |

Any method the wallet does not handle itself is forwarded to the RPC node of the active chain, so standard read calls such as `eth_call`, `eth_getBalance`, `eth_blockNumber` and `eth_getTransactionReceipt` work through the provider without a separate RPC client.

### Alternative Stacks: viem and wagmi

Both consume the same EIP-1193 provider. Define QIE as a custom chain once and reuse it.

#### viem

```javascript
import { createWalletClient, createPublicClient, custom, http, defineChain } from 'viem';

export const qieMainnet = defineChain({
  id: 1990,
  name: 'QIE Blockchain Mainnet',
  nativeCurrency: { name: 'QIE', symbol: 'QIE', decimals: 18 },
  rpcUrls: { default: { http: ['https://rpc5mainnet.qie.digital'] } },
  blockExplorers: {
    default: { name: 'QIE Explorer', url: 'https://mainnet.qie.digital' },
  },
});

const walletClient = createWalletClient({
  chain: qieMainnet,
  transport: custom(getInjectedProvider()),
});

const publicClient = createPublicClient({
  chain: qieMainnet,
  transport: http(),
});
```

#### wagmi (React)

wagmi picks up QIE Wallet automatically through its `injected()` connector, which uses EIP-6963 discovery under the hood.

```javascript
import { createConfig, http } from 'wagmi';
import { injected } from 'wagmi/connectors';
import { qieMainnet } from './chains';

export const config = createConfig({
  chains: [qieMainnet],
  connectors: [injected()],
  transports: { [qieMainnet.id]: http() },
});
```

Use ethers.js for a minimal vanilla integration, viem for typed low-level control, and wagmi for React dApps with hooks and connection UI.

### Best Practices

> **Never ask users for their recovery phrase or private keys, in any flow, ever.** QIE Wallet is non-custodial; keys stay on the user's device.

* Trigger connection and signing only from explicit user actions.
* Validate all transaction parameters client-side and show the user exactly what they are approving (recipient, amount, contract).
* Verify signed login messages server-side and include a one-time nonce to prevent replay.
* Do not hardcode gas prices; let the wallet estimate.
* Treat a non-empty `eth_accounts` result as "connected", not "unlocked". After the wallet auto-locks it keeps returning the granted addresses, so a signing or transaction request will still open an unlock prompt first. Expect that extra step and a possible `4001` rejection.

### Troubleshooting

| Symptom                                  | Likely cause                                                               | Fix                                                                  |
| ---------------------------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| Provider is `undefined`                  | Extension not installed, page on `file://`, or script ran before injection | Check after page load; prompt install; serve over HTTPS or localhost |
| Error `4001`                             | User rejected the request in the wallet popup                              | Expected path; show a friendly retry message                         |
| Error `4902` on switch                   | QIE chain not present in the connected wallet                              | Call `wallet_addEthereumChain` with the params from Step 3           |
| Error `4902` switching to testnet (1983) | Testnet toggle is off in QIE Wallet settings, so the chain is hidden       | Ask the user to enable testnet in wallet settings, then retry        |
| Transactions fail or balance wrong       | dApp pointed at pre-V3 params (chain ID 5656 or 9731)                      | Use chain ID `1990` and the mainnet RPCs from Step 3                 |
| Wrong account used                       | `accountsChanged` not handled                                              | Subscribe to events per Step 6                                       |
| Popup never appears                      | A different wallet captured `window.ethereum`                              | Use EIP-6963 detection per Step 1                                    |

### Pre-Ship Test Checklist

* \[ ] Connect and disconnect
* \[ ] Reject each popup once
* \[ ] Switch accounts mid-session
* \[ ] Switch networks mid-session
* \[ ] Confirm a transaction end-to-end on the QIE explorer
