ecash-agora
Version:
Library for interacting with the eCash Agora protocol
373 lines • 13.8 kB
TypeScript
import { ChronikClient, Token, WsEndpoint } from 'chronik-client';
import { Op, OutPoint, Script, Tx, TxBuilderInput, TxInput } from 'ecash-lib';
import { Wallet } from 'ecash-wallet';
import { AgoraBroadcastParams } from './broadcast';
import { AgoraOneshot } from './oneshot.js';
import { AgoraPartial, AgoraPartialParams } from './partial.js';
/** Offer variant, determines the Script used to enforce the offer */
export type AgoraOfferVariant = {
type: 'ONESHOT';
params: AgoraOneshot;
} | {
type: 'PARTIAL';
params: AgoraPartial;
};
/** Status of the offer, i.e. if it open/taken/canceled */
export type AgoraOfferStatus = 'OPEN' | 'TAKEN' | 'CANCELED';
/** If an offer is TAKEN */
export interface TakenInfo {
/** satoshis paid in taking an offer */
sats: bigint;
/**
* amount of token purchased in atoms aka base tokens
*/
atoms: bigint;
/** taker outputScript as a hex string*/
takerScriptHex: string;
}
/**
* Individual token offer on the Agora, i.e. one UTXO offering tokens.
*
* It can be used to accept or cancel the offer.
*/
export declare class AgoraOffer {
variant: AgoraOfferVariant;
outpoint: OutPoint;
txBuilderInput: TxInput;
token: Token;
status: AgoraOfferStatus;
takenInfo?: TakenInfo;
constructor(params: {
variant: AgoraOfferVariant;
outpoint: OutPoint;
txBuilderInput: TxInput;
token: Token;
status: AgoraOfferStatus;
takenInfo?: TakenInfo;
});
/**
* Build and broadcast an acceptTx, effectively taking this offer.
*
* Agora offers are UTXOs on the blockchain that can be accepted by anyone
* sending sufficient satoshis to a required output.
*
* `fuelInputs` has to provide enough sats for this offer to cover ask + tx fee.
* */
acceptTx(params: {
/**
* Arbitrary secret key to sign the accept tx with. Recommended to set
* this to a random key. Must be paired with covenantSk.
**/
covenantSk: Uint8Array;
/**
* Arbitrary public key to sign the accept tx with, must be paired with
* covenantSk.
**/
covenantPk: Uint8Array;
/**
* Inputs fueling this tx to cover tx fee and asked sats for the
* enforced outputs. Must have signatory and input.signData set
* correctly. If it is set incorrectly, may fail silently and build an
* invalid tx, failing at broadcast.
*
* The free sats of these inputs must be at least askedSats + acceptFeeSats.
**/
fuelInputs: TxBuilderInput[];
/** Script to send the tokens and the leftover sats (if any) to. */
recipientScript: Script;
/** For partial offers: Number of accepted atoms (base tokens) */
acceptedAtoms?: bigint;
/** Dust amount to use for the token output. */
dustSats?: bigint;
/** Fee per kB to use when building the tx. */
feePerKb?: bigint;
/** Allow accepting an offer such that the remaining quantity is unacceptable */
allowUnspendable?: boolean;
}): Tx;
/**
* Build and broadcast a tx that accepts an agora offer
* according to user-provided params, to a user-provided
* ecash-wallet Wallet
*
* NB there is no option to "only build" by passing a Wallet, but this can be
* accomplished using the acceptTx method above
*/
take(
/**
* NB these params are identical to the params required for acceptTx method above,
* except
*
* 1) We require a Wallet
* 2) We get fuelInputs from the provided wallet instead of accepting them
* as a param
*/
params: {
/**
* An initialized Wallet from ecash-wallet
* This is the wallet that will take the offer
*/
wallet: Wallet;
/**
* Arbitrary secret key to sign the accept tx with. Recommended to set
* this to a random key. Must be paired with covenantSk.
*/
covenantSk: Uint8Array;
/**
* Arbitrary public key to sign the accept tx with, must be paired with
* covenantSk.
*/
covenantPk: Uint8Array;
/** For partial offers: Number of accepted atoms (base tokens) */
acceptedAtoms?: bigint;
/** Dust amount to use for the token output. */
dustSats?: bigint;
/** Fee per kB to use when building the tx. */
feePerKb?: bigint;
/** Allow accepting an offer such that the remaining quantity is unacceptable */
allowUnspendable?: boolean;
} & AgoraBroadcastParams): Promise<{
success: boolean;
broadcasted: string[];
unbroadcasted: string[];
errors: string[];
} | {
success: boolean;
broadcasted: string[];
unbroadcasted?: undefined;
errors?: undefined;
}>;
/**
* How many extra satoshis are required to fuel this offer so it can be
* broadcast on the network, excluding the asked sats.
* This should be displayed to the user as network fee.
* The total required input amount is askedSats + acceptFeeSats.
**/
acceptFeeSats(params: {
/** Script to send the tokens and the leftover sats (if any) to. */
recipientScript: Script;
/** Extra inputs */
extraInputs?: TxBuilderInput[];
/** Fee per kB to use when building the tx. */
feePerKb?: bigint;
acceptedAtoms?: bigint;
}): bigint;
private _acceptTxBuilder;
/**
* Build a tx canceling the offer.
*
* An offer can only be cancelled using the secret key that created it.
*
* `fuelInputs` must cover the tx fee, you can calculate it with cancelFeeSats.
**/
cancelTx(params: {
/**
* Cancel secret key of the offer, must be paired with the cancelPk of
* the offer.
**/
cancelSk: Uint8Array;
/**
* Inputs fueling this tx with sats. Must have signatory and
* input.signData set correctly. If it is set incorrectly, may fail
* silently and build an invalid tx, failing at broadcast.
*
* The free sats of these inputs must be at least cancelFeeSats.
**/
fuelInputs: TxBuilderInput[];
/** Script to send canceled tokens and the leftover sats (if any) to. */
recipientScript: Script;
/** Dust amount to use for the token output. */
dustSats?: bigint;
/** Fee per kB to use when building the tx. */
feePerKb?: bigint;
}): Tx;
/**
* Convenience method to cancel an offer using a Wallet
* The cancel tx is broadcast by the Wallet and canceled
* tokens / leftover sats are returned to this wallet
*
* NB this wallet must have the correct cancelSk to
* actually cancel the offer, otherwise expect an error
*
* NB you can see in the oneshot.test.ts that cancelTx can be used
* to "relist" an agora oneshot offer; this method will not do this
*
* Though we could add a relist() method to also simplify that procedure
*/
cancel(params: {
/**
* An initialized Wallet from ecash-wallet
* This is the wallet that will take the offer
*
* Note this wallet must have the correct cancelSk
* for the method to work
*
* This is also the wallet that will receive canceled
* tokens and leftover sats
*/
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[];
}>;
/**
* Update an AgoraOffer by changing its price or quantity
*
* We use the method name "relist" as this method will cancel the existing AgoraOffer
* and create a new one. What it is doing is closer to "updating" ... but we do
* in fact relist and lose this AgoraOffer
*
* Under the hood, this is canceling an offer and relisting it in a single tx
*
* While this is technically possible to do on SLP, e.g. you can see
* an example in oneshot.test.ts, for now we only support ALP
* as it is easier to implement and anticipated to have more use cases
*/
relist(params: {
/**
* An initialized Wallet from ecash-wallet
* This is the wallet that will take the offer
*
* Note this wallet must have the correct cancelSk
* for the method to work
*
* This is also the wallet that will receive canceled
* tokens and leftover sats
*/
wallet: Wallet;
/**
* The updated offer to relist
*
* After 64-bit ints, we may consider simply accepting updated terms like
* priceNanoSatsPerAtom and offeredAtoms
*
* But for now, we can't assure that the created offer will reasonably reflect
* user wants, so we accept their already-prepared offer
*/
updatedPartial: AgoraPartial;
/** 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[];
}>;
/**
* How many extra satoshis are required to fuel cancelling this offer,
* so the cancel tx can be broadcast on the network, excluding the asked
* sats and a dust amount to receive the tokens.
*
* extraInputs can be used to add an ad input so we have the correct
* estimate in case of a cancel + reoffer.
*
* This should be displayed to the user as cancellation network fee.
* The total required sats input amount is returned by this function.
**/
cancelFeeSats(params: {
/** Script to send the tokens and the leftover sats (if any) to. */
recipientScript: Script;
/** Extra inputs */
extraInputs?: TxBuilderInput[];
/** Fee per kB to use when building the tx. */
feePerKb?: bigint;
}): bigint;
private _cancelTxBuilder;
/**
* How many satoshis are asked to accept this offer, excluding tx fees.
* This is what should be displayed to the user as the price.
**/
askedSats(acceptedAtoms?: bigint): bigint;
}
/** Which txs to query (confirmed, unconfirmed, reverse history) */
export type TxHistoryTable = 'CONFIRMED' | 'UNCONFIRMED' | 'HISTORY';
export type AgoraQueryParamVariants = {
type: 'TOKEN_ID';
tokenId: string;
} | {
type: 'GROUP_TOKEN_ID';
groupTokenId: string;
} | {
type: 'PUBKEY';
pubkeyHex: string;
};
/** Params which Agora txs to query */
export type AgoraHistoryParams = AgoraQueryParamVariants & {
table: TxHistoryTable;
page?: number;
pageSize?: number;
};
/** Queried offers from the history */
export interface AgoraHistoryResult {
offers: AgoraOffer[];
numTxs: number;
numPages: number;
}
/**
* Enables access to Agora, via Chronik instances that have the "agora" plugin
* loaded.
*
* See agora.py.
**/
export declare class Agora {
private chronik;
private plugin;
private dustSats;
/**
* Create an Agora instance. The provided Chronik instance must have the
* "agora" plugin loaded.
**/
constructor(chronik: ChronikClient, dustSats?: bigint);
/**
* Query all the token IDs, fungible and non-fungible ones, that have active
* Agora offers.
**/
allOfferedTokenIds(): Promise<string[]>;
/** Query all fungible token IDs that have active Agora offers. */
offeredFungibleTokenIds(): Promise<string[]>;
/**
* Query all token IDs of groups of non-fungible tokens that have active
* Agora offers.
**/
offeredGroupTokenIds(): Promise<string[]>;
/** Query all active offers by token ID. */
activeOffersByTokenId(tokenId: string): Promise<AgoraOffer[]>;
/** Query all active offers by group token ID. */
activeOffersByGroupTokenId(groupTokenId: string): Promise<AgoraOffer[]>;
/** Query all active offers with the given cancel pubkey. */
activeOffersByPubKey(pubkeyHex: string): Promise<AgoraOffer[]>;
/**
* Query historic offers (paginated)
*
* These are basically the "candlesticks" of a specific token (and also the
* cancelled offers, but those would have to be ignored).
* Offers can also be queried by pubkey, giving a history of user's offers.
**/
historicOffers(params: AgoraHistoryParams): Promise<AgoraHistoryResult>;
/** Subscribe to updates from the websocket for some params */
subscribeWs(ws: WsEndpoint, params: AgoraQueryParamVariants): void;
/** Unsubscribe from updates from the websocket for some params */
unsubscribeWs(ws: WsEndpoint, params: AgoraQueryParamVariants): void;
/**
* Build a safe AgoraPartial for the given parameters.
*
* This looks at the blockchain to avoid creating an identical offer, by
* tweaking the enforcedLockTime.
*/
selectParams(params: Omit<AgoraPartialParams, 'enforcedLockTime'> | AgoraPartial, scriptIntegerBits?: bigint): Promise<AgoraPartial>;
private _groupHex;
private _allTokenIdsByPrefix;
private _activeOffersByGroup;
private _parseOfferUtxo;
private _parseOneshotOfferUtxo;
private _parsePartialOfferUtxo;
}
export declare function scriptOps(script: Script): Op[];
//# sourceMappingURL=agora.d.ts.map