ecash-agora
Version:
Library for interacting with the eCash Agora protocol
285 lines • 12.5 kB
TypeScript
import { Ecc, Script, Signatory, UnsignedTxInput } from 'ecash-lib';
/**
* "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. On XEC, this must be 32, but if it is raised in the
* future to e.g. 64-bit integers, this can be set to 64 to greatly
* increase accuracy.
**/
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;
}
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