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
| Function | Description |
|---|
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 |