In early 2021, the Filecoin ecosystem had a problem. Developers building DeFi applications needed to sign transactions and interact with built-in actors like Payment Channels. The existing solutions required WASM bindings or Rust dependencies. This made integration painful for React Native apps and browser-based projects.
The Filecoin Foundation awarded a grant to Blits Labs to solve this. The goal was to build decentralized loans using Atomic Swaps. But first, we needed a signing library that worked in pure JavaScript. No WASM. No native dependencies. Just TypeScript that runs anywhere.
I built filecoin-js-signer to fill that gap. It provides everything needed to create wallets, sign transactions, and interact with Filecoin’s Payment Channel and Multisig actors.
What It Does
The library has two main components: a Signer and a Client.
The Signer handles offline operations. It generates mnemonics and derives keys using BIP-32 paths. It creates unsigned messages for sending FIL, opening payment channels, creating vouchers, and managing multisig wallets. It signs messages with secp256k1 and verifies signatures.
The Client wraps the Signer and adds network communication. It connects to Filecoin nodes via JSON-RPC. It handles nonce management, gas estimation, and message broadcasting. You can create a payment channel, sign it, and broadcast it in one call.
Payment channels deserve special attention. They enable off-chain micropayments with on-chain settlement. The library supports the full lifecycle: creating channels, issuing vouchers with optional hash/secret pairs, redeeming vouchers, settling, and collecting funds. This was the missing piece for building Atomic Swaps.
Technical Deep-Dive
Pure JavaScript Cryptography
The biggest challenge was avoiding WASM and native modules. I implemented all cryptographic operations using pure JavaScript libraries.
Key derivation uses BIP-32 with the Filecoin derivation path. The library generates a master key from a mnemonic, then derives child keys for specific accounts. Each key pair includes a secp256k1 private key, public key, and Filecoin address.
Message signing computes a Blake2b-256 hash of the CBOR-encoded message. Then it signs the hash with secp256k1. The signature format matches what Lotus expects: a 65-byte recoverable signature with the recovery byte appended.
Address encoding was tricky. Filecoin uses a custom format with protocol indicators, checksums, and base32 encoding. I had to match the exact behavior of the Rust implementation to ensure interoperability.
CBOR Serialization
Filecoin messages use CBOR (Concise Binary Object Representation) for serialization. The structure matters. Fields must appear in the right order. Numbers must use the correct encoding.
I used a forked version of the IPLD DAG-CBOR library. The fork handles BigNumbers correctly and produces byte-for-byte identical output to the Rust implementation. This was important for signature verification. A single byte difference breaks everything.
Vouchers required extra care. They contain nested structures with amounts, nonces, lanes, and optional secrets. The serialization includes a signature field that gets populated after signing. I had to implement custom logic to handle the signed and unsigned states.
Actor Method Parameters
Filecoin built-in actors expect parameters in a specific format. Creating a payment channel requires encoding the sender and recipient addresses, then wrapping them with a CID reference to the actor code.
The Init actor creates new actors. You send it an “Exec” message with the actor type and constructor parameters. It returns the new actor’s address in the message receipt.
Multisig operations follow a similar pattern. Proposals encode the destination address, amount, and method. Approvals reference the proposal by ID and include a hash of the expected transaction. This prevents front-running attacks.
RPC Client Design
The Client layer handles all network communication. It wraps a JSON-RPC client that connects to any Filecoin node (Lotus, Glif, etc.).
Each operation follows the same flow: get the current nonce, create the message, estimate gas, sign, and broadcast. The library supports both fire-and-forget mode (returns immediately with a CID) and wait mode (blocks until the message is included in a block).
Error handling was important. Network issues, insufficient funds, gas estimation failures, and invalid signatures all need clear error messages. The library throws typed exceptions so callers can handle specific cases.
Learnings
Building filecoin-js-signer taught me about the challenges of blockchain library development.
Interoperability matters more than anything. A signing library must produce outputs that nodes accept. This means matching exact byte sequences, hash algorithms, and encoding formats. Extensive testing against the reference implementation was required.
Documentation drives adoption. I created an API reference with examples for every method. Developers copy-paste from docs. Good examples save hours of debugging.
The library served its purpose. It enabled Blits Labs to build Filecoin Loans. It removed the WASM barrier for JavaScript developers. And it demonstrated that pure TypeScript can handle complex blockchain operations.


