UNPKG

@atomiqlabs/sdk

Version:

atomiq labs SDK for cross-chain swaps between smart chains and bitcoin

281 lines (258 loc) 9.99 kB
import {SwapType} from "../enums/SwapType"; import {SwapHandlerInfoType} from "./IntermediaryDiscovery"; import {ChainSwapType, LNNodeLiquidity, SwapContract} from "@atomiqlabs/base"; import {tryWithRetries} from "../utils/RetryUtils"; /** * Services offered by an intermediary * * @category LPs */ export type ServicesType = { [key in SwapType]?: SwapHandlerInfoType }; /** * Reputation data for an intermediary on a single chain * * @category LPs */ export type SingleChainReputationType = { [token: string]: { [key in ChainSwapType]: { successVolume: bigint, successCount: bigint, failVolume: bigint, failCount: bigint, coopCloseVolume: bigint, coopCloseCount: bigint, } } }; /** * Smart chain liquidity data * * @category LPs */ export type SCLiquidity = { [token: string]: bigint }; /** * Represents an intermediary (liquidity provider) * * @category LPs */ export class Intermediary { /** * Base URL where the intermediary is listening for HTTP requests */ readonly url: string; /** * Addresses of the intermediary on smart chains, used for checking the provided signatures */ readonly addresses: {[chainIdentifier: string]: string}; /** * Contract versions of the intermediary on smart chains */ readonly contractVersions: {[chainIdentifier: string]: string}; /** * Swap protocol services offered by the intermediary */ readonly services: ServicesType; /** * Input/output swap bounds for various swap protocols offered by the intermediary */ readonly swapBounds: { [swapType in SwapType]?: { [chainIdentifier: string]: { [tokenAddress: string]: { input: { min?: bigint, max?: bigint }, output: { min?: bigint, max?: bigint } } } } } /** * Reputation of the intermediary on different smart chains, this is only fetched * on-demand when creating a swap where reputation is checked */ reputation: { [chainIdentifier: string]: SingleChainReputationType } = {}; /** * Liquidity of the intermediary across different smart chains, this is only fetched * on-demand when creating a swap where intermediary's liquidity is checked */ liquidity: { [chainIdentifier: string]: SCLiquidity } = {}; /** * Data about a lightning network node used by this intermediary, if it offers lightning * network swaps, this is only fetched on-demand when creating a Bitcoin Lightning -> Smart chain * swap through the intermediary (which necessitates checking intermediary's channel capacities) */ lnData?: LNNodeLiquidity; constructor( url: string, addresses: {[chainIdentifier: string]: string}, services: ServicesType, reputation: { [chainIdentifier: string]: SingleChainReputationType } = {}, contractVersions: {[chainIdentifier: string]: string} = {}, ) { this.url = url; this.addresses = addresses; this.services = services; this.reputation = reputation; this.contractVersions = contractVersions; this.swapBounds = {}; for(let _swapType in this.services) { const swapType: SwapType = parseInt(_swapType); const serviceInfo = this.services[swapType]!; const btcBounds = {min: BigInt(serviceInfo.min), max: BigInt(serviceInfo.max)}; const isSend = swapType===SwapType.TO_BTC || swapType===SwapType.TO_BTCLN; this.swapBounds[swapType] = {}; for(let chainIdentifier in serviceInfo.chainTokens) { this.swapBounds[swapType]![chainIdentifier] = {}; for(let tokenAddress of serviceInfo.chainTokens[chainIdentifier]) { this.swapBounds[swapType]![chainIdentifier][tokenAddress] = { input: isSend ? {} : btcBounds, output: !isSend ? {} : btcBounds, }; } } } } /** * Returns the input/output swap limit for given swap type, chain and token * * @param swapType Swap protocol service to check * @param chainId Chain identifier of the smart chain to check * @param tokenAddress Address of the token to check */ getSwapLimits(swapType: SwapType, chainId: string, tokenAddress: string): {input: {min?: bigint, max?: bigint}, output: {min?: bigint, max?: bigint}} | undefined { return this.swapBounds[swapType]?.[chainId]?.[tokenAddress]; } /** * Returns tokens supported by the intermediary, optionally constrained to the specific swap types * * @param chainIdentifier Chain identifier of the smart chain to check * @param swapTypesArr An array of swap type services to check * @private */ private getSupportedTokens(chainIdentifier: string, swapTypesArr: SwapType[] = [ SwapType.TO_BTC, SwapType.TO_BTCLN, SwapType.FROM_BTC, SwapType.FROM_BTCLN, SwapType.SPV_VAULT_FROM_BTC, SwapType.FROM_BTCLN_AUTO ]): Set<string> { const swapTypes = new Set(swapTypesArr); let tokens: Set<string> = new Set<string>(); swapTypes.forEach((swapType) => { const supportedTokens = this.services[swapType]?.chainTokens?.[chainIdentifier]; if(supportedTokens!=null) supportedTokens.forEach(token => tokens.add(token)); }); return tokens; } /** * Fetches, returns and saves the reputation of the intermediary, either for all or just for a single token * * @param chainIdentifier Chain identifier of the chain on which to fetch the reputation * @param swapContract Swap contract for the requested smart chain * @param tokens An optional array of tokens to fetch the data for (by default it uses all tokens supported * by the intermediary) * @param abortSignal */ async getReputation( chainIdentifier: string, swapContract: SwapContract<any>, tokens?: string[], abortSignal?: AbortSignal ): Promise<SingleChainReputationType> { const checkReputationTokens: Set<string> = tokens==null ? this.getSupportedTokens(chainIdentifier, [SwapType.TO_BTC, SwapType.TO_BTCLN]) : new Set<string>(tokens); const promises: Promise<void>[] = []; const reputation: SingleChainReputationType = {}; for(let token of checkReputationTokens) { promises.push( tryWithRetries(() => swapContract.getIntermediaryReputation(this.getAddress(chainIdentifier), token), undefined, undefined, abortSignal ).then(result => { if(result!=null) reputation[token] = result; }) ); } await Promise.all(promises); this.reputation ??= {}; this.reputation[chainIdentifier] ??= {}; for(let key in reputation) { this.reputation[chainIdentifier][key] = reputation[key]; } return reputation; } /** * Fetches, returns and saves the liquidity of the intermediary for a specific token * * @param chainIdentifier Chain identifier of the chain on which to fetch the reputation * @param swapContract Swap contract for the requested smart chain * @param token Token address of the token to fetch the liquidity for * @param abortSignal */ async getLiquidity( chainIdentifier: string, swapContract: SwapContract<any>, token: string, abortSignal?: AbortSignal ): Promise<bigint> { const result = await tryWithRetries( () => swapContract.getBalance(this.getAddress(chainIdentifier), token, true), undefined, undefined, abortSignal ); this.liquidity ??= {}; this.liquidity[chainIdentifier] ??= {}; this.liquidity[chainIdentifier][token] = result; return result; } /** * Checks whether the intermediary supports swaps of any tokens on the smart chain * * @param chainIdentifier Chain identifier of the smart chain */ supportsChain(chainIdentifier: string): boolean { if(this.addresses[chainIdentifier]==null) return false; return this.getSupportedTokens(chainIdentifier).size!==0; } /** * Returns intermediary's address on a given smart chain * * @param chainIdentifier Chain identifier of the smart chain */ getAddress(chainIdentifier: string) { return this.addresses[chainIdentifier]; } /** * Returns the contract version used by the intermediary for a given chain * * @param chainIdentifier */ getContractVersion(chainIdentifier: string): string { return this.contractVersions[chainIdentifier] ?? "v1"; } /** * Returns the range of contract versions used by the LPs * * @param chainIdentifier * @param lps */ static getContractVersionsForLps(chainIdentifier: string, lps: Intermediary[]): string[] { const versions: string[] = []; lps.forEach((lp) => { const lpVersion = lp.getContractVersion(chainIdentifier); if(!versions.includes(lpVersion)) versions.push(lpVersion); }); return versions; } }