UNPKG

@unique-nft/utils

Version:

A tiny library to work with Substrate and Ethereum addresses and do some more

616 lines (608 loc) 23.5 kB
import { __export } from "./chunk-SMH5LAMQ.mjs"; // src/Address/index.ts var Address_exports = {}; __export(Address_exports, { Address: () => Address, algorithms: () => imports_exports, collection: () => collection, compare: () => compare, constants: () => constants_exports, extract: () => extract, is: () => is, mirror: () => mirror, nesting: () => nesting, normalize: () => normalize, substrate: () => substrate, validate: () => validate }); // src/Address/constants.ts var constants_exports = {}; __export(constants_exports, { COLLECTION_ADDRESS_PREFIX: () => COLLECTION_ADDRESS_PREFIX, NESTING_PREFIX: () => NESTING_PREFIX, STATIC_ADDRESSES: () => STATIC_ADDRESSES }); var STATIC_ADDRESSES = { contractHelpers: "0x842899ECF380553E8a4de75bF534cdf6fBF64049", collectionHelpers: "0x6C4E9fE1AE37a41E93CEE429e8E1881aBdcbb54F" }; var NESTING_PREFIX = "0xf8238ccfff8ed887463fd5e0"; var COLLECTION_ADDRESS_PREFIX = "0x17c4e6453cc49aaaaeaca894e6d9683e"; // src/Address/imports.ts var imports_exports = {}; __export(imports_exports, { base58: () => base58, base64: () => base64, basex: () => basex, blake2b: () => blake2b, keccak_256: () => keccak_256 }); import basex from "base-x"; import { keccak_256 } from "@noble/hashes/sha3"; import { blake2b } from "@noble/hashes/blake2b"; var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var base58 = basex(BASE58_ALPHABET); var base64 = basex(BASE64_ALPHABET); // src/Address/ethereum.ts import { HexString } from "utf-helpers"; var DWORDHexString = { _checkU32: (num) => { if (typeof num !== "number") throw new Error(`Passed number is not a number: ${typeof num}, ${num}`); if (isNaN(num)) throw new Error(`Passed number is NaN: ${num}`); if (num < 0) throw new Error(`Passed number is less than 0: ${num}`); if (num > 4294967295) throw new Error(`Passed number is more than 2**32: ${num}`); if (num !== Math.floor(num)) throw new Error(`Passed number is not an integer number: ${num}`); return num; }, fromNumber: (n) => { return DWORDHexString._checkU32(n).toString(16).padStart(8, "0"); }, toNumber: (s) => { const num = parseInt(s, 16); if (isNaN(num)) throw new Error(`Passed string is not hexadecimal: ${s}`); return DWORDHexString._checkU32(num); } }; var unsafeNormalizeEthereumAddress = (address) => { const addr = address.toLowerCase().replace(/^0x/i, ""); const addressHash = HexString.fromU8a(keccak_256(addr)).replace(/^0x/i, ""); let checksumAddress = "0x"; for (let i = 0; i < addr.length; i++) { checksumAddress += parseInt(addressHash[i], 16) > 7 ? addr[i].toUpperCase() : addr[i]; } return checksumAddress; }; var normalizeEthereumAddress = (address) => { validate.ethereumAddress(address); return unsafeNormalizeEthereumAddress(address); }; var compareEthereumAddresses = (address1, address2) => { const addr1 = typeof address1 === "string" ? address1 : address1.Ethereum || address1.ethereum; const addr2 = typeof address2 === "string" ? address2 : address2.Ethereum || address2.ethereum; if (!addr1 || !addr2 || !is.ethereumAddress(addr1) || !is.ethereumAddress(addr2)) { return false; } return addr1.toLowerCase() === addr2.toLowerCase(); }; var collectionIdToEthAddress = (collectionId) => { validate.collectionId(collectionId); return unsafeNormalizeEthereumAddress( COLLECTION_ADDRESS_PREFIX + DWORDHexString.fromNumber(collectionId) ); }; var ethAddressToCollectionId = (address) => { validate.collectionAddress(address); return DWORDHexString.toNumber(address.slice(-8)); }; var collectionIdAndTokenIdToNestingAddress = (collectionId, tokenId) => { validate.collectionId(collectionId); validate.tokenId(tokenId); return unsafeNormalizeEthereumAddress( NESTING_PREFIX + DWORDHexString.fromNumber(collectionId) + DWORDHexString.fromNumber(tokenId) ); }; var nestingAddressToCollectionIdAndTokenId = (address) => { validate.nestingAddress(address); return { collectionId: DWORDHexString.toNumber(address.slice(-16, -8)), tokenId: DWORDHexString.toNumber(address.slice(-8)) }; }; // src/Address/substrate.ts import { HexString as HexString2 } from "utf-helpers"; var blake2AsU8a = (u8a, dkLen = 32) => { return blake2b(u8a, { dkLen }); }; var u8aConcat = (u8as) => { let offset = 0; let length = 0; for (let i = 0; i < u8as.length; i++) { length += u8as[i].length; } const result = new Uint8Array(length); for (let i = 0; i < u8as.length; i++) { result.set(u8as[i], offset); offset += u8as[i].length; } return result; }; var SS58_PREFIX = new Uint8Array([83, 83, 53, 56, 80, 82, 69]); var sshash = (data) => { return blake2AsU8a(u8aConcat([SS58_PREFIX, data]), 64); }; var checkAddressChecksum = (decoded, ignoreChecksum = false) => { const ss58Length = decoded[0] & 64 ? 2 : 1; const ss58Decoded = ss58Length === 1 ? decoded[0] : (decoded[0] & 63) << 2 | decoded[1] >> 6 | (decoded[1] & 63) << 8; const isPublicKey = [34 + ss58Length, 35 + ss58Length].includes(decoded.length); const length = decoded.length - (isPublicKey ? 2 : 1); let isValid = false; if (!ignoreChecksum) { const hash = sshash(decoded.subarray(0, length)); isValid = (decoded[0] & 128) === 0 && ![46, 47].includes(decoded[0]) && (isPublicKey ? decoded[decoded.length - 2] === hash[0] && decoded[decoded.length - 1] === hash[1] : decoded[decoded.length - 1] === hash[0]); } return [isValid, length, ss58Length, ss58Decoded]; }; var normalizeSubstrateAddress = (address, prefix = 42) => { return encodeSubstrateAddress(decodeSubstrateAddress(address).u8a, prefix); }; function encodeSubstrateAddress(key, ss58Format = 42) { const u8a = typeof key === "string" ? HexString2.toU8a(key) : typeof key === "bigint" ? HexString2.toU8a(key.toString(16)) : key; if (ss58Format < 0 || ss58Format > 16383 || [46, 47].includes(ss58Format)) { throw new Error(`ss58Format is not valid, received ${typeof ss58Format} "${ss58Format}"`); } const allowedDecodedLengths = [1, 2, 4, 8, 32, 33]; if (!allowedDecodedLengths.includes(u8a.length)) { throw new Error(`key length is not valid, received ${u8a.length}, valid values are ${allowedDecodedLengths.join(", ")}`); } const u8aPrefix = ss58Format < 64 ? new Uint8Array([ss58Format]) : new Uint8Array([ (ss58Format & 252) >> 2 | 64, ss58Format >> 8 | (ss58Format & 3) << 6 ]); const input = u8aConcat([u8aPrefix, u8a]); return base58.encode( u8aConcat([ input, sshash(input).subarray(0, [32, 33].includes(u8a.length) ? 2 : 1) ]) ); } function decodeSubstrateAddress(address, ignoreChecksum, ss58Format = -1) { let realError = null; try { if (is.substratePublicKey(address)) { return { u8a: HexString2.toU8a(address), bigint: BigInt(address), hex: address, ss58Prefix: 42 }; } else if (address.startsWith("0x")) { throw new Error(`Invalid substrate address, received ${address}. Wrong or mangled public key?`); } const decoded = base58.decode(address); const allowedEncodedLengths = [3, 4, 6, 10, 35, 36, 37, 38]; if (!allowedEncodedLengths.includes(decoded.length)) { realError = new Error(`key length is not valid, decoded key length is ${decoded.length}, valid values are ${allowedEncodedLengths.join(", ")}`); throw realError; } const [isValid, endPos, ss58Length, ss58Decoded] = checkAddressChecksum(decoded, ignoreChecksum); if (!ignoreChecksum && !isValid) { realError = new Error(`Invalid decoded address checksum`); throw realError; } if (![-1, ss58Decoded].includes(ss58Format)) { realError = new Error(`Expected ss58Format ${ss58Format}, received ${ss58Decoded}`); throw realError; } const publicKey = decoded.slice(ss58Length, endPos); const hex = HexString2.fromU8a(publicKey); return { u8a: publicKey, hex, bigint: BigInt(hex), ss58Prefix: ss58Decoded }; } catch (error) { throw realError ? realError : new Error(`Decoding ${address}: ${error.message}`); } } var compareSubstrateAddresses = (address1, address2) => { const addr1 = typeof address1 === "string" ? address1 : address1.Substrate || address1.substrate; const addr2 = typeof address2 === "string" ? address2 : address2.Substrate || address2.substrate; if (!addr1 || !addr2) { return false; } try { const decoded1 = decodeSubstrateAddress(addr1); const decoded2 = decodeSubstrateAddress(addr2); return decoded1.bigint === decoded2.bigint; } catch (e) { return false; } }; var addressToEvm = (address, ignoreChecksum) => { const truncated = decodeSubstrateAddress(address, ignoreChecksum).u8a.subarray(0, 20); return normalizeEthereumAddress(HexString2.fromU8a(truncated)); }; var EVM_PREFIX_U8A = new Uint8Array([101, 118, 109, 58]); var evmToAddress = (evmAddress, ss58Format = 42) => { validate.ethereumAddress(evmAddress); const message = u8aConcat([EVM_PREFIX_U8A, HexString2.toU8a(evmAddress)]); return encodeSubstrateAddress(blake2AsU8a(message), ss58Format); }; // src/Address/crossAccountId.ts var guessAddressAndExtractCrossAccountIdUnsafe = (rawAddress, normalize2 = false) => { const address = rawAddress; if (typeof address === "object") { if (address.hasOwnProperty("eth") && address.hasOwnProperty("sub")) { const subPublicKey = address.sub.hasOwnProperty("_hex") && typeof address.sub._hex === "string" ? address.sub._hex : address.sub; if (typeof subPublicKey !== "string" || !subPublicKey.startsWith("0x")) { throw new Error(`Substrate public key must be a hex string, got ${subPublicKey}`); } const subBigInt = BigInt(subPublicKey); const ethBigInt = BigInt(address.eth); if (!(Number(subBigInt === 0n) ^ Number(ethBigInt === 0n))) { throw new Error(`One of the addresses must be 0, got eth ${address.eth} and substrate public key ${address.sub}.`); } if (subBigInt === 0n) { return { Ethereum: normalizeEthereumAddress(address.eth) }; } else { return { Substrate: normalizeSubstrateAddress(subPublicKey) }; } } else if (address.hasOwnProperty("Substrate") || address.hasOwnProperty("substrate")) { const substrateAddress = address.hasOwnProperty("Substrate") ? address.Substrate : address.substrate; if (is.substratePublicKey(substrateAddress)) { return { Substrate: normalizeSubstrateAddress(substrateAddress) }; } else if (is.substrateAddress(substrateAddress)) { return { Substrate: normalize2 ? normalizeSubstrateAddress(substrateAddress) : substrateAddress }; } else { throw new Error(`Address ${substrateAddress} is not a valid Substrate address`); } } else if (address.hasOwnProperty("Ethereum") || address.hasOwnProperty("ethereum")) { const ethereumAddress = address.hasOwnProperty("Ethereum") ? address.Ethereum : address.ethereum; validate.ethereumAddress(ethereumAddress); return { Ethereum: normalize2 ? normalizeEthereumAddress(ethereumAddress) : ethereumAddress }; } else { throw new Error(`Address ${address} is not a valid crossAccountId object (should contain "Substrate"/"substrate" or "Ethereum"/"ethereum" field) or EthCrossAccountId (should contain "eth" and "sub" fields)`); } } if (typeof address === "string") { if (is.substrateAddress(address)) return { Substrate: normalize2 ? normalizeSubstrateAddress(address) : address }; else if (is.ethereumAddress(address)) return { Ethereum: normalize2 ? normalizeEthereumAddress(address) : address }; else if (is.substratePublicKey(address)) return { Substrate: normalizeSubstrateAddress(address) }; else { throw new Error(`Address ${address} is not a valid Substrate or Ethereum address`); } } throw new Error(`Address ${address} is not a string or object: ${typeof address}`); }; var guessAddressAndExtractCrossAccountIdSafe = (address, normalize2 = false) => { try { return guessAddressAndExtractCrossAccountIdUnsafe(address, normalize2); } catch { return null; } }; var substrateOrMirrorIfEthereum = (address, normalize2 = false) => { const addressObject = guessAddressAndExtractCrossAccountIdUnsafe(address, normalize2); return addressObject.Substrate ? addressObject.Substrate : mirror.ethereumToSubstrate(addressObject.Ethereum); }; var addressInAnyFormToEnhancedCrossAccountId = (address, ss58Prefix = 42) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(address); if (crossAccountId.Ethereum) { const normalized = normalizeEthereumAddress(crossAccountId.Ethereum); return { ...crossAccountId, address: normalized, addressSS58: normalized, substratePublicKey: normalized, isEthereum: true, isSubstrate: false, type: "Ethereum" }; } else { return { ...crossAccountId, address: normalizeSubstrateAddress(crossAccountId.Substrate), addressSS58: normalizeSubstrateAddress(crossAccountId.Substrate, ss58Prefix), substratePublicKey: decodeSubstrateAddress(crossAccountId.Substrate).hex, isEthereum: false, isSubstrate: true, type: "Substrate" }; } }; // src/Address/index.ts var ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/; var SUB_PUBLIC_KEY_REGEX = /^0x[a-fA-F0-9]{64}$/; var validate = { substrateAddress: (address) => { decodeSubstrateAddress(address); return true; }, ethereumAddress: (address) => { if (!is.ethereumAddress(address)) { throw new Error(`address "${address}" is not valid ethereum address`); } return true; }, substratePublicKey: (address) => { if (!is.substratePublicKey(address)) { throw new Error(`address "${address}" is not valid substrate public key`); } return true; }, collectionAddress: (address) => { if (!is.collectionAddress(address)) { throw new Error(`address ${address} is not a collection address`); } return true; }, nestingAddress: (address) => { if (!is.nestingAddress(address)) { throw new Error(`address ${address} is not a nesting address`); } return true; }, collectionId: (collectionId) => { if (!is.collectionId(collectionId)) { throw new Error(`collectionId should be a number between 0 and 0xffffffff`); } return true; }, tokenId: (tokenId) => { if (!is.tokenId(tokenId)) { throw new Error(`collectionId should be a number between 0 and 0xffffffff`); } return true; } }; var is = { substrateAddress: (address) => { try { decodeSubstrateAddress(address); return !is.substratePublicKey(address); } catch { return false; } }, ethereumAddress: (address) => { return typeof address === "string" && address.length === 42 && !!address.match(ETH_ADDRESS_REGEX); }, substratePublicKey: (address) => { return typeof address === "string" && address.length === 66 && !!address.match(SUB_PUBLIC_KEY_REGEX); }, collectionAddress: (address) => { return is.ethereumAddress(address) && address.toLowerCase().startsWith(COLLECTION_ADDRESS_PREFIX); }, nestingAddress: (address) => { return is.ethereumAddress(address) && address.toLowerCase().startsWith(NESTING_PREFIX); }, collectionId: (collectionId) => { return !(typeof collectionId !== "number" || isNaN(collectionId) || collectionId < 0 || collectionId > 4294967295); }, tokenId: (tokenId) => { return !(typeof tokenId !== "number" || isNaN(tokenId) || tokenId < 0 || tokenId > 4294967295); }, crossAccountId(obj) { return is.substrateAddressObject(obj) || is.ethereumAddressObject(obj); }, crossAccountIdUncapitalized(obj) { return is.substrateAddressObjectUncapitalized(obj) || is.ethereumAddressObjectUncapitalized(obj); }, substrateAddressObject(obj) { return typeof obj === "object" && typeof obj?.Substrate === "string" && is.substrateAddress(obj.Substrate); }, ethereumAddressObject(obj) { return typeof obj === "object" && typeof obj?.Ethereum === "string" && is.ethereumAddress(obj.Ethereum); }, substrateAddressObjectUncapitalized(obj) { return typeof obj === "object" && typeof obj?.substrate === "string" && is.substrateAddress(obj.substrate); }, ethereumAddressObjectUncapitalized(obj) { return typeof obj === "object" && typeof obj?.ethereum === "string" && is.ethereumAddress(obj.ethereum); }, substrateAddressInAnyForm(address) { return typeof address === "string" ? is.substrateAddress(address) : typeof address === "object" && !!address && (is.substrateAddressObject(address) || is.substrateAddressObjectUncapitalized(address)); }, ethereumAddressInAnyForm(address) { return typeof address === "string" ? is.ethereumAddress(address) : typeof address === "object" && !!address && (is.ethereumAddressObject(address) || is.ethereumAddressObjectUncapitalized(address)); }, validAddressInAnyForm(address) { return is.ethereumAddressInAnyForm(address) || is.substrateAddressInAnyForm(address); } }; var collection = { idToAddress: collectionIdToEthAddress, addressToId: ethAddressToCollectionId }; var nesting = { idsToAddress: collectionIdAndTokenIdToNestingAddress, addressToIds: nestingAddressToCollectionIdAndTokenId }; var extract = { address: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId); return crossAccountId.Substrate || crossAccountId.Ethereum; }, addressSafe: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId); return crossAccountId ? crossAccountId.Substrate || crossAccountId.Ethereum : null; }, addressNormalized: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId, true); return crossAccountId.Substrate || crossAccountId.Ethereum; }, addressNormalizedSafe: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId, true); return crossAccountId ? crossAccountId.Substrate || crossAccountId.Ethereum : null; }, addressForScanNormalized: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId, true); return crossAccountId.Substrate || crossAccountId.Ethereum.toLowerCase(); }, addressForScanNormalizedSafe: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId, true); return crossAccountId ? crossAccountId.Substrate || crossAccountId.Ethereum.toLowerCase() : null; }, crossAccountId: (addressOrCrossAccountId) => { return guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId); }, crossAccountIdSafe: (addressOrCrossAccountId) => { return guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId); }, crossAccountIdNormalized: (addressOrCrossAccountId) => { return guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId, true); }, crossAccountIdNormalizedSafe: (addressOrCrossAccountId) => { return guessAddressAndExtractCrossAccountIdSafe(addressOrCrossAccountId, true); }, crossAccountIdUncapitalized: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId); return crossAccountId.Substrate ? { substrate: crossAccountId.Substrate } : { ethereum: crossAccountId.Ethereum }; }, crossAccountIdUncapitalizedSafe: (addressOrCrossAccountId) => { try { return extract.crossAccountIdUncapitalized(addressOrCrossAccountId); } catch { return null; } }, crossAccountIdUncapitalizedNormalized: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId, true); return crossAccountId.Substrate ? { substrate: crossAccountId.Substrate } : { ethereum: crossAccountId.Ethereum }; }, crossAccountIdUncapitalizedNormalizedSafe: (addressOrCrossAccountId) => { try { return extract.crossAccountIdUncapitalizedNormalized(addressOrCrossAccountId); } catch { return null; } }, substrateOrMirrorIfEthereum: (addressOrCrossAccountId) => { return substrateOrMirrorIfEthereum(addressOrCrossAccountId); }, substrateOrMirrorIfEthereumSafe: (addressOrCrossAccountId) => { try { return substrateOrMirrorIfEthereum(addressOrCrossAccountId); } catch { return null; } }, substrateOrMirrorIfEthereumNormalized: (addressOrCrossAccountId) => { return substrateOrMirrorIfEthereum(addressOrCrossAccountId, true); }, substrateOrMirrorIfEthereumNormalizedSafe: (addressOrCrossAccountId) => { try { return substrateOrMirrorIfEthereum(addressOrCrossAccountId, true); } catch { return null; } }, substratePublicKey: (addressOrCrossAccountId) => { const crossAccountId = guessAddressAndExtractCrossAccountIdUnsafe(addressOrCrossAccountId); if (!crossAccountId.Substrate) { throw new Error("Address is not a substrate address"); } return substrate.decode(crossAccountId.Substrate).hex; }, substratePublicKeySafe: (addressOrCrossAccountId) => { try { return extract.substratePublicKey(addressOrCrossAccountId); } catch { return null; } }, enhancedCrossAccountId: (addressInAnyForm, ss58Prefix = 42) => { return addressInAnyFormToEnhancedCrossAccountId(addressInAnyForm, ss58Prefix); }, enhancedCrossAccountIdSafe: (addressInAnyForm, ss58Prefix = 42) => { try { return addressInAnyFormToEnhancedCrossAccountId(addressInAnyForm, ss58Prefix); } catch { return null; } }, ethCrossAccountId: (addressInAnyForm) => { const addressEnhanced = addressInAnyFormToEnhancedCrossAccountId(addressInAnyForm); if (addressEnhanced.Substrate) { return { eth: "0x0000000000000000000000000000000000000000", sub: addressEnhanced.substratePublicKey }; } else { return { eth: addressEnhanced.address, sub: "0x00" }; } }, ethCrossAccountIdSafe: (addressInAnyForm) => { try { return extract.ethCrossAccountId(addressInAnyForm); } catch { return null; } } }; var mirror = { substrateToEthereum: addressToEvm, ethereumToSubstrate: evmToAddress }; var normalize = { substrateAddress: normalizeSubstrateAddress, ethereumAddress: normalizeEthereumAddress }; var compare = { substrateAddresses: compareSubstrateAddresses, ethereumAddresses: compareEthereumAddresses }; var substrate = { encode: encodeSubstrateAddress, decode: decodeSubstrateAddress, compare: compareSubstrateAddresses }; var Address = { constants: constants_exports, algorithms: imports_exports, is, validate, collection, nesting, extract, mirror, normalize, compare, substrate, utils: { DWORDHexString } }; export { constants_exports, imports_exports, validate, is, collection, nesting, extract, mirror, normalize, compare, substrate, Address, Address_exports }; //# sourceMappingURL=chunk-6B63RM6C.mjs.map