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
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)