sensible-sdk
Version:
Sensible-SDK
954 lines (953 loc) • 131 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SensibleNFT = exports.defaultSignerConfigs = exports.Prevouts = exports.sighashType = void 0;
const scryptlib_1 = require("scryptlib");
const BN = require("../bn.js");
const bsv = require("../bsv");
const DustCalculator_1 = require("../common/DustCalculator");
const error_1 = require("../common/error");
const protoheader_1 = require("../common/protoheader");
const SatotxSigner_1 = require("../common/SatotxSigner");
const satotxSignerUtil_1 = require("../common/satotxSignerUtil");
const SizeTransaction_1 = require("../common/SizeTransaction");
const TokenUtil = require("../common/tokenUtil");
const Utils = require("../common/utils");
const utils_1 = require("../common/utils");
const sensible_api_1 = require("../sensible-api");
const tx_composer_1 = require("../tx-composer");
const nft_1 = require("./contract-factory/nft");
const nftGenesis_1 = require("./contract-factory/nftGenesis");
const nftSell_1 = require("./contract-factory/nftSell");
const nftUnlockContractCheck_1 = require("./contract-factory/nftUnlockContractCheck");
const nftProto = require("./contract-proto/nft.proto");
const nft_proto_1 = require("./contract-proto/nft.proto");
const nftSellProto = require("./contract-proto/nftSell.proto");
const contractUtil_1 = require("./contractUtil");
const Signature = bsv.crypto.Signature;
exports.sighashType = Signature.SIGHASH_ALL | Signature.SIGHASH_FORKID;
const dummyNetwork = "mainnet";
const dummyWif = "L5k7xi4diSR8aWoGKojSNTnc3YMEXEoNpJEaGzqWimdKry6CFrzz";
const dummyPrivateKey = bsv.PrivateKey.fromWIF(dummyWif);
const dummyAddress = dummyPrivateKey.toAddress(dummyNetwork);
const dummyPk = dummyPrivateKey.toPublicKey();
const dummyTxId = "c776133a77886693ba2484fe12d6bdfb8f8bcb7a237e4a8a6d0f69c7d1879a08";
const dummyUtxo = {
txId: dummyTxId,
outputIndex: 0,
satoshis: 1000000000,
wif: dummyWif,
};
class Prevouts {
constructor() {
this._buf = Buffer.alloc(0);
}
addVout(txId, outputIndex) {
const txidBuf = TokenUtil.getTxIdBuf(txId);
const indexBuf = TokenUtil.getUInt32Buf(outputIndex);
this._buf = Buffer.concat([this._buf, txidBuf, indexBuf]);
}
toHex() {
return this._buf.toString("hex");
}
}
exports.Prevouts = Prevouts;
exports.defaultSignerConfigs = [
{
satotxApiPrefix: "https://s1.satoplay.cn,https://s1.satoplay.com",
satotxPubKey: "2c8c0117aa5edba9a4539e783b6a1bdbc1ad88ad5b57f3d9c5cba55001c45e1fedb877ebc7d49d1cfa8aa938ccb303c3a37732eb0296fee4a6642b0ff1976817b603404f64c41ec098f8cd908caf64b4a3aada220ff61e252ef6d775079b69451367eda8fdb37bc55c8bfd69610e1f31b9d421ff44e3a0cfa7b11f334374827256a0b91ce80c45ffb798798e7bd6b110134e1a3c3fa89855a19829aab3922f55da92000495737e99e0094e6c4dbcc4e8d8de5459355c21ff055d039a202076e4ca263b745a885ef292eec0b5a5255e6ecc45534897d9572c3ebe97d36626c7b1e775159e00b17d03bc6d127260e13a252afd89bab72e8daf893075f18c1840cb394f18a9817913a9462c6ffc8951bee50a05f38da4c9090a4d6868cb8c955e5efb4f3be4e7cf0be1c399d78a6f6dd26a0af8492dca67843c6da9915bae571aa9f4696418ab1520dd50dd05f5c0c7a51d2843bd4d9b6b3b79910e98f3d98099fd86d71b2fac290e32bdacb31943a8384a7668c32a66be127b74390b4b0dec6455",
},
{
satotxApiPrefix: "https://satotx.showpay.top",
satotxPubKey: "5b94858991d384c61ffd97174e895fcd4f62e4fea618916dc095fe4c149bbdf1188c9b33bc15cbe963a63b2522e70b80a5b722ac0e6180407917403755df4de27d69cc115c683a99face8c823cbccf73c7f0d546f1300b9ee2e96aea85542527f33b649f1885caebe19cf75d9a645807f03565c65bd4c99c8f6bb000644cfb56969eac3e9331c254b08aa279ceb64c47ef66be3f071e28b3a5a21e48cdfc3335d8b52e80a09a104a791ace6a2c1b4da88c52f9cc28c54a324e126ec91a988c1fe4e21afc8a84d0e876e01502386f74e7fc24fc32aa249075dd222361aea119d4824db2a797d58886e93bdd60556e504bb190b76a451a4e7b0431973c0410e71e808d0962415503931bbde3dfce5186b371c5bf729861f239ef626b7217d071dfd62bac877a847f2ac2dca07597a0bb9dc1969bed40606c025c4ff7b53a4a6bd921642199c16ede8165ed28da161739fa8d33f9f483212759498c1219d246092d14c9ae63808f58f03c8ca746904ba51fa326d793cea80cda411c85d35894bdb5",
},
{
satotxApiPrefix: "https://satotx.volt.id",
satotxPubKey: "3a62ce90c189ae322150cfc68cd00739cd681babf46a9b27793413ad780ea7c4ef22afd0042bc3711588587c2b8a953ced78496cb95579b1272b8979183ea3c66d204c8eeffebfa115c596c0c561f3569fe6d6e8e06d7e82192a24a84b739838ac846db8594a565679d617695f184eb85a3902a036eb8e82f95b83acc207f0deeac87291539865765899d97cfe41169c555480372195729269ae30b6c39324a6731d6f4e46da5ba1789c6e9bd14b16426d35fd4449eecd177e2834e87fb65d9d469176ffe0c12097fcc7e2393dbaa504631487a3ad725235b4d25fe3d09c2460f8a6c0bf4defc1ffe65d5fa28e85fae11eace2a66e48a0ae2ed6bcfb4bb94296717a4a5b1b3fa9b0fb3c165e517b9b69fa8aaca7fdc7351a0ac14d110258f442f423a780bebd87ac10173ca00ee4e9f56ced0510e7f53ed41411b91286f288438c361d2a15868d1c84d6a73510ef23eee9312ae2a7162c1fcd5438788236c0571ee822c326ebd123b8a6636e7b192db2911725a20da027bfaa79c33f58174285",
},
{
satotxApiPrefix: "https://satotx.metasv.com",
satotxPubKey: "19d9193ee2e95d09445d28408e8a3da730b2d557cd8d39a7ae4ebbfbceb17ed5d745623529ad33d043511f3e205c1f92b6322833424d19823c3b611b3adabb74e1006e0e93a8f1e0b97ab801c6060a4c060f775998d9f003568ab4ea7633a0395eb761c36106e229394f2c271b8522a44a5ae759254f5d22927923ba85b3729460ecccca07a5556299aa7f2518814c74a2a4d48b48013d609002631f2d93c906d07077ef58d473e3d971362d1129c1ab9b8f9b1365519f0c023c1cadad5ab57240d19e256e08022fd0708951ff90a8af0655aff806c6382d0a72c13f1e52b88222d7dfc6357179b06ffcf937f9da3b0419908aa589a731e26bbaba2fa0b754bf722e338c5627b11dc24aadc4d83c35851c034936cf0df18167e856a5f0a7121d23cd48b3f8a420869a37bd1362905d7f76ff18a991f75a0f9d1bcfc18416d76691cc357cbdcc8cc0df9dbd9318a40e08adb2fb4e78b3c47bdf07eeed4f3f4e0f7e81e37460a09b857e0194c72ec03bb564b5b409d8a1b84c153186ecbb4cfdfd",
},
{
satotxApiPrefix: "https://satotx.tswap.io",
satotxPubKey: "a36531727b324b34baef257d223b8ba97bac06d6b631cccb271101f20ef1de2523a0a3a5367d89d98ff354fe1a07bcfb00425ab252950ce10a90dc9040930cf86a3081f0c68ea05bfd40aab3e8bfaaaf6b5a1e7a2b202892dc9b1c0fe478210799759b31ee04e842106a58d901eb5bc538c1b58b7eb774a382e7ae0d6ed706bb0b12b9b891828da5266dd9f0b381b05ecbce99fcde628360d281800cf8ccf4630b2a0a1a25cf4d103199888984cf61edaa0dad578b80dbce25b3316985a8f846ada9bf9bdb8c930e2a43e69994a9b15ea33fe6ee29fa1a6f251f8d79a5de9f1f24152efddedc01b63e2f2468005ecce7da382a64d7936b22a7cac697e1b0a48419101a802d3be554a9b582a80e5c5d8b998e5eb9684c7aaf09ef286d3d990c71be6e3f3340fdaeb2dac70a0be928b6de6ef79f353c868def3385bccd36aa871eb7c8047d3f10b0a38135cdb3577eaafa512111a7af088e8f77821a27b195c95bf80da3c59fda5ff3dd1d40f60d61c099a608b58b6de4a76146cf7b89444c1055",
},
];
contractUtil_1.ContractUtil.init();
function checkParamUtxoFormat(utxo) {
if (utxo) {
if (!utxo.txId || !utxo.satoshis || !utxo.wif) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `UtxoFormatError-valid format example :{
txId:'85f583e7a8e8b9cf86e265c2594c1e4eb45db389f6781c3b1ec9aa8e48976caa',
satoshis:1000,
outputIndex:1,
wif:'L3J1A6Xyp7FSg9Vtj3iBKETyVpr6NibxUuLhw3uKpUWoZBLkK1hk'
}`);
}
}
}
function checkParamSigners(signers) {
if (signers.length != 5) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, "only support 5 signers");
}
let signer = signers[0];
if (Utils.isNull(signer.satotxApiPrefix) ||
Utils.isNull(signer.satotxPubKey)) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `SignerFormatError-valid format example :
signers:[{
satotxApiPrefix: "https://api.satotx.com",
satotxPubKey:
"25108ec89eb96b99314619eb5b124f11f00307a833cda48f5ab1865a04d4cfa567095ea4dd47cdf5c7568cd8efa77805197a67943fe965b0a558216011c374aa06a7527b20b0ce9471e399fa752e8c8b72a12527768a9fc7092f1a7057c1a1514b59df4d154df0d5994ff3b386a04d819474efbd99fb10681db58b1bd857f6d5",
},...]`);
}
}
function checkParamNetwork(network) {
if (!["mainnet", "testnet"].includes(network)) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `NetworkFormatError:only support 'mainnet' and 'testnet' but value is ${network}`);
}
}
function checkParamGenesis(genesis) {
if (typeof genesis != "string" || genesis.length != 40) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `GenesisFormatError:genesis should be a string with 40 length `);
}
}
function checkParamCodehash(codehash) {
if (typeof codehash != "string" || codehash.length != 40) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `CodehashFormatError:codehash should be a string with 40 length `);
}
// $.checkArgument(
// codehash == ContractUtil.tokenCodeHash,
// `a valid codehash should be ${ContractUtil.tokenCodeHash}, but the provided is ${codehash} `
// );
}
function checkParamSensibleId(sensibleId) {
if (typeof sensibleId != "string" || sensibleId.length != 72) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `CodehashFormatError:sensibleId should be a string with 72 length `);
}
}
function parseSensibleID(sensibleID) {
let sensibleIDBuf = Buffer.from(sensibleID, "hex");
let genesisTxId = sensibleIDBuf.slice(0, 32).reverse().toString("hex");
let genesisOutputIndex = sensibleIDBuf.readUIntLE(32, 4);
return {
genesisTxId,
genesisOutputIndex,
};
}
/**
Sensible Non Fungible Token
*/
class SensibleNFT {
/**
*
* @param signers
* @param signerSelecteds (Optional) the indexs of the signers which is decided to verify
* @param feeb (Optional) the fee rate. default is 0.5
* @param network (Optional) mainnet/testnet default is mainnet
* @param purse (Optional) the private key to offer transacions fee. If not provided, bsv utoxs must be provided in genesis/issue/transfer.
* @param debug (Optional) specify if verify the tx when genesis/issue/transfer, default is false
* @param apiTarget (Optional) SENSIBLE/METASV, default is SENSIBLE.
* @param dustLimitFactor (Optional) specify the output dust rate, default is 0.25 .If the value is equal to 0, the final dust will be at least 1.
* @param dustAmount (Optional) specify the output dust.
*/
constructor({ signers = exports.defaultSignerConfigs, signerSelecteds, feeb = 0.05, network = sensible_api_1.API_NET.MAIN, purse, debug = false, apiTarget = sensible_api_1.API_TARGET.SENSIBLE, apiUrl, mockData, dustLimitFactor = 300, dustAmount, }) {
this.signerSelecteds = [];
checkParamNetwork(network);
if (mockData) {
this.signers = mockData.satotxSigners;
}
else {
checkParamSigners(signers);
this.signers = signers.map((v) => new SatotxSigner_1.SatotxSigner(v.satotxApiPrefix, v.satotxPubKey));
}
this.feeb = feeb;
this.network = network;
if (mockData) {
this.sensibleApi = mockData.sensibleApi;
}
else {
this.sensibleApi = new sensible_api_1.SensibleApi(network, apiTarget, apiUrl);
}
this.debug = debug;
this.dustCalculator = new DustCalculator_1.DustCalculator(dustLimitFactor, dustAmount);
if (network == sensible_api_1.API_NET.MAIN) {
this.zeroAddress = new bsv.Address("1111111111111111111114oLvT2");
}
else {
this.zeroAddress = new bsv.Address("mfWxJ45yp2SFn7UciZyNpvDKrzbhyfKrY8");
}
if (purse) {
const privateKey = bsv.PrivateKey.fromWIF(purse);
const address = privateKey.toAddress(this.network);
this.purse = {
privateKey,
address,
};
}
if (signerSelecteds) {
if (signerSelecteds.length < nft_proto_1.SIGNER_VERIFY_NUM) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, `the length of signerSeleteds should not less than ${nft_proto_1.SIGNER_VERIFY_NUM}`);
}
this.signerSelecteds = signerSelecteds;
}
else {
for (let i = 0; i < nft_proto_1.SIGNER_VERIFY_NUM; i++) {
this.signerSelecteds.push(i);
}
}
this.signerSelecteds.sort((a, b) => a - b);
let rabinPubKeys = this.signers.map((v) => v.satotxPubKey);
let rabinPubKeyHashArray = TokenUtil.getRabinPubKeyHashArray(rabinPubKeys);
this.rabinPubKeyHashArrayHash =
bsv.crypto.Hash.sha256ripemd160(rabinPubKeyHashArray);
this.rabinPubKeyHashArray = new scryptlib_1.Bytes((0, scryptlib_1.toHex)(rabinPubKeyHashArray));
this.rabinPubKeyArray = rabinPubKeys.map((v) => new scryptlib_1.Int(v.toString(10)));
this.unlockContractCodeHashArray = contractUtil_1.ContractUtil.unlockContractCodeHashArray;
}
/**
* Pick the signer with the best connectivity
* @param signerConfigs
* @returns
*/
static selectSigners(signerConfigs = exports.defaultSignerConfigs) {
return __awaiter(this, void 0, void 0, function* () {
return yield (0, satotxSignerUtil_1.selectSigners)(signerConfigs, nftProto.SIGNER_NUM, nftProto.SIGNER_VERIFY_NUM);
});
}
/**
* set dust. DustAmount has a higher priority than dustLimitFactor
* @param dustLimitFactor specify the output dust rate, default is 0.25 .If the value is equal to 0, the final dust will be at least 1.
* @param dustAmount specify the output dust
*/
setDustThreshold({ dustLimitFactor, dustAmount, }) {
this.dustCalculator.dustAmount = dustAmount;
this.dustCalculator.dustLimitFactor = dustLimitFactor;
}
getDustThreshold(size) {
return this.dustCalculator.getDustThreshold(size);
}
_pretreatUtxos(paramUtxos) {
return __awaiter(this, void 0, void 0, function* () {
let utxoPrivateKeys = [];
let utxos = [];
//If utxos are not provided, use purse to fetch utxos
if (!paramUtxos) {
if (!this.purse)
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, "Utxos or Purse must be provided.");
paramUtxos = yield this.sensibleApi.getUnspents(this.purse.address.toString());
paramUtxos.forEach((v) => {
utxoPrivateKeys.push(this.purse.privateKey);
});
}
else {
paramUtxos.forEach((v) => {
if (v.wif) {
let privateKey = new bsv.PrivateKey(v.wif);
utxoPrivateKeys.push(privateKey);
v.address = privateKey.toAddress(this.network).toString(); //Compatible with the old version, only wif is provided but no address is provided
}
});
}
paramUtxos.forEach((v) => {
utxos.push({
txId: v.txId,
outputIndex: v.outputIndex,
satoshis: v.satoshis,
address: new bsv.Address(v.address, this.network),
});
});
if (utxos.length == 0)
throw new error_1.CodeError(error_1.ErrCode.EC_INSUFFICIENT_BSV, "Insufficient balance.");
return { utxos, utxoPrivateKeys };
});
}
_pretreatNftUtxoToTransfer(tokenIndex, codehash, genesis, senderPrivateKey, senderPublicKey) {
return __awaiter(this, void 0, void 0, function* () {
if (senderPrivateKey) {
senderPublicKey = senderPrivateKey.toPublicKey();
}
let _res = yield this.sensibleApi.getNonFungibleTokenUnspentDetail(codehash, genesis, tokenIndex);
let nftUtxo = {
txId: _res.txId,
outputIndex: _res.outputIndex,
nftAddress: new bsv.Address(_res.tokenAddress, this.network),
publicKey: senderPublicKey,
};
return { nftUtxo, nftUtxoPrivateKey: senderPrivateKey };
});
}
_pretreatNftUtxoToIssue({ sensibleId, genesisPublicKey, }) {
return __awaiter(this, void 0, void 0, function* () {
let genesisContract = nftGenesis_1.NftGenesisFactory.createContract(genesisPublicKey);
let { genesisTxId, genesisOutputIndex } = parseSensibleID(sensibleId);
let genesisUtxo = yield this.getIssueUtxo(genesisContract.getCodeHash(), genesisTxId, genesisOutputIndex);
if (!genesisUtxo) {
throw new error_1.CodeError(error_1.ErrCode.EC_FIXED_TOKEN_SUPPLY, "token supply is fixed");
}
let txHex = yield this.sensibleApi.getRawTxData(genesisUtxo.txId);
const tx = new bsv.Transaction(txHex);
let preTxId = tx.inputs[0].prevTxId.toString("hex");
let preOutputIndex = tx.inputs[0].outputIndex;
let preTxHex = yield this.sensibleApi.getRawTxData(preTxId);
genesisUtxo.satotxInfo = {
txId: genesisUtxo.txId,
outputIndex: genesisUtxo.outputIndex,
txHex,
preTxId,
preOutputIndex,
preTxHex,
};
let output = tx.outputs[genesisUtxo.outputIndex];
genesisUtxo.satoshis = output.satoshis;
genesisUtxo.lockingScript = output.script;
genesisContract.setFormatedDataPartFromLockingScript(genesisUtxo.lockingScript);
return {
genesisContract,
genesisTxId,
genesisOutputIndex,
genesisUtxo,
};
});
}
_pretreatNftUtxoToTransferOn(nftUtxo, codehash, genesis) {
return __awaiter(this, void 0, void 0, function* () {
let txHex = yield this.sensibleApi.getRawTxData(nftUtxo.txId);
const tx = new bsv.Transaction(txHex);
let tokenScript = tx.outputs[nftUtxo.outputIndex].script;
let curDataPartObj = nftProto.parseDataPart(tokenScript.toBuffer());
let input = tx.inputs.find((input) => {
let script = new bsv.Script(input.script);
if (script.chunks.length > 0) {
const lockingScriptBuf = TokenUtil.getLockingScriptFromPreimage(script.chunks[0].buf);
if (lockingScriptBuf) {
if (nftProto.getQueryGenesis(lockingScriptBuf) == genesis) {
return true;
}
let dataPartObj = nftProto.parseDataPart(lockingScriptBuf);
dataPartObj.sensibleID = curDataPartObj.sensibleID;
dataPartObj.tokenIndex = BN.Zero;
const newScriptBuf = nftProto.updateScript(lockingScriptBuf, dataPartObj);
let genesisHash = (0, scryptlib_1.toHex)(bsv.crypto.Hash.sha256ripemd160(newScriptBuf));
if (genesisHash == curDataPartObj.genesisHash) {
return true;
}
}
}
});
if (!input)
throw new error_1.CodeError(error_1.ErrCode.EC_INNER_ERROR, "invalid nftUtxo");
let preTxId = input.prevTxId.toString("hex");
let preOutputIndex = input.outputIndex;
let preTxHex = yield this.sensibleApi.getRawTxData(preTxId);
const preTx = new bsv.Transaction(preTxHex);
nftUtxo.satotxInfo = {
txId: nftUtxo.txId,
outputIndex: nftUtxo.outputIndex,
txHex,
preTxId,
preOutputIndex,
preTxHex,
};
nftUtxo.preLockingScript = preTx.outputs[preOutputIndex].script;
nftUtxo.lockingScript = tx.outputs[nftUtxo.outputIndex].script;
nftUtxo.satoshis = tx.outputs[nftUtxo.outputIndex].satoshis;
nftUtxo.preNftAddress = bsv.Address.fromPublicKeyHash(Buffer.from(nftProto.getNftAddress(preTx.outputs[preOutputIndex].script.toBuffer()), "hex"), this.network);
return nftUtxo;
});
}
/**
* Create a transaction for genesis
* @param genesisWif the private key of the token genesiser
* @param totalSupply total supply, 8 bytes unsign int
* @param opreturnData (Optional) append an opReturn output
* @param utxos (Optional) specify bsv utxos
* @param changeAddress (Optional) specify bsv changeAddress
* @param noBroadcast (Optional) whether not to broadcast the transaction, the default is false
* @returns
*/
genesis({ genesisWif, totalSupply, opreturnData, utxos, changeAddress, noBroadcast = false, }) {
return __awaiter(this, void 0, void 0, function* () {
const genesisPrivateKey = new bsv.PrivateKey(genesisWif);
const genesisPublicKey = genesisPrivateKey.toPublicKey();
let utxoInfo = yield this._pretreatUtxos(utxos);
if (changeAddress) {
changeAddress = new bsv.Address(changeAddress, this.network);
}
else {
changeAddress = utxoInfo.utxos[0].address;
}
totalSupply = new BN(totalSupply.toString());
let { txComposer } = yield this._genesis({
genesisPublicKey,
totalSupply,
opreturnData,
utxos: utxoInfo.utxos,
utxoPrivateKeys: utxoInfo.utxoPrivateKeys,
changeAddress,
});
let { codehash, genesis, sensibleId } = this.getCodehashAndGensisByTx(txComposer.tx);
let txHex = txComposer.getRawHex();
if (!noBroadcast) {
yield this.sensibleApi.broadcast(txHex);
}
return {
codehash,
genesis,
sensibleId,
tx: txComposer.tx,
txid: txComposer.tx.id,
txHex,
};
});
}
/**
* create an unsigned transaction for genesis
* @param genesisPublicKey the public key of the token genesiser
* @param totalSupply total supply, 8 bytes unsign int
* @param opreturnData (Optional) append an opReturn output
* @param utxos (Optional) specify bsv utxos
* @param changeAddress (Optional) specify bsv changeAddress
* @returns
*/
unsignGenesis({ genesisPublicKey, totalSupply, opreturnData, utxos, changeAddress, }) {
return __awaiter(this, void 0, void 0, function* () {
genesisPublicKey = new bsv.PublicKey(genesisPublicKey);
let utxoInfo = yield this._pretreatUtxos(utxos);
if (changeAddress) {
changeAddress = new bsv.Address(changeAddress, this.network);
}
else {
changeAddress = utxoInfo.utxos[0].address;
}
totalSupply = new BN(totalSupply.toString());
let { txComposer } = yield this._genesis({
genesisPublicKey,
totalSupply,
opreturnData,
utxos: utxoInfo.utxos,
utxoPrivateKeys: utxoInfo.utxoPrivateKeys,
changeAddress,
});
let tx = txComposer.tx;
let sigHashList = txComposer.getSigHashLit();
return { tx, sigHashList };
});
}
_genesis({ genesisPublicKey, totalSupply, opreturnData, utxos, utxoPrivateKeys, changeAddress, }) {
return __awaiter(this, void 0, void 0, function* () {
let genesisContract = nftGenesis_1.NftGenesisFactory.createContract(genesisPublicKey);
genesisContract.setFormatedDataPart({
totalSupply,
rabinPubKeyHashArrayHash: (0, scryptlib_1.toHex)(this.rabinPubKeyHashArrayHash),
});
const txComposer = new tx_composer_1.TxComposer();
const p2pkhInputIndexs = utxos.map((utxo) => {
const inputIndex = txComposer.appendP2PKHInput(utxo);
txComposer.addSigHashInfo({
inputIndex,
address: utxo.address.toString(),
sighashType: exports.sighashType,
contractType: Utils.CONTRACT_TYPE.P2PKH,
});
return inputIndex;
});
const genesisOutputIndex = txComposer.appendOutput({
lockingScript: genesisContract.lockingScript,
satoshis: this.getDustThreshold(genesisContract.lockingScript.toBuffer().length),
});
//If there is opReturn, add it to the second output
if (opreturnData) {
txComposer.appendOpReturnOutput(opreturnData);
}
txComposer.appendChangeOutput(changeAddress, this.feeb);
if (utxoPrivateKeys && utxoPrivateKeys.length > 0) {
p2pkhInputIndexs.forEach((inputIndex) => {
let privateKey = utxoPrivateKeys.splice(0, 1)[0];
txComposer.unlockP2PKHInput(privateKey, inputIndex);
});
}
this._checkTxFeeRate(txComposer);
return { txComposer };
});
}
/**
* Mint a NFT
* @param genesis the genesis of NFT.
* @param codehash the codehash of NFT.
* @param sensibleId the sensibleId of NFT.
* @param genesisWif the private key of the NFT genesiser
* @param receiverAddress the NFT receiver address
* @param metaTxId the txid of meta info outpoint.To describe NFT status, metaId is recommended
* @param metaOutputIndex the index of meta info outpoint.
* @param opreturnData (Optional) append an opReturn output
* @param utxos (Optional) specify bsv utxos
* @param changeAddress (Optional) specify bsv changeAddress
* @param noBroadcast (Optional) whether not to broadcast the transaction, the default is false
* @returns
*/
issue({ genesis, codehash, sensibleId, genesisWif, receiverAddress, metaTxId, metaOutputIndex, opreturnData, utxos, changeAddress, noBroadcast = false, }) {
return __awaiter(this, void 0, void 0, function* () {
checkParamGenesis(genesis);
checkParamCodehash(codehash);
checkParamSensibleId(sensibleId);
const genesisPrivateKey = new bsv.PrivateKey(genesisWif);
const genesisPublicKey = genesisPrivateKey.toPublicKey();
let utxoInfo = yield this._pretreatUtxos(utxos);
if (changeAddress) {
changeAddress = new bsv.Address(changeAddress, this.network);
}
else {
changeAddress = utxoInfo.utxos[0].address;
}
receiverAddress = new bsv.Address(receiverAddress, this.network);
let { txComposer, tokenIndex } = yield this._issue({
genesis,
codehash,
sensibleId,
genesisPrivateKey,
genesisPublicKey,
receiverAddress,
metaTxId,
metaOutputIndex,
opreturnData,
utxos: utxoInfo.utxos,
utxoPrivateKeys: utxoInfo.utxoPrivateKeys,
changeAddress: changeAddress,
});
let txHex = txComposer.getRawHex();
if (!noBroadcast) {
yield this.sensibleApi.broadcast(txHex);
}
return {
txHex,
txid: txComposer.getTxId(),
tx: txComposer.getTx(),
tokenIndex,
};
});
}
/**
* Create the unsigned transaction for issue NFT,
* @param genesis the genesis of NFT.
* @param codehash the codehash of NFT.
* @param genesisPublicKey the public key of the NFT genesiser
* @param receiverAddress the NFT receiver address
* @param metaTxId the txid of meta info outpoint.To describe NFT status, metaId is recommended
* @param metaOutputIndex the index of meta info outpoint..
* @param opreturnData (Optional) append an opReturn output
* @param utxos (Optional) specify bsv utxos
* @param changeAddress (Optional) specify bsv changeAddress
* @returns
*/
unsignIssue({ genesis, codehash, sensibleId, genesisPublicKey, receiverAddress, metaTxId, metaOutputIndex, opreturnData, utxos, changeAddress, }) {
return __awaiter(this, void 0, void 0, function* () {
checkParamGenesis(genesis);
checkParamCodehash(codehash);
checkParamSensibleId(sensibleId);
genesisPublicKey = new bsv.PublicKey(genesisPublicKey);
let utxoInfo = yield this._pretreatUtxos(utxos);
if (changeAddress) {
changeAddress = new bsv.Address(changeAddress, this.network);
}
else {
changeAddress = utxoInfo.utxos[0].address;
}
receiverAddress = new bsv.Address(receiverAddress, this.network);
let { txComposer, tokenIndex } = yield this._issue({
genesis,
codehash,
sensibleId,
genesisPublicKey,
receiverAddress,
metaTxId,
metaOutputIndex,
opreturnData,
utxos: utxoInfo.utxos,
utxoPrivateKeys: utxoInfo.utxoPrivateKeys,
changeAddress: changeAddress,
});
let tx = txComposer.getTx();
let sigHashList = txComposer.getSigHashLit();
return { tx, sigHashList, tokenIndex };
});
}
getIssueUtxo(codehash, genesisTxId, genesisOutputIndex) {
return __awaiter(this, void 0, void 0, function* () {
let unspent;
let firstGenesisTxHex = yield this.sensibleApi.getRawTxData(genesisTxId);
let firstGenesisTx = new bsv.Transaction(firstGenesisTxHex);
let scriptBuffer = firstGenesisTx.outputs[genesisOutputIndex].script.toBuffer();
let originGenesis = nftProto.getQueryGenesis(scriptBuffer);
let genesisUtxos = yield this.sensibleApi.getNonFungibleTokenUnspents(codehash, originGenesis, this.zeroAddress.toString());
unspent = genesisUtxos.find((v) => v.txId == genesisTxId && v.outputIndex == genesisOutputIndex);
// let spent = await this.sensibleApi.getOutpointSpent(
// genesisTxId,
// genesisOutputIndex
// );
// if (!spent) {
// return {
// txId: genesisTxId,
// outputIndex: genesisOutputIndex,
// };
// }
if (!unspent) {
let _dataPartObj = nftProto.parseDataPart(scriptBuffer);
_dataPartObj.sensibleID = {
txid: genesisTxId,
index: genesisOutputIndex,
};
let newScriptBuf = nftProto.updateScript(scriptBuffer, _dataPartObj);
let issueGenesis = nftProto.getQueryGenesis(newScriptBuf);
let issueUtxos = yield this.sensibleApi.getNonFungibleTokenUnspents(codehash, issueGenesis, this.zeroAddress.toString());
if (issueUtxos.length > 0) {
unspent = issueUtxos[0];
}
}
if (unspent) {
return {
txId: unspent.txId,
outputIndex: unspent.outputIndex,
};
}
});
}
prepareNftUtxo2(nftUtxo) {
return __awaiter(this, void 0, void 0, function* () {
let txHex = yield this.sensibleApi.getRawTxData(nftUtxo.txId);
const tx = new bsv.Transaction(txHex);
let preTxId = tx.inputs[0].prevTxId.toString("hex");
let preOutputIndex = tx.inputs[0].outputIndex;
let preTxHex = yield this.sensibleApi.getRawTxData(preTxId);
nftUtxo.satotxInfo = {
txId: nftUtxo.txId,
outputIndex: nftUtxo.outputIndex,
txHex,
preTxId,
preOutputIndex,
preTxHex,
};
let output = tx.outputs[nftUtxo.outputIndex];
nftUtxo.satoshis = output.satoshis;
nftUtxo.lockingScript = output.script;
});
}
_issue({ genesis, codehash, sensibleId, genesisPrivateKey, genesisPublicKey, receiverAddress, metaTxId, metaOutputIndex, opreturnData, utxos, utxoPrivateKeys, changeAddress, }) {
return __awaiter(this, void 0, void 0, function* () {
let { genesisContract, genesisTxId, genesisOutputIndex, genesisUtxo } = yield this._pretreatNftUtxoToIssue({ sensibleId, genesisPublicKey });
let balance = utxos.reduce((pre, cur) => pre + cur.satoshis, 0);
let estimateSatoshis = yield this._calIssueEstimateFee({
genesisUtxoSatoshis: genesisUtxo.satoshis,
opreturnData,
utxoMaxCount: utxos.length,
});
if (balance < estimateSatoshis) {
throw new error_1.CodeError(error_1.ErrCode.EC_INSUFFICIENT_BSV, `Insufficient balance.It take more than ${estimateSatoshis}, but only ${balance}.`);
}
let originDataPart = genesisContract.getFormatedDataPart();
genesisContract.setFormatedDataPart({
sensibleID: {
txid: genesisTxId,
index: genesisOutputIndex,
},
tokenIndex: BN.Zero,
});
let genesisHash = genesisContract.getScriptHash();
genesisContract.setFormatedDataPart(originDataPart);
let nftContract = nft_1.NftFactory.createContract(this.unlockContractCodeHashArray, codehash);
nftContract.setFormatedDataPart({
metaidOutpoint: {
txid: metaTxId,
index: metaOutputIndex,
},
nftAddress: (0, scryptlib_1.toHex)(receiverAddress.hashBuffer),
totalSupply: genesisContract.getFormatedDataPart().totalSupply,
tokenIndex: genesisContract.getFormatedDataPart().tokenIndex,
genesisHash,
rabinPubKeyHashArrayHash: genesisContract.getFormatedDataPart().rabinPubKeyHashArrayHash,
sensibleID: {
txid: genesisTxId,
index: genesisOutputIndex,
},
});
if (originDataPart.rabinPubKeyHashArrayHash !=
(0, scryptlib_1.toHex)(this.rabinPubKeyHashArrayHash)) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_SIGNERS, "Invalid signers.");
}
let { rabinData, rabinPubKeyIndexArray, rabinPubKeyVerifyArray } = yield (0, satotxSignerUtil_1.getRabinData)(this.signers, this.signerSelecteds, genesisContract.isFirstGenesis() ? null : genesisUtxo.satotxInfo);
const txComposer = new tx_composer_1.TxComposer();
//The first input is the genesis contract
const genesisInputIndex = txComposer.appendInput(genesisUtxo);
txComposer.addSigHashInfo({
inputIndex: genesisInputIndex,
address: genesisPublicKey.toAddress(this.network).toString(),
sighashType: exports.sighashType,
contractType: utils_1.CONTRACT_TYPE.BCP01_NFT_GENESIS,
});
const p2pkhInputIndexs = utxos.map((utxo) => {
const inputIndex = txComposer.appendP2PKHInput(utxo);
txComposer.addSigHashInfo({
inputIndex,
address: utxo.address.toString(),
sighashType: exports.sighashType,
contractType: utils_1.CONTRACT_TYPE.P2PKH,
});
return inputIndex;
});
let genesisContractSatoshis = 0;
const genesisDataPartObj = genesisContract.getFormatedDataPart();
if (genesisDataPartObj.tokenIndex.lt(genesisDataPartObj.totalSupply.sub(BN.One))) {
genesisDataPartObj.tokenIndex = genesisDataPartObj.tokenIndex.add(BN.One);
genesisDataPartObj.sensibleID =
nftContract.getFormatedDataPart().sensibleID;
let nextGenesisContract = genesisContract.clone();
nextGenesisContract.setFormatedDataPart(genesisDataPartObj);
genesisContractSatoshis = this.getDustThreshold(nextGenesisContract.lockingScript.toBuffer().length);
txComposer.appendOutput({
lockingScript: nextGenesisContract.lockingScript,
satoshis: genesisContractSatoshis,
});
}
//The following output is the NFT
const nftOutputIndex = txComposer.appendOutput({
lockingScript: nftContract.lockingScript,
satoshis: this.getDustThreshold(nftContract.lockingScript.toBuffer().length),
});
//If there is opReturn, add it to the output
let opreturnScriptHex = "";
if (opreturnData) {
const opreturnOutputIndex = txComposer.appendOpReturnOutput(opreturnData);
opreturnScriptHex = txComposer
.getOutput(opreturnOutputIndex)
.script.toHex();
}
for (let c = 0; c < 2; c++) {
txComposer.clearChangeOutput();
const changeOutputIndex = txComposer.appendChangeOutput(changeAddress, this.feeb);
let unlockResult = genesisContract.unlock({
txPreimage: txComposer.getInputPreimage(genesisInputIndex),
sig: new scryptlib_1.Sig(genesisPrivateKey
? (0, scryptlib_1.toHex)(txComposer.getTxFormatSig(genesisPrivateKey, genesisInputIndex))
: utils_1.PLACE_HOLDER_SIG),
rabinMsg: rabinData.rabinMsg,
rabinPaddingArray: rabinData.rabinPaddingArray,
rabinSigArray: rabinData.rabinSigArray,
rabinPubKeyIndexArray,
rabinPubKeyVerifyArray,
rabinPubKeyHashArray: this.rabinPubKeyHashArray,
genesisSatoshis: genesisContractSatoshis,
nftScript: new scryptlib_1.Bytes(txComposer.getOutput(nftOutputIndex).script.toHex()),
nftSatoshis: txComposer.getOutput(nftOutputIndex).satoshis,
changeAddress: new scryptlib_1.Ripemd160((0, scryptlib_1.toHex)(changeAddress.hashBuffer)),
changeSatoshis: changeOutputIndex != -1
? txComposer.getOutput(changeOutputIndex).satoshis
: 0,
opReturnScript: new scryptlib_1.Bytes(opreturnScriptHex),
});
if (this.debug && genesisPrivateKey && c == 1) {
let ret = unlockResult.verify({
tx: txComposer.tx,
inputIndex: genesisInputIndex,
inputSatoshis: txComposer.getInput(genesisInputIndex).output.satoshis,
});
if (ret.success == false)
throw ret;
}
txComposer
.getInput(genesisInputIndex)
.setScript(unlockResult.toScript());
}
if (utxoPrivateKeys && utxoPrivateKeys.length > 0) {
p2pkhInputIndexs.forEach((inputIndex) => {
let privateKey = utxoPrivateKeys.splice(0, 1)[0];
txComposer.unlockP2PKHInput(privateKey, inputIndex);
});
}
this._checkTxFeeRate(txComposer);
return {
txComposer,
tokenIndex: nftContract.getFormatedDataPart().tokenIndex.toString(10),
};
});
}
/**
* Create a transaction and broadcast it to transfer a NFT.
* @param genesis the genesis of NFT.
* @param codehash the codehash of NFT.
* @param tokenIndex the tokenIndex of NFT.
* @param senderWif the private key of the token sender,can be wif or other format
* @param senderPrivateKey the private key of the token sender
* @param receiverAddress the NFT receiver address
* @param opreturnData (Optional) append an opReturn output
* @param utxos (Optional) specify bsv utxos
* @param changeAddress (Optional) specify bsv changeAddress
* @param noBroadcast (Optional) whether not to broadcast the transaction, the default is false
* @returns
*/
transfer({ genesis, codehash, tokenIndex, senderWif, senderPrivateKey, receiverAddress, opreturnData, utxos, changeAddress, noBroadcast = false, }) {
return __awaiter(this, void 0, void 0, function* () {
checkParamGenesis(genesis);
checkParamCodehash(codehash);
let senderPublicKey;
if (senderWif) {
senderPrivateKey = new bsv.PrivateKey(senderWif);
senderPublicKey = senderPrivateKey.publicKey;
}
else if (senderPrivateKey) {
senderPrivateKey = new bsv.PrivateKey(senderPrivateKey);
senderPublicKey = senderPrivateKey.publicKey;
}
else {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_ARGUMENT, "senderPrivateKey should be provided!");
}
let nftInfo = yield this._pretreatNftUtxoToTransfer(tokenIndex, codehash, genesis, senderPrivateKey, senderPublicKey);
let utxoInfo = yield this._pretreatUtxos(utxos);
if (changeAddress) {
changeAddress = new bsv.Address(changeAddress, this.network);
}
else {
changeAddress = utxoInfo.utxos[0].address;
}
receiverAddress = new bsv.Address(receiverAddress, this.network);
let { txComposer } = yield this._transfer({
genesis,
codehash,
nftUtxo: nftInfo.nftUtxo,
nftPrivateKey: nftInfo.nftUtxoPrivateKey,
receiverAddress,
opreturnData,
utxos: utxoInfo.utxos,
utxoPrivateKeys: utxoInfo.utxoPrivateKeys,
changeAddress,
});
let txHex = txComposer.getRawHex();
if (!noBroadcast) {
yield this.sensibleApi.broadcast(txHex);
}
return { tx: txComposer.tx, txHex, txid: txComposer.tx.id };
});
}
/**
* Create an unsigned transaction to transfer a NFT.
* @param genesis the genesis of NFT.
* @param codehash the codehash of NFT.
* @param tokenIndex the tokenIndex of NFT.
* @param senderPublicKey the public key of the NFT sender
* @param receiverAddress the NFT receiver address
* @param opreturnData (Optional) append an opReturn output
* @param utxos (Optional) specify bsv utxos
* @param changeAddress (Optional) specify bsv changeAddress
* @returns
*/
unsignTransfer({ genesis, codehash, tokenIndex, senderPublicKey, receiverAddress, opreturnData, utxos, changeAddress, }) {
return __awaiter(this, void 0, void 0, function* () {
checkParamGenesis(genesis);
checkParamCodehash(codehash);
let nftInfo = yield this._pretreatNftUtxoToTransfer(tokenIndex, codehash, genesis, null, senderPublicKey);
let utxoInfo = yield this._pretreatUtxos(utxos);
if (changeAddress) {
changeAddress = new bsv.Address(changeAddress, this.network);
}
else {
changeAddress = utxoInfo.utxos[0].address;
}
receiverAddress = new bsv.Address(receiverAddress, this.network);
let { txComposer } = yield this._transfer({
genesis,
codehash,
nftUtxo: nftInfo.nftUtxo,
receiverAddress,
opreturnData,
utxos: utxoInfo.utxos,
utxoPrivateKeys: utxoInfo.utxoPrivateKeys,
changeAddress,
});
let tx = txComposer.tx;
let sigHashList = txComposer.getSigHashLit();
return { tx, sigHashList };
});
}
_transfer({ genesis, codehash, nftUtxo, nftPrivateKey, receiverAddress, opreturnData, utxos, utxoPrivateKeys, changeAddress, }) {
return __awaiter(this, void 0, void 0, function* () {
nftUtxo = yield this._pretreatNftUtxoToTransferOn(nftUtxo, codehash, genesis);
let genesisScript = nftUtxo.preNftAddress.hashBuffer.equals(Buffer.alloc(20, 0))
? new scryptlib_1.Bytes(nftUtxo.preLockingScript.toHex())
: new scryptlib_1.Bytes("");
let balance = utxos.reduce((pre, cur) => pre + cur.satoshis, 0);
let estimateSatoshis = yield this._calTransferEstimateFee({
nftUtxoSatoshis: nftUtxo.satoshis,
genesisScript,
opreturnData,
utxoMaxCount: utxos.length,
});
if (balance < estimateSatoshis) {
throw new error_1.CodeError(error_1.ErrCode.EC_INSUFFICIENT_BSV, `Insufficient balance.It take more than ${estimateSatoshis}, but only ${balance}.`);
}
//validate signers
const nftScriptBuf = nftUtxo.lockingScript.toBuffer();
let dataPartObj = nftProto.parseDataPart(nftScriptBuf);
dataPartObj.nftAddress = (0, scryptlib_1.toHex)(receiverAddress.hashBuffer);
const lockingScriptBuf = nftProto.updateScript(nftScriptBuf, dataPartObj);
if (dataPartObj.rabinPubKeyHashArrayHash !=
(0, scryptlib_1.toHex)(this.rabinPubKeyHashArrayHash)) {
throw new error_1.CodeError(error_1.ErrCode.EC_INVALID_SIGNERS, "Invalid signers.");
}
let { rabinDatas, rabinPubKeyIndexArray, rabinPubKeyVerifyArray } = yield (0, satotxSignerUtil_1.getRabinDatas)(this.signers, this.signerSelecteds, [
nftUtxo.satotxInfo,
]);
const txComposer = new tx_composer_1.TxComposer();
let nftInput = nftUtxo;
let prevouts = new Prevouts();
// token contract input
const nftInputIndex = txComposer.appendInput(nftInput);
prevouts.addVout(nftInput.txId, nftInput.outputIndex);
txComposer.addSigHashInfo({
inputIndex: nftInputIndex,
address: nftUtxo.nftAddress.toString(),
sighashType: exports.sighashType,
contractType: utils_1.CONTRACT_TYPE.BCP01_NFT,
});
const p2pkhInputIndexs = utxos.map((utxo) => {
const inputIndex = txComposer.appendP2PKHInput(utxo);
prevouts.addVout(utxo.txId, utxo.outputIndex);
txComposer.addSigHashInfo({
inputIndex,
address: utxo.address.toString(),
sighashType: exports.sighashType,
contractType: utils_1.CONTRACT_TYPE.P2PKH,
});
return inputIndex;
});
//tx addOutput nft
const nftOutputIndex = txComposer.appendOutput({
lockingScript: bsv.Script.fromBuffer(lockingScriptBuf),
satoshis: this.getDustThreshold(lockingScriptBuf.length),
});
//tx addOutput OpReturn
let opreturnScriptHex = "";
if (opreturnData) {
const opreturnOutputIndex = txComposer.appendOpReturnOutput(opreturnData);
opreturnScriptHex = txComposer
.getOutput(opreturnOutputIndex)
.script.toHex();
}
//The first round of calculations get the exact size of the final transaction, and then change again
//Due to the change, the script needs to be unlocked again in the second round
//let the fee to be exact in the second round
for (let c = 0; c < 2; c++) {
txComposer.clearChangeOutput();
const changeOutputIndex = txComposer.appendChangeOutput(changeAddress, this.feeb);
const nftContract = nft_1.NftFactory.createContract(this.unlockContractCodeHashArray, codehash);
let dataPartObj = nftProto.parseDataPart(nftInput.lockingScript.toBuffer());
nftContract.setFormatedDataPart(dataPartObj);
const unlockingContract = nftContract.unlock({
txPreimage: txComposer.getInputPreimage(nftInputIndex),
prevouts: new scryptlib_1.Bytes(prevouts.toHex()),