@noir-wallet/sdk
v0.1.9
Published
TypeScript SDK for Noir Wallet dApp integration
Maintainers
Readme
Noir Wallet SDK
For regular use, install Noir Wallet from the Chrome Web Store. Preview builds for testing are available on the Releases page.
TypeScript SDK and example dApp for integrating with Noir Wallet.
Install
pnpm installBuild
pnpm build
pnpm --filter @noir-wallet/example buildExample
The example app lives in example/ and uses the workspace SDK package:
pnpm example:devNoir Wallet must be installed in the browser for wallet connection flows.
Extension compatibility: The optional
fundingSourceparameter forgetMaxTransfer()andsendTransaction()requires Noir Wallet extension 1.0.27 or later. When omitted, the SDK continues to use shielded funds.
SDK API
TypeScript SDK for integrating with Noir Wallet Chrome Extension.
Installation
npm install @noir-wallet/sdk
# or
yarn add @noir-wallet/sdk
# or
pnpm add @noir-wallet/sdkUsage
Basic Example
import { getNoirWallet } from '@noir-wallet/sdk'
// Get Noir Wallet
const noirWallet = getNoirWallet()
if (!noirWallet) {
throw new Error('Noir Wallet not installed')
}
const zcash = noirWallet.zcash
// Check existing connection (silent, no popup)
const accounts = await zcash.getAccounts()
// Connect wallet if not connected (shows popup)
if (!accounts) {
const newAccounts = await zcash.connect()
console.log('Wallet connected:', newAccounts)
} else {
console.log('Already connected:', accounts)
}
// Get balance
const balance = await zcash.getBalance()
console.log('Transparent:', balance.transparent, 'ZEC')
console.log('Shielded:', balance.shielded, 'ZEC')
console.log('Available:', balance.available, 'ZEC') // Destination-agnostic fallback
// Get public key
const publicKeyInfo = await zcash.getPublicKey()
if (publicKeyInfo) {
console.log('Public Key:', publicKeyInfo.pubkey)
console.log('Address:', publicKeyInfo.address)
}
// Use the same funding source for Max and Send
const fundingSource = 'transparent' as const
// Calculate the exact Max after the destination, memo, and source are known
const max = await zcash.getMaxTransfer({
to: 'u1XYZ...',
memo: 'Payment for services',
fundingSource
})
// Send transaction with optional memo
const txid = await zcash.sendTransaction({
to: 'u1XYZ...',
amount: max.maxAmount,
memo: 'Payment for services',
fundingSource
})
console.log('Transaction sent:', txid)
// Sign message
const result = await zcash.signMessage('Hello World')
console.log('Signature:', result.signature)
console.log('Address:', result.address)Detect Provider
import { getNoirWallet, isNoirWalletInstalled } from '@noir-wallet/sdk'
// Check if installed
if (isNoirWalletInstalled()) {
const noirWallet = getNoirWallet()
console.log('Noir Wallet detected')
} else {
console.error('Noir Wallet not found')
}Event Listeners
const noirWallet = getNoirWallet()
const zcash = noirWallet.zcash
// Connect first
await zcash.connect()
// Listen to account changes (unlock/lock, switch account)
zcash.on('accountsChanged', async addresses => {
if (!addresses) {
console.log('Wallet locked or disconnected')
return
}
console.log('Primary account changed:', addresses.transparent)
// Multi-wallet dApps: refresh the authorized account list
const result = await zcash.getAccounts()
console.log('Authorized wallets:', result?.accounts.length)
})
// Listen to chain/network changes
zcash.on('chainChanged', chainInfo => {
console.log('Network changed:', chainInfo)
})API
Methods
All methods are available on noirWallet.zcash:
connect()
Request wallet connection (shows popup if not authorized). The approval screen lets the user authorize one or several independent wallets in a single action (MetaMask-style: the current account is preselected, more can be added).
Returns: Promise<ZcashConnectResult> — the primary account's transparent/shielded addresses plus an accounts array listing every authorized wallet.
const result = await zcash.connect()
// Primary (connected) account — same shape as before, fully backward compatible
console.log('Transparent:', result.transparent)
console.log('Shielded:', result.shielded)
// Every authorized wallet (always contains at least the primary)
result.accounts.forEach(acc => {
console.log(acc.label, acc.addresses.transparent, acc.addresses.shielded)
})getAccounts()
Query existing connection silently (no popup).
Returns: Promise<ZcashConnectResult | null> — same enhanced shape as connect(), or null if not connected.
const result = await zcash.getAccounts()
if (result) {
console.log('Connected:', result.transparent)
console.log('Authorized wallets:', result.accounts.length)
} else {
console.log('Not connected')
}getBalance(accountId?)
Get wallet balance.
Params (optional):
accountId: string— an accountid(the${walletId}:${accountId}key fromaccounts). Omit to read the primary (connected) account.
Returns: Promise<ZcashBalanceResult> — the primary (or requested) account's balance fields plus an accounts array with every authorized account's balance.
// Primary account balance (backward compatible)
const balance = await zcash.getBalance()
console.log('Shielded:', balance.shielded, 'ZEC')
console.log('Available:', balance.available, 'ZEC') // Destination-agnostic fallback
// Per-wallet balances
balance.accounts.forEach(b => {
console.log(b.id, b.balance.shielded, b.synced ? '(synced)' : '(cached)')
})
// A specific authorized wallet
const second = await zcash.getBalance(balance.accounts[1]?.id)Balance fields:
transparent: Transparent address balanceshielded: Shielded balance (Sapling + Orchard)total: Total balance (transparent + shielded)available: A destination-agnostic, cached compatibility value. It does not account for the final recipient, memo, selected fee tier, or exact transaction action count. UsegetMaxTransfer()for an exact Max value once those inputs are known.accounts: Balance of every authorized account; each entry carries asyncedflag (false= cached/zero fallback because the wallet is locked or that account hasn't synced yet).
Multi-wallet access & compatibility:
connect()/getAccounts()/getBalance()are backward compatible — their original top-level fields are unchanged, and theaccountsarray is purely additive. dApps on older extensions that don't returnaccountsstill work: the SDK normalizes the single-account response into a one-elementaccountsarray, so your code path is identical regardless of extension version. To react to changes, listen foraccountsChangedand re-callgetAccounts()/getBalance()to refresh the array.
getMaxTransfer(params)
Calculate the exact transferable amount and proposal fee for a specific recipient, memo, and fee tier using the current connected account.
Params:
to: string- Recipient addressmemo?: string- Private memo (max 512 bytes UTF-8, shielded recipients only)feeTier?: 'standard' | 'fast'- Fee tier used for the estimate; defaults tostandardfundingSource?: 'shielded' | 'transparent'- Balance used for the estimate; defaults toshielded
Returns: Promise<MaxTransferEstimate>
maxAmount: string- Exact payment amount in ZECfee: string- Fee for the exact send-max proposal in ZEC
const params = {
to: 'u1XYZ...',
memo: 'Payment for services',
feeTier: 'standard' as const,
fundingSource: 'transparent' as const
}
const { maxAmount, fee } = await zcash.getMaxTransfer(params)
console.log('Max:', maxAmount, 'ZEC')
console.log('Fee:', fee, 'ZEC')
const txid = await zcash.sendTransaction({
to: params.to,
amount: maxAmount,
memo: params.memo,
fundingSource: params.fundingSource
})Compatibility:
fundingSourcerequires Noir Wallet extension 1.0.27 or later. Omit it to retain the existing shielded-only behavior. Do not request transparent funding from an older extension because it does not understand the parameter. Legacy dApps can continue usingbalance.available, but it is a conservative fallback rather than an exact recipient-aware Max.
getPublicKey(options?)
Get the public key of the transparent address.
Params (optional):
options.signingMode:'current'(default) or'derived'
Returns: Promise<{ pubkey: string, address: string, signingMode: SigningMode, originAddress?: string } | null>
pubkey: Hex-encoded public keyaddress: Transparent address corresponding to the keysigningMode: The actual signing mode usedoriginAddress: (only in'derived'mode) The user's main transparent address
// Default: current transparent address key
const publicKeyInfo = await zcash.getPublicKey()
// Derived: privacy-preserving key (unlinkable to main address)
const derivedKey = await zcash.getPublicKey({ signingMode: 'derived' })
console.log('Derived Key:', derivedKey.pubkey)
console.log('Main Address:', derivedKey.originAddress)Note: This method requires the wallet to be connected but does not trigger an unlock popup. Returns null if the wallet is locked.
sendTransaction(params)
Send a transaction using shielded funds by default, or explicitly select transparent funds.
Params:
to: string- Recipient addressamount: string- Amount in ZECmemo?: string- Private memo (max 512 bytes UTF-8, shielded recipients only; not allowed for transparent recipients)fundingSource?: 'shielded' | 'transparent'- Balance used to fund the transaction; defaults toshielded
Returns: Promise<string> - Transaction ID
Privacy: Transparent funding reveals and may link the selected transparent UTXOs on-chain. It is not supported by Keystone hardware wallets.
Compatibility:
fundingSourceis supported by Noir Wallet extension 1.0.27 or later.
const txid = await zcash.sendTransaction({
to: 'u1XYZ...',
amount: '0.1',
memo: 'Payment for services',
fundingSource: 'transparent'
})signMessage(message, options?)
Sign a message with a transparent address key.
Params:
message: string- Message to signoptions.signingMode:'current'(default) or'derived'
Returns: Promise<SignMessageResult>
signature: Hex-encoded ECDSA signaturepubkey: Hex-encoded public keyaddress: Transparent address used for signingsigningMode: The actual signing mode usedoriginAddress: (only in'derived'mode) The user's main transparent address
// Default: sign with current transparent address key
const result = await zcash.signMessage('Hello World')
// Derived: sign with a privacy-preserving derived key
// Recommended for identity binding (MCA, DID) to prevent on-chain asset linkage
const derived = await zcash.signMessage('Hello World', { signingMode: 'derived' })
console.log('Signature:', derived.signature)
console.log('Origin Address:', derived.originAddress)getAddresses()
Get the connected wallet's transparent and shielded addresses.
Returns: Promise<ZcashAddress> - { transparent, shielded }
const addresses = await zcash.getAddresses()
console.log('Transparent:', addresses.transparent)
console.log('Shielded:', addresses.shielded)shieldFunds()
Shield transparent funds to the private (shielded) balance. Requires user approval via popup.
Returns: Promise<string> - Transaction ID
const txid = await zcash.shieldFunds()
console.log('Shield transaction:', txid)Note: This moves all transparent balance into the shielded pool for enhanced privacy. The user will see an approval popup.
getTransactionHistory()
Fetch transaction history from the wallet (includes on-chain and local pending transactions).
Returns: Promise<TransactionHistoryEntry[]>
Each entry contains:
txid: Transaction hash (hex)type:'send'|'receive'|'shield'|'swap'|'lending_supply'|'lending_withdraw'|'lending_claim'amount: Amount in ZECstatus:'mined'|'pending'|'failed'timestamp: Unix timestamp in millisecondsmemo: Optional memo string
const history = await zcash.getTransactionHistory()
history.forEach(tx => {
console.log(`${tx.type} ${tx.amount} ZEC - ${tx.status}`)
})switchNetwork(network) (deprecated)
Deprecated: Mainnet and testnet are now separate extension builds. Install the testnet extension for testnet usage. This method is retained for backward compatibility but has no effect.
Utility Functions
publicKeyToAddress(pubkey, network)
Convert a public key to a Zcash transparent address.
Params:
pubkey: string- Public key in hexadecimal format (compressed 33 bytes or uncompressed 65 bytes)network: 'mainnet' | 'testnet'- Network type (defaults to 'mainnet')
Returns: string - Zcash transparent address (P2PKH format)
Throws: Error if public key format is invalid
import { publicKeyToAddress } from '@noir-wallet/sdk'
// Get public key from wallet
const { pubkey } = await zcash.getPublicKey()
// Convert to address for verification
const address = publicKeyToAddress(pubkey, 'mainnet')
console.log('Address:', address)
// Convert external public key
const externalPubkey = '03a1b2c3d4e5f6...'
const externalAddress = publicKeyToAddress(externalPubkey, 'mainnet')Public Key Formats:
- Compressed (33 bytes): Starts with
02or03 - Uncompressed (65 bytes): Starts with
04
Note: This function implements the Bitcoin/Zcash P2PKH address generation algorithm (SHA256 → RIPEMD160 → Base58Check).
Events
accountsChanged
Triggered when accounts change (unlock/lock, switch account).
Data: ZcashAddress | null - Current addresses (null if locked/disconnected)
chainChanged
Triggered when network changes.
Data: { chainId: string, network: string }
Multi-wallet dApps: there is no separate batch event. When
accountsChangedfires, re-callgetAccounts()(andgetBalance()) to refresh theaccountsarray.
Types
interface ZcashAddress {
transparent: string
shielded: string
}
interface Balance {
transparent: string
shielded: string
total?: string
available?: string // Destination-agnostic compatibility value
}
// One account from a batch (multi-wallet) authorization.
// `id` is the stable `${walletId}:${accountId}` key; `label` is the wallet name.
interface ZcashAccount {
id: string
label: string
walletId: string
accountId: string
addresses: ZcashAddress
}
// Balance for one authorized account.
// `synced: false` means a cached/zero fallback (locked or not yet synced).
interface ZcashAccountBalance {
id: string
walletId: string
accountId: string
balance: Balance
synced: boolean
}
// Result of connect() / getAccounts(): primary account fields + every authorized wallet.
interface ZcashConnectResult extends ZcashAddress {
accounts: ZcashAccount[]
}
// Result of getBalance(): primary (or requested) balance + every authorized balance.
interface ZcashBalanceResult extends Balance {
accounts: ZcashAccountBalance[]
}
interface SendTransactionParams {
to: string
amount: string
memo?: string // Private memo (max 512 bytes UTF-8)
}
type FeeTier = 'standard' | 'fast'
interface MaxTransferParams {
to: string
memo?: string
feeTier?: FeeTier
}
interface MaxTransferEstimate {
maxAmount: string
fee: string
}
interface TransactionHistoryEntry {
txid: string
type: string // 'send' | 'receive' | 'shield' | 'swap' | 'lending_supply' | 'lending_withdraw' | 'lending_claim'
amount: string // ZEC amount
status: string // 'mined' | 'pending' | 'failed'
timestamp: number // Unix ms
memo?: string
}
type SigningMode = 'derived' | 'current'
interface SignMessageOptions {
signingMode?: SigningMode // Default: 'current'
}
interface SignMessageResult {
signature: string // Hex-encoded ECDSA signature
pubkey: string // Hex-encoded public key
address: string // Transparent address used for signing
signingMode: SigningMode // Actual signing mode used
originAddress?: string // Main transparent address (only in 'derived' mode)
}
type Network = 'mainnet' | 'testnet'Error Handling
const noirWallet = getNoirWallet()
if (!noirWallet) {
console.error('Please install Noir Wallet extension')
return
}
try {
await noirWallet.zcash.connect()
} catch (error) {
if (error.code === 4001) {
console.error('User rejected the request')
} else {
console.error('Connection failed:', error.message)
}
}License
MIT
