Skip to main content
All signing operations require crypto services. See Crypto Overview for details on CryptoLive and individual services.

Simple ETH Transfer

import { Effect, Schedule, Layer } from 'effect'
import { SignerService, Signer, LocalAccount, Provider, HttpTransport, withTimeout } from 'voltaire-effect'
import { Secp256k1Live, KeccakLive } from 'voltaire-effect/crypto'
import { Hex } from '@tevm/voltaire'

const privateKey = Hex.fromHex('0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80')

// Compose layers first
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)
const TransportLayer = HttpTransport({
  url: 'https://eth.llamarpc.com',
  timeout: '30 seconds',
  retrySchedule: Schedule.exponential('500 millis').pipe(Schedule.jittered, Schedule.recurs(3))
})
const DepsLayer = Layer.mergeAll(CryptoLayer, TransportLayer)
const SignerLayer = Signer.fromPrivateKey(privateKey, Provider).pipe(Layer.provide(DepsLayer))

const program = Effect.gen(function* () {
  const signer = yield* SignerService
  return yield* signer.sendTransaction({
    to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
    value: 1000000000000000000n
  })
}).pipe(
  withTimeout('30 seconds'),
  Effect.provide(SignerLayer)
)

EIP-1559 Transaction

import { Effect, Layer } from 'effect'

// Compose layers first
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)
const TransportLayer = HttpTransport({ url: 'https://eth.llamarpc.com' })
const DepsLayer = Layer.mergeAll(CryptoLayer, TransportLayer)
const SignerLayer = Signer.fromPrivateKey(privateKey, Provider).pipe(Layer.provide(DepsLayer))

const program = Effect.gen(function* () {
  const signer = yield* SignerService
  return yield* signer.sendTransaction({
    to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
    value: 1000000000000000000n,
    maxFeePerGas: 50000000000n,
    maxPriorityFeePerGas: 2000000000n
  })
}).pipe(Effect.provide(SignerLayer))

Sign Message (EIP-191)

import { Effect, Layer } from 'effect'

// Compose layers first
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)
const TransportLayer = HttpTransport({ url: 'https://eth.llamarpc.com' })
const DepsLayer = Layer.mergeAll(CryptoLayer, TransportLayer)
const SignerLayer = Signer.fromPrivateKey(privateKey, Provider).pipe(Layer.provide(DepsLayer))

const program = Effect.gen(function* () {
  const signer = yield* SignerService
  return yield* signer.signMessage(Hex.fromString('Hello, Ethereum!'))
}).pipe(Effect.provide(SignerLayer))

Sign EIP-712 Typed Data

import { Effect, Layer } from 'effect'

const typedData = {
  types: {
    EIP712Domain: [
      { name: 'name', type: 'string' },
      { name: 'version', type: 'string' },
      { name: 'chainId', type: 'uint256' },
      { name: 'verifyingContract', type: 'address' }
    ],
    Permit: [
      { name: 'owner', type: 'address' },
      { name: 'spender', type: 'address' },
      { name: 'value', type: 'uint256' },
      { name: 'nonce', type: 'uint256' },
      { name: 'deadline', type: 'uint256' }
    ]
  },
  primaryType: 'Permit',
  domain: { name: 'MyToken', version: '1', chainId: 1n, verifyingContract: '0x...' },
  message: { owner: '0x...', spender: '0x...', value: 1000000n, nonce: 0n, deadline: 1700000000n }
}

// Compose layers first
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)
const TransportLayer = HttpTransport({ url: 'https://eth.llamarpc.com' })
const DepsLayer = Layer.mergeAll(CryptoLayer, TransportLayer)
const SignerLayer = Signer.fromPrivateKey(privateKey, Provider).pipe(Layer.provide(DepsLayer))

const program = Effect.gen(function* () {
  const signer = yield* SignerService
  return yield* signer.signTypedData(typedData)
}).pipe(Effect.provide(SignerLayer))

Browser Wallet (MetaMask)

import { Effect, Layer } from 'effect'
import { JsonRpcAccount, BrowserTransport, Signer, Provider } from 'voltaire-effect'

// Compose layers first
const BrowserLayer = Layer.mergeAll(
  Signer.Live,
  JsonRpcAccount(userAddress),
  Provider
).pipe(Layer.provide(BrowserTransport))

const program = Effect.gen(function* () {
  const signer = yield* SignerService
  const accounts = yield* signer.requestAddresses()
  return yield* signer.sendTransaction({ to: '0x...', value: 1000000000000000000n })
}).pipe(Effect.provide(BrowserLayer))

Send and Wait

import { Effect, Layer } from 'effect'
import { SignerService, waitForTransactionReceipt, Signer, Provider, HttpTransport } from 'voltaire-effect'
import { Secp256k1Live, KeccakLive } from 'voltaire-effect/crypto'

// Compose layers first
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)
const TransportLayer = HttpTransport({ url: 'https://eth.llamarpc.com' })
const DepsLayer = Layer.mergeAll(CryptoLayer, TransportLayer)
const SignerLayer = Signer.fromPrivateKey(privateKey, Provider).pipe(Layer.provide(DepsLayer))

const program = Effect.gen(function* () {
  const signer = yield* SignerService

  const txHash = yield* signer.sendTransaction({ to: '0x...', value: 1000000000000000000n })
  const receipt = yield* waitForTransactionReceipt(txHash, { confirmations: 3, timeout: '60 seconds' })

  return { hash: txHash, status: receipt.status, blockNumber: receipt.blockNumber }
}).pipe(Effect.provide(SignerLayer))

Switch Chain

import { Effect, Layer } from 'effect'

// Compose layers first
const BrowserLayer = Layer.mergeAll(
  Signer.Live,
  JsonRpcAccount(userAddress),
  Provider
).pipe(Layer.provide(BrowserTransport))

const program = Effect.gen(function* () {
  const signer = yield* SignerService
  yield* signer.switchChain(10) // Optimism
}).pipe(Effect.provide(BrowserLayer))

See Also