Added combineLocalAndExternalData to bridge

This commit is contained in:
Aleksandr Kraiz
2023-08-23 17:46:43 +04:00
parent 859cca326b
commit 35713407a7
5 changed files with 252 additions and 31 deletions

View File

@@ -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",

View File

@@ -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
}
}

View File

@@ -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<ReturnType<Orion['bridge']['getHistory']>>;
type BridgeHistoryItem = NonNullable<BridgeHistory[string]>;
export type AtomicSwap = Partial<
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
> & Partial<
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
> & {
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<string, TransactionInfo>();
type BridgeTxs = {
lockTx?: TransactionInfo | undefined
redeemTx?: TransactionInfo | undefined
refundTx?: TransactionInfo | undefined
};
const bySecretHashMap = new Map<string, BridgeTxs>();
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<string, AtomicSwap>();
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,

View File

@@ -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<AtomicSwap>) => 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
}

18
src/utils/invariant.ts Normal file
View File

@@ -0,0 +1,18 @@
export function invariant<T = unknown>(
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));
}