Skip to main content

Wallet Client

A wallet client signs and submits transactions: calling program functions, transferring credits, deploying programs, and signing arbitrary messages. Where a public client only ever reads, a wallet client's methods carry a side effect — they reach the network and, for anything that touches the chain, cost a fee.

The client takes one of two account shapes, and the shape it takes decides who builds and proves the transaction:

  • An RPC account delegates signing, proving, and record access to a connected wallet (Shield, Leo, Puzzle, Fox). The application never sees a private key.
  • A local account holds the private key directly. The caller supplies a proving configuration, and Veil builds and proves the transaction in-process or through a delegated prover before broadcasting it.

The action surface is identical either way — writeContract, transfer, and the rest behave the same from the caller's side. Only the construction differs, and only internally does the routing change.

Create a wallet client

From a wallet adapter (browser)

The wallet adapter path turns any Provable-standard wallet (Shield, Leo, Puzzle, Fox) into an RPC account and a matching transport. Pair the wallet transport with an HTTP transport through fallback — the wallet transport handles writes and wallet-specific reads, and the HTTP transport serves everything else:

import { createWalletClient, http, fallback } from '@provablehq/veil-core'
import { fromWalletAdapter } from '@provablehq/veil-aleo-wallet-adapter'

const { account, transport } = fromWalletAdapter(connectedAdapter)

const client = createWalletClient({
account,
transport: fallback([transport, http('https://api.provable.com/v2')]),
})

connectedAdapter is any adapter that already satisfies AleoWalletAdapter and has completed its own connect() call — see @provablehq/veil-aleo-wallet-adapter.

From a local private key (Node.js, scripts, bots)

A local account needs its proving configuration built from a network handle. loadNetwork loads the WASM binaries for one network and returns a handle (AleoSdk) whose methods derive accounts, build proving configs, and create scanners — privateKeyToAccount and createProvingConfig are methods on that handle, not top-level exports:

import { createWalletClient, http } from '@provablehq/veil-core'
import { loadNetwork } from '@provablehq/veil-aleo-sdk'

const aleo = await loadNetwork('testnet')
const account = aleo.privateKeyToAccount('APrivateKey1...')

const recordProvider = aleo.createRemoteScanner({
url: 'https://api.provable.com/v2',
consumerId: '<consumer-id>',
apiKey: '<api-key>',
})
recordProvider.setAccount({ viewKey: account.viewKey })

const client = createWalletClient({
account,
transport: http('https://api.provable.com/v2', { network: 'testnet' }),
proving: aleo.createProvingConfig({
mode: 'delegated',
networkUrl: 'https://api.provable.com/v2',
proverUrl: 'https://api.provable.com/prove/testnet',
account,
}),
recordProvider,
})

mode: 'delegated' submits proving to the remote prover service at proverUrl; mode: 'local' builds proofs in-process from the handle's WASM binaries instead, dropping proverUrl. See createProvingConfig for the full option set. The scanner's setAccount call points record scanning at the account's view key — without it, requestRecords throws.

From React

A React application does not construct a wallet client directly — the useVeilWallet hook holds the connection and hands back a wallet client once one is connected:

import { useVeilWallet } from '@provablehq/veil-aleo-react-hooks'

const { walletClient } = useVeilWallet()

See useVeilWallet.

Configuration

createWalletClient takes one of two shapes, discriminated by the account's type.

With an RPC account — the wallet handles proving and records, so no proving or recordProvider field applies:

FieldTypeDescription
accountRpcAccountWallet-delegated signer, typically from fromWalletAdapter.
transportTransportCarries requests to the network and the wallet.
keystring (optional)Identifier for the client's type. Defaults to 'wallet'.
namestring (optional)Human-readable name. Defaults to 'Wallet Client'.

With a local account — the caller supplies proving, and optionally a record provider:

FieldTypeDescription
accountLocalAccountHolds the private key and view key directly.
transportTransportCarries requests to the network.
provingProvingConfigRequired. Selects where and how transactions are built and proved — see createProvingConfig.
recordProviderRecordProvider (optional)Source of unspent records for requestRecords. Required to call requestRecords with a local account; omitted, the call throws.
keystring (optional)Identifier for the client's type. Defaults to 'wallet'.
namestring (optional)Human-readable name. Defaults to 'Wallet Client'.

Actions

Writing & deploying

ActionDescription
writeContractExecute a program function and return the transaction id.
executeTransactionAlias for writeContract, matching Aleo wallet-adapter terminology.
executeContractExecute a program function and return its per-transition outputs, waiting for confirmation.
simulateContractRun a function locally and return its outputs without broadcasting.
deployContractDeploy a program.
transferTransfer credits, selecting the transfer function from a visibility mode.
sendTransactionBroadcast an already-built transaction.

Signing & records

ActionDescription
signMessageSign an arbitrary message with the account.
decryptDecrypt a record or transition-output ciphertext.
requestRecordsFetch the account's records for a program (see routing below).
requestTransactionHistoryGet a program's transaction history. RPC accounts only.

Status & network

ActionDescription
transactionStatusCheck a submitted transaction's status.
switchChainSwitch the client's target network.
switchNetworkAlias for switchChain.
getChainIdGet the current network.
getNetworkAlias for getChainId.

Examples

Execute a program function

const txId = await walletClient.writeContract({
program: 'loyalty_token.aleo',
function: 'mint_card',
inputs: ['aleo1...', '0u64', '12345field'],
})

Transfer credits

const txId = await walletClient.transfer({
to: 'aleo1recipient...',
amount: 1_000_000n, // 1 credit = 1,000,000 microcredits
})

Fetch and spend a record

const records = await walletClient.requestRecords({
program: 'loyalty_token.aleo',
statusFilter: 'unspent',
})

const card = records.find((r) => r.recordName === 'LoyaltyCard')

const txId = await walletClient.writeContract({
program: 'loyalty_token.aleo',
function: 'add_points',
inputs: [card!.recordPlaintext, '100u64'],
})

See Working with Records for the full scanning and spending flow, including the wallet-adapter path where inputs can carry unresolved InputRequest objects instead of literals.

Track transaction status

const status = await walletClient.transactionStatus({ transactionId: txId })
// { status: 'accepted', transactionId: 'at1...' }

Status is one of accepted, rejected, pending, or not_found — see Transaction status.

requestRecords routing

requestRecords resolves differently depending on the account backing the client:

Account typeRecord sourceConfig required
RPC (wallet adapter)The connected wallet's own record scanningNone
Local (SDK key)The client's recordProviderrecordProvider

An RPC account routes the call straight to the wallet, which already tracks its own records. A local account has no wallet to ask, so the wallet client looks up recordProvider — supplied at construction — and throws if none was given.

import { loadNetwork } from '@provablehq/veil-aleo-sdk'

const aleo = await loadNetwork('testnet')
const account = aleo.privateKeyToAccount('APrivateKey1...')

// Remote scanner: registers a view key with the hosted Record Scanning
// Service on the first scan and reuses the registration across calls.
const recordProvider = aleo.createRemoteScanner({
url: 'https://api.provable.com/v2',
consumerId: '<consumer-id>',
apiKey: '<api-key>',
})
recordProvider.setAccount({ viewKey: account.viewKey })

const walletClient = createWalletClient({
account,
transport: http('https://api.provable.com/v2', { network: 'testnet' }),
proving: aleo.createProvingConfig({
mode: 'delegated',
networkUrl: 'https://api.provable.com/v2',
proverUrl: 'https://api.provable.com/prove/testnet',
account,
}),
recordProvider,
})

See createRemoteScanner for the provider's own options, and createStandaloneScanner for scanning records outside a wallet client entirely — view-only dashboards or auditing that need no signer.

extend()

createWalletClient builds on the base client and layers walletActions on with extend, the same pattern createPublicClient and createTestClient use. A wallet client can take further extensions the same way — for example, composing with leoActions to add a .leo property for building and deploying Leo programs against the same client used to call them:

import { leoActions } from '@provablehq/veil-leo'

const client = createWalletClient({ account, transport }).extend(
leoActions({ cwd: './programs/token' }),
)

await client.leo.build()