Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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 hashing
  • signTypedData() applies EIP-712 typed data hashing
  • signTransaction() serializes and signs transactions
  • sign() 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:

FormatExampleDescription
String'Hello'UTF-8 encoded message
Raw hex{ raw: '0x...' }Hex-encoded bytes
Uint8Arraynew 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:

TypeRequired fields
EIP-1559chainId, maxFeePerGas, maxPriorityFeePerGas
EIP-2930chainId, gasPrice, accessList
LegacygasPrice
// 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>;
}
Copyright © 2023-present Alamas Labs, Inc