@base-org/account
Version:
Base Account SDK
166 lines • 5.26 kB
JavaScript
// Copyright (c) 2018-2025 Coinbase, Inc. <https://www.coinbase.com/>
import { Bytes, Hex } from 'ox';
/**
* Field encoding helpers for canonical encoding
*/
/**
* Encode an Ethereum address to 20 bytes
* @param address - Hex address string (with or without 0x prefix)
* @returns 20-byte address
* @throws Error if address is not 20 bytes
*/
export function encodeAddress(address) {
// Normalize to ensure 0x prefix
const normalized = address.startsWith('0x') ? address : `0x${address}`;
// Validate length (0x + 40 hex chars)
if (normalized.length !== 42) {
throw new Error(`Invalid address length: expected 40 hex chars, got ${normalized.length - 2}`);
}
const bytes = Bytes.fromHex(normalized);
if (bytes.length !== 20) {
throw new Error(`Invalid address length: expected 20 bytes, got ${bytes.length}`);
}
return bytes;
}
/**
* Decode 20-byte address to hex string
* @param bytes - 20-byte address
* @returns Hex address string with 0x prefix
*/
export function decodeAddress(bytes) {
if (bytes.length !== 20) {
throw new Error(`Invalid address length: expected 20 bytes, got ${bytes.length}`);
}
return Hex.fromBytes(bytes);
}
/**
* Encode an amount to minimal big-endian bytes (no leading zeros)
* @param value - Amount as bigint or hex string
* @returns Minimal big-endian bytes
*/
export function encodeAmount(value) {
let bigintValue;
if (typeof value === 'string') {
// Handle hex strings
const normalized = value.toLowerCase().replace(/^0x/, '');
if (normalized === '' || normalized === '0') {
return new Uint8Array([0x00]);
}
bigintValue = BigInt(`0x${normalized}`);
}
else {
bigintValue = value;
}
// Handle zero
if (bigintValue === 0n) {
return new Uint8Array([0x00]);
}
// Handle negative (not allowed)
if (bigintValue < 0n) {
throw new Error('Cannot encode negative amounts');
}
// Convert to minimal big-endian bytes using Ox
const hex = bigintValue.toString(16);
// Pad to even length if needed (for proper byte conversion)
const paddedHex = hex.length % 2 === 0 ? hex : `0${hex}`;
return Bytes.fromHex(`0x${paddedHex}`);
}
/**
* Decode minimal big-endian bytes to bigint
* @param bytes - Minimal big-endian bytes (or empty for zero)
* @returns Amount as bigint
*/
export function decodeAmount(bytes) {
if (bytes.length === 0) {
return 0n;
}
// Validate no leading zeros (except for single 0x00)
if (bytes.length > 1 && bytes[0] === 0) {
throw new Error('Invalid amount encoding: leading zeros not allowed');
}
const hex = Hex.fromBytes(bytes);
return BigInt(hex);
}
/**
* Encode capabilities map to protobuf format
* @param caps - Capabilities object
* @returns Map with UTF-8 JSON-encoded values
*/
export function encodeCapabilities(caps) {
const map = new Map();
for (const [key, value] of Object.entries(caps)) {
const json = JSON.stringify(value);
const bytes = new TextEncoder().encode(json);
map.set(key, bytes);
}
return map;
}
/**
* Decode capabilities map from protobuf format
* @param map - Map with UTF-8 JSON-encoded values
* @returns Capabilities object
*/
export function decodeCapabilities(map) {
const caps = {};
for (const [key, bytes] of map.entries()) {
try {
const json = new TextDecoder().decode(bytes);
caps[key] = JSON.parse(json);
}
catch (error) {
throw new Error(`Failed to decode capability '${key}': ${error instanceof Error ? error.message : 'unknown error'}`);
}
}
return caps;
}
/**
* Pad a value to 32 bytes (for EIP-712 encoding)
* @param bytes - Value to pad
* @returns 32-byte padded value
*/
export function pad32(bytes) {
if (bytes.length > 32) {
throw new Error(`Cannot pad value larger than 32 bytes: ${bytes.length}`);
}
const padded = new Uint8Array(32);
// Left-pad with zeros
padded.set(bytes, 32 - bytes.length);
return padded;
}
/**
* Convert bytes to hex string with 0x prefix
* Minimal encoding: no leading zeros unless value is zero
* @param bytes - Bytes to convert
* @returns Hex string
*/
export function bytesToHex(bytes) {
if (bytes.length === 0) {
return '0x0';
}
// For single byte 0, return 0x0
if (bytes.length === 1 && bytes[0] === 0) {
return '0x0';
}
// Use Ox to convert bytes to hex
const fullHex = Hex.fromBytes(bytes);
// Strip leading zeros for minimal encoding
// Keep at least one character (for 0x0)
let hex = fullHex.replace(/^0x0+/, '0x') || '0x0';
// Ensure we don't end up with just '0x'
if (hex === '0x') {
hex = '0x0';
}
return hex;
}
/**
* Convert hex string to bytes
* @param hex - Hex string (with or without 0x prefix)
* @returns Bytes
*/
export function hexToBytes(hex) {
// Normalize to ensure 0x prefix
const normalized = hex.startsWith('0x') ? hex : `0x${hex}`;
// Use Ox's Bytes.fromHex which properly handles odd-length hex strings
return Bytes.fromHex(normalized);
}
//# sourceMappingURL=encoding.js.map