UNPKG

ecash-agora

Version:

Library for interacting with the eCash Agora protocol

315 lines 13.5 kB
import { Ecc, Script, Signatory, UnsignedTxInput } from 'ecash-lib'; import { Wallet } from 'ecash-wallet'; import { AgoraBroadcastParams } from './broadcast.js'; /** * "Human viable" parameters for partial Agora offers, can serve as a basis to * approximate the actual Script parameters via AgoraPartial.approximateParams. **/ export interface AgoraPartialParams { /** * Offered tokens in base tokens. After param approximation, this may differ * from `AgoraPartial`.offeredAtoms(), so make sure to use that when * preparing the offer! * * For SLP, the maximum allowed value here is 0xffffffffffffffff, for ALP it * is 0xffffffffffff. **/ offeredAtoms: bigint; /** * Price in nano sats per atom (aka base token). * Using nsats allows users to specify a very large range of prices, from * tokens where billions of them cost a single sat, to offers where single * tokens can cost millions of XEC. **/ priceNanoSatsPerAtom: bigint; /** * Public key of the offering party. * This is the public key of the wallet, and it serves both as the pubkey to * cancel the offer, as well as the pubkey of the P2PKH script to send the * sats to. **/ makerPk: Uint8Array; /** * Minimum number of atoms that can be accepted. * Can be used to avoid spam and prevent exploits with really small * accept amounts. * Also, small amounts can have very bad precision, and raising the minimum * amount can mitigate this. * It can also just be used to increase the minimum for which tokens are * available. * It is recommended to set this to 0.1% of the offered amount. **/ minAcceptedAtoms: bigint; /** Token ID of the offered token, in big-endian hex. */ tokenId: string; /** Token type of the offered token. */ tokenType: number; /** Token protocol of the offered token. */ tokenProtocol: 'SLP' | 'ALP'; /** * Locktime enforced by the Script. Used to make identical offers unique. * * Use Agora.selectParams to automatically select a good value for this, * only set this manually if you know what you're doing. * * If there's two offers with identical terms, it would be possible to burn * one of them by accepting both in one transaction. * To prevent this for identical offers, set to unique (past) locktimes. **/ enforcedLockTime: number; /** Dust amount to be used by the script. */ dustSats?: bigint; /** * Minimum atomsScaleFactor when approximating numAtomsTruncBytes. * It is recommended to leave this at the default (1000), but it is exposed * to either increase price precision and granularity of token amounts (by * raising the limit), or to lower price precision but allow more fine- * grained token amounts (by lowering the limit). **/ minAtomsScaleFactor?: bigint; /** * Minimum integer when representing the price * (scaledTruncAtomsPerTruncSat), the approximation will truncate * additional sats bytes in order to make scaledTruncAtomsPerTruncSat * bigger. * It is recommended to leave this at the default (1000), but it is exposed * for cases where a small number of tokens are offered for a big price, * this can be used to improve precision. **/ minPriceInteger?: bigint; /** * Minimum ratio atomsScaleFactor / scaledTruncAtomsPerTruncSat, this can * be used to limit the additional truncation introduced by minPriceInteger. * It is recommended to leave this at the default (1000), but it is exposed * for cases where the askedSats for small accept amounts are very * inaccurate. **/ minScaleRatio?: bigint; } /** * 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. **/ export declare class AgoraPartial { static COVENANT_VARIANT: string; /** * Truncated amount that's offered. * The last numAtomsTruncBytes bytes are truncated to allow representing it * in Script or to increase precision. * This means that tokens can only be accepted at a granularity of * 2^(8*numAtomsTruncBytes). * offeredAtoms = truncAtoms * 2^(8*numAtomsTruncBytes). **/ truncAtoms: bigint; /** * How many bytes are truncated from the real token amount, so it fits into * 31-bit ints, or to increase precision. **/ numAtomsTruncBytes: number; /** * Factor token amounts will be multiplied with in the Script to improve * precision. **/ atomsScaleFactor: bigint; /** * Price in scaled trunc tokens per truncated sat. * This unit may seem a bit bizzare, but it is exactly what is needed in the * Script calculation: The "acceptedAtoms" coming from the taker is both * scaled by atomsScaleFactor and also truncated by numAtomsTruncBytes * bytes, so we only have to divide the acceptedAtoms by this number to get * the required (truncated) sats. So we only have to un-truncate that and we * have the asked sats. **/ scaledTruncAtomsPerTruncSat: bigint; /** * How many bytes are truncated from the real sats amount, so it fits into * 31-bit ints or to improve precision. **/ numSatsTruncBytes: number; /** * Where the sats for the tokens should go, and who can cancel the trade. **/ makerPk: Uint8Array; /** * How many tokens (scaled and truncated) at minimum have to be accepted. **/ minAcceptedScaledTruncAtoms: bigint; /** Token of the contract, in big-endian hex. */ tokenId: string; /** Token type offered */ tokenType: number; /** Token protocol of the offered token */ tokenProtocol: 'SLP' | 'ALP'; /** Byte length of the Script, after OP_CODESEPARATOR. */ scriptLen: number; /** * Locktime enforced by the Script. Used to make identical offers unique. * * Use Agora.selectParams to automatically select a good value for this, * only set this manually if you know what you're doing. * * If there's two offers with identical terms, it would be possible to burn * one of them by accepting both in one transaction. * To prevent this for identical offers, set to unique (past) locktimes. **/ enforcedLockTime: number; /** * Dust amount of the network, the Script will enforce token outputs to have * this amount. **/ dustSats: bigint; constructor(params: { truncAtoms: bigint; numAtomsTruncBytes: number; atomsScaleFactor: bigint; scaledTruncAtomsPerTruncSat: bigint; numSatsTruncBytes: number; makerPk: Uint8Array; minAcceptedScaledTruncAtoms: bigint; tokenId: string; tokenType: number; tokenProtocol: 'SLP' | 'ALP'; scriptLen: number; enforcedLockTime: number; dustSats: bigint; }); /** * 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: AgoraPartialParams, scriptIntegerBits?: bigint): AgoraPartial; updateScriptLen(): void; /** * How many tokens are accually offered by the Script. * This may differ from the offeredAtoms in the AgoraPartialParams used to * approximate this AgoraPartial. **/ offeredAtoms(): bigint; /** * Actual minimum acceptable tokens of this Script. * This may differ from the minAcceptedAtoms in the AgoraPartialParams used * to approximate this AgoraPartial. **/ minAcceptedAtoms(): bigint; /** * 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: bigint): bigint; /** * Throw an error if accept amount is invalid * Note we do not prepare amounts in this function * @param acceptedAtoms */ preventUnacceptableRemainder(acceptedAtoms: bigint): void; /** * 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: bigint): bigint; /** * 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?: bigint): bigint; adPushdata(): Uint8Array; covenantConsts(): [Uint8Array, number]; script(): Script; private _scriptBuildOpReturn; private _scriptBuildSlpOpReturn; private _scriptBuildAlpOpReturn; private _scriptSerTruncAtoms; private _scriptOutro; /** * 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(): Script; /** * 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 */ list(params: { /** * An initialized Wallet from ecash-wallet. * This wallet must hold the tokens to be listed. */ wallet: Wallet; /** * Dust amount to use for the token output. */ dustSats?: bigint; /** * Fee per kB to use when building the tx. */ feePerKb?: bigint; } & AgoraBroadcastParams): Promise<{ success: boolean; broadcasted: string[]; unbroadcasted?: string[]; errors?: string[]; }>; } export declare const AgoraPartialSignatory: (params: AgoraPartial, acceptedTruncAtoms: bigint, covenantSk: Uint8Array, covenantPk: Uint8Array) => Signatory; export declare const AgoraPartialCancelSignatory: (makerSk: Uint8Array, tokenProtocol: "SLP" | "ALP") => Signatory; export declare const AgoraPartialAdSignatory: (makerSk: Uint8Array) => (ecc: Ecc, input: UnsignedTxInput) => Script; //# sourceMappingURL=partial.d.ts.map