Typed TypeScript SDK for connecting web apps to the Noir Wallet browser extension.
- Integration guide
- Provider API reference
- Example dApp
- Install Noir Wallet for mainnet
- Download the testnet build
@noir-wallet/sdk wraps the provider injected at window.noirwallet and gives your app a typed Zcash API. Use it to:
- request access to one or more wallet accounts;
- read addresses, balances, and transaction history;
- estimate the exact maximum transfer and send ZEC;
- shield transparent funds;
- sign and verify messages;
- react to account changes.
The SDK is browser-only. It does not create or custody wallets, and Noir Wallet must be installed for provider calls to work.
Mainnet and testnet are separate Noir Wallet extensions:
| Network | Download | Use |
|---|---|---|
| Mainnet | Chrome Web Store | Production apps and real ZEC |
| Testnet | GitHub Releases | Development and test ZEC |
To install testnet:
- Open the official Releases page and choose the release you want to test.
- Download the asset whose filename ends with
-testnet.zip. - Unzip the downloaded file.
- Open
chrome://extensions, enable Developer mode, and select Load unpacked. - Choose the extracted extension directory and confirm the installed name is [Testnet] Noir Wallet.
Only install testnet ZIP files published by the NoirWallet/noir-wallet-sdk repository. The mainnet asset does not contain -testnet in its filename.
The builds use different extension IDs and isolated wallet data. The SDK does not switch networks at runtime; switchNetwork() is deprecated and always throws.
pnpm add @noir-wallet/sdk
npm install @noir-wallet/sdkThere are two different connection calls:
getAccounts()checks an existing authorization silently and returnsnullwhen the current site is not connected.connect()opens Noir Wallet so the user can approve access. Call it from an intentional user action such as a button click.
import { getNoirWallet } from '@noir-wallet/sdk'
const wallet = getNoirWallet()
if (!wallet) {
throw new Error('Install Noir Wallet to continue')
}
const { zcash } = wallet
// Safe to call during page initialization: this never requests approval.
const existingConnection = await zcash.getAccounts()
if (existingConnection) {
console.log('Already connected:', existingConnection.accounts)
}
// Call this from your Connect button.
async function connectWallet() {
const connection = await zcash.connect()
console.log('Primary shielded address:', connection.shielded)
console.log('Authorized accounts:', connection.accounts)
const balance = await zcash.getBalance()
console.log('Total ZEC:', balance.total)
console.log('Spendable estimate:', balance.available)
}getNoirWallet() returns null during server-side rendering and when the extension is unavailable. Resolve it in browser code, not at module initialization in an SSR application.
The examples below use the zcash instance created in the quick start.
connect() and getAccounts() return the primary account at the top level for simple integrations:
const connection = await zcash.connect()
console.log(connection.transparent)
console.log(connection.shielded)They also return every account the user authorized:
for (const account of connection.accounts) {
console.log(account.id, account.label)
console.log(account.addresses.transparent)
console.log(account.addresses.shielded)
}Use an account id when you need a specific authorized account's balance:
const allBalances = await zcash.getBalance()
for (const account of allBalances.accounts) {
console.log(account.id, account.balance.total, account.synced)
}
const selected = connection.accounts[1]
if (selected) {
const selectedBalance = await zcash.getBalance(selected.id)
console.log(selectedBalance.total)
}synced: false marks a zero fallback without current synced data. A locked wallet returns an empty accounts array.
Amounts are decimal ZEC strings. Keep them as strings instead of converting them to JavaScript floating-point numbers.
getBalance().available is the amount currently available for selection, excluding pending funds. It cannot account for the final recipient, memo, fee tier, or transaction action count. Use getMaxTransfer() when you need a Max estimate for the same destination, memo, and funding source.
Use to, not address or recipient, for the destination.
const txid = await zcash.sendTransaction({
to: 'u1...',
amount: '0.25',
memo: 'Invoice 1042',
fundingSource: 'shielded'
})
console.log('Broadcast transaction:', txid)The wallet opens an approval screen before sending. A memo is supported only for shielded recipients and is limited to 512 UTF-8 bytes.
The estimate and transaction must use the same destination, memo, and funding source:
import type { MaxTransferParams } from '@noir-wallet/sdk'
const transfer = {
to: 'u1...',
memo: 'Withdraw remaining balance',
feeTier: 'standard',
fundingSource: 'shielded'
} satisfies MaxTransferParams
const estimate = await zcash.getMaxTransfer(transfer)
console.log('Maximum payment:', estimate.maxAmount)
console.log('Transaction fee:', estimate.fee)
const txid = await zcash.sendTransaction({
to: transfer.to,
amount: estimate.maxAmount,
memo: transfer.memo,
fundingSource: transfer.fundingSource
})The approval screen controls the final transaction fee. If the user chooses a fee tier different from the estimate, recalculate Max before sending.
Transparent funding can reveal and link selected UTXOs. It is not supported by Keystone accounts.
derived mode creates a privacy-preserving identity key that is unlinkable to the main transparent address. It is the recommended mode for off-chain identity binding.
import { verifyMessageSignature } from '@noir-wallet/sdk'
const message = 'Sign in to Example'
const signed = await zcash.signMessage(message, {
signingMode: 'derived'
})
const verification = verifyMessageSignature({
message,
signature: signed.signature,
pubkey: signed.pubkey,
address: signed.address,
network: 'mainnet'
})
if (!verification.valid) {
throw new Error(verification.error ?? 'Invalid signature')
}Available signing modes are current, derived, and legacy_index0. See the Provider API reference before choosing a compatibility mode.
Ledger and Keystone accounts do not support message signing, public-key identity access, or lending identity methods through this API.
Refresh account-scoped state after accountsChanged. Remove the listener when your component or integration is disposed.
async function refreshWalletState() {
const connection = await zcash.getAccounts()
if (!connection) {
// The site is disconnected or the selected account is unavailable.
return
}
const balance = await zcash.getBalance()
console.log(connection.accounts, balance.accounts)
}
function handleAccountsChanged() {
void refreshWalletState()
}
zcash.on('accountsChanged', handleAccountsChanged)
// During cleanup:
zcash.removeListener('accountsChanged', handleAccountsChanged)All wallet methods are available on getNoirWallet().zcash.
| Method | Approval | Purpose |
|---|---|---|
connect() |
Yes | Request access to one or more accounts |
getAccounts() |
No | Read the current authorization, or null |
disconnect() |
No | Remove this site's wallet authorization |
getAddresses() |
No | Read the primary transparent and shielded addresses |
getBalance(accountId?) |
No | Read primary, selected, and authorized-account balances |
getMaxTransfer(params) |
No | Calculate the exact Max amount and fee |
sendTransaction(params) |
Yes | Approve and broadcast a Zcash transaction |
shieldFunds() |
Yes | Move transparent funds into the shielded balance |
getTransactionHistory() |
No | Read on-chain and locally pending transactions |
getPublicKey(options?) |
No | Read the public identity key for a signing mode |
signMessage(message, options?) |
Yes | Approve and sign a message |
checkLendingMcaAccount() |
No | Read lending MCA and signing-mode status |
on(event, handler) |
No | Subscribe to provider events |
removeListener(event, handler) |
No | Unsubscribe from provider events |
switchNetwork() is deprecated and always throws. Mainnet and testnet are separate extension builds; install the appropriate extension instead of switching at runtime.
The package also exports lower-level helpers such as detectProvider(), getZcashProvider(), publicKeyToAddress(), and verifyMessageSignature(). Most applications should start with getNoirWallet().
Provider calls can fail when the user rejects a request, the wallet is locked, the account is no longer authorized, a parameter is invalid, or a transaction cannot be built.
try {
await zcash.sendTransaction({
to: 'u1...',
amount: '0.25'
})
} catch (error) {
const message = error instanceof Error ? error.message : 'Wallet request failed'
console.error(message)
}Do not treat getAccounts() === null as an exception; it is the normal result for a site without an active authorization.
The optional fundingSource parameter in getMaxTransfer() and sendTransaction() requires Noir Wallet 1.0.27 or later. When omitted, it defaults to shielded funds.
The SDK normalizes older single-account responses into the current accounts array shape, so one integration path works with both response formats.
pnpm install
pnpm build
pnpm type-check
pnpm --filter @noir-wallet/example build
pnpm example:devThe example dApp requires Noir Wallet to be installed in the same browser.
For parameter definitions, return types, privacy considerations, and provider RPC details, read: