Skip to content
Get startedGetting started →

Send a transaction with viem

mera exports an adapter function toViemAccount that adapts a signing session into the account shape viem accepts, so viem signs and broadcasts while the key is owned by the session.

import {
function getPasskeyPrfOutput({ rpId, credential: allowCredential, prfSalt, timeout, webAuthnClient, }: getPasskeyPrfOutput.Options): Promise<getPasskeyPrfOutput.Result>

Requests a passkey PRF evaluation and returns the first output.

@paramoptions - Passkey PRF request inputs.

@returnsThe selected credential ID and first WebAuthn PRF output.

@remarks

Runs one assertion ceremony and shows one user-verification prompt.

The WebAuthn challenge is generated internally.

The default salt is sha256("mera.prf.salt.v1") and will not change across library versions. The PRF output is a deterministic function of the credential, rpId, and salt; a different salt yields an unrelated output.

The assertion requires user verification, and the requirement is not configurable. User verification is the authenticator's local check; the gesture depends on the platform (a biometric, a device PIN, or a password). Authenticators built on CTAP's hmac-secret keep two PRFs per credential, one for user-verified requests and one for the rest; WebAuthn exposes only the user-verified PRF and overrides a weaker userVerification setting when evaluating it, so a configurable setting could neither change the PRF output nor skip the check.

@seehttps://www.w3.org/TR/webauthn-3/#prf-extension WebAuthn: the PRF extension

@seehttps://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement WebAuthn: UserVerificationRequirement

@throwsMeraError with code PRF_UNAVAILABLE when the authenticator does not return a usable 32-byte PRF output.

@throwsMeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes, or credential.credentialId is empty or not canonical base64url.

@throwsMeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throwsMeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, cancelled, or returns an unexpected credential.

getPasskeyPrfOutput
} from "@category-labs/mera";
const
const rpId: string
rpId
=
var location: Location

The Window.location read-only property returns a Location object with information about the current location of the document.

MDN Reference

location
.
Location.hostname: string

The hostname property of the Location interface is a string containing either the domain name or IP address of the location URL.

MDN Reference

hostname
;
const {
const prfOutput: Uint8Array<ArrayBuffer>

First PRF output from WebAuthn. Always 32 bytes.

prfOutput
} = await
function getPasskeyPrfOutput({ rpId, credential: allowCredential, prfSalt, timeout, webAuthnClient, }: getPasskeyPrfOutput.Options): Promise<getPasskeyPrfOutput.Result>

Requests a passkey PRF evaluation and returns the first output.

@paramoptions - Passkey PRF request inputs.

@returnsThe selected credential ID and first WebAuthn PRF output.

@remarks

Runs one assertion ceremony and shows one user-verification prompt.

The WebAuthn challenge is generated internally.

The default salt is sha256("mera.prf.salt.v1") and will not change across library versions. The PRF output is a deterministic function of the credential, rpId, and salt; a different salt yields an unrelated output.

The assertion requires user verification, and the requirement is not configurable. User verification is the authenticator's local check; the gesture depends on the platform (a biometric, a device PIN, or a password). Authenticators built on CTAP's hmac-secret keep two PRFs per credential, one for user-verified requests and one for the rest; WebAuthn exposes only the user-verified PRF and overrides a weaker userVerification setting when evaluating it, so a configurable setting could neither change the PRF output nor skip the check.

@seehttps://www.w3.org/TR/webauthn-3/#prf-extension WebAuthn: the PRF extension

@seehttps://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement WebAuthn: UserVerificationRequirement

@throwsMeraError with code PRF_UNAVAILABLE when the authenticator does not return a usable 32-byte PRF output.

@throwsMeraError with code INPUT_INVALID when an explicit prfSalt is not 32 bytes, or credential.credentialId is empty or not canonical base64url.

@throwsMeraError with code CRYPTO_UNAVAILABLE when crypto.getRandomValues is unavailable.

@throwsMeraError with code PASSKEY_OPERATION_FAILED when WebAuthn is unavailable, cancelled, or returns an unexpected credential.

getPasskeyPrfOutput
({
rpId: string

Relying party ID for the WebAuthn assertion.

rpId
});

The seed and path come from the same mapping Create passkey accounts uses (Keys and accounts introduces the standards).

import {
class HDKey

HDKey from BIP32

@paramopt - Node fields used to construct one HDKey instance.

@example

import { HDKey } from '@scure/bip32';
import { randomBytes } from '@noble/hashes/utils.js';
const seed = randomBytes(32);
const root = HDKey.fromMasterSeed(seed);
const account0 = root.derive("m/0/1'");
account0.publicKey;

HDKey
} from "@scure/bip32";
import {
function entropyToMnemonic(entropy: TArg<Uint8Array>, wordlist: string[]): string

Reversible: Converts raw entropy in form of byte array to mnemonic string.

@paramentropy - Byte array.

@paramwordlist - Imported wordlist for a specific language.

@returns12-24 words.

@throwsOn wrong argument types. TypeError

@throwsOn wrong argument ranges or values. RangeError

@example

Convert raw entropy into an English mnemonic.

import { entropyToMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english.js';
const ent = new Uint8Array([
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
]);
const mnemonic = entropyToMnemonic(ent, wordlist);
// 'legal winner thank year wave sausage worth useful legal winner thank yellow'

entropyToMnemonic
,
function mnemonicToSeedSync(mnemonic: string, passphrase?: string): TRet<Uint8Array>

Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.

@parammnemonic - 12-24 words.

@parampassphrase - String that will additionally protect the key.

@returns64 bytes of key data.

@throwsIf the mnemonic shape is invalid. Error

@throwsOn wrong argument types. TypeError

@example

Derive a seed from a mnemonic with the sync PBKDF2 helper.

const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
const seed = mnemonicToSeedSync(mnem, 'password');
// => new Uint8Array([...64 bytes])

mnemonicToSeedSync
} from "@scure/bip39";
import {
const wordlist: string[]

English BIP39 wordlist.

wordlist
} from "@scure/bip39/wordlists/english.js";
const
const firstEthereumAccountPath: "m/44'/60'/0'/0/0"
firstEthereumAccountPath
= "m/44'/60'/0'/0/0";
const
const seed: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>
seed
=
function mnemonicToSeedSync(mnemonic: string, passphrase?: string): TRet<Uint8Array>

Irreversible: Uses KDF to derive 64 bytes of key data from mnemonic + optional password.

@parammnemonic - 12-24 words.

@parampassphrase - String that will additionally protect the key.

@returns64 bytes of key data.

@throwsIf the mnemonic shape is invalid. Error

@throwsOn wrong argument types. TypeError

@example

Derive a seed from a mnemonic with the sync PBKDF2 helper.

const mnem = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
const seed = mnemonicToSeedSync(mnem, 'password');
// => new Uint8Array([...64 bytes])

mnemonicToSeedSync
(
function entropyToMnemonic(entropy: TArg<Uint8Array>, wordlist: string[]): string

Reversible: Converts raw entropy in form of byte array to mnemonic string.

@paramentropy - Byte array.

@paramwordlist - Imported wordlist for a specific language.

@returns12-24 words.

@throwsOn wrong argument types. TypeError

@throwsOn wrong argument ranges or values. RangeError

@example

Convert raw entropy into an English mnemonic.

import { entropyToMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english.js';
const ent = new Uint8Array([
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f
]);
const mnemonic = entropyToMnemonic(ent, wordlist);
// 'legal winner thank year wave sausage worth useful legal winner thank yellow'

entropyToMnemonic
(
const prfOutput: Uint8Array<ArrayBufferLike>
prfOutput
,
const wordlist: string[]

English BIP39 wordlist.

wordlist
));
const
const node: HDKey
node
=
class HDKey

HDKey from BIP32

@paramopt - Node fields used to construct one HDKey instance.

@example

import { HDKey } from '@scure/bip32';
import { randomBytes } from '@noble/hashes/utils.js';
const seed = randomBytes(32);
const root = HDKey.fromMasterSeed(seed);
const account0 = root.derive("m/0/1'");
account0.publicKey;

HDKey
.
HDKey.fromMasterSeed(seed: Uint8Array, versions?: Versions): HDKey
fromMasterSeed
(
const seed: Uint8Array<ArrayBufferLike> & Uint8Array<ArrayBuffer>
seed
).
HDKey.derive(path: string): HDKey
derive
(
const firstEthereumAccountPath: "m/44'/60'/0'/0/0"
firstEthereumAccountPath
);
if (
const node: HDKey
node
.
HDKey.privateKey: Uint8Array<ArrayBufferLike> | null
privateKey
=== null) throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
("derivation produced no key");

toViemAccount lives in the @category-labs/mera/viem entry point, which requires the optional viem peer dependency.

import {
function createSecp256k1SigningSession({ privateKey, }: CreateSigningSessionOptions): Secp256k1SigningSession

Creates a signing session from a secp256k1 private key.

@paramoptions - Signing session inputs.

@returnsA live secp256k1 signing session.

@throwsMeraError with code INPUT_INVALID when privateKey is not a valid secp256k1 scalar.

createSecp256k1SigningSession
} from "@category-labs/mera";
import {
function toViemAccount(session: Secp256k1SigningSession, options?: ToViemAccountOptions): LocalAccount<"mera">

Adapts a secp256k1 signing session into a viem local account.

Each signing method hashes its input with viem's own hashers and signs the resulting 32-byte digest with session.signDigest, so signing never shows a passkey prompt. The account's address is the EIP-55 checksummed address of the session key, and publicKey is the 65-byte uncompressed public key as hex.

@paramsession - Live secp256k1 signing session that backs the account.

@paramoptions - Adapter inputs.

@returnsA viem local account with source: "mera" implementing signTransaction (honoring a custom serializer), signMessage (EIP-191), signTypedData (EIP-712), signAuthorization (EIP-7702), and raw-hash sign.

@throwsMeraError with code SESSION_ENDED, rejected from every signing method after session.end() has been called.

@throwsMeraError with code INPUT_INVALID, rejected from sign when hash is not exactly 32 bytes.

toViemAccount
} from "@category-labs/mera/viem";
const
const session: Secp256k1SigningSession
session
=
function createSecp256k1SigningSession({ privateKey, }: CreateSigningSessionOptions): Secp256k1SigningSession

Creates a signing session from a secp256k1 private key.

@paramoptions - Signing session inputs.

@returnsA live secp256k1 signing session.

@throwsMeraError with code INPUT_INVALID when privateKey is not a valid secp256k1 scalar.

createSecp256k1SigningSession
({
privateKey: Uint8Array<ArrayBufferLike>

Curve private key. Must be exactly 32 bytes and, for secp256k1, a valid scalar.

privateKey
:
const node: HDKey
node
.
HDKey.privateKey: Uint8Array<ArrayBufferLike>
privateKey
,
});
const
const account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
}
account
=
function toViemAccount(session: Secp256k1SigningSession, options?: ToViemAccountOptions): LocalAccount<"mera">

Adapts a secp256k1 signing session into a viem local account.

Each signing method hashes its input with viem's own hashers and signs the resulting 32-byte digest with session.signDigest, so signing never shows a passkey prompt. The account's address is the EIP-55 checksummed address of the session key, and publicKey is the 65-byte uncompressed public key as hex.

@paramsession - Live secp256k1 signing session that backs the account.

@paramoptions - Adapter inputs.

@returnsA viem local account with source: "mera" implementing signTransaction (honoring a custom serializer), signMessage (EIP-191), signTypedData (EIP-712), signAuthorization (EIP-7702), and raw-hash sign.

@throwsMeraError with code SESSION_ENDED, rejected from every signing method after session.end() has been called.

@throwsMeraError with code INPUT_INVALID, rejected from sign when hash is not exactly 32 bytes.

toViemAccount
(
const session: Secp256k1SigningSession
session
);
import {
function createWalletClient<transport extends Transport, chain extends Chain | undefined = undefined, accountOrAddress extends Account | Address | undefined = undefined, rpcSchema extends RpcSchema | undefined = undefined, const tokens extends Tokens | undefined = undefined>(parameters: WalletClientConfig<transport, chain, accountOrAddress, rpcSchema, tokens>): WalletClient<transport, chain, ParseAccount<accountOrAddress>, rpcSchema, tokens>

Creates a Wallet Client with a given Transport configured for a Chain.

A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.

The Wallet Client supports signing over:

@paramconfig - WalletClientConfig

@returnsA Wallet Client. WalletClient

@example

// JSON-RPC Account import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), })

@example

// Local Account import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…') chain: mainnet, transport: http(), })

createWalletClient
,
function http<rpcSchema extends RpcSchema | undefined = undefined, raw extends boolean = false>(url?: string | undefined, config?: HttpTransportConfig<rpcSchema, raw>): HttpTransport<rpcSchema, raw>

@description Creates a HTTP transport that connects to a JSON-RPC API.

http
,
function parseEther(ether: string, unit?: "wei" | "gwei"): bigint

Converts a string representation of ether to numerical wei.

@example

import { parseEther } from 'viem'

parseEther('420') // 420000000000000000000n

parseEther
} from "viem";
import {
const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}
sepolia
} from "viem/chains";
const
const client: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
};
... 43 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
client
=
createWalletClient<HttpTransport<undefined, false>, {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}, {
...;
}, undefined, undefined>(parameters: {
...;
}): {
...;
}

Creates a Wallet Client with a given Transport configured for a Chain.

A Wallet Client is an interface to interact with Ethereum Account(s) and provides the ability to retrieve accounts, execute transactions, sign messages, etc. through Wallet Actions.

The Wallet Client supports signing over:

@paramconfig - WalletClientConfig

@returnsA Wallet Client. WalletClient

@example

// JSON-RPC Account import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), })

@example

// Local Account import { createWalletClient, custom } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…') chain: mainnet, transport: http(), })

createWalletClient
({
account?: `0x${string}` | Account | {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
} | undefined

The Account to use for the Client. This will be used for Actions that require an account as an argument.

account
,
chain?: Chain | {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
} | undefined

Chain for the client.

chain
:
const sepolia: {
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}
sepolia
,
transport: HttpTransport<undefined, false>

The RPC transport

transport
:
http<undefined, false>(url?: string | undefined, config?: HttpTransportConfig<undefined, false> | undefined): HttpTransport<undefined, false>

@description Creates a HTTP transport that connects to a JSON-RPC API.

http
(),
});
const
const recipient: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
recipient
= "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
const
const hash: `0x${string}`
hash
= await
const client: {
account: {
address: Address;
nonceManager?: NonceManager | undefined;
sign?: ((parameters: {
hash: Hash;
}) => Promise<Hex>) | undefined | undefined;
signAuthorization?: ((parameters: AuthorizationRequest) => Promise<SignAuthorizationReturnType>) | undefined | undefined;
signMessage: ({ message }: {
message: SignableMessage;
}) => Promise<Hex>;
... 4 more ...;
type: "local";
};
... 43 more ...;
extend: <const client extends {
...;
} & ExactPartial<...>>(fn: (client: Client<...>) => client) => Client<...>;
}
client
.
sendTransaction: <{
readonly to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
readonly value: bigint;
}, undefined>(args: SendTransactionParameters<{
blockExplorers: {
readonly default: {
readonly name: "Etherscan";
readonly url: "https://sepolia.etherscan.io";
readonly apiUrl: "https://api-sepolia.etherscan.io/api";
};
};
blockTime?: number | undefined | undefined;
contracts: {
readonly multicall3: {
readonly address: "0xca11bde05977b3631167028862be2a173976ca11";
readonly blockCreated: 751532;
};
readonly ensUniversalResolver: {
readonly address: "0xeeeeeeee14d718c2b47d9923deab1335e144eeee";
readonly blockCreated: 8928790;
};
};
... 15 more ...;
verifyHash?: ((client: Client<...>, parameters: VerifyHashParameters) => Promise<boolean>) | undefined;
}, {
...;
}, undefined, {
readonly to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
readonly value: bigint;
}>) => Promise<SendTransactionReturnType>

Creates, signs, and sends a new transaction to the network.

@paramargs - SendTransactionParameters

@returnsThe Transaction hash. SendTransactionReturnType

@example

import { createWalletClient, custom } from 'viem' import { mainnet } from 'viem/chains'

const client = createWalletClient({ chain: mainnet, transport: custom(window.ethereum), }) const hash = await client.sendTransaction({ account: '0xA0Cf798816D4b9b9866b5330EEa46a18382f251e', to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000000000000000000n, })

@example

// Account Hoisting import { createWalletClient, http } from 'viem' import { privateKeyToAccount } from 'viem/accounts' import { mainnet } from 'viem/chains'

const client = createWalletClient({ account: privateKeyToAccount('0x…'), chain: mainnet, transport: http(), }) const hash = await client.sendTransaction({ to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8', value: 1000000000000000000n, })

sendTransaction
({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
to
:
const recipient: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
recipient
,
value: bigint
value
:
function parseEther(ether: string, unit?: "wei" | "gwei"): bigint

Converts a string representation of ether to numerical wei.

@example

import { parseEther } from 'viem'

parseEther('420') // 420000000000000000000n

parseEther
("0.01"),
});
const session: Secp256k1SigningSession
session
.
function end(): void

Zeroes the session-owned private-key copy; later signing throws SESSION_ENDED.

end
();

sendTransaction fills the missing transaction fields from the RPC, signs through the session and broadcasts.