UNPKG

lotus-sdk

Version:

Central repository for several classes of tools for integrating with, and building for, the Lotusia ecosystem

408 lines (407 loc) 16.2 kB
import { Preconditions } from './util/preconditions.js'; import { BitcoreError } from './errors.js'; import { Base58Check } from './encoding/base58check.js'; import { get as getNetwork, defaultNetwork } from './networks.js'; import { Hash } from './crypto/hash.js'; import { JSUtil } from './util/js.js'; import { PublicKey } from './publickey.js'; import { XAddress } from './xaddress.js'; import { Script } from './script.js'; export class Address { static PayToPublicKeyHash = 'pubkeyhash'; static PayToScriptHash = 'scripthash'; static PayToTaproot = 'taproot'; hashBuffer; network; type; constructor(data, network, type) { if (Array.isArray(data) && typeof network === 'number') { return Address.createMultisig(data, network, type); } if (data instanceof Address) { return data; } Preconditions.checkArgument(data !== undefined, 'data', 'First argument is required, please include address data.', 'guide/address.html'); if (network && !getNetwork(network)) { throw new TypeError('Second argument must be "livenet", "testnet", or "regtest".'); } const networkExplicitlyProvided = network !== undefined; network ||= defaultNetwork; if (type && type !== Address.PayToPublicKeyHash && type !== Address.PayToScriptHash && type !== Address.PayToTaproot) { throw new TypeError('Third argument must be "pubkeyhash", "scripthash", or "taproot".'); } const info = this._classifyArguments(data, network, type, networkExplicitlyProvided); info.network = info.network || getNetwork(network) || defaultNetwork; info.type = info.type || type || Address.PayToPublicKeyHash; JSUtil.defineImmutable(this, { hashBuffer: info.hashBuffer, network: info.network, type: info.type, }); } _classifyArguments(data, network, type, networkExplicitlyProvided = true) { if (typeof network === 'string') { const networkObj = getNetwork(network); if (!networkObj) { throw new TypeError('Unknown network'); } network = networkObj; } if ((Buffer.isBuffer(data) || data instanceof Uint8Array) && data.length === 20) { return Address._transformHash(data); } else if ((Buffer.isBuffer(data) || data instanceof Uint8Array) && data.length === 21) { return Address._transformBuffer(data, network, type); } else if ((Buffer.isBuffer(data) || data instanceof Uint8Array) && data.length === 33) { return { hashBuffer: Buffer.from(data), network: typeof network === 'string' ? getNetwork(network) : network, type: type || Address.PayToTaproot, }; } else if (data instanceof PublicKey) { return Address._transformPublicKey(data, network); } else if (data instanceof Script) { return Address._transformScript(data, network); } else if (typeof data === 'string') { return Address._transformString(data, networkExplicitlyProvided ? network : undefined, type); } else if (Array.isArray(data)) { throw new Error('Multisig addresses should be created with createMultisig'); } else if (typeof data === 'object' && data !== null) { return Address._transformObject(data); } else { throw new TypeError('First argument is an unrecognized data format.'); } } static _classifyFromVersion(buffer) { const version = {}; const pubkeyhashNetwork = getNetwork(buffer[0], 'pubkeyhash'); const scripthashNetwork = getNetwork(buffer[0], 'scripthash'); if (pubkeyhashNetwork) { version.network = pubkeyhashNetwork; version.type = Address.PayToPublicKeyHash; } else if (scripthashNetwork) { version.network = scripthashNetwork; version.type = Address.PayToScriptHash; } return version; } static _transformString(data, network, type) { if (typeof data !== 'string') { throw new TypeError('data parameter supplied is not a string.'); } data = data.trim(); const networkObj = getNetwork(network); if (network && !networkObj) { throw new TypeError('Unknown network'); } if (data.indexOf(':') !== -1) { const info = Address.decodeCashAddress(data); if (!info.network || (networkObj && networkObj.name !== info.network.name)) { throw new TypeError('Address has mismatched network type.'); } return { hashBuffer: Buffer.from(info.hashBuffer), network: info.network, type: info.type, }; } if (Address._isXAddress(data)) { const info = Address._transformXAddressString(data, network, type); if (!info.network || (networkObj && networkObj.name !== info.network.name)) { throw new TypeError('Address has mismatched network type.'); } return info; } const info = Address._transformLegacyString(data, network, type); if (!info.network || (networkObj && networkObj.name !== info.network.name)) { throw new TypeError('Address has mismatched network type.'); } return info; } static _transformLegacyString(data, network, type) { const info = {}; const decoded = Base58Check.decode(data); const version = Address._classifyFromVersion(decoded); if (!version.network || !version.type) { throw new TypeError('Address has invalid version.'); } info.hashBuffer = decoded.subarray(1); info.network = version.network; info.type = version.type; return info; } static _isXAddress(data) { const match = /[A-Z]|_/.exec(data); return match !== null && match.index > 0; } static _transformXAddressString(data, network, type) { if (typeof network === 'string') { network = getNetwork(network); } network ||= defaultNetwork; const decodedXAddress = XAddress._decode(data); if (!decodedXAddress.hashBuffer) { throw new TypeError('Invalid XAddress.'); } let hashBuffer = decodedXAddress.hashBuffer; const decodedNetwork = decodedXAddress.network || network; const decodedType = decodedXAddress.type; if (decodedType === 'taproot' || decodedType === Address.PayToTaproot) { if (hashBuffer.length === 36 && hashBuffer[0] === 0x62 && hashBuffer[1] === 0x51 && hashBuffer[2] === 0x21) { hashBuffer = hashBuffer.subarray(3, 36); } else if (hashBuffer.length === 33) { } else { throw new TypeError(`Taproot address has invalid payload length: ${hashBuffer.length} bytes (expected 33 or 36)`); } return { hashBuffer: hashBuffer, network: decodedNetwork, type: Address.PayToTaproot, }; } if (hashBuffer.length === 25 && hashBuffer[0] === 0x76 && hashBuffer[1] === 0xa9 && hashBuffer[2] === 0x14) { hashBuffer = hashBuffer.subarray(3, 23); } else if (hashBuffer.length === 23 && hashBuffer[0] === 0xa9 && hashBuffer[1] === 0x14 && hashBuffer[22] === 0x87) { hashBuffer = hashBuffer.subarray(2, 22); return { hashBuffer: hashBuffer, network: decodedNetwork, type: Address.PayToScriptHash, }; } return { hashBuffer: hashBuffer, network: decodedNetwork, type: type ?? Address.PayToPublicKeyHash, }; } static _transformHash(hash) { const info = {}; if (!Buffer.isBuffer(hash) && !(hash instanceof Uint8Array)) { throw new TypeError('Address supplied is not a buffer.'); } if (hash.length !== 20) { throw new TypeError('Address hashbuffers must be exactly 20 bytes.'); } info.hashBuffer = Buffer.from(hash); return info; } static _transformBuffer(buffer, network, type) { const info = {}; if (!Buffer.isBuffer(buffer) && !(buffer instanceof Uint8Array)) { throw new TypeError('Address supplied is not a buffer.'); } if (buffer.length !== 21) { throw new TypeError('Address buffers must be exactly 21 bytes.'); } const networkObj = getNetwork(network); const bufferVersion = Address._classifyFromVersion(Buffer.from(buffer)); if (network && !networkObj) { throw new TypeError('Unknown network'); } if (!bufferVersion.network || (networkObj && networkObj !== bufferVersion.network)) { throw new TypeError('Address has mismatched network type.'); } if (!bufferVersion.type || (type && type !== bufferVersion.type)) { throw new TypeError('Address has mismatched type.'); } info.hashBuffer = Buffer.from(buffer).subarray(1); info.network = bufferVersion.network; info.type = bufferVersion.type; return info; } static _transformPublicKey(pubkey, network) { const info = {}; if (!(pubkey instanceof PublicKey)) { throw new TypeError('Address must be an instance of PublicKey.'); } info.hashBuffer = Hash.sha256ripemd160(pubkey.toBuffer()); info.type = Address.PayToPublicKeyHash; info.network = network ?? defaultNetwork; return info; } static _transformScript(script, network) { Preconditions.checkArgument(script instanceof Script, 'script', 'script must be a Script instance'); const address = script.getAddressInfo(); if (!address) { throw new BitcoreError.Script.CantDeriveAddress('Cannot derive address from script'); } if (typeof network === 'string') { network = getNetwork(network); } if (network && network !== address.network) { throw new TypeError('Provided network does not match the Address network.'); } return { hashBuffer: address.hashBuffer, network: address.network, type: address.type, }; } static _transformObject(data) { Preconditions.checkArgument(data.hashBuffer !== undefined, 'data', 'Must provide a `hash` or `hashBuffer` property'); Preconditions.checkArgument(data.type !== undefined, 'data', 'Must provide a `type` property'); return { hashBuffer: data.hashBuffer || Buffer.from(data.hashBuffer.toString(), 'hex'), network: getNetwork(data.network) || defaultNetwork, type: data.type, }; } static createMultisig(publicKeys, threshold, network) { const networkObj = network || publicKeys[0].network || defaultNetwork; return Address.payingTo(Script.buildMultisigOut(publicKeys, threshold, {}), networkObj); } static fromPublicKey(data, network) { const networkObj = getNetwork(network) || defaultNetwork; const info = Address._transformPublicKey(data, networkObj); return new Address(info.hashBuffer, info.network, info.type); } static fromPublicKeyHash(hash, network) { const networkObj = getNetwork(network) || defaultNetwork; return new Address(hash, networkObj, Address.PayToPublicKeyHash); } static fromScriptHash(hash, network) { const networkObj = getNetwork(network) || defaultNetwork; return new Address(hash, networkObj, Address.PayToScriptHash); } static fromTaprootCommitment(commitment, network) { const networkObj = getNetwork(network) || defaultNetwork; const commitmentBuf = commitment instanceof PublicKey ? commitment.toBuffer() : commitment; if (commitmentBuf.length !== 33) { throw new Error('Taproot commitment must be 33-byte compressed public key'); } return new Address(commitmentBuf, networkObj, Address.PayToTaproot); } static fromBuffer(buffer, network, type) { const info = Address._transformBuffer(buffer, network, type); return new Address(info.hashBuffer, info.network, info.type); } static fromString(str, network) { const info = Address._transformString(str, network); return new Address(info.hashBuffer, info.network, info.type); } static fromObject(obj) { Preconditions.checkState(JSUtil.isHexa(obj.hash), 'Unexpected hash property, "' + obj.hash + '", expected to be hex.'); const hashBuffer = Buffer.from(obj.hash, 'hex'); return new Address(hashBuffer, obj.network, obj.type); } static fromScript(script, network) { Preconditions.checkArgument(script instanceof Script, 'script', 'script must be a Script instance'); const info = Address._transformScript(script, network); return new Address(info.hashBuffer, network, info.type); } static payingTo(script, network) { Preconditions.checkArgument(script !== null, 'script', 'script is required'); Preconditions.checkArgument(script instanceof Script, 'script', 'script must be instance of Script'); return Address.fromScriptHash(Hash.sha256ripemd160(script.toBuffer()), network); } static getValidationError(data, network, type) { try { new Address(data, network, type); return null; } catch (e) { return e; } } static isValid(data, network, type) { return !Address.getValidationError(data, network, type); } isPayToPublicKeyHash() { return this.type === Address.PayToPublicKeyHash; } isPayToScriptHash() { return this.type === Address.PayToScriptHash; } isPayToTaproot() { return this.type === Address.PayToTaproot; } toBuffer() { return this.hashBuffer; } toFullBuffer() { const version = Buffer.from([ this.network[this.type], ]); const buf = Buffer.concat([version, this.hashBuffer]); return buf; } toCashBuffer() { return this.toBuffer(); } toObject() { return { hash: this.hashBuffer.toString('hex'), type: this.type, network: this.network.toString(), }; } toJSON() { return this.toObject(); } toString(network) { return this.toXAddress(network); } toLegacyAddress() { return this.toString(); } toCashAddress() { return this.toString(); } toXAddress(network) { if (this.isPayToTaproot()) { const xaddr = new XAddress(this.hashBuffer, network ?? this.network, this.type); return xaddr.toString(); } const script = Script.fromAddress(this); const xaddr = new XAddress(script.toBuffer(), network ?? this.network, this.type); return xaddr.toString(); } static decodeCashAddress(address) { const info = Address._transformString(address); return { network: info.network, type: info.type, hashBuffer: info.hashBuffer, }; } inspect() { return ('<Address: ' + this.toString() + ', type: ' + this.type + ', network: ' + this.network + '>'); } }