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
JavaScript
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 +
'>');
}
}