@atomiqlabs/sdk
Version:
atomiq labs SDK for cross-chain swaps between smart chains and bitcoin
182 lines (159 loc) • 7.6 kB
text/typescript
import {
ChainData,
BitcoinNetwork,
BitcoinRpc,
BaseTokenType,
ChainType,
StorageObject,
IStorageManager
} from "@atomiqlabs/base";
import {
BitcoinTokens,
BtcToken, CustomPriceFunction, CustomPriceProvider,
MempoolApi,
MempoolBitcoinRpc, RedundantSwapPrice,
RedundantSwapPriceAssets, SCToken, SingleSwapPrice, Swapper,
SwapperOptions
} from "@atomiqlabs/sdk-lib";
import {SmartChainAssets} from "./SmartChainAssets";
import {LocalStorageManager} from "./storage/LocalStorageManager";
type ChainInitializer<O, C extends ChainType, T extends BaseTokenType> = {
chainId: ChainType["ChainId"],
chainType: ChainType,
initializer: (
options: O,
bitcoinRelay: BitcoinRpc<any>,
network: BitcoinNetwork,
storageCtor: <T extends StorageObject>(name: string) => IStorageManager<T>
) => ChainData<C>,
tokens: T,
options: O
}
type TokensDict<T extends ChainInitializer<any, any, any>> = {
[K in T["chainId"]]: {
[val in keyof T["tokens"]]: SCToken<K>
}
};
type GetAllTokens<T extends readonly ChainInitializer<any, any, any>[]> =
(T extends readonly [infer First extends ChainInitializer<any, any, any>, ...infer Rest extends ChainInitializer<any, any, any>[]]
? TokensDict<First> & GetAllTokens<Rest>
: unknown);
export type TokenResolverDict<T extends ChainInitializer<any, any, any>> = {[K in T["chainId"]]: {
getToken: (address: string) => SCToken<K>
}};
type GetAllTokenResolvers<T extends readonly ChainInitializer<any, any, any>[]> =
(T extends readonly [infer First extends ChainInitializer<any, any, any>, ...infer Rest extends ChainInitializer<any, any, any>[]]
? TokenResolverDict<First> & GetAllTokenResolvers<Rest>
: unknown);
type OptionsDict<T extends ChainInitializer<any, any, any>> = {[K in T["chainId"]]: T["options"]};
type GetAllOptions<T extends readonly ChainInitializer<any, any, any>[]> =
(T extends readonly [infer First extends ChainInitializer<any, any, any>, ...infer Rest extends ChainInitializer<any, any, any>[]]
? OptionsDict<First> & GetAllOptions<Rest>
: unknown);
type ChainTypeDict<T extends ChainInitializer<any, any, any>> = {[K in T["chainId"]]: T["chainType"]};
type ToMultichain<T extends readonly ChainInitializer<any, any, any>[]> =
(T extends readonly [infer First extends ChainInitializer<any, any, any>, ...infer Rest extends ChainInitializer<any, any, any>[]]
? ChainTypeDict<First> & ToMultichain<Rest>
: {});
export type MultichainSwapperOptions<T extends readonly ChainInitializer<any, any, any>[]> = SwapperOptions & {
chains: GetAllOptions<T>
} & {
chainStorageCtor?: <T extends StorageObject>(name: string) => IStorageManager<T>,
pricingFeeDifferencePPM?: bigint,
mempoolApi?: MempoolApi,
getPriceFn?: CustomPriceFunction
};
export class SwapperFactory<T extends readonly ChainInitializer<any, any, any>[]> {
Tokens: GetAllTokens<T> & {
BITCOIN: {
BTC: BtcToken<false>,
BTCLN: BtcToken<true>
}
} = {
BITCOIN: BitcoinTokens
} as any;
TokenResolver: GetAllTokenResolvers<T> = {} as any;
constructor(readonly initializers: T) {
this.initializers = initializers;
initializers.forEach(initializer => {
const addressMap: {[tokenAddress: string]: SCToken} = {};
this.Tokens[initializer.chainId] = {} as any;
for(let ticker in initializer.tokens) {
const assetData = initializer.tokens[ticker] as any;
this.Tokens[initializer.chainId][ticker] = addressMap[assetData.address] = {
chain: "SC",
chainId: initializer.chainId,
address: assetData.address,
name: SmartChainAssets[ticker].name,
decimals: assetData.decimals,
displayDecimals: assetData.displayDecimals,
ticker
} as any;
}
this.TokenResolver[initializer.chainId] = {
getToken: (address: string) => addressMap[address]
} as any;
});
}
newSwapper(options: MultichainSwapperOptions<T>) {
options.bitcoinNetwork ??= BitcoinNetwork.MAINNET as any;
options.storagePrefix ??= "atomiqsdk-"+options.bitcoinNetwork+"-";
options.defaultTrustedIntermediaryUrl ??= options.bitcoinNetwork===BitcoinNetwork.MAINNET ?
"https://node3.gethopa.com:34100" :
"https://node3.gethopa.com:24100";
options.registryUrl ??= options.bitcoinNetwork===BitcoinNetwork.MAINNET ?
"https://api.github.com/repos/adambor/SolLightning-registry/contents/registry-mainnet.json?ref=main" :
"https://api.github.com/repos/adambor/SolLightning-registry/contents/registry.json?ref=main";
const mempoolApi = options.mempoolApi ?? new MempoolApi(
options.bitcoinNetwork===BitcoinNetwork.TESTNET ?
[
"https://mempool.space/testnet/api/",
"https://mempool.fra.mempool.space/testnet/api/",
"https://mempool.va1.mempool.space/testnet/api/",
"https://mempool.tk7.mempool.space/testnet/api/"
] :
[
"https://mempool.space/api/",
"https://mempool.fra.mempool.space/api/",
"https://mempool.va1.mempool.space/api/",
"https://mempool.tk7.mempool.space/api/"
]
);
const bitcoinRpc = new MempoolBitcoinRpc(mempoolApi);
const pricingAssets: (RedundantSwapPriceAssets<ToMultichain<T>>[number] & {ticker: string, name: string})[] = [];
Object.keys(SmartChainAssets).forEach((ticker) => {
const chains: any = {};
for(let {tokens, chainId} of this.initializers) {
if(tokens[ticker]!=null) chains[chainId] = tokens[ticker];
}
const assetData = SmartChainAssets[ticker];
pricingAssets.push({
...assetData.pricing,
chains,
ticker,
name: assetData.name
})
});
options.chainStorageCtor ??= (name: string) => new LocalStorageManager(name);
const chains: {[key in T[number]["chainId"]]: ChainData<any>} = {} as any;
for(let {initializer, chainId} of this.initializers) {
if(options.chains[chainId]==null) continue;
chains[chainId] = initializer(options.chains[chainId], bitcoinRpc, options.bitcoinNetwork, options.chainStorageCtor) as any;
}
const swapPricing = options.getPriceFn!=null ?
new SingleSwapPrice(options.pricingFeeDifferencePPM ?? 10000n, new CustomPriceProvider(pricingAssets.map(val => {
return {
coinId: val.ticker,
chains: val.chains
}
}), options.getPriceFn)) :
RedundantSwapPrice.createFromTokenMap<ToMultichain<T>>(options.pricingFeeDifferencePPM ?? 10000n, pricingAssets);
return new Swapper<ToMultichain<T>>(
bitcoinRpc,
chains as any,
swapPricing,
pricingAssets,
options
);
}
}