From 35713407a7aff7275f826dc35102c1e872f1bab8 Mon Sep 17 00:00:00 2001 From: Aleksandr Kraiz Date: Wed, 23 Aug 2023 17:46:43 +0400 Subject: [PATCH] Added combineLocalAndExternalData to bridge --- package.json | 2 +- src/Orion/bridge/getHistory.ts | 12 +-- src/Orion/bridge/index.ts | 172 ++++++++++++++++++++++++++++++++- src/types.ts | 79 +++++++++++---- src/utils/invariant.ts | 18 ++++ 5 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 src/utils/invariant.ts diff --git a/package.json b/package.json index 1cd2a78..a7bb329 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@orionprotocol/sdk", - "version": "0.19.59", + "version": "0.19.60", "description": "Orion Protocol SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", diff --git a/src/Orion/bridge/getHistory.ts b/src/Orion/bridge/getHistory.ts index b0cc1db..402a163 100644 --- a/src/Orion/bridge/getHistory.ts +++ b/src/Orion/bridge/getHistory.ts @@ -130,19 +130,19 @@ const getHistory = async (units: Unit[], address: string, limit = 1000) => { asset: string sender: string secretHash: string - receiver: string | undefined - secret: string | undefined + receiver?: string | undefined + secret?: string | undefined timestamp: TargetItem['timestamp'] & SourceItem['timestamp'] expiration: TargetItem['expiration'] & SourceItem['expiration'] transactions: TargetItem['transactions'] & SourceItem['transactions'] - lockOrder: AggItem['lockOrder'] | undefined - redeemOrder: AggItem['redeemOrder'] | undefined + lockOrder?: AggItem['lockOrder'] | undefined + redeemOrder?: AggItem['redeemOrder'] | undefined amountToReceive: SourceItem['amountToReceive'] amountToSpend: SourceItem['amountToSpend'] status: { source: SourceItem['state'] - target: TargetItem['state'] | undefined - aggregator: AggItem['status'] | undefined + target?: TargetItem['state'] | undefined + aggregator?: AggItem['status'] | undefined } } diff --git a/src/Orion/bridge/index.ts b/src/Orion/bridge/index.ts index c539a53..8dce712 100644 --- a/src/Orion/bridge/index.ts +++ b/src/Orion/bridge/index.ts @@ -1,16 +1,43 @@ import { ethers } from 'ethers'; -import { - type AtomicSwap, type SupportedChainId, - type Unit, INTERNAL_PROTOCOL_PRECISION +import type { + Orion, Unit, AtomicSwapLocal, SupportedChainId, TransactionInfo } from '../../index.js'; +import { INTERNAL_PROTOCOL_PRECISION, TxStatus, TxType } from '../../index.js'; import getHistoryExt from './getHistory.js'; import swapExt from './swap.js'; import { BigNumber } from 'bignumber.js'; import generateSecret from '../../utils/generateSecret.js'; +import { isPresent } from 'ts-is-present'; +import { invariant } from '../../utils/invariant.js'; export const SECONDS_IN_DAY = 60 * 60 * 24; export const EXPIRATION_DAYS = 4; + +type BridgeHistory = Awaited>; + +type BridgeHistoryItem = NonNullable; + +export type AtomicSwap = Partial< + Omit +> & Partial< + Omit +> & { + sourceChainId: SupportedChainId + targetChainId: SupportedChainId + lockExpiration: number + secretHash: string + walletAddress: string + secret?: string | undefined + + creationDate?: number | undefined + redeemExpired?: boolean | undefined + + lockTx?: TransactionInfo | undefined + redeemTx?: TransactionInfo | undefined + refundTx?: TransactionInfo | undefined + liquidityMigrationTx?: TransactionInfo | undefined +} export default class Bridge { constructor( private readonly unitsArray: Unit[], @@ -74,6 +101,145 @@ export default class Bridge { }); } + async combineLocalAndExternalData( + walletAddress: string, + localAtomicSwaps: AtomicSwapLocal[], + transactions: TransactionInfo[], + ) { + // Prepare transactions data + const byTxHashMap = new Map(); + type BridgeTxs = { + lockTx?: TransactionInfo | undefined + redeemTx?: TransactionInfo | undefined + refundTx?: TransactionInfo | undefined + }; + const bySecretHashMap = new Map(); + transactions.forEach((tx) => { + if (tx.hash !== undefined) byTxHashMap.set(tx.hash, tx); + if (tx.payload) { + const { type, data } = tx.payload; + if (type === TxType.BRIDGE_LOCK || type === TxType.BRIDGE_REDEEM || type === TxType.BRIDGE_REFUND) { + const item = bySecretHashMap.get(data.secretHash); + bySecretHashMap.set(data.secretHash, { + ...item, + lockTx: type === TxType.BRIDGE_LOCK ? tx : item?.lockTx, + redeemTx: type === TxType.BRIDGE_REDEEM ? tx : item?.redeemTx, + refundTx: type === TxType.BRIDGE_REFUND ? tx : item?.refundTx, + }); + } + } + }); + + // Combine local data and external data + const bridgeHistory = await this.getHistory(walletAddress); + const atomicSwapsMap = new Map(); + Object.values(bridgeHistory) + .filter(isPresent) + .forEach((atomicHistoryItem) => { + const { lock, redeem, refund } = atomicHistoryItem.transactions ?? {}; + const lockTx = lock !== undefined + ? { + hash: lock, + status: TxStatus.SETTLED, + } + : undefined; + const redeemTx = redeem !== undefined + ? { + hash: redeem, + status: TxStatus.SETTLED, + } + : undefined; + const refundTx = refund !== undefined + ? { + hash: refund, + status: TxStatus.SETTLED, + } + : undefined; + + let redeemExpired = false; + + // If redeem order is expired + if (atomicHistoryItem.redeemOrder) { + const currentTime = Date.now(); + if (currentTime > atomicHistoryItem.redeemOrder.expiration) redeemExpired = true; + } + + // const assetName = combinedAddressToAsset[atomicHistoryItem.asset]?.[atomicHistoryItem.sourceChainId]; + const assetName = 'asdf'; + + const amount = atomicHistoryItem.amountToReceive ?? atomicHistoryItem.amountToSpend; + + invariant(atomicHistoryItem.expiration?.lock, 'Lock expiration must be defined'); + atomicSwapsMap.set(atomicHistoryItem.secretHash, { + ...atomicHistoryItem, + walletAddress: atomicHistoryItem.sender, + creationDate: atomicHistoryItem.creationDate.getTime(), + assetName, + lockTx, + amount: amount !== undefined ? amount.toString() : undefined, + redeemTx, + refundTx, + lockExpiration: atomicHistoryItem.expiration.lock, + redeemExpired, + }); + }); + localAtomicSwaps.forEach((atomic) => { + const atomicInMap = atomicSwapsMap.get(atomic.secretHash); + + const { liquidityMigrationTxHash, redeemSettlement, secretHash } = atomic; + + const secretHashTxs = bySecretHashMap.get(secretHash); + let redeemTx: TransactionInfo | undefined; + if (redeemSettlement) { + if (redeemSettlement.type === 'own_tx') { + redeemTx = secretHashTxs?.redeemTx; + } else if (redeemSettlement.result) { + redeemTx = { + status: redeemSettlement.result.value === 'success' ? TxStatus.SETTLED : TxStatus.FAILED, + } + } else if (redeemSettlement.requestedAt !== undefined) { + redeemTx = { + status: TxStatus.PENDING, + } + } + } + const liquidityMigrationTx = liquidityMigrationTxHash !== undefined ? byTxHashMap.get(liquidityMigrationTxHash) : undefined; + const amount = atomic.amount !== undefined + ? new BigNumber(atomic.amount).div(10 ** INTERNAL_PROTOCOL_PRECISION).toString() + : undefined; + if (atomicInMap) { // Merge local and backend data + atomicSwapsMap.set(atomic.secretHash, { + ...atomicInMap, + ...atomic, + lockExpiration: atomicInMap.lockExpiration, + targetChainId: atomicInMap.targetChainId, + sourceChainId: atomicInMap.sourceChainId, + amount: atomicInMap.amount ?? amount, + lockTx: atomicInMap.lockTx ?? secretHashTxs?.lockTx, + redeemTx: atomicInMap.redeemTx ?? redeemTx, + refundTx: atomicInMap.refundTx ?? secretHashTxs?.refundTx, + liquidityMigrationTx: atomicInMap.liquidityMigrationTx ?? liquidityMigrationTx, + }); + } else { + invariant(atomic.targetChainId, 'Target chain id is not defined'); + invariant(atomic.sourceChainId, 'Source chain id is not defined'); + invariant(atomic.lockExpiration, 'Lock expiration is not defined'); + + atomicSwapsMap.set(atomic.secretHash, { + ...atomic, + sourceChainId: atomic.sourceChainId, + targetChainId: atomic.targetChainId, + lockExpiration: atomic.lockExpiration, + lockTx: secretHashTxs?.lockTx, + redeemTx, + refundTx: secretHashTxs?.refundTx, + liquidityMigrationTx, + }); + } + }); + return atomicSwapsMap; + } + makeAtomicSwap( walletAddress: string, networkFrom: SupportedChainId, diff --git a/src/types.ts b/src/types.ts index 0c19b6c..db51432 100644 --- a/src/types.ts +++ b/src/types.ts @@ -283,36 +283,73 @@ export type RedeemOrder = { claimReceiver: string } -export type AtomicSwap = { +export interface AtomicSwapLocal { secret: string secretHash: string - walletAddress: string env?: string | undefined - sourceNetwork?: SupportedChainId - targetNetwork?: SupportedChainId + sourceChainId?: SupportedChainId | undefined + targetChainId?: SupportedChainId | undefined - amount?: string - asset?: string + amount?: string | undefined + assetName?: string | undefined - creationDate?: number - expiration?: number + liquidityMigrationTxHash?: string | undefined + lockTransactionHash?: string | undefined + refundTransactionHash?: string | undefined - lockTransactionHash?: string - redeemTransactionHash?: string - refundTransactionHash?: string - liquidityMigrationTxHash?: string - - redeemOrder?: RedeemOrder + creationDate?: number | undefined + lockExpiration?: number | undefined + placingOrderError?: string | undefined + redeemSettlement?: { + type: 'own_tx' + } | { + type: 'orion_tx' + requestedAt?: number + result?: { + timestamp: number + value: 'success' | 'failed' + } + } | undefined } -export type ExternalStorage = { - bridge: { - getAtomicSwaps: () => AtomicSwap[] - setAtomicSwaps: (atomics: AtomicSwap[]) => void - addAtomicSwap: (atomic: AtomicSwap) => void - updateAtomicSwap: (secretHash: string, atomic: Partial) => void - removeAtomicSwaps: (secretHashes: string[]) => void +export enum TxStatus { + PENDING = 'pending', + FAILED = 'failed', + SETTLED = 'settled', +} + +export enum TxType { + BRIDGE_LOCK = 'BRIDGE_LOCK', + BRIDGE_REDEEM = 'BRIDGE_REDEEM', + BRIDGE_REFUND = 'BRIDGE_REFUND', +} + +type BridgeRedeemTxPayload = { + type: TxType.BRIDGE_REDEEM + data: { + secretHash: string } } + +type BridgeLockTxPayload = { + type: TxType.BRIDGE_LOCK + data: { + secretHash: string + } +} + +type BridgeRefundTxPayload = { + type: TxType.BRIDGE_REFUND + data: { + secretHash: string + } +} + +export type TransactionInfo = { + id?: string + status?: TxStatus + hash?: string + payload?: BridgeLockTxPayload | BridgeRedeemTxPayload | BridgeRefundTxPayload +} diff --git a/src/utils/invariant.ts b/src/utils/invariant.ts new file mode 100644 index 0000000..d637c9f --- /dev/null +++ b/src/utils/invariant.ts @@ -0,0 +1,18 @@ +export function invariant( + condition: T, + errorMessage?: ((condition: T) => string) | string, +): asserts condition { + if (condition) { + return; + } + + if (typeof errorMessage === 'undefined') { + throw new Error('Invariant failed'); + } + + if (typeof errorMessage === 'string') { + throw new Error(errorMessage); + } + + throw new Error(errorMessage(condition)); +}