LibChain Docs Lib Testnet

Build

lib1 in your app

Convert between lib1 and 0x addresses in TypeScript, Python and Go, validate user input, and show addresses the way Lib Wallet does.

lib1 is standard Bech32 (BIP-173) with the prefix lib over the 20 address bytes. Any Bech32 library can encode and decode it. Convert at the edges of your app: show lib1 to people, use 0x with wallets, RPC and contracts.

#Convert

lib1.ts
// npm install @scure/base
import { bech32 } from '@scure/base'

export function toLib1(address: string): string {
  const bytes = Uint8Array.from(address.slice(2).match(/../g)!.map((b) => parseInt(b, 16)))
  return bech32.encode('lib', bech32.toWords(bytes))
}

/** Returns the 0x address, or null if the input isn't a valid lib1 or 0x address. */
export function fromAddressInput(input: string): string | null {
  const s = input.trim()
  if (/^0x[0-9a-fA-F]{40}$/.test(s)) return s.toLowerCase()
  try {
    const { prefix, words } = bech32.decode(s as `${string}1${string}`)
    const bytes = bech32.fromWords(words)
    if (prefix !== 'lib' || bytes.length !== 20) return null
    return '0x' + Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')
  } catch {
    return null // bad checksum, bad characters or mixed case
  }
}
lib1.py
# pip install bech32
from bech32 import bech32_decode, bech32_encode, convertbits


def to_lib1(address: str) -> str:
    return bech32_encode("lib", convertbits(bytes.fromhex(address[2:]), 8, 5))


def from_address_input(value: str) -> str | None:
    s = value.strip()
    if s.startswith("0x") and len(s) == 42:
        return s.lower()
    prefix, data = bech32_decode(s)
    if prefix != "lib" or data is None:
        return None
    raw = convertbits(data, 5, 8, False)
    return "0x" + bytes(raw).hex() if raw and len(raw) == 20 else None
lib1.go
// go get github.com/btcsuite/btcd/btcutil
package lib1

import (
	"encoding/hex"
	"errors"
	"strings"

	"github.com/btcsuite/btcd/btcutil/bech32"
)

func ToLib1(address string) (string, error) {
	raw, err := hex.DecodeString(strings.TrimPrefix(address, "0x"))
	if err != nil || len(raw) != 20 {
		return "", errors.New("not a 20-byte address")
	}
	words, err := bech32.ConvertBits(raw, 8, 5, true)
	if err != nil {
		return "", err
	}
	return bech32.Encode("lib", words)
}

func FromLib1(s string) (string, error) {
	prefix, words, err := bech32.Decode(s)
	if err != nil || prefix != "lib" {
		return "", errors.New("not a lib1 address")
	}
	raw, err := bech32.ConvertBits(words, 5, 8, false)
	if err != nil || len(raw) != 20 {
		return "", errors.New("not a 20-byte address")
	}
	return "0x" + hex.EncodeToString(raw), nil
}

Check your implementation against this pair:

Text
0x form   0x87052f1f17b2b4054b014e93e53d6ce6dc912bda
lib1 form lib1suzj78chk26q2jcpf6f720tvumwfz276ynschv

#In Solidity

Contracts don't need lib1: they see 20-byte address values. Convert in your front end or back end before calling a contract.

#Display guidelines

  • Full addresses where it matters. On review and confirm screens show the whole lib1 address. Lib Wallet splits it over two lines instead of cutting it.
  • Shorten elsewhere by keeping the prefix: lib1hq0tuu…cf8rpj, not …cf8rpj.
  • Use a monospace font so characters don't get confused.
  • Offer the 0x form next to lib1 for people who need it for other tools.
  • Validate on paste and say whether it's valid before the user continues.
Updated 5 November 2026LibChain Docs · Lib Testnet