Viem integration
The Openfort Node SDK integrates with viem to provide type-safe signing operations for EVM backend wallets. This integration handles message hashing, transaction serialization, and typed data encoding according to Ethereum standards.
Overview
Backend wallet accounts implement signing methods that use viem internally:
signMessage()applies EIP-191 message hashingsignTypedData()applies EIP-712 typed data hashingsignTransaction()serializes and signs transactionssign()signs raw hashes directly
All methods return signatures as hex strings compatible with viem and other EVM libraries.
Signing messages
Sign arbitrary messages using EIP-191 personal sign format. The SDK hashes the message with the "\x19Ethereum Signed Message:\n" prefix before signing.
import Openfort from '@openfort/openfort-node';
const openfort = new Openfort(YOUR_SECRET_KEY, {
walletSecret: YOUR_WALLET_SECRET,
});
const account = await openfort.accounts.evm.backend.create({
name: 'SigningWallet',
});
// Sign a string message
const signature = await account.signMessage({
message: 'Hello, Openfort!',
});
// Sign raw bytes
const rawSignature = await account.signMessage({
message: { raw: '0x68656c6c6f' },
});
// Sign a Uint8Array
const bytes = new Uint8Array([72, 101, 108, 108, 111]);
const bytesSignature = await account.signMessage({
message: bytes,
});Message formats
The signMessage method accepts three message formats:
| Format | Example | Description |
|---|---|---|
| String | 'Hello' | UTF-8 encoded message |
| Raw hex | { raw: '0x...' } | Hex-encoded bytes |
| Uint8Array | new Uint8Array([...]) | Raw byte array |
Signing typed data
Sign structured data according to EIP-712. The SDK uses viem's hashTypedData to compute the typed data hash before signing.
import Openfort from '@openfort/openfort-node';
const openfort = new Openfort(YOUR_SECRET_KEY, {
walletSecret: YOUR_WALLET_SECRET,
});
const account = await openfort.accounts.evm.backend.create({
name: 'TypedDataWallet',
});
// Define EIP-712 typed data
const signature = await account.signTypedData({
domain: {
name: 'MyApp',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC',
},
types: {
Person: [
{ name: 'name', type: 'string' },
{ name: 'wallet', type: 'address' },
],
Mail: [
{ name: 'from', type: 'Person' },
{ name: 'to', type: 'Person' },
{ name: 'contents', type: 'string' },
],
},
primaryType: 'Mail',
message: {
from: {
name: 'Alice',
wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826',
},
to: {
name: 'Bob',
wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
},
contents: 'Hello, Bob!',
},
});Permit signatures
A common use case for typed data signing is ERC-20 permit signatures:
import Openfort from '@openfort/openfort-node';
const openfort = new Openfort(YOUR_SECRET_KEY, {
walletSecret: YOUR_WALLET_SECRET,
});
const account = await openfort.accounts.evm.backend.get({
address: '0x...',
});
// Sign an ERC-20 permit
const permitSignature = await account.signTypedData({
domain: {
name: 'USD Coin',
version: '2',
chainId: 1,
verifyingContract: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
types: {
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
],
},
primaryType: 'Permit',
message: {
owner: account.address,
spender: '0x...',
value: 1000000n,
nonce: 0n,
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
},
});Signing transactions
Sign EVM transactions for later broadcast. The SDK serializes the transaction, signs it, and returns the fully signed transaction ready to submit to the network.
import Openfort from '@openfort/openfort-node';
import { parseEther, parseGwei } from 'viem';
const openfort = new Openfort(YOUR_SECRET_KEY, {
walletSecret: YOUR_WALLET_SECRET,
});
const account = await openfort.accounts.evm.backend.create({
name: 'TransactionWallet',
});
// Sign an EIP-1559 transaction
const signedTx = await account.signTransaction({
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
value: parseEther('0.001'),
nonce: 0,
gas: 21000n,
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('1'),
chainId: 1,
});
// The signed transaction is ready to broadcast
console.log('Signed transaction:', signedTx);Transaction types
The signTransaction method accepts any viem TransactionSerializable type:
| Type | Required fields |
|---|---|
| EIP-1559 | chainId, maxFeePerGas, maxPriorityFeePerGas |
| EIP-2930 | chainId, gasPrice, accessList |
| Legacy | gasPrice |
// Legacy transaction
const legacyTx = await account.signTransaction({
to: '0x...',
value: parseEther('0.1'),
nonce: 0,
gas: 21000n,
gasPrice: parseGwei('30'),
});
// EIP-2930 transaction with access list
const accessListTx = await account.signTransaction({
to: '0x...',
value: 0n,
nonce: 1,
gas: 50000n,
gasPrice: parseGwei('30'),
chainId: 1,
accessList: [
{
address: '0x...',
storageKeys: ['0x...'],
},
],
});Signing raw hashes
Sign a raw 32-byte hash without any additional encoding. Use this for custom signing schemes or when you need direct control over what gets signed.
import Openfort from '@openfort/openfort-node';
import { keccak256, toBytes } from 'viem';
const openfort = new Openfort(YOUR_SECRET_KEY, {
walletSecret: YOUR_WALLET_SECRET,
});
const account = await openfort.accounts.evm.backend.create({
name: 'RawSigningWallet',
});
// Compute a custom hash
const hash = keccak256(toBytes('Custom data to sign'));
// Sign the hash directly (no EIP-191 prefix)
const signature = await account.sign({ hash });Type exports
The SDK re-exports viem types for convenience:
import type {
Address,
Hash,
Hex,
SignableMessage,
TransactionSerializable,
TypedData,
TypedDataDefinition,
} from '@openfort/openfort-node';Using with viem clients
Combine backend wallet signing with viem's public client for complete transaction workflows:
import Openfort from '@openfort/openfort-node';
import { createPublicClient, http, parseEther, parseGwei } from 'viem';
import { mainnet } from 'viem/chains';
const openfort = new Openfort(YOUR_SECRET_KEY, {
walletSecret: YOUR_WALLET_SECRET,
});
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
});
const account = await openfort.accounts.evm.backend.get({
address: '0x...',
});
// Get current nonce and gas prices
const [nonce, gasPrice] = await Promise.all([
publicClient.getTransactionCount({ address: account.address }),
publicClient.getGasPrice(),
]);
// Sign the transaction
const signedTx = await account.signTransaction({
to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
value: parseEther('0.001'),
nonce,
gas: 21000n,
maxFeePerGas: gasPrice,
maxPriorityFeePerGas: parseGwei('1'),
chainId: mainnet.id,
});
// Broadcast the transaction
const txHash = await publicClient.sendRawTransaction({
serializedTransaction: signedTx,
});
console.log('Transaction hash:', txHash);Account interface
Each EVM backend account exposes the following signing methods:
interface EvmAccount {
/** Account unique identifier */
id: string;
/** Ethereum address */
address: Address;
/** Custody type (always 'Developer' for backend wallets) */
custody: 'Developer';
/** Sign a raw hash */
sign(parameters: { hash: Hash }): Promise<Hex>;
/** Sign a message (EIP-191) */
signMessage(parameters: { message: SignableMessage }): Promise<Hex>;
/** Sign a transaction */
signTransaction(transaction: TransactionSerializable): Promise<Hex>;
/** Sign typed data (EIP-712) */
signTypedData<T extends TypedData>(
parameters: TypedDataDefinition<T>
): Promise<Hex>;
}