A standards-first EVM provider
Nora Wallet exposes an EIP-1193-compatible provider and announces it through EIP-6963. Your dApp should discover the provider explicitly instead of assuming that window.ethereum belongs to a particular wallet.
Connecting, signing, sending transactions, and changing networks may open a Nora Wallet approval window. Unlocking a locked wallet only resumes the review; it does not approve the request.
Discover Nora Wallet
Use EIP-6963 as the primary path. It lets multiple installed wallets coexist without overwriting one another. Dispatch the request event after registering your listener so wallets can announce themselves immediately.
type EIP1193Provider = {
isTerraWallet?: boolean
request(args: { method: string; params?: unknown[] }): Promise<unknown>
on(event: string, listener: (...args: unknown[]) => void): void
removeListener(event: string, listener: (...args: unknown[]) => void): void
}
type EIP6963Detail = {
info: { uuid: string; name: string; icon: string; rdns: string }
provider: EIP1193Provider
}
export function findTerraWallet(timeoutMs = 1_000) {
return new Promise<EIP1193Provider>((resolve, reject) => {
const timeout = window.setTimeout(() => {
window.removeEventListener("eip6963:announceProvider", onProvider)
reject(new Error("Nora Wallet was not found"))
}, timeoutMs)
function onProvider(event: Event) {
const { info, provider } = (event as CustomEvent<EIP6963Detail>).detail
if (provider.isTerraWallet || info.name === "Nora Wallet") {
window.clearTimeout(timeout)
window.removeEventListener("eip6963:announceProvider", onProvider)
resolve(provider)
}
}
window.addEventListener("eip6963:announceProvider", onProvider)
window.dispatchEvent(new Event("eip6963:requestProvider"))
})
}Optional compatibility fallback
Nora Wallet also exposes window.terraEthereum. Use it only as a fallback for environments where your connector does not yet support EIP-6963.
declare global {
interface Window {
terraEthereum?: EIP1193Provider
}
}
const provider = await findTerraWallet().catch(() => window.terraEthereum)
if (!provider) {
throw new Error("Install Nora Wallet to continue")
}window.ethereum alone. Another installed wallet may own that namespace.Connect an account
Call eth_requestAccounts only after a clear user action such as clicking “Connect Nora Wallet.” Nora Wallet records permission per website origin and may let the user choose an EOA or a deployed Token Bound Account.
const accounts = await provider.request({
method: "eth_requestAccounts",
}) as string[]
const account = accounts[0]
const chainId = await provider.request({ method: "eth_chainId" }) as string
// Use eth_accounts for a silent permission check on later visits.
const permittedAccounts = await provider.request({
method: "eth_accounts",
}) as string[]Before rendering a previously connected session, use eth_accounts. An empty array means the current origin does not have an exposed account.
Keep application state synchronized
Subscribe to account and network changes. Never keep using an old address after accountsChanged, and treat an empty account list as a disconnected permission state.
const handleAccounts = (accounts: unknown) => {
const next = Array.isArray(accounts) ? accounts[0] : undefined
// Clear account-scoped state when next is undefined.
}
const handleChain = (chainId: unknown) => {
// chainId is a hexadecimal EVM quantity, for example "0x38".
window.location.reload()
}
provider.on("accountsChanged", handleAccounts)
provider.on("chainChanged", handleChain)
provider.on("disconnect", (error) => console.warn("Wallet disconnected", error))
// Remove listeners when your application unmounts.
provider.removeListener("accountsChanged", handleAccounts)
provider.removeListener("chainChanged", handleChain)Switch or propose an EVM network
Chain IDs must be hexadecimal quantities. Nora Wallet shows the requested network details before applying a sensitive network action. Provide HTTPS RPC and explorer URLs suitable for production.
await provider.request({
method: "wallet_switchEthereumChain",
params: [{ chainId: "0x38" }], // BNB Smart Chain
})
// If your application offers an add-network flow, send complete metadata.
await provider.request({
method: "wallet_addEthereumChain",
params: [{
chainId: "0x89",
chainName: "Polygon Mainnet",
nativeCurrency: { name: "POL", symbol: "POL", decimals: 18 },
rpcUrls: ["https://polygon-rpc.com"],
blockExplorerUrls: ["https://polygonscan.com"],
}],
})Request a signature
Explain what the signature does before opening the wallet. Include a domain, purpose, and one-time nonce in authentication messages, then verify the recovered signer on your server.
const message = "Sign in to Example dApp
Nonce: 8f23c1"
const signature = await provider.request({
method: "personal_sign",
params: [message, account],
}) as string
const typedSignature = await provider.request({
method: "eth_signTypedData_v4",
params: [account, JSON.stringify(typedData)],
}) as stringpersonal_sign or typed-data signing. Connect the controlling EOA when a message signature is required.Submit a transaction
Nora Wallet prepares the transaction on the active network and displays a dedicated approval screen. Contract interactions may carry additional warnings. Do not treat a returned hash as final confirmation; follow its receipt.
const transactionHash = await provider.request({
method: "eth_sendTransaction",
params: [{
from: account,
to: "0x000000000000000000000000000000000000dEaD",
value: "0x0",
data: "0x",
}],
}) as stringA connected Token Bound Account can execute supported transactions through its verified NFT controller. Its account must be deployed, owned by the current controller, and connected on the matching chain.
Handle provider errors
Wrap every request in try/catch and branch on the numeric code. Rejection is an expected user decision, not an application crash.
| Code | Meaning | Recommended response |
|---|---|---|
4001 | User rejected the request | Return the user to a safe, retryable state. |
4100 | Unauthorized | Request account access or ask the user to unlock Nora Wallet. |
4200 | Unsupported method | Use a supported EIP-1193 method or your own RPC client. |
4900 | Provider disconnected | Disable write actions and wait for connectivity. |
4901 | Chain disconnected | Ask the user to select or add a reachable network. |
Integration security checklist
- Discover wallets through EIP-6963 and let the user choose explicitly.
- Request accounts only in response to intentional user interaction.
- Verify the active account and chain immediately before every write.
- Never request or transmit a recovery phrase, private key, or wallet password.
- Display transaction purpose, destination, value, and network in your own UI.
- Treat RPC data, token metadata, dApp input, and provider icons as untrusted.
- Handle rejection, lock, timeout, disconnect, and chain changes without retry loops.
Ready to connect?
Start with provider discovery, add a user-triggered connect button, and test locked, rejected, disconnected, and multi-wallet states before release.
Review discovery