Next-gen Ethereum library for TypeScript
npm i @hazae41/cubane
- 100% TypeScript and ESM
- Minimal dependencies
- Rust-like patterns
- Human-readable code
- Bottom-up abstractions
- Bring your own algorithms
- Zero-copy bytes encoding
- Compile-time codegen
- ABI / ABIv2 (except
fixed
) - Function parsing (e.g.
f(uint256)
) - Recursive-Length Prefix (RLP) coding
- TypedData (EIP-712) hashing
- ENS (e.g.
namehash
) - Signatures
- JSON-ABI parsing
- Return type of functions
- Transactions
Cubane can encode both to hexadecimal string and to Uint8Array, this benchmark aims to check the speed difference between both engines and between other libraries
- @hazae41/cubane@0.1.14
- viem@2.4.1
- ethers@6.10.0
Running on Node 20.3.1 on Apple M1 Max
┌────────────────┬──────────────────┬─────────────┬─────────────┐
│ (index) │ average │ minimum │ maximum │
├────────────────┼──────────────────┼─────────────┼─────────────┤
│ cubane (bytes) │ '4.10 μs/iter' │ '3.08 μs' │ '129.37 μs' │
│ cubane (hex) │ '4.47 μs/iter' │ '3.83 μs' │ '76.13 μs' │
│ viem │ '18.77 μs/iter' │ '16.58 μs' │ '184.83 μs' │
│ ethers │ '211.55 μs/iter' │ '194.08 μs' │ '586.21 μs' │
└────────────────┴──────────────────┴─────────────┴─────────────┘
Summary
- cubane (hex) is 4.20x faster than viem
- cubane (hex) is 47.28x faster than ethers
Summary
- cubane (bytes) is 4.58x faster than viem
- cubane (bytes) is 51.59x faster than ethers
You may need to polyfill Symbol.dispose
import "@hazae41/symbol-dispose-polyfill"
See https://github.com/hazae41/symbol-dispose-polyfill for more
You can bring your own implementation for some algorithms
See https://github.com/hazae41/keccak256 for setup
See https://github.com/hazae41/secp256k1 for setup
See https://github.com/hazae41/base16 for setup
Parse the function from its signature
import { Abi } from "@hazae41/cubane"
const f = Abi.FunctionSignature.parseOrThrow("f(bool,uint256,string)")
Encode the function selector and its arguments (it will return a 0x
-prefixed hex string)
const hex = f.from(true, 123456789n, "hello world").encodeOrThrow()
// c4b71e130000000000000000000000000000000000000000000000000000000000000001...
Cubane provides Saumon macros to generate typed ABI functions
f.abi.macro.ts
import "@hazae41/symbol-dispose-polyfill"
import { Abi } from "@hazae41/cubane"
/**
* Your Keccak256 adapter code
*/
import "./keccak256.js"
export const f = Abi.FunctionSignature.$parse$("f(bool,uint256,string)")
saumon build ./f.abi.macro.ts
main.ts
import { f } from "./f.abi.ts"
/**
* f is fully typed as (bool,uint256,string)
*/
const hex = f.from(true, 123456789n, "hello world").encodeOrThrow()
// c4b71e130000000000000000000000000000000000000000000000000000000000000001...
You can generate the function from its signature
> import { Abi } from "@hazae41/cubane"
> console.log(Abi.FunctionSignature.parseOrThrow("f(bool,uint256,string)").codegen())
Paste it in a file f.abi.ts
export const f = /*generated code*/
Encode the function selector and its arguments (it will return a 0x
-prefixed hex string)
import { f } from "./f.abi.ts"
/**
* f is fully typed as (bool,uint256,string)
*/
const hex = f.from(true, 123456789n, "hello world").encodeOrThrow()
// c4b71e130000000000000000000000000000000000000000000000000000000000000001...
import { RlpString, RlpList } from "@hazae41/cubane"
import { Writable } from "@hazae41/binary"
const cat = RlpString.from(Bytes.fromUtf8("cat"))
const dog = RlpString.from(Bytes.fromUtf8("dog"))
const catAndDog = RlpList.from([cat, dog])
const bytes = Writable.writeToBytesOrThrow(catAndDog)
import { RlpString, RlpList } from "@hazae41/cubane"
import { Writable } from "@hazae41/binary"
const cat = RlpString.from(Bytes.fromUtf8("cat"))
const dog = RlpString.from(Bytes.fromUtf8("dog"))
const catAndDog = RlpList.from([cat, dog])
const bytes = Writable.writeToBytesOrThrow(catAndDog)
const hex = "0x" + Base16.get().getOrThrow().encodeOrThrow(bytes)
import { Bytes } from "@hazae41/bytes"
import { ExtPrivateKey } from "@hazae41/cubane"
import { Secp256k1 } from "@hazae41/secp256k1"
import { Base16 } from "@hazae41/base16"
const message = "hello world"
const privateKeyBytes = Bytes.random(32)
const privateKeyExt = new ExtPrivateKey(Secp256k1.get().PrivateKey.importOrThrow(privateKeyBytes))
const signatureExt = privateKeyExt.signPersonalMessageOrThrow(message)
const signatureBytes = signatureExt.value.exportOrThrow().copyAndDispose()
const signatureZeroHex = `0x${Base16.get().getOrThrow().encodeOrThrow(signatureBytes)}`
import { ExtPublicKey, Address } from "@hazae41/cubane"
const recoveredPublicKeyExt = ExtPublicKey.recoverPersonalMessageOrThrow(message, signatureExt)
const recoveredPublicKeyBytes = recoveredPublicKeyExt.value.exportUncompressedOrThrow().copyAndDispose()
const recoveredAddressZeroHex = Address.computeOrThrow(recoveredPublicKeyBytes)