Bridge: improvements

This commit is contained in:
Aleksandr Kraiz
2023-08-07 00:23:19 +04:00
parent b6cb9ae1a1
commit 321b2a2787
5 changed files with 256 additions and 56 deletions

139
src/Orion/bridge/index.ts Normal file
View File

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

View File

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

View File

@@ -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<SupportedChainId, {
address: string
}>
>
>
>;
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,
})
}
}

View File

@@ -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<SupportedChainId, {
address: string
}>
>
>
>;
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<AtomicSwap>) => void
removeAtomicSwaps: (secretHashes: string[]) => void
}
}