ecash-agora
Version:
Library for interacting with the eCash Agora protocol
997 lines (996 loc) • 60.5 kB
JavaScript
"use strict";
// Copyright (c) 2024 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
Object.defineProperty(exports, "__esModule", { value: true });
exports.AgoraPartialAdSignatory = exports.AgoraPartialCancelSignatory = exports.AgoraPartialSignatory = exports.AgoraPartial = void 0;
const ecash_lib_1 = require("ecash-lib");
const ecash_wallet_1 = require("ecash-wallet");
const broadcast_js_1 = require("./broadcast.js");
const consts_js_1 = require("./consts.js");
const inputs_js_1 = require("./inputs.js");
const actions_js_1 = require("./actions.js");
const walletUtxoReconcile_js_1 = require("./walletUtxoReconcile.js");
/**
* An Agora offer that can partially be accepted.
* In contrast to oneshot offers, these can be partially accepted, with the
* remainder sent back to a new UTXO with the same terms but reduced token
* amount.
* This is useful for fungible tokens, where the maker doesn't know upfront how
* many tokens the takers would like to acquire.
*
* The Script enforces that the taker re-creates an offer with the same terms
* with tokens he didn't buy.
* It calculates the required sats to accept the offer based on the price per
* token, and the number of tokens requested by the taker, and enforces the
* correct amount of satoshis are sent to the P2PKH of the maker of this offer.
*
* Offers can also be cancelled by the maker of the offer.
*
* One complication is the price calculation, due to eCash's limited precision
* and range (31-bits plus 1 sign bit) of its Script integers.
* We employ two strategies to increase precision and range:
* - "Scaling": We scale up values to the maximum representable, such that we
* make full use of the 31 bits available. Values that have been scaled up
* have the prefix "scaled", and the scale factor is "atomsScaleFactor". We
* only scale token amounts.
* - "Truncation": We cut off bytes at the "end" of numbers, essentially
* dividing them by 256 for each truncation, until they fit in 31 bits, so we
* can use arithmetic opcodes. Later we "un-truncate" values again by adding
* the bytes back. We use OP_CAT to un-truncate values, which doesn't care
* about the 31-bit limit. Values that have been truncated have the "trunc"
* prefix. We truncate both token amounts (by numAtomsTruncBytes bytes) and
* sats amounts (by numSatsTruncBytes).
*
* Scaling and truncation can be combined, such that the token price is in
* "scaledTruncAtomsPerTruncSat".
* Together, they give us a very large range of representable values, while
* keeping a decent precision.
*
* Ideally, eCash can eventually raise the maximum integer size to e.g. 64-bits,
* which would greatly increase the precision. The strategies employed are
* useful there too, we simply get a much more accurate price calculation.
**/
class AgoraPartial {
constructor(params) {
this.truncAtoms = params.truncAtoms;
this.numAtomsTruncBytes = params.numAtomsTruncBytes;
this.atomsScaleFactor = params.atomsScaleFactor;
this.scaledTruncAtomsPerTruncSat = params.scaledTruncAtomsPerTruncSat;
this.numSatsTruncBytes = params.numSatsTruncBytes;
this.makerPk = params.makerPk;
this.minAcceptedScaledTruncAtoms = params.minAcceptedScaledTruncAtoms;
this.tokenId = params.tokenId;
this.tokenType = params.tokenType;
this.tokenProtocol = params.tokenProtocol;
this.scriptLen = params.scriptLen;
this.enforcedLockTime = params.enforcedLockTime;
this.dustSats = params.dustSats;
}
/**
* Approximate good script parameters for the given offer params.
* Note: This is not guaranteed to be optimal and is done on a best-effort
* basis.
* @param params Offer params to approximate, see AgoraPartialParams for
* details.
* @param scriptIntegerBits How many bits Script integers have on the
* network. Defaults to 64-bit for better accuracy. Can be set to
* 32 for compatibility with older networks.
**/
static approximateParams(params, scriptIntegerBits = 64n) {
if (params.offeredAtoms < 1n) {
throw new Error('offeredAtoms must be at least 1');
}
if (params.priceNanoSatsPerAtom < 1n) {
throw new Error('priceNanoSatsPerAtom must be at least 1');
}
if (params.minAcceptedAtoms < 1n) {
throw new Error('minAcceptedAtoms must be at least 1');
}
if (params.tokenProtocol === 'SLP' &&
params.offeredAtoms > 0xffffffffffffffffn) {
throw new Error('For SLP, offeredAtoms can be at most 0xffffffffffffffff');
}
if (params.tokenProtocol === 'ALP' &&
params.offeredAtoms > 0xffffffffffffn) {
throw new Error('For ALP, offeredAtoms can be at most 0xffffffffffff');
}
if (params.offeredAtoms < params.minAcceptedAtoms) {
throw new Error('offeredAtoms must be greater than or equal to minAcceptedAtoms');
}
// Script uses 1 bit as sign bit, which we can't use in our calculation
const scriptIntegerWithoutSignBits = scriptIntegerBits - 1n;
// Max integer that can be represented in Script on the network
const maxScriptInt = (1n << scriptIntegerWithoutSignBits) - 1n;
// Edge case where price can be represented exactly,
// no need to introduce extra approximation.
const isPrecisePrice = 1000000000n % params.priceNanoSatsPerAtom === 0n;
// The Script can only handle a maximum level of truncation
const maxTokenTruncBytes = params.tokenProtocol === 'SLP' ? 5 : 3;
const minAtomsScaleFactor = isPrecisePrice
? 1n
: (params.minAtomsScaleFactor ?? 10000n);
// If we can't represent the offered tokens in a script int, we truncate 8
// bits at a time until it fits.
let truncAtoms = params.offeredAtoms;
let numAtomsTruncBytes = 0n;
while (truncAtoms * minAtomsScaleFactor > maxScriptInt &&
numAtomsTruncBytes < maxTokenTruncBytes) {
truncAtoms >>= 8n;
numAtomsTruncBytes++;
}
// Required sats to fully accept the trade (rounded down)
const requiredSats = (params.offeredAtoms * params.priceNanoSatsPerAtom) / 1000000000n;
// For bigger trades (>=2^31 sats), we need also to truncate sats
let requiredTruncSats = requiredSats;
let numSatsTruncBytes = 0n;
while (requiredTruncSats > maxScriptInt) {
requiredTruncSats >>= 8n;
numSatsTruncBytes++;
}
// We scale up the token values to get some extra precision
let atomsScaleFactor = maxScriptInt / truncAtoms;
// How many scaled trunc tokens can be gotten for each trunc sat.
// It is the inverse of the price specified by the user, and truncated +
// scaled as required by the Script.
const calcScaledTruncAtomsPerTruncSat = () => ((1n << (8n * numSatsTruncBytes)) *
atomsScaleFactor *
1000000000n) /
((1n << (8n * numAtomsTruncBytes)) * params.priceNanoSatsPerAtom);
// For trades offering a few tokens for many sats, truncate the sats
// amounts some more to increase precision.
const minPriceInteger = params.minPriceInteger ?? 1000n;
// However, only truncate sats if atomsScaleFactor is well above
// scaledTruncAtomsPerTruncSat, otherwise we lose precision because
// we're rounding up for the sats calculation in the Script.
const minScaleRatio = params.minScaleRatio ?? 1000n;
let scaledTruncAtomsPerTruncSat = calcScaledTruncAtomsPerTruncSat();
while (scaledTruncAtomsPerTruncSat < minPriceInteger &&
scaledTruncAtomsPerTruncSat * minScaleRatio < atomsScaleFactor) {
numSatsTruncBytes++;
scaledTruncAtomsPerTruncSat = calcScaledTruncAtomsPerTruncSat();
}
// Edge case where the sats calculation can go above the integer limit
if (truncAtoms * atomsScaleFactor + scaledTruncAtomsPerTruncSat - 1n >
maxScriptInt) {
if (truncAtoms * atomsScaleFactor <= scaledTruncAtomsPerTruncSat) {
// Case where we just overshot the atomsScaleFactor
atomsScaleFactor /= 2n;
scaledTruncAtomsPerTruncSat = calcScaledTruncAtomsPerTruncSat();
}
const maxTruncAtoms = maxScriptInt - scaledTruncAtomsPerTruncSat + 1n;
if (maxTruncAtoms < 0n) {
throw new Error('Parameters cannot be represented in Script');
}
if (truncAtoms > maxTruncAtoms) {
// Case where truncAtoms itself is close to maxScriptInt
atomsScaleFactor = 1n;
truncAtoms = maxTruncAtoms;
}
else {
// Case where scaled tokens would exceed maxScriptInt
atomsScaleFactor = maxTruncAtoms / truncAtoms;
}
// Recalculate price
scaledTruncAtomsPerTruncSat = calcScaledTruncAtomsPerTruncSat();
}
// Scale + truncate the minimum accepted tokens
const minAcceptedScaledTruncAtoms = params.minAcceptedAtoms === params.offeredAtoms
? // If minAcceptedAtoms is intended to be the whole offer, set it this way.
// This prevents creating an unacceptable offer (minAcceptedAtoms > offeredAtoms)
truncAtoms * atomsScaleFactor
: (params.minAcceptedAtoms * atomsScaleFactor) >>
(8n * numAtomsTruncBytes);
const dustSats = params.dustSats ?? ecash_lib_1.DEFAULT_DUST_SATS;
const agoraPartial = new AgoraPartial({
truncAtoms,
numAtomsTruncBytes: Number(numAtomsTruncBytes),
atomsScaleFactor,
scaledTruncAtomsPerTruncSat,
numSatsTruncBytes: Number(numSatsTruncBytes),
makerPk: params.makerPk,
minAcceptedScaledTruncAtoms,
tokenId: params.tokenId,
tokenType: params.tokenType,
tokenProtocol: params.tokenProtocol,
scriptLen: 0x7f,
enforcedLockTime: params.enforcedLockTime,
dustSats,
});
const minAcceptedAtoms = agoraPartial.minAcceptedAtoms();
if (minAcceptedAtoms < 1n) {
throw new Error('minAcceptedAtoms too small, got truncated to 0');
}
const minAskedSats = agoraPartial.askedSats(minAcceptedAtoms);
if (minAskedSats < dustSats) {
throw new Error('minAcceptedAtoms would cost less than dust at this price');
}
agoraPartial.updateScriptLen();
return agoraPartial;
}
updateScriptLen() {
let measuredLength = this.script().cutOutCodesep(0).bytecode.length;
if (measuredLength >= 0x80) {
this.scriptLen = 0x80;
measuredLength = this.script().cutOutCodesep(0).bytecode.length;
}
this.scriptLen = measuredLength;
}
/**
* How many tokens are accually offered by the Script.
* This may differ from the offeredAtoms in the AgoraPartialParams used to
* approximate this AgoraPartial.
**/
offeredAtoms() {
return this.truncAtoms << BigInt(8 * this.numAtomsTruncBytes);
}
/**
* Actual minimum acceptable tokens of this Script.
* This may differ from the minAcceptedAtoms in the AgoraPartialParams used
* to approximate this AgoraPartial.
**/
minAcceptedAtoms() {
const minAcceptedAtoms = (this.minAcceptedScaledTruncAtoms <<
BigInt(8 * this.numAtomsTruncBytes)) /
this.atomsScaleFactor;
let preparedMinAcceptedAtoms = this.prepareAcceptedAtoms(minAcceptedAtoms);
if (preparedMinAcceptedAtoms < minAcceptedAtoms) {
// It's possible that, after adjusting for acceptable discrete intervals,
// minAcceptedAtoms becomes less than the script minimum
// In this case, we "round up" to the true min accepted tokens
const tickSize = (this.atomsScaleFactor << BigInt(8 * this.numAtomsTruncBytes)) /
this.atomsScaleFactor;
preparedMinAcceptedAtoms += tickSize;
}
return preparedMinAcceptedAtoms;
}
/**
* Calculate the actually asked satoshi amount for the given accepted number of tokens.
* This is the exact amount that has to be sent to makerPk's P2PKH address
* to accept the offer.
* `acceptedAtoms` must have the lowest numAtomsTruncBytes bytes set to 0,
* use prepareAcceptedAtoms to do so.
**/
askedSats(acceptedAtoms) {
const numSatsTruncBits = BigInt(8 * this.numSatsTruncBytes);
const numTokenTruncBits = BigInt(8 * this.numAtomsTruncBytes);
const acceptedTruncAtoms = acceptedAtoms >> numTokenTruncBits;
if (acceptedTruncAtoms << numTokenTruncBits != acceptedAtoms) {
throw new Error(`acceptedAtoms must have the last ${numTokenTruncBits} bits ` +
'set to zero, use prepareAcceptedAtoms to get a valid amount');
}
// Divide rounding up
const askedTruncSats = (acceptedTruncAtoms * this.atomsScaleFactor +
this.scaledTruncAtomsPerTruncSat -
1n) /
this.scaledTruncAtomsPerTruncSat;
// Un-truncate sats
return askedTruncSats << numSatsTruncBits;
}
/**
* Throw an error if accept amount is invalid
* Note we do not prepare amounts in this function
* @param acceptedAtoms
*/
preventUnacceptableRemainder(acceptedAtoms) {
// Validation to avoid creating an offer that cannot be accepted
//
// 1 - confirm the remaining offer amount is more than the
// min accept amount for this agora partial
//
// 2 - Confirm the cost of accepting the (full) remainder is
// at least dust. This is already confirmed...for offers
// created by this lib... as minAcceptedAtoms() must
// cost more than dust
//
//
// If these condtions are not met, an AgoraOffer would be created
// that is impossible to accept; can only be canceld by its maker
// Get the token qty that would remain after this accept
const offeredAtoms = this.offeredAtoms();
const remainingTokens = offeredAtoms - acceptedAtoms;
if (remainingTokens <= 0n) {
return;
}
// Full accepts are always ok
const minAcceptedAtoms = this.minAcceptedAtoms();
const priceOfRemainingTokens = this.askedSats(remainingTokens);
if (remainingTokens < minAcceptedAtoms) {
throw new Error(`Accepting ${acceptedAtoms} token satoshis would leave an amount lower than the min acceptable by the terms of this contract, and hence unacceptable. Accept fewer tokens or the full offer.`);
}
if (priceOfRemainingTokens < this.dustSats) {
throw new Error(`Accepting ${acceptedAtoms} token satoshis would leave an amount priced lower than dust. Accept fewer tokens or the full offer.`);
}
}
/**
* Prepare the given acceptedAtoms amount for the Script; `acceptedAtoms`
* must have the lowest numAtomsTruncBytes bytes set to 0 and this function
* does this for us.
**/
prepareAcceptedAtoms(acceptedAtoms) {
const numTokenTruncBits = BigInt(8 * this.numAtomsTruncBytes);
return (acceptedAtoms >> numTokenTruncBits) << numTokenTruncBits;
}
/**
* Calculate the actual priceNanoSatsPerAtom of this offer, factoring in
* all approximation inacurracies.
* Due to the rounding, the price can change based on the accepted token
* amount. By default it calculates the price per token for accepting the
* entire offer.
**/
priceNanoSatsPerAtom(acceptedAtoms) {
acceptedAtoms ?? (acceptedAtoms = this.offeredAtoms());
const prepared = this.prepareAcceptedAtoms(acceptedAtoms);
const sats = this.askedSats(prepared);
return (sats * 1000000000n) / prepared;
}
adPushdata() {
const serAdPushdata = (writer) => {
if (this.tokenProtocol === 'ALP') {
// On ALP, we signal AGR0 in the pushdata
writer.putBytes(consts_js_1.AGORA_LOKAD_ID);
writer.putU8(AgoraPartial.COVENANT_VARIANT.length);
writer.putBytes((0, ecash_lib_1.strToBytes)(AgoraPartial.COVENANT_VARIANT));
}
writer.putU8(this.numAtomsTruncBytes);
writer.putU8(this.numSatsTruncBytes);
writer.putU64(this.atomsScaleFactor);
writer.putU64(this.scaledTruncAtomsPerTruncSat);
writer.putU64(this.minAcceptedScaledTruncAtoms);
writer.putU32(this.enforcedLockTime);
writer.putBytes(this.makerPk);
};
const lengthWriter = new ecash_lib_1.WriterLength();
serAdPushdata(lengthWriter);
const bytesWriter = new ecash_lib_1.WriterBytes(lengthWriter.length);
serAdPushdata(bytesWriter);
return bytesWriter.data;
}
covenantConsts() {
const adPushdata = this.adPushdata();
// "Consts" is serialized data with the terms of the offer + the token
// protocol intros.
if (this.tokenProtocol === 'SLP') {
const slpSendIntro = (0, ecash_lib_1.slpSend)(this.tokenId, this.tokenType, [
0n,
]).bytecode;
const covenantConstsWriter = new ecash_lib_1.WriterBytes(slpSendIntro.length + adPushdata.length);
covenantConstsWriter.putBytes(slpSendIntro);
covenantConstsWriter.putBytes(adPushdata);
return [covenantConstsWriter.data, slpSendIntro.length];
}
else if (this.tokenProtocol === 'ALP') {
const alpSendTemplate = (0, ecash_lib_1.alpSend)(this.tokenId, this.tokenType, []);
// ALP SEND section, but without the num amounts
const alpSendIntro = alpSendTemplate.slice(0, alpSendTemplate.length - 1);
// eMPP script with Agora ad, but without the ALP section
const emppIntro = (0, ecash_lib_1.emppScript)([adPushdata]);
const covenantConstsWriter = new ecash_lib_1.WriterBytes(alpSendIntro.length + emppIntro.bytecode.length);
covenantConstsWriter.putBytes(alpSendIntro);
covenantConstsWriter.putBytes(emppIntro.bytecode);
return [covenantConstsWriter.data, alpSendIntro.length];
}
else {
throw new Error('Not implemented');
}
}
script() {
const [covenantConsts, tokenIntroLen] = this.covenantConsts();
// Serialize scaled tokens as 8-byte little endian.
// Even though Script currently doesn't support 64-bit integers,
// this allows us to eventually upgrade to 64-bit without changing this
// Script at all.
const scaledTruncAtoms8LeWriter = new ecash_lib_1.WriterBytes(8);
scaledTruncAtoms8LeWriter.putU64(this.truncAtoms * this.atomsScaleFactor);
const scaledTruncAtoms8Le = scaledTruncAtoms8LeWriter.data;
const enforcedLockTime4LeWriter = new ecash_lib_1.WriterBytes(4);
enforcedLockTime4LeWriter.putU32(this.enforcedLockTime);
const enforcedLockTime4Le = enforcedLockTime4LeWriter.data;
return ecash_lib_1.Script.fromOps([
// # Push consts
(0, ecash_lib_1.pushBytesOp)(covenantConsts),
// # Push offered token amount as scaled trunc tokens, as u64 LE
(0, ecash_lib_1.pushBytesOp)(scaledTruncAtoms8Le),
// # Use OP_CODESEPERATOR to remove the above two (large) pushops
// # from the sighash preimage (tx size optimization)
ecash_lib_1.OP_CODESEPARATOR,
// OP_ROT(isPurchase, _, _)
ecash_lib_1.OP_ROT,
// OP_IF(isPurchase)
ecash_lib_1.OP_IF,
// scaledTruncAtoms = OP_BIN2NUM(scaledTruncAtoms8Le)
ecash_lib_1.OP_BIN2NUM,
// OP_ROT(acceptedScaledTruncAtoms, _, _)
ecash_lib_1.OP_ROT,
// # Verify accepted amount doesn't exceed available amount
// OP_2DUP(scaledTruncAtoms, acceptedScaledTruncAtoms)
ecash_lib_1.OP_2DUP,
// isNotExcessive = OP_GREATERTHANOREQUAL(scaledTruncAtoms,
// acceptedScaledTruncAtoms)
ecash_lib_1.OP_GREATERTHANOREQUAL,
// OP_VERIFY(isNotExcessive)
ecash_lib_1.OP_VERIFY,
// # Verify accepted amount is above a required minimum
// OP_DUP(acceptedScaledTruncAtoms)
ecash_lib_1.OP_DUP,
// # Ensure minimum accepted amount is not violated
(0, ecash_lib_1.pushNumberOp)(this.minAcceptedScaledTruncAtoms),
// isEnough = OP_GREATERTHANOREQUAL(acceptedScaledTruncAtoms,
// minAcceptedScaledTruncAtoms)
ecash_lib_1.OP_GREATERTHANOREQUAL,
// OP_VERIFY(isEnough)
ecash_lib_1.OP_VERIFY,
// # Verify accepted amount is scaled correctly, must be a
// # multiple of atomsScaleFactor.
// OP_DUP(acceptedScaledTruncAtoms)
ecash_lib_1.OP_DUP,
(0, ecash_lib_1.pushNumberOp)(this.atomsScaleFactor),
// scaleRemainder = OP_MOD(acceptedScaledTruncAtoms,
// atomsScaleFactor)
ecash_lib_1.OP_MOD,
ecash_lib_1.OP_0,
// OP_EQUALVERIFY(scaleRemainder, 0)
ecash_lib_1.OP_EQUALVERIFY,
// OP_TUCK(_, acceptedScaledTruncAtoms);
ecash_lib_1.OP_TUCK,
// # Calculate tokens left over after purchase
// leftoverScaledTruncAtoms = OP_SUB(scaledTruncAtoms,
// acceptedScaledTruncAtoms)
ecash_lib_1.OP_SUB,
// # Get token intro from consts
// depthConsts = depth_of(consts)
(0, ecash_lib_1.pushNumberOp)(2),
// consts = OP_PICK(depthConsts);
ecash_lib_1.OP_PICK,
// # Size of the token protocol intro
(0, ecash_lib_1.pushNumberOp)(tokenIntroLen),
// tokenIntro, agoraIntro = OP_SPLIT(consts, introSize)
ecash_lib_1.OP_SPLIT,
// OP_DROP(agoraIntro)
ecash_lib_1.OP_DROP,
// OP_OVER(leftoverScaledTruncAtoms, _)
ecash_lib_1.OP_OVER,
// hasLeftover = OP_0NOTEQUAL(leftoverScaledTruncAtoms)
// # (SCRIPT_VERIFY_MINIMALIF is not on eCash, but better be safe)
ecash_lib_1.OP_0NOTEQUAL,
// Insert (sub)script that builds the OP_RETURN for SLP/ALP
...this._scriptBuildOpReturn(tokenIntroLen),
// # Add trunc padding for sats to un-truncate sats
(0, ecash_lib_1.pushBytesOp)(new Uint8Array(this.numSatsTruncBytes)),
// outputsOpreturnPad = OP_CAT(opreturnOutput, truncPaddingSats)
ecash_lib_1.OP_CAT,
// OP_ROT(acceptedScaledTruncAtoms, _, _)
ecash_lib_1.OP_ROT,
// # We divide rounding up when we calc sats, so add divisor - 1
(0, ecash_lib_1.pushNumberOp)(this.scaledTruncAtomsPerTruncSat - 1n),
ecash_lib_1.OP_ADD,
// # Price (scaled + truncated)
(0, ecash_lib_1.pushNumberOp)(this.scaledTruncAtomsPerTruncSat),
// # Calculate how many (truncated) sats the user has to pay
// requiredTruncSats = OP_DIV(acceptedScaledTruncAtoms,
// scaledTruncAtomsPerTruncSat)
ecash_lib_1.OP_DIV,
// # Build the required sats with the correct byte length
// truncLen = 8 - numSatsTruncBytes
(0, ecash_lib_1.pushNumberOp)(8 - this.numSatsTruncBytes),
// requiredTruncSatsLe = OP_NUM2BIN(requiredTruncSats, truncLen)
ecash_lib_1.OP_NUM2BIN,
// # Build OP_RETURN output + satoshi amount (8 bytes LE).
// # We already added the padding to un-truncate sats in the
// # previous OP_CAT to the output.
// outputsOpreturnSats =
// OP_CAT(outputsOpreturnPad, requiredTruncSatsLe)
ecash_lib_1.OP_CAT,
// # Build maker's P2PKH script
// p2pkhIntro = [25, OP_DUP, OP_HASH160, 20]
(0, ecash_lib_1.pushBytesOp)(new Uint8Array([25, ecash_lib_1.OP_DUP, ecash_lib_1.OP_HASH160, 20])),
// OP_2OVER(consts, leftoverScaledTruncAtoms, _, _);
ecash_lib_1.OP_2OVER,
// OP_DROP(leftoverScaledTruncAtoms);
ecash_lib_1.OP_DROP,
// # Slice out pubkey from the consts (always the last 33 bytes)
// pubkeyIdx = consts.length - 33
(0, ecash_lib_1.pushNumberOp)(covenantConsts.length - 33),
// rest, makerPk = OP_SPLIT(consts, pubkeyIdx)
ecash_lib_1.OP_SPLIT,
// OP_NIP(rest, _)
ecash_lib_1.OP_NIP,
// makerPkh = OP_HASH160(makerPk)
ecash_lib_1.OP_HASH160,
// makerP2pkh1 = OP_CAT(p2pkhIntro, makerPkh)
ecash_lib_1.OP_CAT,
// p2pkhOutro = [OP_EQUALVERIFY, OP_CHECKSIG]
(0, ecash_lib_1.pushBytesOp)(new Uint8Array([ecash_lib_1.OP_EQUALVERIFY, ecash_lib_1.OP_CHECKSIG])),
// makerScript = OP_CAT(makerP2pkh1, p2pkhOutro)
ecash_lib_1.OP_CAT,
// # Now we have the first 2 outputs: OP_RETURN + maker P2PKH
// outputsOpreturnMaker = OP_CAT(outputsOpreturnSats, makerScript)
ecash_lib_1.OP_CAT,
// # Move to altstack, we need it when calculating hashOutputs
// OP_TOALTSTACK(outputsOpreturnMaker)
ecash_lib_1.OP_TOALTSTACK,
// # Build loopback P2SH, will receive the leftover tokens with
// # a Script with the same terms.
// OP_TUCK(_, leftoverScaledTruncAtoms);
ecash_lib_1.OP_TUCK,
// P2SH has dust sats
(0, ecash_lib_1.pushNumberOp)(this.dustSats),
ecash_lib_1.OP_8,
// dustSats8le = OP_NUM2BIN(dustSats, 8)
ecash_lib_1.OP_NUM2BIN,
// p2shIntro = [23, OP_HASH160, 20]
(0, ecash_lib_1.pushBytesOp)(new Uint8Array([23, ecash_lib_1.OP_HASH160, 20])),
// loopbackOutputIntro = OP_CAT(dustSats8le, p2shIntro);
ecash_lib_1.OP_CAT,
// # Build the new redeem script; same terms but different
// # scaledTruncAtoms8Le.
// # Build opcode to push consts. Sometimes they get long and we
// # need OP_PUSHDATA1.
// pushConstsOpcode = if consts.length >= OP_PUSHDATA1 {
// [OP_PUSHDATA1, consts.length]
// } else {
// [consts.length]
// }
(0, ecash_lib_1.pushBytesOp)(new Uint8Array(covenantConsts.length >= ecash_lib_1.OP_PUSHDATA1
? [ecash_lib_1.OP_PUSHDATA1, covenantConsts.length]
: [covenantConsts.length])),
// OP_2SWAP(consts, leftoverScaledTruncAtoms, _, _)
ecash_lib_1.OP_2SWAP,
ecash_lib_1.OP_8,
// OP_TUCK(_, 8)
ecash_lib_1.OP_TUCK,
// leftoverScaledTruncAtoms8le =
// OP_NUM2BIN(leftoverScaledTruncAtoms, 8)
ecash_lib_1.OP_NUM2BIN,
// pushLeftoverScaledTruncAtoms8le =
// OP_CAT(8, leftoverScaledTruncAtoms8le)
ecash_lib_1.OP_CAT,
// constsPushLeftover =
// OP_CAT(consts, pushLeftoverScaledTruncAtoms8le)
ecash_lib_1.OP_CAT,
// # The two ops that push consts plus amount
// pushState = OP_CAT(pushConstsOpcode, constsPushLeftover)
ecash_lib_1.OP_CAT,
// opcodesep = [OP_CODESEPARATOR]
(0, ecash_lib_1.pushBytesOp)(new Uint8Array([ecash_lib_1.OP_CODESEPARATOR])),
// loopbackScriptIntro = OP_CAT(pushState, opcodesep)
ecash_lib_1.OP_CAT,
// depthPreimage4_10 = depth_of(preimage4_10);
(0, ecash_lib_1.pushNumberOp)(3),
// preimage4_10 = OP_PICK(depthPreimage4_10);
ecash_lib_1.OP_PICK,
// scriptCodeIdx = 36 + if scriptLen < 0xfd { 1 } else { 3 }
(0, ecash_lib_1.pushNumberOp)(36 + (this.scriptLen < 0xfd ? 1 : 3)),
// outpoint, preimage5_10 = OP_SPLIT(preimage4_10, scriptCodeIdx)
ecash_lib_1.OP_SPLIT,
// OP_NIP(outpoint, __)
ecash_lib_1.OP_NIP,
// # Split out scriptCode
(0, ecash_lib_1.pushNumberOp)(this.scriptLen),
// script_code, preimage6_10 = OP_SPLIT(preimage5_10, scriptLen)
ecash_lib_1.OP_SPLIT,
// # Extract hashOutputs
ecash_lib_1.OP_12,
// (preimage6_7, preimage8_10) = OP_SPLIT(preimage6_10, 12)
ecash_lib_1.OP_SPLIT,
// OP_NIP(preimage6_7, _)
ecash_lib_1.OP_NIP,
// # Split out hashOutputs
(0, ecash_lib_1.pushNumberOp)(32),
// actualHashOutputs, preimage9_10 = OP_SPLIT(preimage8_10, 32)
ecash_lib_1.OP_SPLIT,
(0, ecash_lib_1.pushNumberOp)(4),
// actualLocktime4Le, sighashType = OP_SPLIT(preimage9_10)
ecash_lib_1.OP_SPLIT,
// OP_DROP(preimage10)
ecash_lib_1.OP_DROP,
(0, ecash_lib_1.pushBytesOp)(enforcedLockTime4Le),
// OP_EQUALVERIFY(actualLocktime4Le, enforcedLockTime4Le)
ecash_lib_1.OP_EQUALVERIFY,
// # Move to altstack, will be needed later
// OP_TOALTSTACK(actualHashOutputs)
ecash_lib_1.OP_TOALTSTACK,
// # Build redeemScript of loopback P2SH output
// loopbackScript = OP_CAT(loopbackScriptIntro, scriptCode)
ecash_lib_1.OP_CAT,
// # Calculate script hash for P2SH script
// loopbackScriptHash = OP_HASH160(loopbackScript)
ecash_lib_1.OP_HASH160,
// loopbackOutputIntroSh =
// OP_CAT(loopbackOutputIntro, loopbackScriptHash)
ecash_lib_1.OP_CAT,
// p2shEnd = [OP_EQUAL]
(0, ecash_lib_1.pushBytesOp)(new Uint8Array([ecash_lib_1.OP_EQUAL])),
// # Build loopback P2SH output
// loopbackOutput = OP_CAT(loopbackOutputIntroSh, p2shEnd)
ecash_lib_1.OP_CAT,
// # Check if we have tokens left over and send them back
// # It is cheaper (in bytes) to build the loopback output and then
// # throw it away if needed than to not build it at all.
// OP_SWAP(leftoverScaledTruncAtoms, _)
ecash_lib_1.OP_SWAP,
// hasLeftover = OP_0NOTEQUAL(leftoverScaledTruncAtoms)
ecash_lib_1.OP_0NOTEQUAL,
// OP_NOTIF(hasLeftover)
ecash_lib_1.OP_NOTIF,
// OP_DROP(loopbackOutput)
ecash_lib_1.OP_DROP,
// loopbackOutput = []
(0, ecash_lib_1.pushBytesOp)(new Uint8Array()),
ecash_lib_1.OP_ENDIF,
// OP_ROT(buyerOutputs, _, _)
ecash_lib_1.OP_ROT,
// # Verify user specified output, otherwise total burn on ALP
// buyerOutputs, buyerOutputsSize = OP_SIZE(buyerOutputs)
ecash_lib_1.OP_SIZE,
// isNotEmpty = OP_0NOTEQUAL(buyerOutputsSize)
ecash_lib_1.OP_0NOTEQUAL,
// OP_VERIFY(isNotEmpty)
ecash_lib_1.OP_VERIFY,
// # Loopback + taker outputs
// outputsLoopbackTaker = OP_CAT(loopbackOutput, buyerOutputs)
ecash_lib_1.OP_CAT,
// OP_FROMALTSTACK(actualHashOutputs)
ecash_lib_1.OP_FROMALTSTACK,
// OP_FROMALTSTACK(outputsOpreturnMaker)
ecash_lib_1.OP_FROMALTSTACK,
// OP_ROT(outputsLoopbackTaker, _, _)
ecash_lib_1.OP_ROT,
// # Outputs expected by this Script
// expectedOutputs = OP_CAT(outputsOpreturnMaker,
// outputsLoopbackTaker)
ecash_lib_1.OP_CAT,
// expectedHashOutputs = OP_HASH256(expectedOutputs)
ecash_lib_1.OP_HASH256,
// # Verify tx has the expected outputs
// OP_EQUALVERIFY(actualHashOutputs, expectedHashOutputs)
ecash_lib_1.OP_EQUALVERIFY,
// # Build sighash preimage parts 1 to 3 via OP_NUM2BIN
// txVersion = 2
ecash_lib_1.OP_2,
// preimage1_3Len = 4 + 32 + 32
(0, ecash_lib_1.pushNumberOp)(4 + 32 + 32),
// preimage1_3 = OP_NUM2BIN(txVersion, preimage1_3Len)
ecash_lib_1.OP_NUM2BIN,
// # Build full sighash preimage
// OP_SWAP(preimage4_10, preimage1_3)
ecash_lib_1.OP_SWAP,
// preimage = OP_CAT(preimage1_3, preimage4_10)
ecash_lib_1.OP_CAT,
// # Sighash for this covenant
// preimageSha256 = OP_SHA256(preimage)
ecash_lib_1.OP_SHA256,
// # Verify our sighash actually matches that of the transaction
// OP_3DUP(covenantPk, covenantSig, preimageSha256)
ecash_lib_1.OP_3DUP,
// OP_ROT(covenantPk, covenantSig, preimageSha256)
ecash_lib_1.OP_ROT,
// OP_CHECKDATASIGVERIFY(covenantSig, preimageSha256, covenantPk)
ecash_lib_1.OP_CHECKDATASIGVERIFY,
// OP_DROP(preimageSha256)
ecash_lib_1.OP_DROP,
// sigHashFlags = [ALL_ANYONECANPAY_BIP143]
(0, ecash_lib_1.pushBytesOp)(new Uint8Array([ecash_lib_1.ALL_ANYONECANPAY_BIP143.toInt()])),
// covenantSigFlagged = OP_CAT(covenantSig, sigHashFlags)
ecash_lib_1.OP_CAT,
// covenantSig, pk = OP_SWAP(covenantPk, covenantSigFlagged)
ecash_lib_1.OP_SWAP,
ecash_lib_1.OP_ELSE,
// # "Cancel" branch, split out the maker pubkey and verify sig
// # is for the maker pubkey.
// OP_DROP(scaledTruncAtoms8le);
ecash_lib_1.OP_DROP,
// pubkeyIdx = consts.length - 33
(0, ecash_lib_1.pushNumberOp)(covenantConsts.length - 33),
// rest, pk = OP_SPLIT(consts, pubkeyIdx)
ecash_lib_1.OP_SPLIT,
// OP_NIP(rest, __)
ecash_lib_1.OP_NIP,
ecash_lib_1.OP_ENDIF,
// # SLP and ALP differ at the end of the Script
...this._scriptOutro(),
]);
}
_scriptBuildOpReturn(tokenIntroLen) {
// Script takes in the token amounts and builds the OP_RETURN for the
// corresponding protocol
if (this.tokenProtocol === 'SLP') {
return this._scriptBuildSlpOpReturn();
}
else if (this.tokenProtocol === 'ALP') {
return this._scriptBuildAlpOpReturn(tokenIntroLen);
}
else {
throw new Error('Only SLP implemented');
}
}
_scriptBuildSlpOpReturn() {
return [
// # If there's a leftover, append it to the token amounts
// OP_IF(hasLeftover)
ecash_lib_1.OP_IF,
// # Size of an SLP amount
ecash_lib_1.OP_8,
// tokenIntro8 = OP_CAT(tokenIntro, 8);
ecash_lib_1.OP_CAT,
// OP_OVER(leftoverScaledTruncAtoms, _)
ecash_lib_1.OP_OVER,
// # Scale down the scaled leftover amount
(0, ecash_lib_1.pushNumberOp)(this.atomsScaleFactor),
// leftoverTokensTrunc = OP_DIV(leftoverScaledTruncAtoms,
// atomsScaleFactor)
ecash_lib_1.OP_DIV,
// # Serialize the leftover trunc tokens (overflow-safe)
...this._scriptSerTruncAtoms(8),
// # SLP uses big-endian, so we have to use OP_REVERSEBYTES
// leftoverTokenTruncBe = OP_REVERSEBYTES(leftoverTokenTruncLe)
ecash_lib_1.OP_REVERSEBYTES,
// # Bytes to un-truncate the leftover tokens
(0, ecash_lib_1.pushBytesOp)(new Uint8Array(this.numAtomsTruncBytes)),
// # Build the actual 8 byte big-endian leftover
// leftoverToken8be = OP_CAT(leftoverTokenTruncBe, untruncatePad);
ecash_lib_1.OP_CAT,
// # Append the leftover to the token intro
// tokenScript = OP_CAT(tokenIntro8, leftoverToken8be);
ecash_lib_1.OP_CAT,
ecash_lib_1.OP_ENDIF,
// # Append accepted token amount going to the taker
// # Size of an SLP amount
ecash_lib_1.OP_8,
// tokenScript = OP_CAT(tokenScript, 8)
ecash_lib_1.OP_CAT,
// # Get the accepted token amount
// depthAcceptedScaledTruncAtoms =
// depth_of(acceptedScaledTruncAtoms)
(0, ecash_lib_1.pushNumberOp)(2),
// acceptedScaledTruncAtoms =
// OP_PICK(depthAcceptedScaledTruncAtoms)
ecash_lib_1.OP_PICK,
// # Scale down the accepted token amount
(0, ecash_lib_1.pushNumberOp)(this.atomsScaleFactor),
// acceptedAtomsTrunc = OP_DIV(acceptedScaledTruncAtoms,
// atomsScaleFactor)
ecash_lib_1.OP_DIV,
// # Serialize the accepted token amount (overflow-safe)
...this._scriptSerTruncAtoms(8),
// # SLP uses big-endian, so we have to use OP_REVERSEBYTES
// acceptedAtomsTruncBe = OP_REVERSEBYTES(acceptedAtomsTruncLe);
ecash_lib_1.OP_REVERSEBYTES,
// # Bytes to un-truncate the leftover tokens
(0, ecash_lib_1.pushBytesOp)(new Uint8Array(this.numAtomsTruncBytes)),
// acceptedAtoms8be = OP_CAT(acceptedAtomsTruncBe, untruncatePad);
ecash_lib_1.OP_CAT,
// # Finished SLP token script
// tokenScript = OP_CAT(tokenScript, acceptedAtoms8be);
ecash_lib_1.OP_CAT,
// # Build OP_RETURN script with 0u64 and size prepended
// # tokenScript, tokenScriptSize = OP_SIZE(tokenScript)
ecash_lib_1.OP_SIZE,
// # Build output value (0u64) + tokenScriptSize.
// # In case the tokenScriptSize > 127, it will be represented as
// # 0xXX00 in Script, but it should be just 0xXX.
// # We could serialize to 2 bytes and then cut one off, but here we
// # use a neat optimization: We serialize to 9 bytes (resulting in
// # 0xXX0000000000000000) and then call OP_REVERSEBYTES, which
// # will result in 0x0000000000000000XX, which is exactly what the
// # first 9 bytes of the OP_RETURN output should look like.
ecash_lib_1.OP_9,
// tokenScriptSize9Le = OP_NUM2BIN(tokenScriptSize, 9)
ecash_lib_1.OP_NUM2BIN,
// opreturnValueSize = OP_REVERSEBYTES(tokenScriptSize9Le);
ecash_lib_1.OP_REVERSEBYTES,
// OP_SWAP(tokenScript, opreturnValueSize);
ecash_lib_1.OP_SWAP,
// opreturnOutput = OP_CAT(opreturnValueSize, tokenScript);
ecash_lib_1.OP_CAT,
];
}
_scriptBuildAlpOpReturn(tokenIntroLen) {
// Script takes in the token amounts and builds the OP_RETURN for the
// ALP token protocol
return [
// # If there's a leftover, add it to the token amounts
// OP_IF(hasLeftover)
ecash_lib_1.OP_IF,
// numTokenAmounts = 3
ecash_lib_1.OP_3,
// # Append the number of token amounts + the first 0 amount +
// # un-truncate padding for the 2nd output.
// # We meld these three ops into one by using OP_NUM2BIN using
// # 7 + numAtomsTruncBytes bytes, which gives us the number of
// # amounts in the first byte, followed by 6 zero bytes for the
// # first output, and then numAtomsTruncBytes bytes for the
// # un-truncate padding.
(0, ecash_lib_1.pushNumberOp)(7 + this.numAtomsTruncBytes),
// tokenAmounts1 = OP_NUM2BIN(numTokenAmounts, size)
ecash_lib_1.OP_NUM2BIN,
// tokenIntro = OP_CAT(tokenIntro, tokenAmounts1)
ecash_lib_1.OP_CAT,
// OP_OVER(leftoverScaledTruncAtoms, __)
ecash_lib_1.OP_OVER,
// # Scale down the scaled leftover amount
(0, ecash_lib_1.pushNumberOp)(this.atomsScaleFactor),
// nextSerValue = OP_DIV(leftoverScaledTruncAtoms,
// atomsScaleFactor)
ecash_lib_1.OP_DIV,
// # Serialize size for leftoverTokensTrunc, and also already add the un-truncate padding for the 3rd amount
// # Combining these two ops also doesn't require us to serialize overflow-aware
(0, ecash_lib_1.pushNumberOp)(6 /*- this.numAtomsTruncBytes + this.numAtomsTruncBytes*/),
ecash_lib_1.OP_ELSE,
// # Append the number of token amounts + the first 0 amount +
// # un-truncate padding for the 3rd output.
// nextSerValue = 2
ecash_lib_1.OP_2,
// serializeSize = 7 + numAtomsTruncBytes
(0, ecash_lib_1.pushNumberOp)(7 + this.numAtomsTruncBytes),
ecash_lib_1.OP_ENDIF,
// tokenAmounts2 = OP_NUM2BIN(numTokenAmounts, serializeSize)
// # Serialize 1st/2nd output + padding for 2nd/3rd output
ecash_lib_1.OP_NUM2BIN,
// # Build the part of the token section that has all the amounts
// # for the maker (i.e. 0) and covenant loopback, and the
// # un-truncate padding for the accepted token amount.
// tokenSection1Pad = OP_CAT(tokenIntro, tokenAmounts2)
ecash_lib_1.OP_CAT,
// depthAcceptedScaledTruncAtoms =
// depth_of(acceptedScaledTruncAtoms)
(0, ecash_lib_1.pushNumberOp)(2),
// acceptedScaledTruncAtoms =
// OP_PICK(depthAcceptedScaledTruncAtoms)
ecash_lib_1.OP_PICK,
// # Scale down the accepted token amount
(0, ecash_lib_1.pushNumberOp)(this.atomsScaleFactor),
// acceptedAtomsTrunc = OP_DIV(acceptedScaledTruncAtoms,
// atomsScaleFactor)
ecash_lib_1.OP_DIV,
// # Serialize accepted token amount (overflow-safe)
...this._scriptSerTruncAtoms(6),
// # Finished token section
// tokenSection = OP_CAT(tokenSection1Pad, acceptedAtomsTruncLe);
ecash_lib_1.OP_CAT,
// Turn token section into a pushdata op
// tokenSection, tokenSectionSize = OP_SIZE(tokenSection)
ecash_lib_1.OP_SIZE,
// OP_SWAP(tokenSection, tokenSectionSize)
ecash_lib_1.OP_SWAP,
// let pushTokenSection = OP_CAT(tokenSectionSize, tokenSection);
ecash_lib_1.OP_CAT,
// Get empp intro from consts
// depthConsts = depth_of(consts)
(0, ecash_lib_1.pushNumberOp)(3),
// consts = OP_PICK(depthConsts)
ecash_lib_1.OP_PICK,
// # Split out the emppAgoraIntro to prepend it to the OP_RETURN
(0, ecash_lib_1.pushNumberOp)(tokenIntroLen),
// tokenIntro, emppAgoraIntro = OP_SPLIT(consts, alpIntroSize)
ecash_lib_1.OP_SPLIT,
// # We don't need the tokenIntro
// OP_NIP(tokenIntro, _)
ecash_lib_1.OP_NIP,
// Build OP_RETURN script with 0u64 and size prepended
// OP_SWAP(pushTokenSection, _)
ecash_lib_1.OP_SWAP,
// emppScript = OP_CAT(emppIntro, pushTokenSection)
ecash_lib_1.OP_CAT,
// emppScript, emppScriptSize = OP_SIZE(emppScript)
ecash_lib_1.OP_SIZE,
// # Build output value (0u64) + tokenScriptSize.
// # See _scriptBuildSlpOpReturn for an explanation
ecash_lib_1.OP_9,
// emppScriptSizeZero8 = OP_NUM2BIN(emppScriptSize, _9)
ecash_lib_1.OP_NUM2BIN,
// zero8EmppScriptSize = OP_REVERSEBYTES(emppScriptSizeZero8)
ecash_lib_1.OP_REVERSEBYTES,
// OP_SWAP(emppScript, zero8EmppScriptSize)
ecash_lib_1.OP_SWAP,
// let opreturnOutput = OP_CAT(zero8EmppScriptSize, emppScript)
ecash_lib_1.OP_CAT,
];
}
_scriptSerTruncAtoms(numSerBytes) {
// Serialize the number on the stack using the configured truncation
if (this.numAtomsTruncBytes === numSerBytes - 3) {
// Edge case where we only have 3 bytes space to serialize the
// number, but if the MSB of the number is set, OP_NUM2BIN will
// serialize using 4 bytes (with the last byte being just 0x00),
// so we always serialize using 4 bytes and then cut the last
// byte (that's always 0x00) off.
return [
(0, ecash_lib_1.pushNumberOp)(4),
ecash_lib_1.OP_NUM2BIN,
(0, ecash_lib_1.pushNumberOp)(3),
ecash_lib_1.OP_SPLIT,
ecash_lib_1.OP_DROP,
];
}
else {
// If we have 4 or more bytes space, we can always serialize
// just using normal OP_NUM2BIN.
return [
(0, ecash_lib_1.pushNumberOp)(numSerBytes - this.numAtomsTruncBytes),
ecash_lib_1.OP_NUM2BIN,
];
}
}
_scriptOutro() {
if (this.tokenProtocol === 'SLP') {
// Verify the sig, and also ensure the first two push ops of the
// scriptSig are "AGR0" "PARTIAL", which will always have to be the
// first two ops because of the cleanstack rule.
return [
ecash_lib_1.OP_CHECKSIGVERIFY,
(0, ecash_lib_1.pushBytesOp)((0, ecash_lib_1.strToBytes)(AgoraPartial.COVENANT_VARIANT)),
ecash_lib_1.OP_EQUALVERIFY,
(0, ecash_lib_1.pushBytesOp)(consts_js_1.AGORA_LOKAD_ID),
ecash_lib_1.OP_EQUAL,
];
}
else if (this.tokenProtocol === 'ALP') {
return [ecash_lib_1.OP_CHECKSIG];
}
else {
throw new Error('Only SLP implemented');
}
}
/**
* redeemScript of the Script advertizing this offer.
* It requires a setup tx followed by the actual offer, which reveals
* the covenantConsts.
* The reason we have an OP_CHECKSIGVERIFY (as opposed to just leaving it
* as "anyone can spend with this pushdata") is so that others on the
* network can't spend this UTXO (and potentially take the tokens in it),
* and only the maker can spend it.
**/
adScript() {
const [covenantConsts, _] = this.covenantConsts();
return ecash_lib_1.Script.fromOps([
(0, ecash_lib_1.pushBytesOp)(covenantConsts),
(0, ecash_lib_1.pushNumberOp)(covenantConsts.length - 33),
ecash_lib_1.OP_SPLIT,
ecash_lib_1.OP_NIP,
ecash_lib_1.OP_CHECKSIGVERIFY,
(0, ecash_lib_1.pushBytesOp)((0, ecash_lib_1.strToBytes)(AgoraPartial.COVENANT_VARIANT)),
ecash_lib_1.OP_EQUALVERIFY,
(0, ecash_lib_1.pushBytesOp)(consts_js_1.AGORA_LOKAD_ID),
ecash_lib_1.OP_EQUAL,
]);
}
/**
* Build and broadcast a transaction to list tokens.
* For ALP tokens, this creates a single transaction.
* For SLP tokens (non-NFT), this creates a chained transaction with an "advertising" tx followed by the offer tx.
*
* @param params - Parameters for listing the tokens
* @returns Promise resolving to broadcast result
* @throws Error if token type is SLP NFT
*/
async list(params) {
// Construct TokenType from class properties
let tokenType;
if (this.tokenProtocol === 'SLP') {
// Validate token type - NFT is not supported
if (this.tokenType === ecash_lib_1.SLP_NFT1_CHILD) {
throw new Error('AgoraPartial.list() does not support SLP NFT tokens (SLP_TOKEN_TYPE_NFT1_CHILD). Use AgoraOneshot.list() instead.');
}
// Map tokenType number to TokenType object
switch (this.tokenType) {
case ecash_lib_1.SLP_FUNGIBLE:
tokenType = ecash_lib_1.SLP_TOKEN_TYPE_FUNGIBLE;
break;
case ecash_lib_1.SLP_MINT_VAULT:
tokenType = ecash_lib_1.SLP_TOKEN_TYPE_MINT_VAULT;
break;
case ecash_lib_1.SLP_NFT1_GROUP:
tokenType = ecash_lib_1.SLP_TOKEN_TYPE_NFT1_GROUP;
break;
default:
throw new Error(`Unsupported SLP token type: ${this.tokenType}`);
}
}
else if (this.tokenProtocol === 'ALP') {
if (this.tokenType === ecash_lib_1.ALP_STANDARD) {
tokenType = ecash_lib_1.ALP_TOKEN_TYPE_STANDARD;
}
else {
throw new Error(`Unsupported ALP token type: ${this.tokenType}`);
}