iso-filecoin
Version:
Isomorphic filecoin abstractions for RPC, signatures, address, token and wallet
351 lines (318 loc) • 9.21 kB
JavaScript
;
var blake2b = require('../node_modules/.pnpm/@noble_hashes@1.8.0/node_modules/@noble/hashes/esm/blake2b.cjs');
var sha3 = require('../node_modules/.pnpm/@noble_hashes@1.8.0/node_modules/@noble/hashes/esm/sha3.cjs');
var utf8 = require('../node_modules/.pnpm/iso-base@4.1.0/node_modules/iso-base/src/utf8.cjs');
var utils = require('../node_modules/.pnpm/iso-base@4.1.0/node_modules/iso-base/src/utils.cjs');
var index = require('../node_modules/.pnpm/iso-kv@3.0.3/node_modules/iso-kv/src/index.cjs');
var memory = require('../node_modules/.pnpm/iso-kv@3.0.3/node_modules/iso-kv/src/drivers/memory.cjs');
var src_chains = require('./chains.cjs');
/**
* @typedef {import('./types').NetworkPrefix} NetworkPrefix
*/
/**
* Signature types filecoin network has to sign transactions
*/
const SIGNATURES = /** @type {const} */ ({
SECP256K1: 1,
BLS: 3,
});
/**
* Filecoin network prefixes
*/
const NETWORKS = /** @type {const} */ ({
mainnet: 'f',
testnet: 't',
});
/**
* Get network prefix from network
*
* @param {import("./types.js").Network} network
* @example
* ```ts twoslash
* import { getNetworkPrefix } from 'iso-filecoin/utils'
*
* const prefix = getNetworkPrefix('mainnet')
* // => 'f'
*/
function getNetworkPrefix(network) {
return network === 'mainnet' ? 'f' : 't'
}
/**
* Get network from prefix
*
* @param {NetworkPrefix} networkPrefix
* @returns {import('./types').Network}
* @example
* ```ts twoslash
* import { getNetwork } from 'iso-filecoin/utils'
*
* const network = getNetwork('f')
* // => 'mainnet'
*/
function getNetwork(networkPrefix) {
return networkPrefix === 'f' ? 'mainnet' : 'testnet'
}
/**
* Returns the third position from derivation path
*
* @param {string} path - path to parse
* @returns {import('./types.js').Network}
* @example
* ```ts twoslash
* import { getNetworkFromPath } from 'iso-filecoin/utils'
*
* const network = getNetworkFromPath("m/44'/461'/0'/0/0")
* // => 'testnet'
*/
function getNetworkFromPath(path) {
const type = parseDerivationPath(path).coinType;
if (type === 1) {
return 'testnet'
}
return 'mainnet'
}
/**
* Get network from any chain designation
*
* @param {number | string} chainId
*/
function getNetworkFromChainId(chainId) {
switch (chainId) {
case src_chains.testnet.id:
case src_chains.testnet.chainId:
case src_chains.testnet.caipNetworkId:
case 'testnet':
return 'testnet'
case src_chains.mainnet.id:
case src_chains.mainnet.chainId:
case src_chains.mainnet.caipNetworkId:
case 'mainnet':
return 'mainnet'
default:
throw new Error(`Unknown chain id: ${chainId}`)
}
}
/**
* Derivation path from chain
*
* @param {import('./types').Network} network
* @param {number} [index=0] - Account index (default 0)
* @example
* ```ts twoslash
* import { pathFromNetwork } from 'iso-filecoin/utils'
*
* const path = pathFromNetwork('mainnet')
* // => 'm/44'/461'/0'/0/0'
*/
function pathFromNetwork(network, index = 0) {
switch (network) {
case 'mainnet':
return `m/44'/461'/0'/0/${index}`
case 'testnet':
return `m/44'/1'/0'/0/${index}`
default:
throw new Error(`Unknown network: ${network}`)
}
}
/**
* Checks if the prefix is a valid network prefix
*
* @param {string} prefix
* @returns {prefix is NetworkPrefix}
* @example
* ```ts twoslash
* import { checkNetworkPrefix } from 'iso-filecoin/utils'
*
* checkNetworkPrefix('f') // true
* checkNetworkPrefix('t') // true
* checkNetworkPrefix('x') // false
* ```
*/
function checkNetworkPrefix(prefix) {
return Object.values(NETWORKS).includes(/** @type {NetworkPrefix} */ (prefix))
}
const BIP_32_PATH_REGEX = /^\d+'?$/u;
/**
* Parse a derivation path into its components
*
* @see https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#path-levels
* @param {string} path - The derivation path to parse
* @returns {import('./types').DerivationPathComponents} An object containing the derivation path components
* @example
* ```ts twoslash
* import { parseDerivationPath } from 'iso-filecoin/utils'
*
* const components = parseDerivationPath("m/44'/461'/0'/0/0")
* // {
* // purpose: 44,
* // coinType: 461,
* // account: 0,
* // change: 0,
* // addressIndex: 0
* // }
* ```
*/
function parseDerivationPath(path) {
const parts = path.split('/');
if (parts.length !== 6) {
throw new Error(
"Invalid derivation path: depth must be 5 \"m / purpose' / coin_type' / account' / change / address_index\""
)
}
if (parts[0] !== 'm') {
throw new Error('Invalid derivation path: depth 0 must be "m"')
}
if (parts[1] !== "44'") {
throw new Error(
'Invalid derivation path: The "purpose" node (depth 1) must be the string "44\'"'
)
}
if (!BIP_32_PATH_REGEX.test(parts[2]) || !parts[2].endsWith("'")) {
throw new Error(
'Invalid derivation path: The "coin_type" node (depth 2) must be a hardened BIP-32 node.'
)
}
if (!BIP_32_PATH_REGEX.test(parts[3]) || !parts[3].endsWith("'")) {
throw new Error(
'Invalid derivation path: The "account" node (depth 3) must be a hardened BIP-32 node.'
)
}
if (!BIP_32_PATH_REGEX.test(parts[4])) {
throw new Error(
'Invalid derivation path: The "change" node (depth 4) must be a BIP-32 node.'
)
}
if (!BIP_32_PATH_REGEX.test(parts[5])) {
throw new Error(
'Invalid derivation path: The "address_index" node (depth 5) must be a BIP-32 node.'
)
}
const purpose = Number.parseInt(parts[1], 10);
const coinType = Number.parseInt(parts[2], 10);
const account = Number.parseInt(parts[3], 10);
const change = Number.parseInt(parts[4], 10);
const addressIndex = Number.parseInt(parts[5], 10);
if (
Number.isNaN(purpose) ||
Number.isNaN(coinType) ||
Number.isNaN(account) ||
Number.isNaN(change) ||
Number.isNaN(addressIndex)
) {
throw new TypeError(
'Invalid derivation path: some of the components cannot be parsed as numbers'
)
}
return { purpose, coinType, account, change, addressIndex }
}
/**
* Checksum ethereum address
*
* @param {string} address - Ethereum address
* @returns {string} Checksummed ethereum address
* @example
* ```ts twoslash
* import { checksumEthAddress } from 'iso-filecoin/utils'
*
* const address = '0xfb6916095ca1df60bb79ce92ce3ea74c37c5d359'
* const checksummed = checksumEthAddress(address)
* // => '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359'
* ```
*/
function checksumEthAddress(address) {
const hexAddress = address.substring(2).toLowerCase();
const hash = sha3.keccak_256(utf8.utf8.decode(hexAddress));
const addressArr = hexAddress.split('');
for (let i = 0; i < 40; i += 2) {
if (hash[i >> 1] >> 4 >= 8 && addressArr[i]) {
addressArr[i] = addressArr[i].toUpperCase();
}
if ((hash[i >> 1] & 0x0f) >= 8 && addressArr[i + 1]) {
addressArr[i + 1] = addressArr[i + 1].toUpperCase();
}
}
const result = `0x${addressArr.join('')}`;
return result
}
const defaultDriver = new memory.MemoryDriver();
/**
* Get cache instance from cache config
*
* @param {import('./types').Cache} cache - Cache config
* @returns {import('iso-kv').KV}
* @example
* ```js
* import { getCache } from 'iso-filecoin'
* import { MemoryDriver } from 'iso-kv/drivers/memory.js'
*
* // use default memory driver
* const cache = getCache(true)
*
* // use custom driver
* const customCache = getCache(new MemoryDriver())
* ```
*/
function getCache(cache) {
let kv;
if (cache === true || cache === undefined) {
kv = new index.KV({ driver: defaultDriver });
}
if (
typeof cache === 'object' &&
'get' in cache &&
'has' in cache &&
'set' in cache
) {
kv = new index.KV({ driver: cache });
}
return kv ?? new index.KV({ driver: defaultDriver })
}
/**
* Create a Lotus CID from a BufferSource
*
* @param {Uint8Array} data
* @example
* ```js
* import { lotusCid } from 'iso-filecoin/utils'
*
* const data = new Uint8Array([1, 2, 3])
* const cid = lotusCid(data)
* ```
*/
function lotusCid(data) {
return utils.concat([
// cidv1 1byte + dag-cbor 1byte + blake2b-256 4bytes
Uint8Array.from([0x01, 0x71, 0xa0, 0xe4, 0x02, 0x20]),
blake2b.blake2b(data, {
dkLen: 32,
}),
])
}
/**
* Check if an error is a ZodError
*
* @param {unknown} err
* @returns {err is import('zod').ZodError}
*/
function isZodErrorLike(err) {
return (
err instanceof Error &&
err.name === 'ZodError' &&
'issues' in err &&
Array.isArray(err.issues)
)
}
exports.BIP_32_PATH_REGEX = BIP_32_PATH_REGEX;
exports.NETWORKS = NETWORKS;
exports.SIGNATURES = SIGNATURES;
exports.checkNetworkPrefix = checkNetworkPrefix;
exports.checksumEthAddress = checksumEthAddress;
exports.getCache = getCache;
exports.getNetwork = getNetwork;
exports.getNetworkFromChainId = getNetworkFromChainId;
exports.getNetworkFromPath = getNetworkFromPath;
exports.getNetworkPrefix = getNetworkPrefix;
exports.isZodErrorLike = isZodErrorLike;
exports.lotusCid = lotusCid;
exports.parseDerivationPath = parseDerivationPath;
exports.pathFromNetwork = pathFromNetwork;