diff --git a/package.json b/package.json index 4506f78..3f471f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.45", + "version": "0.19.46", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts new file mode 100644 index 0000000..f935eda --- /dev/null +++ b/src/Orion/bridge/index.ts @@ -0,0 +1,139 @@ +import type { ethers } from 'ethers'; +import { + AtomicSwapStatus, type AtomicSwap, type SupportedChainId, + type Unit, INTERNAL_PROTOCOL_PRECISION +} from '../../index.js'; +import getHistoryExt from './getHistory.js'; +import swapExt from './swap.js'; + +import { BigNumber } from 'bignumber.js'; + +const backendStatusToAtomicSwapStatus = { + LOCKED: AtomicSwapStatus.ROUTING, + CLAIMED: AtomicSwapStatus.SETTLED, + REFUNDED: AtomicSwapStatus.REFUNDED, + REDEEMED: AtomicSwapStatus.SETTLED, + 'BEFORE-REDEEM': AtomicSwapStatus.ROUTING, +} as const; + +export default class Bridge { + constructor( + private readonly unitsArray: Unit[], + private readonly env?: string, + ) {} + + async getMergedHistory( + externalStoredAtomicSwaps: AtomicSwap[], + walletAddress: string, + ) { + const bridgeHistory = await this.getHistory(walletAddress); + + return Object.values(bridgeHistory).map((atomicSwap) => { + if (atomicSwap === undefined) throw new Error('No atomic swap'); + + const { + secretHash, + amountToReceive, + amountToSpend, + targetChainId, + asset, + sourceChainId, + status: asStatus, + claimed, + sender, + transactions, + expiration, + creationDate, + } = atomicSwap; + const localSwap = externalStoredAtomicSwaps.find( + (swap) => secretHash === swap.secretHash, + ); + + const amount = amountToReceive ?? amountToSpend ?? 0; + + // Checking if transaction hash from blockchain is different from the same in local storage + // and changing it to the correct one + + let assetName = asset; + + // LEGACY. Some old atomic swaps have address instead of asset name. Here we handle this case + if (asset.includes('0x')) { + assetName = '—'; // We don't want to display address even if we can't find asset name + } + + // Define status + let historyStatus = backendStatusToAtomicSwapStatus[asStatus.source]; + if (asStatus.source === 'LOCKED') { + const historySwap = bridgeHistory[secretHash]; + if (historySwap?.status.target === 'REDEEMED') { + historyStatus = AtomicSwapStatus.SETTLED; + } + } + if (claimed) historyStatus = AtomicSwapStatus.SETTLED; + let status: AtomicSwapStatus | undefined; + if ( + [AtomicSwapStatus.SETTLED, AtomicSwapStatus.REFUNDED].includes( + historyStatus, + ) + ) { + status = historyStatus; + } else { + status = localSwap !== undefined ? localSwap.status : historyStatus; + } + + // Define secret + const secret = localSwap !== undefined ? localSwap.secret : ''; + + // Define environment + const env = localSwap?.env; + + return { + liquidityMigrationTxHash: localSwap?.liquidityMigrationTxHash, + sourceNetwork: sourceChainId, + targetNetwork: targetChainId, + amount: new BigNumber(amount) + .multipliedBy(new BigNumber(10).pow(INTERNAL_PROTOCOL_PRECISION)) + .toString(), + walletAddress: sender, + secret, + secretHash, + lockTransactionHash: transactions?.lock, + refundTransactionHash: transactions?.refund, + status, + asset: assetName, + expiration: + expiration?.lock ?? creationDate.getTime() + 60 * 60 * 24 * 4, // Or default 4 days + creationDate: creationDate.getTime(), + env, + redeemOrder: atomicSwap.redeemOrder, + }; + }).filter((swap) => swap.env === undefined || swap.env === this.env); + } + + getHistory(address: string, limit = 1000) { + return getHistoryExt(this.unitsArray, address, limit); + } + + swap( + assetName: string, + amount: BigNumber.Value, + sourceChain: SupportedChainId, + targetChain: SupportedChainId, + signer: ethers.Signer, + options: { + autoApprove?: boolean + logger?: (message: string) => void + withdrawToWallet?: boolean + } + ) { + return swapExt({ + amount, + assetName, + sourceChain, + targetChain, + signer, + unitsArray: this.unitsArray, + options, + }) + } +} diff --git a/src/Orion/bridge/swap.ts b/src/Orion/bridge/swap.ts index aa723b2..9fe41be 100644 --- a/src/Orion/bridge/swap.ts +++ b/src/Orion/bridge/swap.ts @@ -14,10 +14,10 @@ import { import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js'; import { denormalizeNumber, generateSecret, normalizeNumber, toUpperCase } from '../../utils/index.js'; import type { SupportedChainId } from '../../types.js'; -import type Orion from '../index.js'; import type { z } from 'zod'; import type { placeAtomicSwapSchema } from '../../services/Aggregator/schemas/index.js'; import { simpleFetch } from 'simple-typed-fetch'; +import type { Unit } from '../../index.js'; type Params = { assetName: string @@ -25,7 +25,7 @@ type Params = { sourceChain: SupportedChainId targetChain: SupportedChainId signer: ethers.Signer - orion: Orion + unitsArray: Unit[] options?: { withdrawToWallet?: boolean // By default, the transfer amount remains in the exchange contract autoApprove?: boolean @@ -40,7 +40,7 @@ export default async function swap({ targetChain, signer, options, - orion + unitsArray, }: Params) { const startProcessingTime = Date.now(); if (amount === '') throw new Error('Amount can not be empty'); @@ -50,8 +50,11 @@ export default async function swap({ if (amountBN.isNaN()) throw new Error(`Amount '${amountBN.toString()}' is not a number`); if (amountBN.lte(0)) throw new Error(`Amount '${amountBN.toString()}' should be greater than 0`); - const sourceChainUnit = orion.getUnit(sourceChain); - const targetChainUnit = orion.getUnit(targetChain); + const sourceChainUnit = unitsArray.find(({ chainId }) => chainId === sourceChain); + const targetChainUnit = unitsArray.find(({ chainId }) => chainId === targetChain); + + if (sourceChainUnit === undefined) throw new Error(`Source chain '${sourceChain}' not found`); + if (targetChainUnit === undefined) throw new Error(`Target chain '${targetChain}' not found`); const { blockchainService: sourceBlockchainService, diff --git a/src/Orion/index.ts b/src/Orion/index.ts index 6ca00c3..0da2614 100644 --- a/src/Orion/index.ts +++ b/src/Orion/index.ts @@ -1,36 +1,12 @@ -import type { BigNumber } from 'bignumber.js'; -import type { ethers } from 'ethers'; import { merge } from 'merge-anything'; import { chains, envs } from '../config/index.js'; import type { networkCodes } from '../constants/index.js'; import Unit from '../Unit/index.js'; import { ReferralSystem } from '../services/ReferralSystem/index.js'; -import type { SupportedChainId, DeepPartial, VerboseUnitConfig, KnownEnv } from '../types.js'; +import type { SupportedChainId, DeepPartial, VerboseUnitConfig, KnownEnv, EnvConfig, AggregatedAssets } from '../types.js'; import { isValidChainId } from '../utils/index.js'; -import swap from './bridge/swap.js'; -import getHistory from './bridge/getHistory.js'; import { simpleFetch } from 'simple-typed-fetch'; - -type EnvConfig = { - analyticsAPI: string - referralAPI: string - networks: Partial< - Record< - SupportedChainId, - VerboseUnitConfig - > - > -} -type AggregatedAssets = Partial< - Record< - string, - Partial< - Record - > - > - >; +import Bridge from './bridge/index.js'; export default class Orion { public readonly env?: string; @@ -39,6 +15,8 @@ export default class Orion { public readonly referralSystem: ReferralSystem; + public readonly bridge: Bridge; + // TODO: get tradable assets (aggregated) // TODO: get tradable pairs (aggregated) @@ -117,6 +95,11 @@ export default class Orion { [chainId]: unit, } }, {}); + + this.bridge = new Bridge( + this.unitsArray, + this.env, + ); } get unitsArray() { @@ -211,28 +194,4 @@ export default class Orion { return result; } - - bridge = { - getHistory: (address: string, limit = 1000) => getHistory(this.unitsArray, address, limit), - swap: ( - assetName: string, - amount: BigNumber.Value, - sourceChain: SupportedChainId, - targetChain: SupportedChainId, - signer: ethers.Signer, - options: { - autoApprove?: boolean - logger?: (message: string) => void - withdrawToWallet?: boolean - } - ) => swap({ - amount, - assetName, - sourceChain, - targetChain, - signer, - orion: this, - options, - }) - } } diff --git a/src/types.ts b/src/types.ts index b97e2f1..3e7be3d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -250,3 +250,102 @@ export type VerboseUnitConfig = { export type KnownEnv = typeof knownEnvs[number]; export type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; + +export type EnvConfig = { + analyticsAPI: string + referralAPI: string + networks: Partial< + Record< + SupportedChainId, + VerboseUnitConfig + > + > +} +export type AggregatedAssets = Partial< + Record< + string, + Partial< + Record + > + > + >; + +export type RedeemOrder = { + sender: string + receiver: string + asset: string + amount: number + expiration: number + secretHash: string + signature: string + claimReceiver: string +} + +export enum AtomicSwapStatus { + ROUTING_REQUESTED = 'ROUTING_REQUESTED', + ROUTING_PENDING = 'ROUTING_PENDING', + ROUTING = 'ROUTING', + ROUTING_FAILED = 'ROUTING_FAILED', + + // ACCEPTED = 'ACCEPTED', + FAILED = 'FAILED', + REJECTED = 'REJECTED', + + CHECK_REDEEM_THROUGH_OB_AVAILABLE = 'CHECK_REDEEM_THROUGH_OB_AVAILABLE', + // Redeem + // Blockchain redeem + READY_TO_REDEEM = 'READY_TO_REDEEM', + REDEEM_REQUESTED = 'REDEEM_REQUESTED', + REDEEM_PENDING = 'REDEEM_PENDING', + REDEEM_FAILED = 'REDEEM_FAILED', + + // Orion blockchain redeem + READY_TO_REDEEM_THROUGH_OB = 'READY_TO_REDEEM_THROUGH_OB', + REDEEM_PENDING_THROUGH_OB = 'REDEEM_PENDING_THROUGH_OB', + REDEEM_FAILED_THROUGH_OB = 'REDEEM_FAILED_THROUGH_OB', + + SETTLED = 'SETTLED', + + // Refund + REFUND_REQUESTED = 'REFUND_REQUESTED', + REFUND_PENDING = 'REFUND_PENDING', + REFUNDED = 'REFUNDED', + REFUND_FAILED = 'REFUND_FAILED', +} + +export type AtomicSwap = { + secret: string + secretHash: string + status: AtomicSwapStatus + + walletAddress: string + env?: string | undefined + + sourceNetwork?: SupportedChainId + targetNetwork?: SupportedChainId + + amount?: string + asset?: string + + creationDate?: number + expiration?: number + + lockTransactionHash?: string + redeemTransactionHash?: string + refundTransactionHash?: string + liquidityMigrationTxHash?: string + + redeemOrder?: RedeemOrder +} + +export type ExternalStorage = { + bridge: { + getAtomicSwaps: () => AtomicSwap[] + setAtomicSwaps: (atomics: AtomicSwap[]) => void + addAtomicSwap: (atomic: AtomicSwap) => void + updateAtomicSwap: (secretHash: string, atomic: Partial) => void + removeAtomicSwaps: (secretHashes: string[]) => void + } +}