Ethereum API

The EthereumApp class provides Ethereum address retrieval, message signing, transaction signing, EIP-712 typed data signing, and EIP-7702 authorization signing.

Import

import {
  EthereumApp,
  DEFAULT_ETH_PATH,
  type SignatureResult,
} from '@openloop/sdk-core'

Initialization

const eth = new EthereumApp(transport)

transport is any transport instance that implements the ITransport interface.

Methods

Method Description
getAddress(path) Retrieves the address and public key
signPersonalMessage(path, message, chainId?) EIP-191 personal_sign
signTransaction(path, rawTx) Signs an RLP-encoded transaction
signTypedData(path, domainHash, msgHash, chainId?) Signs EIP-712 typed data
signAuthorization(path, authorizationRlp) Signs an EIP-7702 authorization

getAddress

Retrieves the Ethereum address and public key from the device.

async getAddress(path: string): Promise<{
  publicKey: string  // uncompressed public key (hex, 65 bytes)
  address: string    // checksummed address ("0x...")
}>

Parameters

Name Type Description
path string BIP44 path (e.g. "44'/60'/0'/0/0")

Example

import { DEFAULT_ETH_PATH } from '@openloop/sdk-core'

const { address, publicKey } = await eth.getAddress(DEFAULT_ETH_PATH)
// address: "0x742d35Cc6634C0532925a3b844Bc9e7595f2bD18"
// publicKey: "04a1b2c3d4..."

Default Path

DEFAULT_ETH_PATH = "44'/60'/0'/0/0"


signPersonalMessage

Signs a message with EIP-191 personal_sign. It uses the Openloop-specific extended protocol that is aware of the chain ID.

async signPersonalMessage(
  path: string,
  message: string,
  chainId?: number
): Promise<SignatureResult>

Parameters

Name Type Default Description
path string BIP44 path
message string The message — a UTF-8 string or 0x-prefixed hex
chainId number 1 EVM chain ID

Returns: SignatureResult

Field Type Description
v number Recovery ID (27 or 28)
r string R value (32 bytes hex, no 0x)
s string S value (32 bytes hex, no 0x)

Example

const sig = await eth.signPersonalMessage(
  DEFAULT_ETH_PATH,
  'Hello, Openloop!',
  1
)
// sig.v = 27
// sig.r = "a1b2c3..."
// sig.s = "d4e5f6..."

Note: Long messages are split into chunks automatically before they are sent to the device — 150 bytes for the first chunk, then 255 bytes each.


signTransaction

Signs an RLP-encoded Ethereum transaction.

async signTransaction(
  path: string,
  rawTx: string
): Promise<SignatureResult>

Parameters

Name Type Description
path string BIP44 path
rawTx string RLP-encoded transaction (hex, with or without the 0x prefix)

Example

const sig = await eth.signTransaction(
  DEFAULT_ETH_PATH,
  'f86c0a8502540be400825208...'
)

signTypedData

Signs EIP-712 typed data. Pass the pre-hashed domainSeparator and message.

async signTypedData(
  path: string,
  domainSeparatorHash: string,
  messageHash: string,
  chainId?: number
): Promise<SignatureResult>

Parameters

Name Type Default Description
path string BIP44 path
domainSeparatorHash string EIP-712 domain separator hash (32 bytes hex)
messageHash string EIP-712 message hash (32 bytes hex)
chainId number 1 EVM chain ID

Example

const sig = await eth.signTypedData(
  DEFAULT_ETH_PATH,
  'aabbccddee...', // domainSeparatorHash
  '1122334455...', // messageHash
  1
)

signAuthorization

Signs an EIP-7702 authorization — Set Code for an EOA.

async signAuthorization(
  path: string,
  authorizationRlp: string
): Promise<SignatureResult>

Parameters

Name Type Description
path string BIP44 path
authorizationRlp string Hex of RLP([chain_id, address, nonce])

Example

const sig = await eth.signAuthorization(
  DEFAULT_ETH_PATH,
  'c3010a...' // RLP([1, "0x...", 0])
)

ethers.js Integration

import { ethers } from 'ethers'
import { EthereumApp, DEFAULT_ETH_PATH } from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

const transport = await WebHidTransport.connect()
const eth = new EthereumApp(transport)

// Get the address
const { address } = await eth.getAddress(DEFAULT_ETH_PATH)

// Provider (connection to an Ethereum node)
const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com')

// Build the transaction
const tx = {
  to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
  value: ethers.parseEther('0.01'),
  gasLimit: 21000n,
  gasPrice: (await provider.getFeeData()).gasPrice,
  nonce: await provider.getTransactionCount(address),
  chainId: 1n,
}

// RLP-encode and sign
const unsignedTx = ethers.Transaction.from(tx).unsignedSerialized
const sig = await eth.signTransaction(DEFAULT_ETH_PATH, unsignedTx)

// Assemble the signed transaction
const signedTx = ethers.Transaction.from({
  ...tx,
  signature: {
    v: sig.v,
    r: '0x' + sig.r,
    s: '0x' + sig.s,
  },
})

// Broadcast
const txResponse = await provider.broadcastTransaction(signedTx.serialized)

viem Integration

import { createPublicClient, http, parseEther } from 'viem'
import { mainnet } from 'viem/chains'
import { EthereumApp, DEFAULT_ETH_PATH } from '@openloop/sdk-core'
import { WebHidTransport } from '@openloop/transport-webhid'

const transport = await WebHidTransport.connect()
const eth = new EthereumApp(transport)

const { address } = await eth.getAddress(DEFAULT_ETH_PATH)

// personal_sign
const sig = await eth.signPersonalMessage(
  DEFAULT_ETH_PATH,
  'Hello from viem!',
  1
)

// Convert to viem's signature format
const signature = `0x${sig.r}${sig.s}${sig.v.toString(16)}` as `0x${string}`

Next Steps