Skip to main content

Generate New Wallet

Create a new wallet from scratch with a fresh mnemonic:
import { Effect, Layer } from 'effect'
import { Bip39, HDWallet, Hex, Address as A } from '@tevm/voltaire/native'
import { LocalAccount, AccountService, SignerService, Signer, Provider, HttpTransport } from 'voltaire-effect'
import { Secp256k1Live, KeccakLive, Secp256k1Service, KeccakService } from 'voltaire-effect/crypto'

const generateWallet = Effect.gen(function* () {
  // Generate 24-word mnemonic (256 bits entropy)
  const mnemonic = Bip39.generateMnemonic(256)
  
  // Derive seed from mnemonic
  const seed = yield* Effect.promise(() => Bip39.mnemonicToSeed(mnemonic))
  
  // Create HD wallet root
  const root = HDWallet.fromSeed(seed)
  
  // Derive first Ethereum account (m/44'/60'/0'/0/0)
  const account = HDWallet.deriveEthereum(root, 0, 0)
  const privateKey = HDWallet.getPrivateKey(account)
  
  if (!privateKey) {
    return yield* Effect.fail(new Error('Failed to derive private key'))
  }
  
  // Get address from private key
  const secp = yield* Secp256k1Service
  const keccak = yield* KeccakService
  const publicKey = yield* secp.getPublicKey(privateKey)
  const addressBytes = yield* keccak.hash(publicKey.slice(1))
  const address = A.fromBytes(addressBytes.slice(-20))
  
  return {
    mnemonic,
    privateKey: Hex.fromBytes(privateKey),
    address: A.toHex(address)
  }
})

// Compose layers once (reusable)
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)

const wallet = await Effect.runPromise(generateWallet.pipe(Effect.provide(CryptoLayer)))
console.log('Mnemonic:', wallet.mnemonic)
console.log('Address:', wallet.address)

Restore from Mnemonic

Restore an existing wallet using its mnemonic phrase:
import { Effect, Layer } from 'effect'
import { AccountService } from 'voltaire-effect'
import { MnemonicAccount } from 'voltaire-effect/native'
import { Secp256k1Live, KeccakLive } from 'voltaire-effect/crypto'

const mnemonic = "test test test test test test test test test test test junk"

// Compose layers: account needs crypto for key derivation
const AccountLayer = MnemonicAccount(mnemonic).pipe(
  Layer.provide(Layer.mergeAll(Secp256k1Live, KeccakLive))
)

const program = Effect.gen(function* () {
  const account = yield* AccountService
  
  console.log('Restored address:', account.address)
  
  return account.address
})

await Effect.runPromise(program.pipe(Effect.provide(AccountLayer)))

Derive Multiple Addresses

Generate multiple addresses from a single mnemonic:
import { Effect, Layer } from 'effect'
import { AccountService } from 'voltaire-effect'
import { MnemonicAccount } from 'voltaire-effect/native'
import { Secp256k1Live, KeccakLive } from 'voltaire-effect/crypto'

const mnemonic = "test test test test test test test test test test test junk"

// Shared crypto layer
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)

// Create account layer for specific index
const makeAccountLayer = (index: number) =>
  MnemonicAccount(mnemonic, { index }).pipe(Layer.provide(CryptoLayer))

const deriveAddresses = (count: number) =>
  Effect.gen(function* () {
    const addresses: string[] = []
    
    for (let i = 0; i < count; i++) {
      const result = yield* Effect.gen(function* () {
        const account = yield* AccountService
        return account.address
      }).pipe(Effect.provide(makeAccountLayer(i)))
      addresses.push(result)
    }
    
    return addresses
  })

// Get first 5 addresses
const addresses = await Effect.runPromise(deriveAddresses(5))
addresses.forEach((addr, i) => console.log(`Address ${i}: ${addr}`))

Complete Wallet Setup

Full workflow: create wallet, check balance, send transaction:
import { Effect, Layer } from 'effect'
import {
  SignerService,
  Signer,
  getBalance,
  waitForTransactionReceipt,
  Provider,
  HttpTransport
} from 'voltaire-effect'
import { MnemonicAccount } from 'voltaire-effect/native'
import { Secp256k1Live, KeccakLive } from 'voltaire-effect/crypto'

const mnemonic = process.env.MNEMONIC!
const rpcUrl = 'https://mainnet.infura.io/v3/YOUR_KEY'

// Compose all layers once
const CryptoLayer = Layer.mergeAll(Secp256k1Live, KeccakLive)
const TransportLayer = HttpTransport(rpcUrl)
const ProviderLayer = Provider.pipe(Layer.provide(TransportLayer))

const WalletLayer = Layer.mergeAll(
  Signer.Live,
  CryptoLayer,
  ProviderLayer
).pipe(
  Layer.provideMerge(MnemonicAccount(mnemonic).pipe(Layer.provide(CryptoLayer)))
)

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

  // Get wallet address and balance
  const address = signer.address
  const balance = yield* getBalance(address, 'latest')

  console.log(`Address: ${address}`)
  console.log(`Balance: ${balance} wei`)

  // Send transaction if balance is sufficient
  if (balance > 1000000000000000000n) {
    const txHash = yield* signer.sendTransaction({
      to: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',
      value: 100000000000000000n // 0.1 ETH
    })

    const receipt = yield* waitForTransactionReceipt(txHash, { confirmations: 1 })
    console.log(`Transaction confirmed in block ${receipt.blockNumber}`)

    return { address, balance, txHash }
  }

  return { address, balance, txHash: null }
})

await Effect.runPromise(walletWorkflow.pipe(Effect.provide(WalletLayer)))

Wallet with Encryption

Encrypt the mnemonic for secure storage:
import { Effect, Layer } from 'effect'
import { AesGcmService, AesGcmLive, KeccakService, KeccakLive } from 'voltaire-effect/crypto'

// Compose encryption layers once
const EncryptionLayer = Layer.mergeAll(AesGcmLive, KeccakLive)

const encryptMnemonic = (mnemonic: string, password: string) =>
  Effect.gen(function* () {
    const aes = yield* AesGcmService
    const keccak = yield* KeccakService
    
    // Derive encryption key from password
    const passwordBytes = new TextEncoder().encode(password)
    const key = yield* keccak.hash(passwordBytes)
    
    // Encrypt mnemonic
    const nonce = yield* aes.generateNonce()
    const plaintext = new TextEncoder().encode(mnemonic)
    const ciphertext = yield* aes.encrypt(key, plaintext, nonce)
    
    // Combine nonce + ciphertext for storage
    const encrypted = new Uint8Array(nonce.length + ciphertext.length)
    encrypted.set(nonce, 0)
    encrypted.set(ciphertext, nonce.length)
    
    return encrypted
  })

const decryptMnemonic = (encrypted: Uint8Array, password: string) =>
  Effect.gen(function* () {
    const aes = yield* AesGcmService
    const keccak = yield* KeccakService
    
    // Derive encryption key from password
    const passwordBytes = new TextEncoder().encode(password)
    const key = yield* keccak.hash(passwordBytes)
    
    // Split nonce and ciphertext (AES-GCM nonce is 12 bytes)
    const nonce = encrypted.slice(0, 12)
    const ciphertext = encrypted.slice(12)
    
    // Decrypt
    const plaintext = yield* aes.decrypt(key, ciphertext, nonce)
    return new TextDecoder().decode(plaintext)
  })

// Usage - provide layer once at the edge
const mnemonic = "test test test test test test test test test test test junk"
const encrypted = await Effect.runPromise(
  encryptMnemonic(mnemonic, "my-password").pipe(Effect.provide(EncryptionLayer))
)
const decrypted = await Effect.runPromise(
  decryptMnemonic(encrypted, "my-password").pipe(Effect.provide(EncryptionLayer))
)
console.log('Decrypted:', decrypted)

Security Best Practices

Critical Security Considerations:
  • Never log mnemonics or private keys in production
  • Never hardcode mnemonics - use environment variables or secure storage
  • Use strong passwords for encrypted storage (12+ characters, mixed case, numbers, symbols)
  • Consider hardware wallets for production user funds
  • Clear sensitive data from memory when done
  • Use passphrases (25th word) for additional security layer

API Reference

FunctionDescription
Bip39.generateMnemonic(bits)Generate mnemonic (128=12 words, 256=24 words)
Bip39.validateMnemonic(mnemonic)Check if mnemonic is valid
Bip39.mnemonicToSeed(mnemonic, passphrase?)Derive 64-byte seed
HDWallet.fromSeed(seed)Create root key from seed
HDWallet.deriveEthereum(root, account, index)Derive Ethereum key (BIP-44)
MnemonicAccount(mnemonic, options?)Create account layer from mnemonic