mirror of
https://github.com/orionprotocol/sdk.git
synced 2026-03-14 06:02:36 +03:00
Merge remote-tracking branch 'origin/main' into op-4438/pf-cex-prices
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import clone from 'just-clone';
|
||||
import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import { APPROVE_ERC20_GAS_LIMIT, NATIVE_CURRENCY_PRECISION } from './constants/index.js';
|
||||
import type {
|
||||
AggregatedBalanceRequirement, ApproveFix, Asset, BalanceIssue, BalanceRequirement, Source,
|
||||
@@ -9,6 +9,8 @@ import type {
|
||||
import { denormalizeNumber } from './utils/index.js';
|
||||
import arrayEquals from './utils/arrayEquals.js';
|
||||
|
||||
// BalanceGuard helps to check if there is enough balance to perform a swap
|
||||
// Also it can fix some balance issues (e.g. approve tokens)
|
||||
export default class BalanceGuard {
|
||||
private readonly balances: Partial<
|
||||
Record<
|
||||
@@ -24,7 +26,7 @@ export default class BalanceGuard {
|
||||
|
||||
private readonly nativeCryptocurrency: Asset;
|
||||
|
||||
private readonly provider: ethers.providers.Provider;
|
||||
private readonly provider: ethers.Provider;
|
||||
|
||||
private readonly signer: ethers.Signer;
|
||||
|
||||
@@ -33,7 +35,7 @@ export default class BalanceGuard {
|
||||
constructor(
|
||||
balances: Partial<Record<string, Record<'exchange' | 'wallet', BigNumber>>>,
|
||||
nativeCryptocurrency: Asset,
|
||||
provider: ethers.providers.Provider,
|
||||
provider: ethers.Provider,
|
||||
signer: ethers.Signer,
|
||||
logger?: (message: string) => void,
|
||||
) {
|
||||
@@ -55,6 +57,8 @@ export default class BalanceGuard {
|
||||
assetBalance[source] = assetBalance[source].plus(amount);
|
||||
}
|
||||
|
||||
// Reset is approve some token to zero
|
||||
// This operation may be required by some tokens, e.g. USDT
|
||||
private async checkResetRequired(
|
||||
assetAddress: string,
|
||||
spenderAddress: string,
|
||||
@@ -62,10 +66,10 @@ export default class BalanceGuard {
|
||||
const walletAddress = await this.signer.getAddress();
|
||||
const tokenContract = ERC20__factory
|
||||
.connect(assetAddress, this.provider);
|
||||
const unsignedTx = await tokenContract.populateTransaction
|
||||
.approve(
|
||||
const unsignedTx = await tokenContract.approve
|
||||
.populateTransaction(
|
||||
spenderAddress,
|
||||
ethers.constants.MaxUint256,
|
||||
ethers.MaxUint256,
|
||||
);
|
||||
unsignedTx.from = walletAddress;
|
||||
let resetRequired = false;
|
||||
@@ -109,6 +113,7 @@ export default class BalanceGuard {
|
||||
}, []);
|
||||
}
|
||||
|
||||
// This method can fix some balance issues (e.g. approve tokens)
|
||||
private readonly fixAllAutofixableBalanceIssues = async (
|
||||
balanceIssues: BalanceIssue[],
|
||||
) => {
|
||||
@@ -117,21 +122,23 @@ export default class BalanceGuard {
|
||||
const approve = async ({ spenderAddress, targetAmount }: ApproveFix) => {
|
||||
const bnTargetAmount = new BigNumber(targetAmount);
|
||||
const unsignedApproveTx = await tokenContract
|
||||
.populateTransaction
|
||||
.approve(
|
||||
.approve.populateTransaction(
|
||||
spenderAddress,
|
||||
bnTargetAmount.isZero()
|
||||
? '0' // Reset
|
||||
: ethers.constants.MaxUint256, // Infinite approve
|
||||
: ethers.MaxUint256, // Infinite approve
|
||||
);
|
||||
|
||||
const walletAddress = await this.signer.getAddress();
|
||||
const nonce = await this.provider.getTransactionCount(walletAddress, 'pending');
|
||||
const gasPrice = await this.provider.getGasPrice();
|
||||
const { gasPrice, maxFeePerGas } = await this.provider.getFeeData();
|
||||
const network = await this.provider.getNetwork();
|
||||
|
||||
if (gasPrice !== null && maxFeePerGas !== null) {
|
||||
unsignedApproveTx.gasPrice = gasPrice;
|
||||
unsignedApproveTx.maxFeePerGas = maxFeePerGas;
|
||||
}
|
||||
unsignedApproveTx.chainId = network.chainId;
|
||||
unsignedApproveTx.gasPrice = gasPrice;
|
||||
unsignedApproveTx.nonce = nonce;
|
||||
unsignedApproveTx.from = walletAddress;
|
||||
const gasLimit = await this.provider.estimateGas(unsignedApproveTx);
|
||||
@@ -139,7 +146,7 @@ export default class BalanceGuard {
|
||||
|
||||
this.logger?.('Approve transaction signing...');
|
||||
const signedTx = await this.signer.signTransaction(unsignedApproveTx);
|
||||
const txResponse = await this.provider.sendTransaction(signedTx);
|
||||
const txResponse = await this.provider.broadcastTransaction(signedTx);
|
||||
this.logger?.(`${issue.asset.name} approve transaction sent ${txResponse.hash}. Waiting for confirmation...`);
|
||||
await txResponse.wait();
|
||||
this.logger?.(`${issue.asset.name} approve transaction confirmed.`);
|
||||
@@ -161,6 +168,7 @@ export default class BalanceGuard {
|
||||
return balanceIssues.filter((item) => !autofixableBalanceIssues.includes(item));
|
||||
};
|
||||
|
||||
// Check that all balance requirements are fulfilled
|
||||
async check(fixAutofixable?: boolean) {
|
||||
this.logger?.(`Balance requirements: ${this.requirements
|
||||
.map((requirement) => `${requirement.amount} ${requirement.asset.name} ` +
|
||||
@@ -171,6 +179,7 @@ export default class BalanceGuard {
|
||||
const remainingBalances = clone(this.balances);
|
||||
const aggregatedRequirements = BalanceGuard.aggregateBalanceRequirements(this.requirements);
|
||||
|
||||
// DO NOT IGNORE THIS COMMENT. THIS IS VERY IMPORTANT TO UNDERSTAND THIS CODE
|
||||
// Balance absorption order is important!
|
||||
// 1. Exchange-contract only
|
||||
// 2. Exchange + wallet (can produce approves requirements)
|
||||
@@ -230,7 +239,7 @@ export default class BalanceGuard {
|
||||
const lackAmount = remainingBalance.exchange.abs(); // e.g. -435.234234 to 434.234234
|
||||
|
||||
let denormalizedAllowance: BigNumber;
|
||||
if (asset.address === ethers.constants.AddressZero) {
|
||||
if (asset.address === ethers.ZeroAddress) {
|
||||
denormalizedAllowance = remainingBalance.wallet;
|
||||
} else {
|
||||
if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`);
|
||||
@@ -272,13 +281,11 @@ export default class BalanceGuard {
|
||||
asset.address,
|
||||
spenderAddress,
|
||||
);
|
||||
const gasPriceWei = await this.provider.getGasPrice();
|
||||
const approveTransactionCost = ethers.BigNumber
|
||||
.from(APPROVE_ERC20_GAS_LIMIT)
|
||||
.mul(gasPriceWei);
|
||||
const { gasPrice: gasPriceWei } = await this.provider.getFeeData();
|
||||
const approveTransactionCost = BigInt(APPROVE_ERC20_GAS_LIMIT) * (gasPriceWei ?? 0n);
|
||||
const denormalizedApproveTransactionCost = denormalizeNumber(
|
||||
approveTransactionCost,
|
||||
NATIVE_CURRENCY_PRECISION
|
||||
BigInt(NATIVE_CURRENCY_PRECISION)
|
||||
);
|
||||
|
||||
requiredApproves.items = {
|
||||
@@ -321,6 +328,7 @@ export default class BalanceGuard {
|
||||
const walletTokensAggregatedRequirements = flattedAggregatedRequirements
|
||||
.filter(({ sources, asset }) => sources[0] === 'wallet' && asset.name !== this.nativeCryptocurrency.name);
|
||||
|
||||
// This requirements can be fulfilled by wallet
|
||||
await Promise.all(walletTokensAggregatedRequirements
|
||||
.map(async ({ asset, spenderAddress, items }) => {
|
||||
const remainingBalance = remainingBalances[asset.name];
|
||||
@@ -329,7 +337,7 @@ export default class BalanceGuard {
|
||||
.reduce<BigNumber>((p, c) => (c !== undefined ? p.plus(c) : p), new BigNumber(0));
|
||||
|
||||
let denormalizedAllowance: BigNumber;
|
||||
if (asset.address === ethers.constants.AddressZero) {
|
||||
if (asset.address === ethers.ZeroAddress) {
|
||||
denormalizedAllowance = remainingBalance.wallet;
|
||||
} else {
|
||||
if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`);
|
||||
@@ -365,13 +373,11 @@ export default class BalanceGuard {
|
||||
asset.address,
|
||||
spenderAddress,
|
||||
);
|
||||
const gasPriceWei = await this.provider.getGasPrice();
|
||||
const approveTransactionCost = ethers.BigNumber
|
||||
.from(APPROVE_ERC20_GAS_LIMIT)
|
||||
.mul(gasPriceWei);
|
||||
const { gasPrice: gasPriceWei } = await this.provider.getFeeData();
|
||||
const approveTransactionCost = BigInt(APPROVE_ERC20_GAS_LIMIT) * (gasPriceWei ?? 0n);
|
||||
const denormalizedApproveTransactionCost = denormalizeNumber(
|
||||
approveTransactionCost,
|
||||
NATIVE_CURRENCY_PRECISION
|
||||
BigInt(NATIVE_CURRENCY_PRECISION)
|
||||
);
|
||||
|
||||
requiredApproves.items = {
|
||||
|
||||
@@ -5,10 +5,10 @@ import { isValidChainId } from '../../utils/index.js';
|
||||
import { simpleFetch } from 'simple-typed-fetch';
|
||||
import bsonObjectId from 'bson-objectid';
|
||||
|
||||
const ObjectID = bsonObjectId.default
|
||||
const ObjectID = bsonObjectId;
|
||||
|
||||
const getHistory = async (units: Unit[], address: string, limit = 1000) => {
|
||||
if (!ethers.utils.isAddress(address)) throw new Error(`Invalid address: ${address}`);
|
||||
if (!ethers.isAddress(address)) throw new Error(`Invalid address: ${address}`);
|
||||
const data = await Promise.all(units.map(async ({ blockchainService, aggregator, chainId }) => {
|
||||
const sourceNetworkHistory = await simpleFetch(blockchainService.getSourceAtomicSwapHistory)({
|
||||
limit,
|
||||
|
||||
@@ -229,7 +229,7 @@ export default class Bridge {
|
||||
env?: string | undefined,
|
||||
) {
|
||||
const secret = generateSecret();
|
||||
const secretHash = ethers.utils.keccak256(secret);
|
||||
const secretHash = ethers.keccak256(secret);
|
||||
const lockExpiration = Date.now() + SECONDS_IN_DAY * EXPIRATION_DAYS * 1000;
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import getAvailableSources from '../../utils/getAvailableFundsSources.js';
|
||||
@@ -123,7 +123,7 @@ export default async function swap({
|
||||
const sourceChainBalances = await getBalances(
|
||||
{
|
||||
[assetName]: sourceChainAssetAddress,
|
||||
[sourceChainNativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
[sourceChainNativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
sourceAggregator,
|
||||
walletAddress,
|
||||
@@ -134,7 +134,7 @@ export default async function swap({
|
||||
const targetChainBalances = await getBalances(
|
||||
{
|
||||
[assetName]: targetChainAssetAddress,
|
||||
[targetChainNativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
[targetChainNativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
targetAggregator,
|
||||
walletAddress,
|
||||
@@ -146,7 +146,7 @@ export default async function swap({
|
||||
sourceChainBalances,
|
||||
{
|
||||
name: sourceChainNativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
sourceProvider,
|
||||
signer,
|
||||
@@ -157,7 +157,7 @@ export default async function swap({
|
||||
targetChainBalances,
|
||||
{
|
||||
name: targetChainNativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
targetProvider,
|
||||
signer,
|
||||
@@ -181,33 +181,33 @@ export default async function swap({
|
||||
BigNumber.ROUND_FLOOR,
|
||||
);
|
||||
const secret = generateSecret();
|
||||
const secretHash = ethers.utils.keccak256(secret);
|
||||
const secretHash = ethers.keccak256(secret);
|
||||
options?.logger?.(`Secret is ${secret}`);
|
||||
options?.logger?.(`Secret hash is ${secretHash}`);
|
||||
|
||||
const secondsInDay = 60 * 60 * 24;
|
||||
const expirationDays = 4;
|
||||
const expirationEtherBN = ethers.BigNumber.from(
|
||||
const expirationEtherBN = BigInt(
|
||||
Date.now() + (secondsInDay * expirationDays * 1000),
|
||||
);
|
||||
|
||||
const unsignedLockAtomicTx = await sourceExchangeContract.populateTransaction.lockAtomic({
|
||||
const unsignedLockAtomicTx = await sourceExchangeContract.lockAtomic.populateTransaction({
|
||||
amount: amountBlockchainParam,
|
||||
asset: sourceChainAssetAddress,
|
||||
expiration: expirationEtherBN,
|
||||
secretHash,
|
||||
sender: walletAddress,
|
||||
targetChainId: ethers.BigNumber.from(targetChain),
|
||||
targetChainId: BigInt(targetChain),
|
||||
});
|
||||
|
||||
let sourceChainGasPrice: ethers.BigNumber;
|
||||
let sourceChainGasPrice: bigint;
|
||||
const sourceChainFeeData = await sourceChainUnit.provider.getFeeData();
|
||||
if (ethers.BigNumber.isBigNumber(sourceChainFeeData.gasPrice)) { //
|
||||
if (sourceChainFeeData.gasPrice !== null) { //
|
||||
unsignedLockAtomicTx.gasPrice = sourceChainFeeData.gasPrice;
|
||||
sourceChainGasPrice = sourceChainFeeData.gasPrice;
|
||||
} else if (
|
||||
ethers.BigNumber.isBigNumber(sourceChainFeeData.maxFeePerGas) &&
|
||||
ethers.BigNumber.isBigNumber(sourceChainFeeData.maxPriorityFeePerGas)
|
||||
sourceChainFeeData.maxFeePerGas !== null &&
|
||||
sourceChainFeeData.maxPriorityFeePerGas !== null
|
||||
) { // EIP-1559
|
||||
unsignedLockAtomicTx.maxFeePerGas = sourceChainFeeData.maxFeePerGas;
|
||||
unsignedLockAtomicTx.maxPriorityFeePerGas = sourceChainFeeData.maxPriorityFeePerGas;
|
||||
@@ -216,7 +216,7 @@ export default async function swap({
|
||||
throw new Error('Can\'t get gas price');
|
||||
}
|
||||
|
||||
unsignedLockAtomicTx.chainId = parseInt(chainId, 10);
|
||||
unsignedLockAtomicTx.chainId = BigInt(chainId);
|
||||
unsignedLockAtomicTx.from = walletAddress;
|
||||
|
||||
let value = new BigNumber(0);
|
||||
@@ -230,16 +230,16 @@ export default async function swap({
|
||||
NATIVE_CURRENCY_PRECISION,
|
||||
BigNumber.ROUND_CEIL,
|
||||
);
|
||||
unsignedLockAtomicTx.gasLimit = ethers.BigNumber.from(LOCKATOMIC_GAS_LIMIT);
|
||||
unsignedLockAtomicTx.gasLimit = BigInt(LOCKATOMIC_GAS_LIMIT);
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(LOCKATOMIC_GAS_LIMIT).mul(sourceChainGasPrice);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const transactionCost = BigInt(LOCKATOMIC_GAS_LIMIT) * sourceChainGasPrice;
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
sourceChainBalanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: sourceChainNativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet']
|
||||
@@ -252,7 +252,7 @@ export default async function swap({
|
||||
|
||||
options?.logger?.('Signing lock tx transaction...');
|
||||
const signedTransaction = await signer.signTransaction(unsignedLockAtomicTx);
|
||||
const lockAtomicTxResponse = await sourceChainUnit.provider.sendTransaction(signedTransaction);
|
||||
const lockAtomicTxResponse = await sourceChainUnit.provider.broadcastTransaction(signedTransaction);
|
||||
options?.logger?.(`Lock tx sent. Tx hash: ${lockAtomicTxResponse.hash}. Waiting for tx to be mined...`);
|
||||
await lockAtomicTxResponse.wait();
|
||||
options?.logger?.('Lock tx mined.');
|
||||
@@ -280,7 +280,7 @@ export default async function swap({
|
||||
options?.logger?.('Atomic swap placed.');
|
||||
|
||||
// const targetChainGasPriceWei = await simpleFetch(targetBlockchainService.getGasPriceWei)();
|
||||
const unsignedRedeemAtomicTx = await targetExchangeContract.populateTransaction.redeemAtomic(
|
||||
const unsignedRedeemAtomicTx = await targetExchangeContract.redeemAtomic.populateTransaction(
|
||||
{
|
||||
amount: amountBlockchainParam,
|
||||
asset: targetChainAssetAddress,
|
||||
@@ -294,14 +294,14 @@ export default async function swap({
|
||||
secret
|
||||
)
|
||||
|
||||
let targetChainGasPrice: ethers.BigNumber;
|
||||
let targetChainGasPrice: bigint;
|
||||
const targetChainFeeData = await targetChainUnit.provider.getFeeData();
|
||||
if (ethers.BigNumber.isBigNumber(targetChainFeeData.gasPrice)) { //
|
||||
if (targetChainFeeData.gasPrice !== null) { //
|
||||
unsignedRedeemAtomicTx.gasPrice = targetChainFeeData.gasPrice;
|
||||
targetChainGasPrice = targetChainFeeData.gasPrice;
|
||||
} else if (
|
||||
ethers.BigNumber.isBigNumber(targetChainFeeData.maxFeePerGas) &&
|
||||
ethers.BigNumber.isBigNumber(targetChainFeeData.maxPriorityFeePerGas)
|
||||
targetChainFeeData.maxFeePerGas !== null &&
|
||||
targetChainFeeData.maxPriorityFeePerGas !== null
|
||||
) { // EIP-1559
|
||||
unsignedRedeemAtomicTx.maxFeePerGas = targetChainFeeData.maxFeePerGas;
|
||||
unsignedRedeemAtomicTx.maxPriorityFeePerGas = targetChainFeeData.maxPriorityFeePerGas;
|
||||
@@ -310,18 +310,18 @@ export default async function swap({
|
||||
throw new Error('Can\'t get gas price');
|
||||
}
|
||||
|
||||
unsignedRedeemAtomicTx.chainId = parseInt(targetChain, 10);
|
||||
unsignedRedeemAtomicTx.chainId = BigInt(parseInt(targetChain, 10));
|
||||
unsignedRedeemAtomicTx.from = walletAddress;
|
||||
unsignedRedeemAtomicTx.gasLimit = ethers.BigNumber.from(REDEEMATOMIC_GAS_LIMIT);
|
||||
unsignedRedeemAtomicTx.gasLimit = BigInt(REDEEMATOMIC_GAS_LIMIT);
|
||||
|
||||
const redeemAtomicTransactionCost = ethers.BigNumber.from(REDEEMATOMIC_GAS_LIMIT).mul(targetChainGasPrice);
|
||||
const targetDenormalizedTransactionCost = denormalizeNumber(redeemAtomicTransactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const redeemAtomicTransactionCost = BigInt(REDEEMATOMIC_GAS_LIMIT) * targetChainGasPrice;
|
||||
const targetDenormalizedTransactionCost = denormalizeNumber(redeemAtomicTransactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
targetChainBalanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: targetChainNativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: targetDenormalizedTransactionCost.toString(),
|
||||
sources: ['wallet']
|
||||
@@ -334,7 +334,7 @@ export default async function swap({
|
||||
options?.logger?.('Signing redeem tx transaction...');
|
||||
|
||||
const targetSignedTransaction = await signer.signTransaction(unsignedRedeemAtomicTx);
|
||||
const targetLockAtomicTxResponse = await targetChainUnit.provider.sendTransaction(targetSignedTransaction);
|
||||
const targetLockAtomicTxResponse = await targetChainUnit.provider.broadcastTransaction(targetSignedTransaction);
|
||||
options?.logger?.(`Redeem tx sent. Tx hash: ${targetLockAtomicTxResponse.hash}. Waiting for tx to be mined...`);
|
||||
|
||||
await targetLockAtomicTxResponse.wait();
|
||||
@@ -343,16 +343,16 @@ export default async function swap({
|
||||
|
||||
if (options?.withdrawToWallet !== undefined && options.withdrawToWallet) {
|
||||
options.logger?.('Withdrawing to wallet...');
|
||||
const unsignedWithdrawTx = await targetExchangeContract.populateTransaction.withdraw(
|
||||
const unsignedWithdrawTx = await targetExchangeContract.withdraw.populateTransaction(
|
||||
targetChainAssetAddress,
|
||||
amountBlockchainParam,
|
||||
);
|
||||
if (ethers.BigNumber.isBigNumber(targetChainFeeData.gasPrice)) { //
|
||||
if (targetChainFeeData.gasPrice !== null) { //
|
||||
unsignedWithdrawTx.gasPrice = targetChainFeeData.gasPrice;
|
||||
targetChainGasPrice = targetChainFeeData.gasPrice;
|
||||
} else if (
|
||||
ethers.BigNumber.isBigNumber(targetChainFeeData.maxFeePerGas) &&
|
||||
ethers.BigNumber.isBigNumber(targetChainFeeData.maxPriorityFeePerGas)
|
||||
targetChainFeeData.maxFeePerGas !== null &&
|
||||
targetChainFeeData.maxPriorityFeePerGas !== null
|
||||
) { // EIP-1559
|
||||
unsignedWithdrawTx.maxFeePerGas = targetChainFeeData.maxFeePerGas;
|
||||
unsignedWithdrawTx.maxPriorityFeePerGas = targetChainFeeData.maxPriorityFeePerGas;
|
||||
@@ -360,12 +360,12 @@ export default async function swap({
|
||||
} else {
|
||||
throw new Error('Can\'t get gas price');
|
||||
}
|
||||
unsignedWithdrawTx.chainId = parseInt(targetChain, 10);
|
||||
unsignedWithdrawTx.gasLimit = ethers.BigNumber.from(WITHDRAW_GAS_LIMIT);
|
||||
unsignedWithdrawTx.chainId = BigInt(parseInt(targetChain, 10));
|
||||
unsignedWithdrawTx.gasLimit = BigInt(WITHDRAW_GAS_LIMIT);
|
||||
unsignedWithdrawTx.from = walletAddress;
|
||||
unsignedWithdrawTx.nonce = await targetProvider.getTransactionCount(walletAddress, 'pending');
|
||||
const signedTx = await signer.signTransaction(unsignedWithdrawTx);
|
||||
const withdrawTx = await targetProvider.sendTransaction(signedTx);
|
||||
const withdrawTx = await targetProvider.broadcastTransaction(signedTx);
|
||||
options.logger?.(`Withdraw tx sent. Tx hash: ${withdrawTx.hash}. Waiting for tx to be mined...`);
|
||||
await withdrawTx.wait();
|
||||
options.logger?.('Withdraw tx mined.');
|
||||
|
||||
@@ -58,6 +58,9 @@ export default class Orion {
|
||||
priceFeed: {
|
||||
api: networkConfig.api + networkConfig.services.priceFeed.all,
|
||||
},
|
||||
indexer: {
|
||||
api: networkConfig.api + networkConfig.services.indexer?.http,
|
||||
}
|
||||
},
|
||||
};
|
||||
})
|
||||
|
||||
49
src/Unit/Exchange/callGenerators/curve.ts
Normal file
49
src/Unit/Exchange/callGenerators/curve.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
SwapExecutor__factory,
|
||||
CurveRegistry__factory,
|
||||
ERC20__factory,
|
||||
} from "@orionprotocol/contracts/lib/ethers-v6/index.js";
|
||||
import { MaxUint256, type BigNumberish, type JsonRpcProvider } from "ethers";
|
||||
import { addCallParams, pathCallWithBalance } from "./utils.js";
|
||||
import type { SingleSwap } from "../../../types.js";
|
||||
import { generateApproveCall } from "./erc20.js";
|
||||
import type { BytesLike } from "ethers";
|
||||
|
||||
export async function generateCurveStableSwapCall(
|
||||
amount: BigNumberish,
|
||||
to: string,
|
||||
swap: SingleSwap,
|
||||
provider: JsonRpcProvider,
|
||||
swapExecutorContractAddress: string,
|
||||
curveRegistry: string,
|
||||
pathWithBalance = false
|
||||
) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface();
|
||||
const registry = CurveRegistry__factory.connect(curveRegistry, provider);
|
||||
const { pool, assetIn, assetOut } = swap;
|
||||
const firstToken = ERC20__factory.connect(assetIn, provider)
|
||||
const executorAllowance = await firstToken.allowance(swapExecutorContractAddress, pool)
|
||||
|
||||
const calls: BytesLike[] = []
|
||||
if (executorAllowance <= BigInt(amount)) {
|
||||
const approveCall = await generateApproveCall(
|
||||
assetIn,
|
||||
pool,
|
||||
MaxUint256
|
||||
);
|
||||
calls.push(approveCall);
|
||||
}
|
||||
|
||||
const [i, j] = await registry.get_coin_indices(pool, assetIn, assetOut);
|
||||
let calldata = executorInterface.encodeFunctionData(
|
||||
"curveSwapStableAmountIn",
|
||||
[pool, assetOut, i, j, to, amount]
|
||||
);
|
||||
calldata = addCallParams(calldata)
|
||||
if (pathWithBalance) {
|
||||
calldata = pathCallWithBalance(calldata, swap.assetIn)
|
||||
}
|
||||
calls.push(calldata)
|
||||
|
||||
return calls
|
||||
}
|
||||
36
src/Unit/Exchange/callGenerators/erc20.ts
Normal file
36
src/Unit/Exchange/callGenerators/erc20.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import type { BigNumberish, AddressLike } from "ethers"
|
||||
import { type CallParams, addCallParams } from "./utils.js"
|
||||
|
||||
export function generateTransferCall(
|
||||
token: AddressLike,
|
||||
target: AddressLike,
|
||||
amount: BigNumberish,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('safeTransfer', [
|
||||
token,
|
||||
target,
|
||||
amount
|
||||
])
|
||||
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
|
||||
export function generateApproveCall(
|
||||
token: AddressLike,
|
||||
target: AddressLike,
|
||||
amount: BigNumberish,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('safeApprove', [
|
||||
token,
|
||||
target,
|
||||
amount
|
||||
])
|
||||
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
21
src/Unit/Exchange/callGenerators/feePayment.ts
Normal file
21
src/Unit/Exchange/callGenerators/feePayment.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import type { BigNumberish, AddressLike } from "ethers"
|
||||
import { type CallParams, addCallParams } from "./utils.js"
|
||||
|
||||
|
||||
export function generateFeePaymentCall(
|
||||
matcher: AddressLike,
|
||||
token: AddressLike,
|
||||
amount: BigNumberish,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('payFeeToMatcher', [
|
||||
matcher,
|
||||
token,
|
||||
amount
|
||||
])
|
||||
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
54
src/Unit/Exchange/callGenerators/uniswapV2.ts
Normal file
54
src/Unit/Exchange/callGenerators/uniswapV2.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import { SafeArray } from "../../../utils/safeGetters.js"
|
||||
import { type BytesLike, type BigNumberish, concat, ethers, toBeHex } from "ethers"
|
||||
import { addCallParams } from "./utils.js"
|
||||
import type { SingleSwap } from "../../../types.js"
|
||||
|
||||
export async function generateUni2Calls(
|
||||
path: SafeArray<SingleSwap>,
|
||||
recipient: string
|
||||
) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calls: BytesLike[] = []
|
||||
if (path.length > 1) {
|
||||
for (let i = 0; i < path.length - 1; ++i) {
|
||||
const currentSwap = path.get(i)
|
||||
const nextSwap = path.get(i + 1)
|
||||
|
||||
const call = await generateUni2Call(
|
||||
currentSwap.pool,
|
||||
currentSwap.assetIn,
|
||||
currentSwap.assetOut,
|
||||
nextSwap.pool
|
||||
)
|
||||
calls.push(call)
|
||||
}
|
||||
}
|
||||
const lastSwap = path.last();
|
||||
const calldata = executorInterface.encodeFunctionData('swapUniV2', [
|
||||
lastSwap.pool,
|
||||
lastSwap.assetIn,
|
||||
lastSwap.assetOut,
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat(['0x03', recipient])]),
|
||||
])
|
||||
calls.push(addCallParams(calldata))
|
||||
|
||||
return calls
|
||||
}
|
||||
|
||||
export function generateUni2Call(
|
||||
pool: string,
|
||||
assetIn: string,
|
||||
assetOut: string,
|
||||
recipient: string,
|
||||
fee: BigNumberish = 3,
|
||||
) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('swapUniV2', [
|
||||
pool,
|
||||
assetIn,
|
||||
assetOut,
|
||||
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(fee), recipient])]),
|
||||
])
|
||||
return addCallParams(calldata)
|
||||
}
|
||||
92
src/Unit/Exchange/callGenerators/uniswapV3.ts
Normal file
92
src/Unit/Exchange/callGenerators/uniswapV3.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { SwapExecutor__factory, UniswapV3Pool__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import { type BigNumberish , ethers, JsonRpcProvider } from "ethers"
|
||||
import { SafeArray } from "../../../utils/safeGetters.js"
|
||||
import { addCallParams } from "./utils.js"
|
||||
import type { SingleSwap } from "../../../types.js"
|
||||
|
||||
export async function generateUni3Call(
|
||||
swap: SingleSwap,
|
||||
amount: BigNumberish | undefined,
|
||||
recipient: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
if (typeof amount === 'undefined') amount = 0
|
||||
|
||||
const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider)
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
let calldata = executorInterface.encodeFunctionData('uniswapV3SingleSwapTo', [encodedPool, recipient, amount])
|
||||
|
||||
return addCallParams(calldata)
|
||||
}
|
||||
|
||||
export async function generateOrion3Call(
|
||||
swap: SingleSwap,
|
||||
amount: BigNumberish | undefined,
|
||||
recipient: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
if (amount === undefined) amount = 0
|
||||
|
||||
const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider)
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('orionV3SingleSwapTo', [encodedPool, recipient, amount])
|
||||
|
||||
return addCallParams(calldata)
|
||||
}
|
||||
|
||||
export async function generateUni3Calls(
|
||||
path: SafeArray<SingleSwap>,
|
||||
amount: BigNumberish,
|
||||
recipient: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
const encodedPools: BigNumberish[] = []
|
||||
for (const swap of path) {
|
||||
const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider)
|
||||
encodedPools.push(encodedPool)
|
||||
}
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
let calldata = executorInterface.encodeFunctionData('uniswapV3SwapTo', [encodedPools, recipient, amount])
|
||||
calldata = addCallParams(calldata)
|
||||
|
||||
return [calldata]
|
||||
}
|
||||
|
||||
export async function generateOrion3Calls(
|
||||
path: SafeArray<SingleSwap>,
|
||||
amount: BigNumberish,
|
||||
recipient: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
const encodedPools: BigNumberish[] = []
|
||||
for (const swap of path) {
|
||||
const encodedPool = await encodePoolV3(swap.pool, swap.assetIn, swap.assetOut, provider)
|
||||
encodedPools.push(encodedPool)
|
||||
}
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
let calldata = executorInterface.encodeFunctionData('orionV3SwapTo', [encodedPools, recipient, amount])
|
||||
calldata = addCallParams(calldata)
|
||||
|
||||
return [calldata]
|
||||
}
|
||||
|
||||
export async function encodePoolV3(
|
||||
poolAddress: string,
|
||||
assetInAddress: string,
|
||||
assetOutAddress: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
const pool = UniswapV3Pool__factory.connect(poolAddress, provider)
|
||||
const token0 = await pool.token0()
|
||||
const zeroForOne = token0.toLowerCase() === assetInAddress.toLowerCase()
|
||||
const unwrapWETH = assetOutAddress === ethers.ZeroAddress
|
||||
|
||||
let encodedPool = ethers.solidityPacked(['uint256'], [await pool.getAddress()])
|
||||
encodedPool = ethers.dataSlice(encodedPool, 1)
|
||||
let firstByte = 0
|
||||
if (unwrapWETH) firstByte += 32
|
||||
if (!zeroForOne) firstByte += 128
|
||||
const encodedFirstByte = ethers.solidityPacked(['uint8'], [firstByte])
|
||||
encodedPool = ethers.hexlify(ethers.concat([encodedFirstByte, encodedPool]))
|
||||
return encodedPool
|
||||
}
|
||||
103
src/Unit/Exchange/callGenerators/utils.ts
Normal file
103
src/Unit/Exchange/callGenerators/utils.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { ERC20__factory, SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import type { AddressLike } from "ethers"
|
||||
import { type BytesLike, ethers, type BigNumberish } from "ethers"
|
||||
|
||||
const EXECUTOR_SWAP_FUNCTION = 'func_70LYiww'
|
||||
|
||||
export type CallParams = {
|
||||
isMandatory?: boolean,
|
||||
target?: string,
|
||||
gaslimit?: BigNumberish,
|
||||
value?: BigNumberish
|
||||
}
|
||||
|
||||
export type PatchParams = {
|
||||
skipOnZeroAmount?: boolean,
|
||||
skipCallDataPatching?: boolean,
|
||||
skipValuePatching?: boolean
|
||||
}
|
||||
|
||||
export function pathCallWithBalance(
|
||||
calldata: BytesLike,
|
||||
tokenAddress: AddressLike,
|
||||
patchParams: PatchParams = { skipCallDataPatching: false, skipValuePatching: true }
|
||||
) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const skipMaskAndOffset = createPatchMask(calldata, patchParams)
|
||||
calldata = executorInterface.encodeFunctionData("patchCallWithTokenBalance", [
|
||||
calldata,
|
||||
skipMaskAndOffset,
|
||||
tokenAddress,
|
||||
ethers.MaxUint256])
|
||||
return addCallParams(calldata)
|
||||
}
|
||||
|
||||
export function addCallParams(
|
||||
calldata: BytesLike,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
let firstByte = 0
|
||||
if (callParams) {
|
||||
if (callParams.value !== undefined) {
|
||||
firstByte += 16 // 00010000
|
||||
const encodedValue = ethers.solidityPacked(['uint128'], [callParams.value])
|
||||
calldata = ethers.hexlify(ethers.concat([encodedValue, calldata]))
|
||||
}
|
||||
if (callParams.target !== undefined) {
|
||||
firstByte += 32 // 00100000
|
||||
const encodedAddress = ethers.solidityPacked(['address'], [callParams.target])
|
||||
calldata = ethers.hexlify(ethers.concat([encodedAddress, calldata]))
|
||||
}
|
||||
if (callParams.gaslimit !== undefined) {
|
||||
firstByte += 64 // 01000000
|
||||
const encodedGaslimit = ethers.solidityPacked(['uint32'], [callParams.gaslimit])
|
||||
calldata = ethers.hexlify(ethers.concat([encodedGaslimit, calldata]))
|
||||
}
|
||||
if (callParams.isMandatory !== undefined) firstByte += 128 // 10000000
|
||||
}
|
||||
|
||||
const encodedFirstByte = ethers.solidityPacked(['uint8'], [firstByte])
|
||||
calldata = ethers.hexlify(ethers.concat([encodedFirstByte, calldata]))
|
||||
return calldata
|
||||
}
|
||||
|
||||
export function createPatchMask(calldata: BytesLike, patchParams?: PatchParams) {
|
||||
let firstByte = 0
|
||||
let mask = ethers.solidityPacked(["uint256"], [(calldata.length - 4) / 2 - 32]) //finding offset of last 32 bytes slot in calldata
|
||||
mask = ethers.dataSlice(mask, 1)
|
||||
if (patchParams) {
|
||||
if (patchParams.skipOnZeroAmount !== undefined && patchParams.skipOnZeroAmount === false) {
|
||||
firstByte += 32
|
||||
}
|
||||
if (patchParams.skipCallDataPatching !== undefined && patchParams.skipCallDataPatching) {
|
||||
firstByte += 64
|
||||
}
|
||||
if (patchParams.skipValuePatching !== undefined && patchParams.skipValuePatching) {
|
||||
firstByte += 128
|
||||
}
|
||||
}
|
||||
const encodedFirstByte = ethers.solidityPacked(["uint8"], [firstByte])
|
||||
mask = ethers.hexlify(ethers.concat([encodedFirstByte, mask]))
|
||||
return mask
|
||||
}
|
||||
|
||||
export function generateCalls(calls: BytesLike[]) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
return '0x' + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.ZeroAddress, calls]).slice(74)
|
||||
}
|
||||
|
||||
export async function exchangeToNativeDecimals(token: AddressLike, amount: BigNumberish, provider: ethers.JsonRpcProvider) {
|
||||
return await toNativeDecimals(token, amount, provider) / (BigInt(10) ** 8n)
|
||||
}
|
||||
|
||||
export async function toNativeDecimals(token: AddressLike, amount: BigNumberish, provider: ethers.JsonRpcProvider) {
|
||||
token = await token
|
||||
if (typeof token !== "string") token = await token.getAddress()
|
||||
|
||||
let decimals = 18n
|
||||
if (token !== ethers.ZeroAddress) {
|
||||
const contract = ERC20__factory.connect(token, provider)
|
||||
decimals = BigInt(await contract.decimals())
|
||||
}
|
||||
return BigInt(amount) * (BigInt(10) ** decimals)
|
||||
}
|
||||
32
src/Unit/Exchange/callGenerators/weth.ts
Normal file
32
src/Unit/Exchange/callGenerators/weth.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { SwapExecutor__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
|
||||
import type { BigNumberish } from "ethers"
|
||||
import { type CallParams, addCallParams } from "./utils.js"
|
||||
import type { AddressLike } from "ethers"
|
||||
|
||||
export function generateWrapAndTransferCall(
|
||||
target: AddressLike,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('wrapAndTransfer', [
|
||||
target,
|
||||
])
|
||||
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
|
||||
export function generateUnwrapAndTransferCall(
|
||||
target: AddressLike,
|
||||
amount: BigNumberish,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calldata = executorInterface.encodeFunctionData('unwrapAndTransfer', [
|
||||
target,
|
||||
amount
|
||||
])
|
||||
|
||||
return addCallParams(calldata, callParams)
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import type Unit from '../index.js';
|
||||
@@ -51,7 +51,7 @@ export default async function deposit({
|
||||
const balances = await getBalances(
|
||||
{
|
||||
[asset]: assetAddress,
|
||||
[nativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
[nativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
aggregator,
|
||||
walletAddress,
|
||||
@@ -63,7 +63,7 @@ export default async function deposit({
|
||||
balances,
|
||||
{
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
provider,
|
||||
signer,
|
||||
@@ -80,34 +80,34 @@ export default async function deposit({
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
let unsignedTx: ethers.PopulatedTransaction;
|
||||
let unsignedTx: ethers.TransactionLike;
|
||||
if (asset === nativeCryptocurrency) {
|
||||
unsignedTx = await exchangeContract.populateTransaction.deposit();
|
||||
unsignedTx = await exchangeContract.deposit.populateTransaction();
|
||||
unsignedTx.value = normalizeNumber(amount, NATIVE_CURRENCY_PRECISION, BigNumber.ROUND_CEIL);
|
||||
unsignedTx.gasLimit = ethers.BigNumber.from(DEPOSIT_ETH_GAS_LIMIT);
|
||||
unsignedTx.gasLimit = BigInt(DEPOSIT_ETH_GAS_LIMIT);
|
||||
} else {
|
||||
unsignedTx = await exchangeContract.populateTransaction.depositAsset(
|
||||
unsignedTx = await exchangeContract.depositAsset.populateTransaction(
|
||||
assetAddress,
|
||||
normalizeNumber(amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL),
|
||||
);
|
||||
unsignedTx.gasLimit = ethers.BigNumber.from(DEPOSIT_ERC20_GAS_LIMIT);
|
||||
unsignedTx.gasLimit = BigInt(DEPOSIT_ERC20_GAS_LIMIT);
|
||||
}
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(unsignedTx.gasLimit).mul(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const transactionCost = BigInt(unsignedTx.gasLimit) * BigInt(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
unsignedTx.chainId = parseInt(chainId, 10);
|
||||
unsignedTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
|
||||
unsignedTx.gasPrice = BigInt(gasPriceWei);
|
||||
unsignedTx.from = walletAddress;
|
||||
|
||||
await balanceGuard.check(true);
|
||||
@@ -117,10 +117,10 @@ export default async function deposit({
|
||||
|
||||
const signedTx = await signer.signTransaction(unsignedTx);
|
||||
try {
|
||||
const txResponse = await provider.sendTransaction(signedTx);
|
||||
const txResponse = await provider.broadcastTransaction(signedTx);
|
||||
console.log(`Deposit tx sent: ${txResponse.hash}. Waiting for confirmation...`);
|
||||
const txReceipt = await txResponse.wait();
|
||||
if (txReceipt.status !== undefined) {
|
||||
if (txReceipt?.status !== undefined) {
|
||||
console.log('Deposit tx confirmed');
|
||||
} else {
|
||||
console.log('Deposit tx failed');
|
||||
|
||||
@@ -1,315 +1,412 @@
|
||||
import type { ExchangeWithGenericSwap } from '@orionprotocol/contracts/lib/ethers-v5/Exchange.js';
|
||||
import { UniswapV3Pool__factory, ERC20__factory, SwapExecutor__factory, CurveRegistry__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { BigNumber, ethers, type BigNumberish } from 'ethers';
|
||||
import { concat, defaultAbiCoder, type BytesLike } from 'ethers/lib/utils.js';
|
||||
import { safeGet, SafeArray } from '../../utils/safeGetters.js';
|
||||
import type Unit from '../index.js';
|
||||
import { simpleFetch } from 'simple-typed-fetch';
|
||||
import type { PromiseOrValue } from '@orionprotocol/contracts/lib/ethers-v5/common.js';
|
||||
import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js";
|
||||
import { ethers, ZeroAddress } from "ethers";
|
||||
import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from "ethers";
|
||||
import cloneDeep from "lodash.clonedeep";
|
||||
import { safeGet, SafeArray } from "../../utils/safeGetters.js";
|
||||
import { simpleFetch } from "simple-typed-fetch";
|
||||
import type Unit from "../index.js";
|
||||
import { generateUni2Calls, generateUni2Call } from "./callGenerators/uniswapV2.js";
|
||||
import {
|
||||
generateUni3Calls,
|
||||
generateOrion3Calls,
|
||||
generateUni3Call,
|
||||
generateOrion3Call,
|
||||
} from "./callGenerators/uniswapV3.js";
|
||||
import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from "./callGenerators/utils.js";
|
||||
import { generateTransferCall } from "./callGenerators/erc20.js";
|
||||
import { generateCurveStableSwapCall } from "./callGenerators/curve.js";
|
||||
import type { SingleSwap } from "../../types.js";
|
||||
import { addressLikeToString } from "../../utils/addressLikeToString.js";
|
||||
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js";
|
||||
import { getExchangeAllowance, getTotalBalance } from "../../utils/getBalance.js";
|
||||
import { generateFeePaymentCall } from "./callGenerators/feePayment.js";
|
||||
|
||||
const EXECUTOR_SWAP_FUNCTION = "func_70LYiww"
|
||||
export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3";
|
||||
|
||||
export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3"
|
||||
|
||||
const exchangeToType: Partial<Record<string, Factory>> = {
|
||||
'SPOOKYSWAP': 'UniswapV2',
|
||||
'PANCAKESWAP': 'UniswapV2',
|
||||
'UNISWAP': 'UniswapV2',
|
||||
'QUICKSWAP': 'UniswapV2',
|
||||
'ORION_POOL': 'UniswapV2',
|
||||
'CHERRYSWAP': 'UniswapV2',
|
||||
'OKXSWAP': 'UniswapV2',
|
||||
'INTERNAL_POOL_V2': 'UniswapV2',
|
||||
'INTERNAL_POOL_V3': "OrionV3",
|
||||
'INTERNAL_POOL_V3_0_01': "OrionV3",
|
||||
'INTERNAL_POOL_V3_0_05': "OrionV3",
|
||||
'INTERNAL_POOL_V3_0_3': "OrionV3",
|
||||
'INTERNAL_POOL_V3_1_0': "OrionV3",
|
||||
'CURVE': "Curve",
|
||||
'CURVE_FACTORY': "Curve",
|
||||
type BaseGenerateSwapCalldataParams = {
|
||||
amount: BigNumberish;
|
||||
minReturnAmount: BigNumberish;
|
||||
initiatorAddress: string;
|
||||
receiverAddress: string;
|
||||
path: ArrayLike<SingleSwap>;
|
||||
matcher?: AddressLike,
|
||||
feeToken?: AddressLike,
|
||||
fee?: BigNumberish;
|
||||
}
|
||||
|
||||
export type SwapInfo = {
|
||||
pool: string,
|
||||
assetIn: string,
|
||||
assetOut: string,
|
||||
factory: string
|
||||
}
|
||||
export type GenerateSwapCalldataWithUnitParams = BaseGenerateSwapCalldataParams & {
|
||||
unit: Unit;
|
||||
};
|
||||
|
||||
export type CallParams = {
|
||||
isMandatory?: boolean,
|
||||
target?: string,
|
||||
gaslimit?: BigNumber,
|
||||
value?: BigNumber
|
||||
}
|
||||
export type GenerateSwapCalldataParams = BaseGenerateSwapCalldataParams & {
|
||||
exchangeContractAddress: AddressLike;
|
||||
wethAddress: AddressLike;
|
||||
curveRegistryAddress: AddressLike;
|
||||
swapExecutorContractAddress: AddressLike;
|
||||
provider: JsonRpcProvider;
|
||||
};
|
||||
|
||||
export type GenerateSwapCalldataParams = {
|
||||
amount: BigNumberish,
|
||||
minReturnAmount: BigNumberish,
|
||||
receiverAddress: string,
|
||||
path: ArrayLike<SwapInfo>,
|
||||
unit: Unit
|
||||
}
|
||||
|
||||
export default async function generateSwapCalldata({
|
||||
export async function generateSwapCalldataWithUnit({
|
||||
amount,
|
||||
minReturnAmount,
|
||||
initiatorAddress,
|
||||
receiverAddress,
|
||||
path: path_,
|
||||
unit
|
||||
}: GenerateSwapCalldataParams
|
||||
): Promise<{ calldata: string, swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct }> {
|
||||
if (path_ == undefined || path_.length == 0) {
|
||||
throw new Error(`Empty path`);
|
||||
}
|
||||
const wethAddress = safeGet(unit.contracts, "WETH")
|
||||
const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry")
|
||||
const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(unit.blockchainService.getInfo)();
|
||||
let path = SafeArray.from(path_).map((swapInfo) => {
|
||||
swapInfo.assetIn = safeGet(assetToAddress, swapInfo.assetIn);
|
||||
swapInfo.assetOut = safeGet(assetToAddress, swapInfo.assetOut);
|
||||
return swapInfo;
|
||||
})
|
||||
const factory = path.first().factory
|
||||
if (!path.every(swapInfo => swapInfo.factory === factory)) {
|
||||
throw new Error(`Supporting only swaps with single factory`);
|
||||
}
|
||||
|
||||
const swapDescription: ExchangeWithGenericSwap.SwapDescriptionStruct = {
|
||||
srcToken: path.first().assetIn,
|
||||
dstToken: path.last().assetOut,
|
||||
srcReceiver: swapExecutorContractAddress ?? '',
|
||||
dstReceiver: receiverAddress,
|
||||
amount: amount,
|
||||
minReturnAmount: minReturnAmount,
|
||||
flags: 0
|
||||
path: arrayLikePath,
|
||||
matcher = ZeroAddress,
|
||||
feeToken = ZeroAddress,
|
||||
fee = 0,
|
||||
unit,
|
||||
}: GenerateSwapCalldataWithUnitParams): Promise<{
|
||||
calldata: string;
|
||||
swapDescription: LibValidator.SwapDescriptionStruct;
|
||||
value: bigint;
|
||||
}> {
|
||||
if (arrayLikePath == undefined || arrayLikePath.length == 0) {
|
||||
throw new Error("Empty path");
|
||||
}
|
||||
const wethAddress = safeGet(unit.contracts, "WETH");
|
||||
const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry");
|
||||
const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(
|
||||
unit.blockchainService.getInfo
|
||||
)();
|
||||
|
||||
const exchangeToNativeDecimals = async (token: PromiseOrValue<string>) => {
|
||||
token = await token
|
||||
let decimals = 18
|
||||
if (token !== ethers.constants.AddressZero) {
|
||||
const contract = ERC20__factory.connect(token, unit.provider)
|
||||
decimals = await contract.decimals()
|
||||
}
|
||||
return BigNumber.from(amount).mul(BigNumber.from(10).pow(decimals)).div(BigNumber.from(10).pow(8))
|
||||
}
|
||||
const amountNativeDecimals = await exchangeToNativeDecimals(swapDescription.srcToken);
|
||||
const arrayLikePathCopy = cloneDeep(arrayLikePath);
|
||||
let path = SafeArray.from(arrayLikePathCopy);
|
||||
|
||||
path = SafeArray.from(path_).map((swapInfo) => {
|
||||
if (swapInfo.assetIn == ethers.constants.AddressZero) swapInfo.assetIn = wethAddress
|
||||
if (swapInfo.assetOut == ethers.constants.AddressZero) swapInfo.assetOut = wethAddress
|
||||
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => {
|
||||
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn
|
||||
swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut
|
||||
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
|
||||
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
|
||||
return swapInfo;
|
||||
});
|
||||
|
||||
|
||||
let calldata: string
|
||||
switch (exchangeToType[factory]) {
|
||||
return await generateSwapCalldata({
|
||||
amount,
|
||||
minReturnAmount,
|
||||
receiverAddress,
|
||||
initiatorAddress,
|
||||
path,
|
||||
matcher,
|
||||
feeToken,
|
||||
fee,
|
||||
exchangeContractAddress,
|
||||
wethAddress,
|
||||
curveRegistryAddress,
|
||||
swapExecutorContractAddress,
|
||||
provider: unit.provider,
|
||||
});
|
||||
}
|
||||
|
||||
export async function generateSwapCalldata({
|
||||
amount,
|
||||
minReturnAmount,
|
||||
initiatorAddress,
|
||||
receiverAddress,
|
||||
path: arrayLikePath,
|
||||
matcher: matcherAddressLike = ZeroAddress,
|
||||
feeToken: feeTokenAddressLike = ZeroAddress,
|
||||
fee = 0,
|
||||
exchangeContractAddress,
|
||||
wethAddress: wethAddressLike,
|
||||
curveRegistryAddress: curveRegistryAddressLike,
|
||||
swapExecutorContractAddress: swapExecutorContractAddressLike,
|
||||
provider,
|
||||
}: GenerateSwapCalldataParams): Promise<{
|
||||
calldata: string;
|
||||
swapDescription: LibValidator.SwapDescriptionStruct;
|
||||
value: bigint;
|
||||
}> {
|
||||
const wethAddress = await addressLikeToString(wethAddressLike);
|
||||
const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike);
|
||||
const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike);
|
||||
const feeToken = await addressLikeToString(feeTokenAddressLike);
|
||||
const matcher = await addressLikeToString(matcherAddressLike);
|
||||
let path = SafeArray.from(arrayLikePath).map((swapInfo) => {
|
||||
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
|
||||
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
|
||||
return swapInfo;
|
||||
});
|
||||
|
||||
const { assetIn: srcToken } = path.first();
|
||||
const { assetOut: dstToken } = path.last();
|
||||
|
||||
let swapDescription: LibValidator.SwapDescriptionStruct = {
|
||||
srcToken,
|
||||
dstToken,
|
||||
srcReceiver: swapExecutorContractAddress,
|
||||
dstReceiver: receiverAddress,
|
||||
amount,
|
||||
minReturnAmount,
|
||||
flags: 0,
|
||||
};
|
||||
const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, provider);
|
||||
const feeNativeDecimals = await exchangeToNativeDecimals(feeToken, fee, provider)
|
||||
|
||||
path = SafeArray.from(arrayLikePath).map((singleSwap) => {
|
||||
if (singleSwap.assetIn == ethers.ZeroAddress) singleSwap.assetIn = wethAddress;
|
||||
if (singleSwap.assetOut == ethers.ZeroAddress) singleSwap.assetOut = wethAddress;
|
||||
return singleSwap;
|
||||
});
|
||||
|
||||
let calls: BytesLike[];
|
||||
({ swapDescription, calls } = await processSwaps(
|
||||
swapDescription,
|
||||
path,
|
||||
amountNativeDecimals,
|
||||
matcher,
|
||||
feeToken,
|
||||
feeNativeDecimals,
|
||||
wethAddress,
|
||||
swapExecutorContractAddress,
|
||||
curveRegistryAddress,
|
||||
provider
|
||||
));
|
||||
const calldata = generateCalls(calls);
|
||||
|
||||
const { useExchangeBalance, additionalTransferAmount } = await shouldUseExchangeBalance(
|
||||
srcToken,
|
||||
initiatorAddress,
|
||||
exchangeContractAddress,
|
||||
amountNativeDecimals,
|
||||
provider
|
||||
);
|
||||
if (useExchangeBalance) {
|
||||
swapDescription.flags = 1n << 255n;
|
||||
}
|
||||
const value = srcToken == ZeroAddress ? additionalTransferAmount : 0n;
|
||||
return { swapDescription, calldata, value };
|
||||
}
|
||||
|
||||
async function processSwaps(
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
path: SafeArray<SingleSwap>,
|
||||
amount: BigNumberish,
|
||||
matcher: string,
|
||||
feeToken: string,
|
||||
fee: BigNumberish,
|
||||
wethAddress: string,
|
||||
swapExecutorContractAddress: string,
|
||||
curveRegistryAddress: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
const { factory: firstSwapFactory } = path.first();
|
||||
const isSingleFactorySwap = path.every((singleSwap) => singleSwap.factory === firstSwapFactory);
|
||||
let calls: BytesLike[];
|
||||
if (isSingleFactorySwap) {
|
||||
({ swapDescription, calls } = await processSingleFactorySwaps(
|
||||
firstSwapFactory,
|
||||
swapDescription,
|
||||
path,
|
||||
amount,
|
||||
swapExecutorContractAddress,
|
||||
curveRegistryAddress,
|
||||
provider
|
||||
));
|
||||
} else {
|
||||
({ swapDescription, calls } = await processMultiFactorySwaps(
|
||||
swapDescription,
|
||||
path,
|
||||
amount,
|
||||
swapExecutorContractAddress,
|
||||
curveRegistryAddress,
|
||||
provider
|
||||
));
|
||||
}
|
||||
|
||||
({swapDescription, calls} = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription));
|
||||
|
||||
({ swapDescription, calls } = wrapOrUnwrapIfNeeded(
|
||||
amount,
|
||||
swapDescription,
|
||||
calls,
|
||||
swapExecutorContractAddress,
|
||||
wethAddress
|
||||
));
|
||||
|
||||
return { swapDescription, calls };
|
||||
}
|
||||
|
||||
async function processSingleFactorySwaps(
|
||||
factory: Factory,
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
path: SafeArray<SingleSwap>,
|
||||
amount: BigNumberish,
|
||||
swapExecutorContractAddress: string,
|
||||
curveRegistryAddress: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
let calls: BytesLike[] = [];
|
||||
switch (factory) {
|
||||
case "OrionV2": {
|
||||
swapDescription.srcReceiver = path.first().pool
|
||||
calldata = await generateUni2Calls(exchangeContractAddress, path);
|
||||
swapDescription.srcReceiver = path.first().pool;
|
||||
calls = await generateUni2Calls(path, swapExecutorContractAddress);
|
||||
break;
|
||||
}
|
||||
case "UniswapV2": {
|
||||
swapDescription.srcReceiver = path.first().pool
|
||||
calldata = await generateUni2Calls(exchangeContractAddress, path);
|
||||
swapDescription.srcReceiver = path.first().pool;
|
||||
calls = await generateUni2Calls(path, swapExecutorContractAddress);
|
||||
break;
|
||||
}
|
||||
case "UniswapV3": {
|
||||
calldata = await generateUni3Calls(amountNativeDecimals, exchangeContractAddress, path, unit.provider)
|
||||
calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider);
|
||||
break;
|
||||
}
|
||||
case "OrionV3": {
|
||||
calldata = await generateOrion3Calls(amountNativeDecimals, exchangeContractAddress, path, unit.provider)
|
||||
calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider);
|
||||
break;
|
||||
}
|
||||
case "Curve": {
|
||||
calldata = await generateCurveStableSwapCalls(
|
||||
amountNativeDecimals,
|
||||
exchangeContractAddress,
|
||||
swapExecutorContractAddress ?? '',
|
||||
path,
|
||||
unit.provider,
|
||||
if (path.length > 1) {
|
||||
throw new Error("Supporting only single stable swap on curve");
|
||||
}
|
||||
calls = await generateCurveStableSwapCall(
|
||||
amount,
|
||||
swapExecutorContractAddress,
|
||||
path.first(),
|
||||
provider,
|
||||
swapExecutorContractAddress,
|
||||
curveRegistryAddress
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Factory ${factory} is not supported`)
|
||||
throw new Error(`Factory ${factory} is not supported`);
|
||||
}
|
||||
}
|
||||
return { swapDescription, calldata }
|
||||
return { swapDescription, calls };
|
||||
}
|
||||
|
||||
export async function generateUni2Calls(
|
||||
exchangeAddress: string,
|
||||
path: SafeArray<SwapInfo>
|
||||
) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const calls: BytesLike[] = []
|
||||
if (path.length > 1) {
|
||||
for (let i = 0; i < path.length - 1; ++i) {
|
||||
const currentSwap = path.get(i)
|
||||
const nextSwap = path.get(i + 1)
|
||||
|
||||
const calldata = executorInterface.encodeFunctionData("swapUniV2", [
|
||||
currentSwap.pool,
|
||||
currentSwap.assetIn,
|
||||
currentSwap.assetOut,
|
||||
defaultAbiCoder.encode(["uint256"], [concat(["0x03", nextSwap.pool])]),
|
||||
]
|
||||
)
|
||||
calls.push(addCallParams(calldata))
|
||||
}
|
||||
}
|
||||
const lastSwap = path.last();
|
||||
const calldata = executorInterface.encodeFunctionData("swapUniV2", [
|
||||
lastSwap.pool,
|
||||
lastSwap.assetIn,
|
||||
lastSwap.assetOut,
|
||||
defaultAbiCoder.encode(["uint256"], [concat(["0x03", exchangeAddress])]),
|
||||
])
|
||||
calls.push(addCallParams(calldata))
|
||||
|
||||
return generateCalls(calls)
|
||||
}
|
||||
|
||||
async function generateUni3Calls(
|
||||
async function processMultiFactorySwaps(
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
path: SafeArray<SingleSwap>,
|
||||
amount: BigNumberish,
|
||||
exchangeContractAddress: string,
|
||||
path: SafeArray<SwapInfo>,
|
||||
provider: ethers.providers.JsonRpcProvider
|
||||
swapExecutorContractAddress: string,
|
||||
curveRegistryAddress: string,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
const encodedPools: BytesLike[] = []
|
||||
let calls: BytesLike[] = [];
|
||||
for (const swap of path) {
|
||||
const pool = UniswapV3Pool__factory.connect(swap.pool, provider)
|
||||
const token0 = await pool.token0()
|
||||
const zeroForOne = token0.toLowerCase() === swap.assetIn.toLowerCase()
|
||||
const unwrapWETH = swap.assetOut === ethers.constants.AddressZero
|
||||
|
||||
let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address])
|
||||
encodedPool = ethers.utils.hexDataSlice(encodedPool, 1)
|
||||
let firstByte = 0
|
||||
if (unwrapWETH) firstByte += 32
|
||||
if (!zeroForOne) firstByte += 128
|
||||
const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte])
|
||||
encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool]))
|
||||
encodedPools.push(encodedPool)
|
||||
switch (swap.factory) {
|
||||
case "OrionV2": {
|
||||
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
|
||||
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
|
||||
calls.push(transferCall, uni2Call);
|
||||
break;
|
||||
}
|
||||
case "UniswapV2": {
|
||||
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
|
||||
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
|
||||
calls.push(transferCall, uni2Call);
|
||||
break;
|
||||
}
|
||||
case "UniswapV3": {
|
||||
let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider);
|
||||
uni3Call = pathCallWithBalance(uni3Call, swap.assetIn);
|
||||
calls.push(uni3Call);
|
||||
break;
|
||||
}
|
||||
case "OrionV3": {
|
||||
let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider);
|
||||
orion3Call = pathCallWithBalance(orion3Call, swap.assetIn);
|
||||
calls.push(orion3Call);
|
||||
break;
|
||||
}
|
||||
case "Curve": {
|
||||
let curveCalls = await generateCurveStableSwapCall(
|
||||
amount,
|
||||
swapExecutorContractAddress,
|
||||
swap,
|
||||
provider,
|
||||
swapExecutorContractAddress,
|
||||
curveRegistryAddress,
|
||||
true
|
||||
);
|
||||
calls.push(...curveCalls);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Factory ${swap.factory} is not supported`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
let calldata = executorInterface.encodeFunctionData("uniswapV3SwapTo", [encodedPools, exchangeContractAddress, amount])
|
||||
calldata = addCallParams(calldata)
|
||||
|
||||
return generateCalls([calldata])
|
||||
return { swapDescription, calls };
|
||||
}
|
||||
|
||||
async function generateOrion3Calls(
|
||||
async function payFeeToMatcher(
|
||||
matcher: string,
|
||||
feeToken: string,
|
||||
feeAmount: BigNumberish,
|
||||
calls: BytesLike[],
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
) {
|
||||
if (BigInt(feeAmount) !== 0n && feeToken === swapDescription.dstToken) {
|
||||
const feePaymentCall = generateFeePaymentCall(matcher, feeToken, feeAmount)
|
||||
calls.push(feePaymentCall)
|
||||
}
|
||||
return {swapDescription, calls}
|
||||
}
|
||||
|
||||
function wrapOrUnwrapIfNeeded(
|
||||
amount: BigNumberish,
|
||||
exchangeContractAddress: string,
|
||||
path: SafeArray<SwapInfo>,
|
||||
provider: ethers.providers.JsonRpcProvider
|
||||
swapDescription: LibValidator.SwapDescriptionStruct,
|
||||
calls: BytesLike[],
|
||||
swapExecutorContractAddress: string,
|
||||
wethAddress: string
|
||||
) {
|
||||
const encodedPools: BytesLike[] = []
|
||||
for (const swap of path) {
|
||||
const pool = UniswapV3Pool__factory.connect(swap.pool, provider)
|
||||
const token0 = await pool.token0()
|
||||
const zeroForOne = token0.toLowerCase() === swap.assetIn.toLowerCase()
|
||||
const unwrapWETH = swap.assetOut === ethers.constants.AddressZero
|
||||
|
||||
let encodedPool = ethers.utils.solidityPack(["uint256"], [pool.address])
|
||||
encodedPool = ethers.utils.hexDataSlice(encodedPool, 1)
|
||||
let firstByte = 0
|
||||
if (unwrapWETH) firstByte += 32
|
||||
if (!zeroForOne) firstByte += 128
|
||||
const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte])
|
||||
encodedPool = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, encodedPool]))
|
||||
encodedPools.push(encodedPool)
|
||||
const {dstReceiver, srcReceiver, srcToken, dstToken} = swapDescription;
|
||||
if (srcToken === ZeroAddress) {
|
||||
const wrapCall = generateWrapAndTransferCall(srcReceiver, { value: amount });
|
||||
swapDescription.srcReceiver = swapExecutorContractAddress;
|
||||
calls = ([wrapCall] as BytesLike[]).concat(calls);
|
||||
}
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
let calldata = executorInterface.encodeFunctionData("orionV3SwapTo", [encodedPools, exchangeContractAddress, amount])
|
||||
calldata = addCallParams(calldata)
|
||||
|
||||
return generateCalls([calldata])
|
||||
if (dstToken === ZeroAddress) {
|
||||
let unwrapCall = generateUnwrapAndTransferCall(dstReceiver, 0);
|
||||
unwrapCall = pathCallWithBalance(unwrapCall, wethAddress);
|
||||
calls.push(unwrapCall);
|
||||
} else {
|
||||
let transferCall = generateTransferCall(dstToken, dstReceiver, 0);
|
||||
transferCall = pathCallWithBalance(transferCall, dstToken);
|
||||
calls.push(transferCall);
|
||||
}
|
||||
return { swapDescription, calls };
|
||||
}
|
||||
|
||||
async function generateCurveStableSwapCalls(
|
||||
amount: BigNumberish,
|
||||
exchangeContractAddress: string,
|
||||
executorAddress: string,
|
||||
path: SafeArray<SwapInfo>,
|
||||
provider: ethers.providers.JsonRpcProvider,
|
||||
curveRegistry: string
|
||||
async function shouldUseExchangeBalance(
|
||||
srcToken: AddressLike,
|
||||
initiatorAddress: AddressLike,
|
||||
exchangeContractAddress: AddressLike,
|
||||
amount: bigint,
|
||||
provider: JsonRpcProvider
|
||||
) {
|
||||
if (path.length > 1) {
|
||||
throw new Error("Supporting only single stable swap on curve")
|
||||
const { walletBalance, exchangeBalance } = await getTotalBalance(
|
||||
srcToken,
|
||||
initiatorAddress,
|
||||
exchangeContractAddress,
|
||||
provider
|
||||
);
|
||||
const exchangeAllowance = await getExchangeAllowance(srcToken, initiatorAddress, exchangeContractAddress, provider);
|
||||
|
||||
if (walletBalance + exchangeBalance < amount) {
|
||||
throw new Error(
|
||||
`Not enough balance to make swap, totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}`
|
||||
);
|
||||
}
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
const registry = CurveRegistry__factory.connect(curveRegistry, provider)
|
||||
let useExchangeBalance = true;
|
||||
let additionalTransferAmount = 0n;
|
||||
|
||||
const swap = path.first()
|
||||
const firstToken = ERC20__factory.connect(swap.assetIn, provider)
|
||||
const { pool, assetIn, assetOut } = swap
|
||||
const [i, j,] = await registry.get_coin_indices(pool, assetIn, assetOut)
|
||||
|
||||
const executorAllowance = await firstToken.allowance(executorAddress, swap.pool)
|
||||
const calls: BytesLike[] = []
|
||||
if (executorAllowance.lt(amount)) {
|
||||
const calldata = addCallParams(
|
||||
executorInterface.encodeFunctionData("safeApprove", [
|
||||
swap.assetIn,
|
||||
swap.pool,
|
||||
ethers.constants.MaxUint256
|
||||
])
|
||||
)
|
||||
calls.push(calldata)
|
||||
if (exchangeBalance == 0n) {
|
||||
useExchangeBalance = false;
|
||||
additionalTransferAmount = amount;
|
||||
} else {
|
||||
additionalTransferAmount = exchangeBalance >= amount ? 0n : amount - exchangeBalance;
|
||||
if (srcToken !== ZeroAddress && additionalTransferAmount > exchangeAllowance) {
|
||||
throw new Error(
|
||||
`Not enough allowance to make swap, allowance - ${exchangeAllowance} needed allowance - ${additionalTransferAmount}`
|
||||
);
|
||||
}
|
||||
}
|
||||
let calldata = executorInterface.encodeFunctionData("curveSwapStableAmountIn", [
|
||||
pool,
|
||||
assetOut,
|
||||
i,
|
||||
j,
|
||||
amount,
|
||||
0,
|
||||
exchangeContractAddress])
|
||||
|
||||
calldata = addCallParams(calldata)
|
||||
calls.push(calldata)
|
||||
|
||||
return generateCalls(calls)
|
||||
}
|
||||
|
||||
// Adds additional byte to single swap with settings
|
||||
function addCallParams(
|
||||
calldata: BytesLike,
|
||||
callParams?: CallParams
|
||||
) {
|
||||
let firstByte = 0
|
||||
if (callParams) {
|
||||
if (callParams.value !== undefined) {
|
||||
firstByte += 16 // 00010000
|
||||
const encodedValue = ethers.utils.solidityPack(["uint128"], [callParams.value])
|
||||
calldata = ethers.utils.hexlify(ethers.utils.concat([encodedValue, calldata]))
|
||||
}
|
||||
if (callParams.target !== undefined) {
|
||||
firstByte += 32 // 00100000
|
||||
const encodedAddress = ethers.utils.solidityPack(["address"], [callParams.target])
|
||||
calldata = ethers.utils.hexlify(ethers.utils.concat([encodedAddress, calldata]))
|
||||
}
|
||||
if (callParams.gaslimit !== undefined) {
|
||||
firstByte += 64 // 01000000
|
||||
const encodedGaslimit = ethers.utils.solidityPack(["uint32"], [callParams.gaslimit])
|
||||
calldata = ethers.utils.hexlify(ethers.utils.concat([encodedGaslimit, calldata]))
|
||||
}
|
||||
if (callParams.isMandatory !== undefined) firstByte += 128 // 10000000
|
||||
}
|
||||
|
||||
const encodedFirstByte = ethers.utils.solidityPack(["uint8"], [firstByte])
|
||||
calldata = ethers.utils.hexlify(ethers.utils.concat([encodedFirstByte, calldata]))
|
||||
return calldata
|
||||
}
|
||||
|
||||
|
||||
async function generateCalls(calls: BytesLike[]) {
|
||||
const executorInterface = SwapExecutor__factory.createInterface()
|
||||
return "0x" + executorInterface.encodeFunctionData(EXECUTOR_SWAP_FUNCTION, [ethers.constants.AddressZero, calls]).slice(74)
|
||||
return { useExchangeBalance, additionalTransferAmount };
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export default async function getSwapInfo({
|
||||
const allPrices = await simpleFetch(blockchainService.getPricesWithQuoteAsset)();
|
||||
const gasPriceWei = await simpleFetch(blockchainService.getGasPriceWei)();
|
||||
|
||||
const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString();
|
||||
const gasPriceGwei = ethers.formatUnits(gasPriceWei, 'gwei').toString();
|
||||
|
||||
const assetInAddress = assetToAddress[assetIn];
|
||||
if (assetInAddress === undefined) throw new Error(`Asset '${assetIn}' not found`);
|
||||
@@ -101,15 +101,15 @@ export default async function getSwapInfo({
|
||||
}
|
||||
|
||||
if (route === 'pool') {
|
||||
const transactionCost = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT).mul(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const transactionCost = BigInt(SWAP_THROUGH_ORION_POOL_GAS_LIMIT) * BigInt(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
return {
|
||||
route,
|
||||
swapInfo,
|
||||
fee: {
|
||||
assetName: nativeCryptocurrencyName,
|
||||
assetAddress: ethers.constants.AddressZero,
|
||||
assetAddress: ethers.ZeroAddress,
|
||||
networkFeeInFeeAsset: denormalizedTransactionCost.toString(),
|
||||
protocolFeeInFeeAsset: undefined,
|
||||
},
|
||||
@@ -134,7 +134,7 @@ export default async function getSwapInfo({
|
||||
gasPriceGwei,
|
||||
feePercent,
|
||||
baseAssetAddress,
|
||||
ethers.constants.AddressZero,
|
||||
ethers.ZeroAddress,
|
||||
feeAssetAddress,
|
||||
allPrices.prices
|
||||
);
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import type Unit from '../index.js';
|
||||
import deposit, { type DepositParams } from './deposit.js';
|
||||
import getSwapInfo, { type GetSwapInfoParams } from './getSwapInfo.js';
|
||||
import generateSwapCalldata, { type GenerateSwapCalldataParams } from './generateSwapCalldata.js';
|
||||
import swapLimit, {type SwapLimitParams} from './swapLimit.js';
|
||||
import swapMarket, { type SwapMarketParams } from './swapMarket.js';
|
||||
import { generateSwapCalldataWithUnit, type GenerateSwapCalldataWithUnitParams } from './generateSwapCalldata.js';
|
||||
import withdraw, { type WithdrawParams } from './withdraw.js';
|
||||
import type { SwapLimitParams } from './swapLimit.js';
|
||||
import swapLimit from './swapLimit.js';
|
||||
import swapMarket from './swapMarket.js';
|
||||
import type { SwapMarketParams } from './swapMarket.js';
|
||||
|
||||
type PureSwapMarketParams = Omit<SwapMarketParams, 'unit'>
|
||||
type PureSwapLimitParams = Omit<SwapLimitParams, 'unit'>
|
||||
type PureDepositParams = Omit<DepositParams, 'unit'>
|
||||
type PureWithdrawParams = Omit<WithdrawParams, 'unit'>
|
||||
type PureGetSwapMarketInfoParams = Omit<GetSwapInfoParams, 'blockchainService' | 'aggregator'>
|
||||
type PureGenerateSwapCalldataParams = Omit<GenerateSwapCalldataParams, 'unit'>
|
||||
type PureGenerateSwapCalldataParams = Omit<GenerateSwapCalldataWithUnitParams, 'unit'>
|
||||
type PureSwapLimitParams = Omit<SwapLimitParams, 'unit'>
|
||||
type PureSwapMarketParams = Omit<SwapMarketParams, 'unit'>
|
||||
|
||||
export default class Exchange {
|
||||
private readonly unit: Unit;
|
||||
@@ -20,20 +22,6 @@ export default class Exchange {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public swapLimit(params: PureSwapLimitParams) {
|
||||
return swapLimit({
|
||||
...params,
|
||||
unit: this.unit,
|
||||
});
|
||||
}
|
||||
|
||||
public swapMarket(params: PureSwapMarketParams) {
|
||||
return swapMarket({
|
||||
...params,
|
||||
unit: this.unit,
|
||||
});
|
||||
}
|
||||
|
||||
public getSwapInfo(params: PureGetSwapMarketInfoParams) {
|
||||
return getSwapInfo({
|
||||
aggregator: this.unit.aggregator,
|
||||
@@ -57,9 +45,23 @@ export default class Exchange {
|
||||
}
|
||||
|
||||
public generateSwapCalldata(params: PureGenerateSwapCalldataParams) {
|
||||
return generateSwapCalldata({
|
||||
return generateSwapCalldataWithUnit({
|
||||
...params,
|
||||
unit: this.unit
|
||||
})
|
||||
}
|
||||
|
||||
public swapLimit(params: PureSwapLimitParams) {
|
||||
return swapLimit({
|
||||
...params,
|
||||
unit: this.unit,
|
||||
});
|
||||
}
|
||||
|
||||
public swapMarket(params: PureSwapMarketParams) {
|
||||
return swapMarket({
|
||||
...params,
|
||||
unit: this.unit,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import getAvailableSources from '../../utils/getAvailableFundsSources.js';
|
||||
@@ -12,6 +12,10 @@ import { signOrder } from '../../crypt/index.js';
|
||||
import type orderSchema from '../../services/Aggregator/schemas/orderSchema.js';
|
||||
import type { z } from 'zod';
|
||||
import { simpleFetch } from 'simple-typed-fetch';
|
||||
import { generateSwapCalldata } from './generateSwapCalldata.js';
|
||||
import isValidFactory from '../../utils/isValidFactory.js';
|
||||
import type { SingleSwap } from '../../types.js';
|
||||
import { must, safeGet } from '../../utils/safeGetters.js';
|
||||
|
||||
export type SwapLimitParams = {
|
||||
type: 'exactSpend' | 'exactReceive'
|
||||
@@ -44,11 +48,15 @@ type PoolSwap = {
|
||||
amountOut: number
|
||||
through: 'pool'
|
||||
txHash: string
|
||||
wait: (confirmations?: number | undefined) => Promise<ethers.providers.TransactionReceipt>
|
||||
wait: (confirmations?: number | undefined) => Promise<ethers.TransactionReceipt | null>
|
||||
}
|
||||
|
||||
export type Swap = AggregatorOrder | PoolSwap;
|
||||
|
||||
const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory: string }): singleSwap is SingleSwap => {
|
||||
return isValidFactory(singleSwap.factory);
|
||||
}
|
||||
|
||||
export default async function swapLimit({
|
||||
type,
|
||||
assetIn,
|
||||
@@ -85,6 +93,7 @@ export default async function swapLimit({
|
||||
exchangeContractAddress,
|
||||
matcherAddress,
|
||||
assetToAddress,
|
||||
swapExecutorContractAddress,
|
||||
} = await simpleFetch(blockchainService.getInfo)();
|
||||
const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress);
|
||||
|
||||
@@ -92,10 +101,11 @@ export default async function swapLimit({
|
||||
const feeAssets = await simpleFetch(blockchainService.getPlatformFees)({ walletAddress, assetIn, assetOut });
|
||||
const allPrices = await simpleFetch(blockchainService.getPricesWithQuoteAsset)();
|
||||
const gasPriceWei = await simpleFetch(blockchainService.getGasPriceWei)();
|
||||
const { factories } = await simpleFetch(blockchainService.getPoolsConfig)();
|
||||
const { factories, WETHAddress } = await simpleFetch(blockchainService.getPoolsConfig)();
|
||||
must(WETHAddress, 'WETHAddress is not defined');
|
||||
const poolExchangesList = factories !== undefined ? Object.keys(factories) : [];
|
||||
|
||||
const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString();
|
||||
const gasPriceGwei = ethers.formatUnits(gasPriceWei, 'gwei').toString();
|
||||
|
||||
const assetInAddress = assetToAddress[assetIn];
|
||||
if (assetInAddress === undefined) throw new Error(`Asset '${assetIn}' not found`);
|
||||
@@ -108,7 +118,7 @@ export default async function swapLimit({
|
||||
{
|
||||
[assetIn]: assetInAddress,
|
||||
[feeAsset]: feeAssetAddress,
|
||||
[nativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
[nativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
aggregator,
|
||||
walletAddress,
|
||||
@@ -120,7 +130,7 @@ export default async function swapLimit({
|
||||
balances,
|
||||
{
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
provider,
|
||||
signer,
|
||||
@@ -138,7 +148,7 @@ export default async function swapLimit({
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const { exchanges: swapExchanges } = swapInfo;
|
||||
const { exchanges: swapExchanges, exchangeContractPath } = swapInfo;
|
||||
|
||||
const [firstSwapExchange] = swapExchanges;
|
||||
|
||||
@@ -236,12 +246,6 @@ export default async function swapLimit({
|
||||
if (factoryAddress !== undefined) options?.logger?.(`Factory address is ${factoryAddress}. Exchange is ${firstSwapExchange}`);
|
||||
}
|
||||
|
||||
const pathAddresses = swapInfo.path.map((name) => {
|
||||
const assetAddress = assetToAddress[name];
|
||||
if (assetAddress === undefined) throw new Error(`No asset address for ${name}`);
|
||||
return assetAddress;
|
||||
});
|
||||
|
||||
const amountSpend = swapInfo.type === 'exactSpend'
|
||||
? swapInfo.amountIn
|
||||
: new BigNumber(swapInfo.orderInfo.amount).multipliedBy(swapInfo.orderInfo.safePrice)
|
||||
@@ -271,45 +275,64 @@ export default async function swapLimit({
|
||||
BigNumber.ROUND_FLOOR,
|
||||
);
|
||||
|
||||
const unsignedSwapThroughOrionPoolTx = await exchangeContract.populateTransaction.swapThroughOrionPool(
|
||||
amountSpendBlockchainParam,
|
||||
amountReceiveBlockchainParam,
|
||||
factoryAddress !== undefined
|
||||
? [factoryAddress, ...pathAddresses]
|
||||
: pathAddresses,
|
||||
type === 'exactSpend',
|
||||
const { calldata, swapDescription, value } = await generateSwapCalldata({
|
||||
amount: amountSpendBlockchainParam,
|
||||
minReturnAmount: amountReceiveBlockchainParam,
|
||||
path: exchangeContractPath.filter(isValidSingleSwap),
|
||||
initiatorAddress: walletAddress,
|
||||
receiverAddress: walletAddress,
|
||||
provider,
|
||||
|
||||
matcher: matcherAddress,
|
||||
feeToken: feeAssetAddress,
|
||||
fee: 0,
|
||||
exchangeContractAddress,
|
||||
swapExecutorContractAddress,
|
||||
wethAddress: WETHAddress,
|
||||
|
||||
curveRegistryAddress: safeGet(unit.contracts, 'curveRegistry'),
|
||||
})
|
||||
|
||||
const unsignedSwapThroughPoolsTx = await exchangeContract.swap.populateTransaction(
|
||||
swapExecutorContractAddress,
|
||||
swapDescription,
|
||||
new Uint8Array(0),
|
||||
calldata,
|
||||
{
|
||||
value
|
||||
}
|
||||
);
|
||||
|
||||
unsignedSwapThroughOrionPoolTx.chainId = parseInt(chainId, 10);
|
||||
unsignedSwapThroughOrionPoolTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
|
||||
unsignedSwapThroughPoolsTx.chainId = BigInt(parseInt(chainId, 10));
|
||||
unsignedSwapThroughPoolsTx.gasPrice = BigInt(gasPriceWei);
|
||||
|
||||
unsignedSwapThroughOrionPoolTx.from = walletAddress;
|
||||
unsignedSwapThroughPoolsTx.from = walletAddress;
|
||||
const amountSpendBN = new BigNumber(amountSpend);
|
||||
|
||||
let value = new BigNumber(0);
|
||||
let txValue = new BigNumber(0);
|
||||
const denormalizedAssetInExchangeBalance = balances[assetIn]?.exchange;
|
||||
if (denormalizedAssetInExchangeBalance === undefined) throw new Error(`Asset '${assetIn}' exchange balance is not found`);
|
||||
if (assetIn === nativeCryptocurrency && amountSpendBN.gt(denormalizedAssetInExchangeBalance)) {
|
||||
value = amountSpendBN.minus(denormalizedAssetInExchangeBalance);
|
||||
txValue = amountSpendBN.minus(denormalizedAssetInExchangeBalance);
|
||||
}
|
||||
unsignedSwapThroughOrionPoolTx.value = normalizeNumber(
|
||||
value.dp(INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL),
|
||||
unsignedSwapThroughPoolsTx.value = normalizeNumber(
|
||||
txValue.dp(INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL),
|
||||
NATIVE_CURRENCY_PRECISION,
|
||||
BigNumber.ROUND_CEIL,
|
||||
);
|
||||
unsignedSwapThroughOrionPoolTx.gasLimit = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT);
|
||||
unsignedSwapThroughPoolsTx.gasLimit = BigInt(SWAP_THROUGH_ORION_POOL_GAS_LIMIT);
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT).mul(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const transactionCost = BigInt(SWAP_THROUGH_ORION_POOL_GAS_LIMIT) * BigInt(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: getAvailableSources('network_fee', ethers.constants.AddressZero, 'pool'),
|
||||
sources: getAvailableSources('network_fee', ethers.ZeroAddress, 'pool'),
|
||||
});
|
||||
|
||||
// if (value.gt(0)) {
|
||||
@@ -317,24 +340,24 @@ export default async function swapLimit({
|
||||
// reason: 'Transaction value (extra amount)',
|
||||
// asset: {
|
||||
// name: nativeCryptocurrency,
|
||||
// address: ethers.constants.AddressZero,
|
||||
// address: ethers.ZeroAddress,
|
||||
// },
|
||||
// amount: value.toString(),
|
||||
// sources: getAvailableSources('amount', ethers.constants.AddressZero, 'pool'),
|
||||
// sources: getAvailableSources('amount', ethers.ZeroAddress, 'pool'),
|
||||
// });
|
||||
// }
|
||||
|
||||
await balanceGuard.check(options?.autoApprove);
|
||||
|
||||
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
|
||||
unsignedSwapThroughOrionPoolTx.nonce = nonce;
|
||||
unsignedSwapThroughPoolsTx.nonce = nonce;
|
||||
|
||||
options?.logger?.('Signing transaction...');
|
||||
const swapThroughOrionPoolTxResponse = await signer.sendTransaction(unsignedSwapThroughOrionPoolTx);
|
||||
const swapThroughOrionPoolTxResponse = await signer.sendTransaction(unsignedSwapThroughPoolsTx);
|
||||
options?.logger?.(`Transaction sent. Tx hash: ${swapThroughOrionPoolTxResponse.hash}`);
|
||||
return {
|
||||
amountOut: swapInfo.amountOut,
|
||||
wait: swapThroughOrionPoolTxResponse.wait,
|
||||
wait: swapThroughOrionPoolTxResponse.wait.bind(swapThroughOrionPoolTxResponse),
|
||||
through: 'pool',
|
||||
txHash: swapThroughOrionPoolTxResponse.hash,
|
||||
};
|
||||
@@ -380,7 +403,7 @@ export default async function swapLimit({
|
||||
gasPriceGwei,
|
||||
feePercent,
|
||||
baseAssetAddress,
|
||||
ethers.constants.AddressZero,
|
||||
ethers.ZeroAddress,
|
||||
feeAssetAddress,
|
||||
allPrices.prices,
|
||||
);
|
||||
@@ -426,7 +449,6 @@ export default async function swapLimit({
|
||||
walletAddress,
|
||||
matcherAddress,
|
||||
feeAssetAddress,
|
||||
false,
|
||||
signer,
|
||||
chainId,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import getAvailableSources from '../../utils/getAvailableFundsSources.js';
|
||||
@@ -12,6 +12,10 @@ import type orderSchema from '../../services/Aggregator/schemas/orderSchema.js';
|
||||
import type { z } from 'zod';
|
||||
import type { SwapLimitParams } from './swapLimit.js';
|
||||
import { simpleFetch } from 'simple-typed-fetch';
|
||||
import { generateSwapCalldata } from './generateSwapCalldata.js';
|
||||
import isValidFactory from '../../utils/isValidFactory.js';
|
||||
import type { SingleSwap } from '../../types.js';
|
||||
import { must, safeGet } from '../../utils/safeGetters.js';
|
||||
|
||||
export type SwapMarketParams = Omit<SwapLimitParams, 'price'> & {
|
||||
slippagePercent: BigNumber.Value
|
||||
@@ -28,11 +32,16 @@ type PoolSwap = {
|
||||
amountOut: number
|
||||
through: 'pool'
|
||||
txHash: string
|
||||
wait: (confirmations?: number | undefined) => Promise<ethers.providers.TransactionReceipt>
|
||||
wait: (confirmations?: number | undefined) => Promise<ethers.TransactionReceipt | null>
|
||||
}
|
||||
|
||||
export type Swap = AggregatorOrder | PoolSwap;
|
||||
|
||||
|
||||
const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory: string }): singleSwap is SingleSwap => {
|
||||
return isValidFactory(singleSwap.factory);
|
||||
}
|
||||
|
||||
export default async function swapMarket({
|
||||
type,
|
||||
assetIn,
|
||||
@@ -45,6 +54,7 @@ export default async function swapMarket({
|
||||
options,
|
||||
}: SwapMarketParams): Promise<Swap> {
|
||||
if (options?.developer) options.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!');
|
||||
|
||||
if (amount === '') throw new Error('Amount can not be empty');
|
||||
if (assetIn === '') throw new Error('AssetIn can not be empty');
|
||||
if (assetOut === '') throw new Error('AssetOut can not be empty');
|
||||
@@ -70,6 +80,7 @@ export default async function swapMarket({
|
||||
exchangeContractAddress,
|
||||
matcherAddress,
|
||||
assetToAddress,
|
||||
swapExecutorContractAddress,
|
||||
} = await simpleFetch(blockchainService.getInfo)();
|
||||
const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress);
|
||||
|
||||
@@ -77,10 +88,11 @@ export default async function swapMarket({
|
||||
const feeAssets = await simpleFetch(blockchainService.getPlatformFees)({ walletAddress, assetIn, assetOut });
|
||||
const allPrices = await simpleFetch(blockchainService.getPricesWithQuoteAsset)();
|
||||
const gasPriceWei = await simpleFetch(blockchainService.getGasPriceWei)();
|
||||
const { factories } = await simpleFetch(blockchainService.getPoolsConfig)();
|
||||
const { factories, WETHAddress } = await simpleFetch(blockchainService.getPoolsConfig)();
|
||||
must(WETHAddress, 'WETHAddress is not defined');
|
||||
const poolExchangesList = factories !== undefined ? Object.keys(factories) : [];
|
||||
|
||||
const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString();
|
||||
const gasPriceGwei = ethers.formatUnits(gasPriceWei, 'gwei').toString();
|
||||
|
||||
const assetInAddress = assetToAddress[assetIn];
|
||||
if (assetInAddress === undefined) throw new Error(`Asset '${assetIn}' not found`);
|
||||
@@ -93,7 +105,7 @@ export default async function swapMarket({
|
||||
{
|
||||
[assetIn]: assetInAddress,
|
||||
[feeAsset]: feeAssetAddress,
|
||||
[nativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
[nativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
aggregator,
|
||||
walletAddress,
|
||||
@@ -105,7 +117,7 @@ export default async function swapMarket({
|
||||
balances,
|
||||
{
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
provider,
|
||||
signer,
|
||||
@@ -123,7 +135,7 @@ export default async function swapMarket({
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const { exchanges: swapExchanges } = swapInfo;
|
||||
const { exchanges: swapExchanges, exchangeContractPath } = swapInfo;
|
||||
|
||||
const [firstSwapExchange] = swapExchanges;
|
||||
|
||||
@@ -184,12 +196,6 @@ export default async function swapMarket({
|
||||
if (factoryAddress !== undefined) options?.logger?.(`Factory address is ${factoryAddress}. Exchange is ${firstSwapExchange}`);
|
||||
}
|
||||
|
||||
const pathAddresses = swapInfo.path.map((name) => {
|
||||
const assetAddress = assetToAddress[name];
|
||||
if (assetAddress === undefined) throw new Error(`No asset address for ${name}`);
|
||||
return assetAddress;
|
||||
});
|
||||
|
||||
const amountOutWithSlippage = new BigNumber(swapInfo.amountOut)
|
||||
.multipliedBy(new BigNumber(1).minus(percent))
|
||||
.toString();
|
||||
@@ -221,45 +227,64 @@ export default async function swapMarket({
|
||||
INTERNAL_PROTOCOL_PRECISION,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
);
|
||||
const unsignedSwapThroughOrionPoolTx = await exchangeContract.populateTransaction.swapThroughOrionPool(
|
||||
amountSpendBlockchainParam,
|
||||
amountReceiveBlockchainParam,
|
||||
factoryAddress !== undefined
|
||||
? [factoryAddress, ...pathAddresses]
|
||||
: pathAddresses,
|
||||
type === 'exactSpend',
|
||||
const { calldata, swapDescription, value } = await generateSwapCalldata({
|
||||
amount: amountSpendBlockchainParam,
|
||||
minReturnAmount: amountReceiveBlockchainParam,
|
||||
path: exchangeContractPath.filter(isValidSingleSwap),
|
||||
initiatorAddress: walletAddress,
|
||||
receiverAddress: walletAddress,
|
||||
provider,
|
||||
|
||||
matcher: matcherAddress,
|
||||
feeToken: feeAssetAddress,
|
||||
fee: 0,
|
||||
exchangeContractAddress,
|
||||
swapExecutorContractAddress,
|
||||
wethAddress: WETHAddress,
|
||||
|
||||
curveRegistryAddress: safeGet(unit.contracts, 'curveRegistry'),
|
||||
})
|
||||
|
||||
const unsignedSwapThroughPoolsTx = await exchangeContract.swap.populateTransaction(
|
||||
swapExecutorContractAddress,
|
||||
swapDescription,
|
||||
new Uint8Array(0),
|
||||
calldata,
|
||||
{
|
||||
value
|
||||
}
|
||||
);
|
||||
|
||||
unsignedSwapThroughOrionPoolTx.chainId = parseInt(chainId, 10);
|
||||
unsignedSwapThroughOrionPoolTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
|
||||
unsignedSwapThroughPoolsTx.chainId = BigInt(parseInt(chainId, 10));
|
||||
unsignedSwapThroughPoolsTx.gasPrice = BigInt(gasPriceWei);
|
||||
|
||||
unsignedSwapThroughOrionPoolTx.from = walletAddress;
|
||||
unsignedSwapThroughPoolsTx.from = walletAddress;
|
||||
const amountSpendBN = new BigNumber(amountSpend);
|
||||
|
||||
let value = new BigNumber(0);
|
||||
let txValue = new BigNumber(0);
|
||||
const denormalizedAssetInExchangeBalance = balances[assetIn]?.exchange;
|
||||
if (denormalizedAssetInExchangeBalance === undefined) throw new Error(`Asset '${assetIn}' exchange balance is not found`);
|
||||
if (assetIn === nativeCryptocurrency && amountSpendBN.gt(denormalizedAssetInExchangeBalance)) {
|
||||
value = amountSpendBN.minus(denormalizedAssetInExchangeBalance);
|
||||
txValue = amountSpendBN.minus(denormalizedAssetInExchangeBalance);
|
||||
}
|
||||
unsignedSwapThroughOrionPoolTx.value = normalizeNumber(
|
||||
value.dp(INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL),
|
||||
unsignedSwapThroughPoolsTx.value = normalizeNumber(
|
||||
txValue.dp(INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL),
|
||||
NATIVE_CURRENCY_PRECISION,
|
||||
BigNumber.ROUND_CEIL,
|
||||
);
|
||||
unsignedSwapThroughOrionPoolTx.gasLimit = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT);
|
||||
unsignedSwapThroughPoolsTx.gasLimit = BigInt(SWAP_THROUGH_ORION_POOL_GAS_LIMIT);
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT).mul(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const transactionCost = BigInt(SWAP_THROUGH_ORION_POOL_GAS_LIMIT) * BigInt(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: getAvailableSources('network_fee', ethers.constants.AddressZero, 'pool'),
|
||||
sources: getAvailableSources('network_fee', ethers.ZeroAddress, 'pool'),
|
||||
});
|
||||
|
||||
// if (value.gt(0)) {
|
||||
@@ -267,24 +292,24 @@ export default async function swapMarket({
|
||||
// reason: 'Transaction value (extra amount)',
|
||||
// asset: {
|
||||
// name: nativeCryptocurrency,
|
||||
// address: ethers.constants.AddressZero,
|
||||
// address: ethers.ZeroAddress,
|
||||
// },
|
||||
// amount: value.toString(),
|
||||
// sources: getAvailableSources('amount', ethers.constants.AddressZero, 'pool'),
|
||||
// sources: getAvailableSources('amount', ethers.ZeroAddress, 'pool'),
|
||||
// });
|
||||
// }
|
||||
|
||||
await balanceGuard.check(options?.autoApprove);
|
||||
|
||||
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
|
||||
unsignedSwapThroughOrionPoolTx.nonce = nonce;
|
||||
unsignedSwapThroughPoolsTx.nonce = nonce;
|
||||
|
||||
options?.logger?.('Signing transaction...');
|
||||
const swapThroughOrionPoolTxResponse = await signer.sendTransaction(unsignedSwapThroughOrionPoolTx);
|
||||
const swapThroughOrionPoolTxResponse = await signer.sendTransaction(unsignedSwapThroughPoolsTx);
|
||||
options?.logger?.(`Transaction sent. Tx hash: ${swapThroughOrionPoolTxResponse.hash}`);
|
||||
return {
|
||||
amountOut: swapInfo.amountOut,
|
||||
wait: swapThroughOrionPoolTxResponse.wait,
|
||||
wait: swapThroughOrionPoolTxResponse.wait.bind(swapThroughOrionPoolTxResponse),
|
||||
through: 'pool',
|
||||
txHash: swapThroughOrionPoolTxResponse.hash,
|
||||
};
|
||||
@@ -338,7 +363,7 @@ export default async function swapMarket({
|
||||
gasPriceGwei,
|
||||
feePercent,
|
||||
baseAssetAddress,
|
||||
ethers.constants.AddressZero,
|
||||
ethers.ZeroAddress,
|
||||
feeAssetAddress,
|
||||
allPrices.prices,
|
||||
);
|
||||
@@ -384,7 +409,6 @@ export default async function swapMarket({
|
||||
walletAddress,
|
||||
matcherAddress,
|
||||
feeAssetAddress,
|
||||
false,
|
||||
signer,
|
||||
chainId,
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import type Unit from '../index.js';
|
||||
@@ -50,7 +50,7 @@ export default async function withdraw({
|
||||
const balances = await getBalances(
|
||||
{
|
||||
[asset]: assetAddress,
|
||||
[nativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
[nativeCryptocurrency]: ethers.ZeroAddress,
|
||||
},
|
||||
aggregator,
|
||||
walletAddress,
|
||||
@@ -62,7 +62,7 @@ export default async function withdraw({
|
||||
balances,
|
||||
{
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
provider,
|
||||
signer,
|
||||
@@ -78,27 +78,27 @@ export default async function withdraw({
|
||||
sources: ['exchange'],
|
||||
});
|
||||
|
||||
const unsignedTx = await exchangeContract.populateTransaction.withdraw(
|
||||
const unsignedTx = await exchangeContract.withdraw.populateTransaction(
|
||||
assetAddress,
|
||||
normalizeNumber(amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR),
|
||||
normalizeNumber(amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR).toString(),
|
||||
);
|
||||
unsignedTx.gasLimit = ethers.BigNumber.from(WITHDRAW_GAS_LIMIT);
|
||||
unsignedTx.gasLimit = BigInt(WITHDRAW_GAS_LIMIT);
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(unsignedTx.gasLimit).mul(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
const transactionCost = BigInt(unsignedTx.gasLimit) * BigInt(gasPriceWei);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
address: ethers.ZeroAddress,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
unsignedTx.chainId = parseInt(chainId, 10);
|
||||
unsignedTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
|
||||
unsignedTx.chainId = BigInt(chainId);
|
||||
unsignedTx.gasPrice = BigInt(gasPriceWei);
|
||||
unsignedTx.from = walletAddress;
|
||||
|
||||
await balanceGuard.check(true);
|
||||
@@ -107,11 +107,11 @@ export default async function withdraw({
|
||||
unsignedTx.nonce = nonce;
|
||||
|
||||
const signedTx = await signer.signTransaction(unsignedTx);
|
||||
const txResponse = await provider.sendTransaction(signedTx);
|
||||
const txResponse = await provider.broadcastTransaction(signedTx);
|
||||
console.log(`Withdraw tx sent: ${txResponse.hash}. Waiting for confirmation...`);
|
||||
try {
|
||||
const txReceipt = await txResponse.wait();
|
||||
if (txReceipt.status !== undefined) {
|
||||
if (txReceipt?.status !== undefined) {
|
||||
console.log('Withdraw tx confirmed');
|
||||
} else {
|
||||
console.log('Withdraw tx failed');
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
import { Exchange__factory, IUniswapV2Pair__factory, IUniswapV2Router__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { simpleFetch } from 'simple-typed-fetch';
|
||||
import type Unit from '../index.js';
|
||||
import BalanceGuard from '../../BalanceGuard.js';
|
||||
import { ADD_LIQUIDITY_GAS_LIMIT, INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from '../../constants/index.js';
|
||||
import { denormalizeNumber, normalizeNumber } from '../../utils/index.js';
|
||||
import getBalances from '../../utils/getBalances.js';
|
||||
import getNativeCryptocurrencyName from '../../utils/getNativeCryptocurrencyName.js';
|
||||
|
||||
const ADD_LIQUIDITY_SLIPPAGE = 0.05;
|
||||
|
||||
export type AddLiquidityParams = {
|
||||
poolName: string
|
||||
amountAsset: string
|
||||
amount: BigNumber.Value
|
||||
signer: ethers.Signer
|
||||
}
|
||||
|
||||
export type RemoveAllLiquidityParams = {
|
||||
poolName: string
|
||||
signer: ethers.Signer
|
||||
}
|
||||
|
||||
export default class FarmingManager {
|
||||
private readonly unit: Unit;
|
||||
|
||||
constructor(unit: Unit) {
|
||||
this.unit = unit;
|
||||
}
|
||||
|
||||
public async addLiquidity({
|
||||
poolName,
|
||||
amountAsset,
|
||||
amount,
|
||||
signer,
|
||||
}: AddLiquidityParams) {
|
||||
const amountBN = new BigNumber(amount);
|
||||
if (amountBN.isNaN()) throw new Error('Invalid amount');
|
||||
if (amountBN.lte(0)) throw new Error('Amount must be greater than 0');
|
||||
if (!poolName.includes('-')) throw new Error('Pool name must be in the format of "assetA-AssetB"');
|
||||
const [assetA, assetB] = poolName.split('-');
|
||||
if (assetA === undefined) throw new Error('Asset A undefined');
|
||||
if (assetB === undefined) throw new Error('Asset B undefined');
|
||||
if (amountAsset !== assetA && amountAsset !== assetB) throw new Error('Amount asset must be either assetA or assetB');
|
||||
|
||||
const {
|
||||
exchangeContractAddress,
|
||||
assetToAddress,
|
||||
assetToDecimals,
|
||||
} = await simpleFetch(this.unit.blockchainService.getInfo)();
|
||||
|
||||
const walletAddress = await signer.getAddress();
|
||||
|
||||
const exchangeContract = Exchange__factory
|
||||
.connect(exchangeContractAddress, this.unit.provider);
|
||||
|
||||
const assetAAddress = assetToAddress[assetA];
|
||||
if (assetAAddress === undefined) throw new Error(`Asset '${assetA}' not found`);
|
||||
const assetBAddress = assetToAddress[assetB];
|
||||
if (assetBAddress === undefined) throw new Error(`Asset '${assetB}' not found`);
|
||||
|
||||
const assetADecimals = assetToDecimals[assetA];
|
||||
if (assetADecimals === undefined) throw new Error(`Decimals for asset '${assetA}' not found`);
|
||||
const assetBDecimals = assetToDecimals[assetB];
|
||||
if (assetBDecimals === undefined) throw new Error(`Decimals for asset '${assetB}' not found`);
|
||||
|
||||
const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress);
|
||||
const balances = await getBalances(
|
||||
{
|
||||
[assetA]: assetAAddress,
|
||||
[assetB]: assetBAddress,
|
||||
[nativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
},
|
||||
this.unit.aggregator,
|
||||
walletAddress,
|
||||
exchangeContract,
|
||||
this.unit.provider,
|
||||
);
|
||||
const balanceGuard = new BalanceGuard(
|
||||
balances,
|
||||
{
|
||||
address: ethers.constants.AddressZero,
|
||||
name: nativeCryptocurrency,
|
||||
},
|
||||
this.unit.provider,
|
||||
signer,
|
||||
);
|
||||
|
||||
const poolsConfig = await simpleFetch(this.unit.blockchainService.getPoolsConfig)();
|
||||
const pool = poolsConfig.pools[poolName];
|
||||
if (!pool) throw new Error(`Pool ${poolName} not found`);
|
||||
|
||||
const pairContract = IUniswapV2Pair__factory
|
||||
.connect(pool.lpTokenAddress, this.unit.provider);
|
||||
const routerContract = IUniswapV2Router__factory
|
||||
.connect(poolsConfig.routerAddress, this.unit.provider);
|
||||
|
||||
let pairTokensIsInversed = false;
|
||||
const token0 = await pairContract.token0();
|
||||
const wrappedNativeAddress = await routerContract.WETH();
|
||||
|
||||
// const token1 = await pairContract.token1();
|
||||
if (token0.toLowerCase() !== wrappedNativeAddress.toLowerCase()) pairTokensIsInversed = true;
|
||||
|
||||
const { _reserve0, _reserve1 } = await pairContract.getReserves();
|
||||
|
||||
const assetAReserve = pairTokensIsInversed ? _reserve1 : _reserve0;
|
||||
const assetBReserve = pairTokensIsInversed ? _reserve0 : _reserve1;
|
||||
|
||||
const denormalizedAssetAReserve = denormalizeNumber(assetAReserve, assetADecimals);
|
||||
const denormalizedAssetBReserve = denormalizeNumber(assetBReserve, assetBDecimals);
|
||||
|
||||
const price = denormalizedAssetBReserve.div(denormalizedAssetAReserve);
|
||||
|
||||
const assetAIsNativeCurrency = assetAAddress === ethers.constants.AddressZero;
|
||||
const assetBIsNativeCurrency = assetBAddress === ethers.constants.AddressZero;
|
||||
|
||||
const assetAAmount = assetA === amountAsset ? amountBN : amountBN.div(price);
|
||||
const assetBAmount = assetA === amountAsset ? amountBN.multipliedBy(price) : amountBN;
|
||||
|
||||
const assetAAmountWithSlippage = assetAAmount.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE);
|
||||
const assetBAmountWithSlippage = assetBAmount.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE);
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: `${assetA} liquidity`,
|
||||
amount: assetAAmount.toString(),
|
||||
asset: {
|
||||
name: assetA,
|
||||
address: assetAAddress,
|
||||
},
|
||||
spenderAddress: exchangeContractAddress,
|
||||
sources: ['exchange', 'wallet'],
|
||||
});
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: `${assetB} liquidity`,
|
||||
amount: assetBAmount.toString(),
|
||||
asset: {
|
||||
name: assetB,
|
||||
address: assetBAddress,
|
||||
},
|
||||
spenderAddress: exchangeContractAddress,
|
||||
sources: ['exchange', 'wallet'],
|
||||
});
|
||||
|
||||
const unsignedTx = await exchangeContract.populateTransaction.withdrawToPool(
|
||||
assetBIsNativeCurrency ? assetBAddress : assetAAddress,
|
||||
assetBIsNativeCurrency ? assetAAddress : assetBAddress,
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetBAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR)
|
||||
: normalizeNumber(assetAAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR),
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetAAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR)
|
||||
: normalizeNumber(assetBAmount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR),
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetBAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR)
|
||||
: normalizeNumber(assetAAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR),
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(assetAAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR)
|
||||
: normalizeNumber(assetBAmountWithSlippage, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR),
|
||||
);
|
||||
|
||||
const gasPrice = await this.unit.provider.getGasPrice();
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(ADD_LIQUIDITY_GAS_LIMIT).mul(gasPrice);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
const nonce = await this.unit.provider.getTransactionCount(walletAddress, 'pending');
|
||||
|
||||
const network = await this.unit.provider.getNetwork();
|
||||
|
||||
if (assetAIsNativeCurrency || assetBIsNativeCurrency) {
|
||||
const contractBalance = balances[nativeCryptocurrency]?.exchange;
|
||||
if (!contractBalance) throw new Error(`No balance for '${nativeCryptocurrency}'`);
|
||||
const nativeAssetAmount = assetBIsNativeCurrency ? assetBAmount : assetAAmount;
|
||||
|
||||
if (nativeAssetAmount.gt(contractBalance)) {
|
||||
unsignedTx.value = normalizeNumber(
|
||||
nativeAssetAmount.minus(contractBalance),
|
||||
NATIVE_CURRENCY_PRECISION,
|
||||
BigNumber.ROUND_CEIL,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsignedTx.chainId = network.chainId;
|
||||
unsignedTx.gasPrice = gasPrice;
|
||||
unsignedTx.nonce = nonce;
|
||||
unsignedTx.from = walletAddress;
|
||||
const gasLimit = await this.unit.provider.estimateGas(unsignedTx);
|
||||
unsignedTx.gasLimit = gasLimit;
|
||||
|
||||
await balanceGuard.check(true);
|
||||
|
||||
const signedTx = await signer.signTransaction(unsignedTx);
|
||||
const txResponse = await this.unit.provider.sendTransaction(signedTx);
|
||||
console.log(`Add liquidity tx sent: ${txResponse.hash}. Waiting for confirmation...`);
|
||||
const txReceipt = await txResponse.wait();
|
||||
if (txReceipt.status === 1) {
|
||||
console.log(`Add liquidity tx confirmed: ${txReceipt.transactionHash}`);
|
||||
} else {
|
||||
console.log(`Add liquidity tx failed: ${txReceipt.transactionHash}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async removeAllLiquidity({
|
||||
poolName,
|
||||
signer,
|
||||
}: RemoveAllLiquidityParams) {
|
||||
if (!poolName.includes('-')) throw new Error('Pool name must be in the format of "assetA-AssetB"');
|
||||
const [assetA, assetB] = poolName.split('-');
|
||||
if (assetA === undefined) throw new Error('Asset A is not defined');
|
||||
if (assetB === undefined) throw new Error('Asset B is not defined');
|
||||
|
||||
const {
|
||||
assetToAddress,
|
||||
assetToDecimals,
|
||||
exchangeContractAddress,
|
||||
} = await simpleFetch(this.unit.blockchainService.getInfo)();
|
||||
|
||||
const assetAAddress = assetToAddress[assetA];
|
||||
if (assetAAddress === undefined) throw new Error(`Asset '${assetA}' not found`);
|
||||
const assetBAddress = assetToAddress[assetB];
|
||||
if (assetBAddress === undefined) throw new Error(`Asset '${assetB}' not found`);
|
||||
|
||||
const assetADecimals = assetToDecimals[assetA];
|
||||
if (assetADecimals === undefined) throw new Error(`Decimals for asset '${assetA}' not found`);
|
||||
const assetBDecimals = assetToDecimals[assetB];
|
||||
if (assetBDecimals === undefined) throw new Error(`Decimals for asset '${assetB}' not found`);
|
||||
|
||||
const poolsConfig = await simpleFetch(this.unit.blockchainService.getPoolsConfig)();
|
||||
const pool = poolsConfig.pools[poolName];
|
||||
if (!pool) throw new Error(`Pool ${poolName} not found`);
|
||||
|
||||
const walletAddress = await signer.getAddress();
|
||||
|
||||
const exchangeContract = Exchange__factory
|
||||
.connect(exchangeContractAddress, this.unit.provider);
|
||||
const nativeCryptocurrency = getNativeCryptocurrencyName(assetToAddress);
|
||||
const balances = await getBalances(
|
||||
{
|
||||
[assetA]: assetAAddress,
|
||||
[assetB]: assetBAddress,
|
||||
[`${poolName} LP Token`]: pool.lpTokenAddress,
|
||||
[nativeCryptocurrency]: ethers.constants.AddressZero,
|
||||
},
|
||||
this.unit.aggregator,
|
||||
walletAddress,
|
||||
exchangeContract,
|
||||
this.unit.provider,
|
||||
);
|
||||
|
||||
const balanceGuard = new BalanceGuard(
|
||||
balances,
|
||||
{
|
||||
address: ethers.constants.AddressZero,
|
||||
name: nativeCryptocurrency,
|
||||
},
|
||||
this.unit.provider,
|
||||
signer,
|
||||
);
|
||||
|
||||
const pairContract = IUniswapV2Pair__factory
|
||||
.connect(pool.lpTokenAddress, this.unit.provider);
|
||||
|
||||
const { _reserve0, _reserve1 } = await pairContract.getReserves();
|
||||
|
||||
const routerContract = IUniswapV2Router__factory
|
||||
.connect(poolsConfig.routerAddress, this.unit.provider);
|
||||
|
||||
let pairTokensIsInversed = false;
|
||||
|
||||
const lpTokenUserBalance = await pairContract.balanceOf(walletAddress);
|
||||
const lpTokenDecimals = await pairContract.decimals();
|
||||
|
||||
const token0 = await pairContract.token0();
|
||||
const totalSupply = await pairContract.totalSupply();
|
||||
const wrappedNativeAddress = await routerContract.WETH();
|
||||
if (token0.toLowerCase() !== wrappedNativeAddress.toLowerCase()) pairTokensIsInversed = true;
|
||||
|
||||
const denormalizedLpTokenUserBalance = denormalizeNumber(lpTokenUserBalance, lpTokenDecimals);
|
||||
const denormalizedLpTokenSupply = denormalizeNumber(totalSupply, lpTokenDecimals);
|
||||
|
||||
const userShare = denormalizedLpTokenUserBalance.div(denormalizedLpTokenSupply);
|
||||
|
||||
const assetAReserve = pairTokensIsInversed ? _reserve1 : _reserve0;
|
||||
const assetBReserve = pairTokensIsInversed ? _reserve0 : _reserve1;
|
||||
|
||||
const denormalizedAssetAReserve = denormalizeNumber(assetAReserve, assetADecimals);
|
||||
const denormalizedAssetBReserve = denormalizeNumber(assetBReserve, assetBDecimals);
|
||||
|
||||
const denormalizedUserPooledAssetA = denormalizedAssetAReserve.multipliedBy(userShare);
|
||||
const denormalizedUserPooledAssetB = denormalizedAssetBReserve.multipliedBy(userShare);
|
||||
|
||||
const denormalizedUserPooledAssetAWithSlippage = denormalizedUserPooledAssetA.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE);
|
||||
const denormalizedUserPooledAssetBWithSlippage = denormalizedUserPooledAssetB.multipliedBy(1 - ADD_LIQUIDITY_SLIPPAGE);
|
||||
|
||||
const assetAIsNativeCurrency = assetAAddress === ethers.constants.AddressZero;
|
||||
const assetBIsNativeCurrency = assetBAddress === ethers.constants.AddressZero;
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: `${poolName} liquidity`,
|
||||
asset: {
|
||||
name: `${poolName} LP Token`,
|
||||
address: pool.lpTokenAddress,
|
||||
},
|
||||
spenderAddress: poolsConfig.routerAddress,
|
||||
amount: denormalizedLpTokenUserBalance.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
let unsignedTx: ethers.PopulatedTransaction;
|
||||
if (assetAIsNativeCurrency || assetBIsNativeCurrency) {
|
||||
unsignedTx = await routerContract.populateTransaction.removeLiquidityETH(
|
||||
assetBIsNativeCurrency ? assetAAddress : assetBAddress, // token
|
||||
lpTokenUserBalance,
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(
|
||||
denormalizedUserPooledAssetAWithSlippage,
|
||||
assetADecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
)
|
||||
: normalizeNumber(
|
||||
denormalizedUserPooledAssetBWithSlippage,
|
||||
assetBDecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
), // token min
|
||||
assetBIsNativeCurrency
|
||||
? normalizeNumber(
|
||||
denormalizedUserPooledAssetBWithSlippage,
|
||||
assetBDecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
)
|
||||
: normalizeNumber(
|
||||
denormalizedUserPooledAssetAWithSlippage,
|
||||
assetADecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
), // eth min
|
||||
walletAddress,
|
||||
Math.floor(Date.now() / 1000) + 60 * 20,
|
||||
);
|
||||
} else {
|
||||
unsignedTx = await routerContract.populateTransaction.removeLiquidity(
|
||||
assetAAddress,
|
||||
assetBAddress,
|
||||
lpTokenUserBalance,
|
||||
normalizeNumber(
|
||||
denormalizedUserPooledAssetAWithSlippage,
|
||||
assetADecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
),
|
||||
normalizeNumber(
|
||||
denormalizedUserPooledAssetBWithSlippage,
|
||||
assetBDecimals,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
),
|
||||
walletAddress,
|
||||
Math.floor(Date.now() / 1000) + 60 * 20,
|
||||
);
|
||||
}
|
||||
|
||||
const gasPrice = await this.unit.provider.getGasPrice();
|
||||
|
||||
const transactionCost = ethers.BigNumber.from(ADD_LIQUIDITY_GAS_LIMIT).mul(gasPrice);
|
||||
const denormalizedTransactionCost = denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
|
||||
|
||||
balanceGuard.registerRequirement({
|
||||
reason: 'Network fee',
|
||||
asset: {
|
||||
name: nativeCryptocurrency,
|
||||
address: ethers.constants.AddressZero,
|
||||
},
|
||||
amount: denormalizedTransactionCost.toString(),
|
||||
sources: ['wallet'],
|
||||
});
|
||||
|
||||
await balanceGuard.check(true);
|
||||
const nonce = await this.unit.provider.getTransactionCount(walletAddress, 'pending');
|
||||
const network = await this.unit.provider.getNetwork();
|
||||
|
||||
unsignedTx.chainId = network.chainId;
|
||||
unsignedTx.gasPrice = gasPrice;
|
||||
unsignedTx.nonce = nonce;
|
||||
unsignedTx.from = walletAddress;
|
||||
const gasLimit = await this.unit.provider.estimateGas(unsignedTx);
|
||||
unsignedTx.gasLimit = gasLimit;
|
||||
|
||||
const signedTx = await signer.signTransaction(unsignedTx);
|
||||
const txResponse = await this.unit.provider.sendTransaction(signedTx);
|
||||
console.log(`Remove all liquidity tx sent: ${txResponse.hash}. Waiting for confirmation...`);
|
||||
const txReceipt = await txResponse.wait();
|
||||
if (txReceipt.status === 1) {
|
||||
console.log(`Remove all liquidity tx confirmed: ${txReceipt.transactionHash}`);
|
||||
} else {
|
||||
console.log(`Remove all liquidity tx failed: ${txReceipt.transactionHash}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,39 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { Aggregator } from '../services/Aggregator/index.js';
|
||||
import { BlockchainService } from '../services/BlockchainService/index.js';
|
||||
import { PriceFeed } from '../services/PriceFeed/index.js';
|
||||
import type { KnownEnv, SupportedChainId, VerboseUnitConfig } from '../types.js';
|
||||
import { JsonRpcProvider } from 'ethers';
|
||||
import { Aggregator } from '../services/Aggregator';
|
||||
import { BlockchainService } from '../services/BlockchainService';
|
||||
import { PriceFeed } from '../services/PriceFeed';
|
||||
import type {
|
||||
KnownEnv,
|
||||
SupportedChainId,
|
||||
VerboseUnitConfig,
|
||||
} from '../types.js';
|
||||
import Exchange from './Exchange/index.js';
|
||||
import FarmingManager from './FarmingManager/index.js';
|
||||
import { chains, envs } from '../config/index.js';
|
||||
import { chains, envs } from '../config';
|
||||
import type { networkCodes } from '../constants/index.js';
|
||||
import { IndexerService } from '../services/Indexer';
|
||||
|
||||
type KnownConfig = {
|
||||
env: KnownEnv
|
||||
chainId: SupportedChainId
|
||||
}
|
||||
};
|
||||
|
||||
export default class Unit {
|
||||
public readonly networkCode: typeof networkCodes[number];
|
||||
public readonly networkCode: (typeof networkCodes)[number];
|
||||
|
||||
public readonly chainId: SupportedChainId;
|
||||
|
||||
public readonly provider: ethers.providers.StaticJsonRpcProvider;
|
||||
public readonly provider: JsonRpcProvider;
|
||||
|
||||
public readonly blockchainService: BlockchainService;
|
||||
|
||||
public readonly indexer: IndexerService | undefined;
|
||||
|
||||
public readonly aggregator: Aggregator;
|
||||
|
||||
public readonly priceFeed: PriceFeed;
|
||||
|
||||
public readonly exchange: Exchange;
|
||||
|
||||
public readonly farmingManager: FarmingManager;
|
||||
|
||||
public readonly config: VerboseUnitConfig;
|
||||
|
||||
public readonly contracts: Record<string, string>;
|
||||
@@ -37,13 +41,33 @@ export default class Unit {
|
||||
constructor(config: KnownConfig | VerboseUnitConfig) {
|
||||
if ('env' in config) {
|
||||
const staticConfig = envs[config.env];
|
||||
if (!staticConfig) throw new Error(`Invalid environment: ${config.env}. Available environments: ${Object.keys(envs).join(', ')}`);
|
||||
if (!staticConfig) {
|
||||
throw new Error(
|
||||
`Invalid environment: ${
|
||||
config.env
|
||||
}. Available environments: ${Object.keys(envs).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const chainConfig = chains[config.chainId];
|
||||
if (!chainConfig) throw new Error(`Invalid chainId: ${config.chainId}. Available chainIds: ${Object.keys(chains).join(', ')}`);
|
||||
if (!chainConfig) {
|
||||
throw new Error(
|
||||
`Invalid chainId: ${
|
||||
config.chainId
|
||||
}. Available chainIds: ${Object.keys(chains).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const networkConfig = staticConfig.networks[config.chainId];
|
||||
if (!networkConfig) throw new Error(`Invalid chainId: ${config.chainId}. Available chainIds: ${Object.keys(staticConfig.networks).join(', ')}`);
|
||||
if (!networkConfig) {
|
||||
throw new Error(
|
||||
`Invalid chainId: ${
|
||||
config.chainId
|
||||
}. Available chainIds: ${Object.keys(staticConfig.networks).join(
|
||||
', '
|
||||
)}`
|
||||
);
|
||||
}
|
||||
this.config = {
|
||||
chainId: config.chainId,
|
||||
nodeJsonRpc: networkConfig.rpc ?? chainConfig.rpc,
|
||||
@@ -58,30 +82,45 @@ export default class Unit {
|
||||
priceFeed: {
|
||||
api: networkConfig.api + networkConfig.services.priceFeed.all,
|
||||
},
|
||||
indexer: {
|
||||
api: networkConfig.api + networkConfig.services.indexer?.http,
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
} else {
|
||||
this.config = config;
|
||||
}
|
||||
const chainInfo = chains[config.chainId];
|
||||
if (!chainInfo) throw new Error('Chain info is required');
|
||||
|
||||
this.chainId = config.chainId;
|
||||
this.networkCode = chainInfo.code;
|
||||
this.contracts = chainInfo.contracts
|
||||
this.contracts = chainInfo.contracts;
|
||||
const intNetwork = parseInt(this.chainId, 10);
|
||||
if (Number.isNaN(intNetwork)) throw new Error('Invalid chainId (not a number)' + this.chainId);
|
||||
this.provider = new ethers.providers.StaticJsonRpcProvider(this.config.nodeJsonRpc, intNetwork);
|
||||
if (Number.isNaN(intNetwork)) {
|
||||
throw new Error('Invalid chainId (not a number)' + this.chainId);
|
||||
}
|
||||
this.provider = new JsonRpcProvider(this.config.nodeJsonRpc, intNetwork);
|
||||
this.provider.pollingInterval = 1000;
|
||||
|
||||
this.blockchainService = new BlockchainService(this.config.services.blockchainService.http, this.config.basicAuth);
|
||||
this.blockchainService = new BlockchainService(
|
||||
this.config.services.blockchainService.http,
|
||||
this.config.basicAuth
|
||||
);
|
||||
this.indexer = this.config.services.indexer
|
||||
? new IndexerService(
|
||||
this.config.services.indexer.api,
|
||||
intNetwork
|
||||
)
|
||||
: undefined;
|
||||
this.aggregator = new Aggregator(
|
||||
this.config.services.aggregator.http,
|
||||
this.config.services.aggregator.ws,
|
||||
this.config.basicAuth,
|
||||
this.config.basicAuth
|
||||
);
|
||||
this.priceFeed = new PriceFeed(
|
||||
this.config.services.priceFeed.api,
|
||||
this.config.basicAuth
|
||||
);
|
||||
this.priceFeed = new PriceFeed(this.config.services.priceFeed.api, this.config.basicAuth);
|
||||
this.exchange = new Exchange(this);
|
||||
this.farmingManager = new FarmingManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,6 +177,9 @@ describe('Orion', () => {
|
||||
priceFeed: {
|
||||
api: orionPriceFeedAPI + '/price-feed',
|
||||
},
|
||||
indexer: {
|
||||
api: '',
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -195,7 +198,7 @@ describe('Orion', () => {
|
||||
expect(unit.aggregator.ws.api).toBe(`ws://localhost:${server1.port}/v1`);
|
||||
expect(unit.blockchainService.api).toBe(blockchainServiceAPI);
|
||||
expect(unit.priceFeed.api).toBe(orionPriceFeedAPI + '/price-feed');
|
||||
expect(unit.provider.connection.url).toBe('https://cloudflare-eth.com/');
|
||||
expect(unit.provider._getConnection().url).toBe('https://cloudflare-eth.com/');
|
||||
|
||||
const info = await simpleFetch(unit.blockchainService.getInfo)();
|
||||
expect(info).toBeDefined();
|
||||
@@ -246,7 +249,7 @@ describe('Orion', () => {
|
||||
});
|
||||
|
||||
const bscUnit = orion.units[SupportedChainId.BSC_TESTNET]
|
||||
expect(bscUnit?.provider.connection.url).toBe('https://data-seed-prebsc-1-s1.binance.org:8545/');
|
||||
expect(bscUnit?.provider._getConnection().url).toBe('https://data-seed-prebsc-1-s1.binance.org:8545/');
|
||||
expect(orion.referralSystem.api).toBe('https://zxczxc.orionprotocol.io');
|
||||
});
|
||||
|
||||
@@ -312,7 +315,7 @@ describe('Orion', () => {
|
||||
const network = await unitBSC.provider.getNetwork();
|
||||
expect(network.chainId).toBe(97);
|
||||
|
||||
const zeroAddressWithout0x = ethers.constants.AddressZero.slice(2);
|
||||
const zeroAddressWithout0x = ethers.ZeroAddress.slice(2);
|
||||
expect(simpleFetch(orion.referralSystem.getMiniStats)(zeroAddressWithout0x))
|
||||
.rejects
|
||||
.toThrow('empty reward history');
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('Fee calculation', () => {
|
||||
const gasPriceGwei = 3;
|
||||
const feePercent = 0.2;
|
||||
const baseAssetAddress = '0xcb2951e90d8dcf16e1fa84ac0c83f48906d6a744';
|
||||
const baseCurrencyAddress = ethers.constants.AddressZero;
|
||||
const baseCurrencyAddress = ethers.ZeroAddress;
|
||||
const feeAssetAddress = '0xf223eca06261145b3287a0fefd8cfad371c7eb34';
|
||||
const { totalFeeInFeeAsset: ornTotalFee } = calculateFeeInFeeAsset(
|
||||
amount,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { z } from 'zod';
|
||||
|
||||
const addressSchema = z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
const addressSchema = z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `Should be an address, got ${value}`,
|
||||
}));
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"1": {
|
||||
"chainId": "1",
|
||||
"explorer": "https://etherscan.io/",
|
||||
"label": "Ethereum Mainnet",
|
||||
"label": "Ethereum",
|
||||
"shortName": "ETH",
|
||||
"code": "eth",
|
||||
"rpc": "https://trade.orionprotocol.io/eth-mainnet/rpc",
|
||||
"rpc": "https://trade.orion.xyz/eth-mainnet/rpc",
|
||||
"baseCurrencyName": "ETH",
|
||||
"contracts": {
|
||||
"WETH": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
|
||||
@@ -15,10 +15,10 @@
|
||||
"56": {
|
||||
"chainId": "56",
|
||||
"explorer": "https://bscscan.com/",
|
||||
"label": "Binance Smart Chain",
|
||||
"label": "BNB Chain",
|
||||
"shortName": "BSC",
|
||||
"code": "bsc",
|
||||
"rpc": "https://bsc-dataseed.binance.org/",
|
||||
"rpc": "https://bsc-dataseed.bnbchain.org/",
|
||||
"baseCurrencyName": "BNB",
|
||||
"contracts": {
|
||||
"WETH": "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
|
||||
@@ -28,14 +28,27 @@
|
||||
"97": {
|
||||
"chainId": "97",
|
||||
"explorer": "https://testnet.bscscan.com/",
|
||||
"label": "Binance Smart Chain Testnet",
|
||||
"label": "BNB Chain Testnet",
|
||||
"shortName": "BSC-Testnet",
|
||||
"code": "bsc",
|
||||
"rpc": "https://data-seed-prebsc-1-s1.bnbchain.org:8545/",
|
||||
"baseCurrencyName": "BNB",
|
||||
"contracts": {
|
||||
"WETH": "0x23eE96bEaAB62abE126AA192e677c52bB7d274F0",
|
||||
"curveRegistry": "0x8845b36C3DE93379F468590FFa102D4aF8C49A6c"
|
||||
"curveRegistry": "0x93461072c00b2740c474a3d52c70be6249c802d8"
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"chainId": "204",
|
||||
"explorer": "http://opbnbscan.com/",
|
||||
"label": "opBNB Chain",
|
||||
"shortName": "opBNB",
|
||||
"code": "opbnb",
|
||||
"rpc": "https://opbnb-mainnet-rpc.bnbchain.org",
|
||||
"baseCurrencyName": "BNB",
|
||||
"contracts": {
|
||||
"WETH": "0x4200000000000000000000000000000000000006",
|
||||
"curveRegistry": ""
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
@@ -44,7 +57,7 @@
|
||||
"label": "Ropsten",
|
||||
"shortName": "ETH-Ropsten",
|
||||
"code": "eth",
|
||||
"rpc": "https://testing.orionprotocol.io/eth-ropsten/rpc",
|
||||
"rpc": "https://testing.orion.xyz/eth-ropsten/rpc",
|
||||
"baseCurrencyName": "ETH",
|
||||
"contracts": {
|
||||
"WETH": "",
|
||||
@@ -57,7 +70,7 @@
|
||||
"label": "Goerli",
|
||||
"shortName": "ETH-Goerli",
|
||||
"code": "eth",
|
||||
"rpc": "https://testing.orionprotocol.io/eth-goerli/rpc",
|
||||
"rpc": "https://testing.orion.xyz/eth-goerli/rpc",
|
||||
"baseCurrencyName": "ETH",
|
||||
"contracts": {
|
||||
"WETH": "",
|
||||
@@ -77,6 +90,19 @@
|
||||
"curveRegistry": ""
|
||||
}
|
||||
},
|
||||
"42161": {
|
||||
"chainId": "42161",
|
||||
"explorer": "https://arbiscan.io/",
|
||||
"label": "Arbitrum",
|
||||
"shortName": "Arbitrum",
|
||||
"code": "arb",
|
||||
"rpc": "https://arb1.arbitrum.io/rpc",
|
||||
"baseCurrencyName": "ETH",
|
||||
"contracts": {
|
||||
"WETH": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
|
||||
"curveRegistry": "0x445FE580eF8d70FF569aB36e80c647af338db351"
|
||||
}
|
||||
},
|
||||
"4002": {
|
||||
"chainId": "4002",
|
||||
"explorer": "https://testnet.ftmscan.com/",
|
||||
@@ -93,27 +119,27 @@
|
||||
"250": {
|
||||
"chainId": "250",
|
||||
"explorer": "https://ftmscan.com/",
|
||||
"label": "Fantom Mainnet",
|
||||
"label": "Fantom",
|
||||
"shortName": "FTM",
|
||||
"code": "ftm",
|
||||
"rpc": "https://rpcapi.fantom.network/",
|
||||
"baseCurrencyName": "FTM",
|
||||
"contracts": {
|
||||
"WETH": "",
|
||||
"curveRegistry": ""
|
||||
"WETH": "0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83",
|
||||
"curveRegistry": "0x0f854EA9F38ceA4B1c2FC79047E9D0134419D5d6"
|
||||
}
|
||||
},
|
||||
"137": {
|
||||
"chainId": "137",
|
||||
"label": "Polygon Mainnet",
|
||||
"label": "Polygon",
|
||||
"shortName": "Polygon",
|
||||
"code": "polygon",
|
||||
"baseCurrencyName": "MATIC",
|
||||
"rpc": "https://polygon-rpc.com/",
|
||||
"explorer": "https://polygonscan.com/",
|
||||
"contracts": {
|
||||
"WETH": "",
|
||||
"curveRegistry": ""
|
||||
"WETH": "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
|
||||
"curveRegistry": "0x094d12e5b541784701FD8d65F11fc0598FBC6332"
|
||||
}
|
||||
},
|
||||
"80001": {
|
||||
@@ -132,13 +158,13 @@
|
||||
"66": {
|
||||
"chainId": "66",
|
||||
"explorer": "https://www.oklink.com/okc/",
|
||||
"label": "OKC Mainnet",
|
||||
"label": "OKT Chain",
|
||||
"shortName": "OKC",
|
||||
"code": "okc",
|
||||
"rpc": "https://exchainrpc.okex.org/",
|
||||
"baseCurrencyName": "OKT",
|
||||
"contracts": {
|
||||
"WETH": "",
|
||||
"WETH": "0x843340759bFCa4a3776F401cD2E03fE9bc0d838f",
|
||||
"curveRegistry": ""
|
||||
}
|
||||
},
|
||||
@@ -161,11 +187,37 @@
|
||||
"shortName": "DRIP Chain",
|
||||
"code": "drip",
|
||||
"baseCurrencyName": "DRIP",
|
||||
"rpc": "testnet.1d.rip",
|
||||
"rpc": "https://testnet.1d.rip/",
|
||||
"explorer": "https://explorer-testnet.1d.rip/",
|
||||
"contracts": {
|
||||
"WETH": "",
|
||||
"curveRegistry": ""
|
||||
}
|
||||
},
|
||||
"2525": {
|
||||
"chainId": "2525",
|
||||
"label": "inEVM",
|
||||
"shortName": "inEVM",
|
||||
"code": "inevm",
|
||||
"baseCurrencyName": "INJ",
|
||||
"rpc": "https://inevm.calderachain.xyz/http/",
|
||||
"explorer": "https://explorer.injective.network/",
|
||||
"contracts": {
|
||||
"WETH": "0x4C3A213bd5e8c4BD70a8396d6F3C8302571598Cd",
|
||||
"curveRegistry": ""
|
||||
}
|
||||
},
|
||||
"59144": {
|
||||
"chainId": "59144",
|
||||
"label": "Linea",
|
||||
"shortName": "Linea",
|
||||
"code": "linea",
|
||||
"baseCurrencyName": "ETH",
|
||||
"rpc": "https://rpc.linea.build/",
|
||||
"explorer": "https://lineascan.build/",
|
||||
"contracts": {
|
||||
"WETH": "0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f",
|
||||
"curveRegistry": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"production": {
|
||||
"analyticsAPI": "https://trade.orionprotocol.io/api/stats",
|
||||
"referralAPI": "https://trade.orionprotocol.io/referral-api",
|
||||
"analyticsAPI": "https://trade.orion.xyz/api/stats",
|
||||
"referralAPI": "https://trade.orion.xyz/referral-api",
|
||||
"networks": {
|
||||
"1": {
|
||||
"api": "https://trade.orionprotocol.io/eth-mainnet",
|
||||
"api": "https://trade.orion.xyz/eth-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -15,12 +15,15 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
},
|
||||
"liquidityMigratorAddress": "0x23a1820a47BcD022E29f6058a5FD224242F50D1A"
|
||||
},
|
||||
"56": {
|
||||
"api": "https://trade.orionprotocol.io/bsc-mainnet",
|
||||
"api": "https://trade.orion.xyz/bsc-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -31,11 +34,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"250": {
|
||||
"api": "https://trade.orionprotocol.io/ftm-mainnet",
|
||||
"api": "https://trade.orion.xyz/ftm-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -46,11 +52,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"137": {
|
||||
"api": "https://trade.orionprotocol.io/polygon-mainnet",
|
||||
"api": "https://trade.orion.xyz/polygon-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -61,11 +70,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"66": {
|
||||
"api": "https://trade.orionprotocol.io/okc-mainnet",
|
||||
"api": "https://trade.orion.xyz/okc-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -76,17 +88,92 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"42161": {
|
||||
"api": "https://trade.orion.xyz/arbitrum-one",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"api": "https://trade.orion.xyz/opbnb-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2525": {
|
||||
"api": "https://trade.orion.xyz/inevm-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"59144": {
|
||||
"api": "https://trade.orion.xyz/linea-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"testing": {
|
||||
"analyticsAPI": "https://trade.orionprotocol.io/api/stats",
|
||||
"referralAPI": "https://testing.orionprotocol.io/referral-api",
|
||||
"analyticsAPI": "https://trade.orion.xyz/api/stats",
|
||||
"referralAPI": "https://testing.orion.xyz/referral-api",
|
||||
"networks": {
|
||||
"97": {
|
||||
"api": "https://testing.orionprotocol.io/bsc-testnet",
|
||||
"api": "https://testing.orion.xyz/bsc-testnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -97,12 +184,15 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
},
|
||||
"liquidityMigratorAddress": "0x01b10dds12478C88A5E18e2707E729906bC25CfF6"
|
||||
},
|
||||
"5": {
|
||||
"api": "https://testing.orionprotocol.io/eth-goerli",
|
||||
"api": "https://testing.orion.xyz/eth-goerli",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -113,11 +203,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"421613": {
|
||||
"api": "https://testing.orionprotocol.io/arbitrum-goerli",
|
||||
"api": "https://testing.orion.xyz/arbitrum-goerli",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -128,11 +221,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"4002": {
|
||||
"api": "https://testing.orionprotocol.io/ftm-testnet",
|
||||
"api": "https://testing.orion.xyz/ftm-testnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -143,11 +239,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"80001": {
|
||||
"api": "https://testing.orionprotocol.io/polygon-mumbai",
|
||||
"api": "https://testing.orion.xyz/polygon-mumbai",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -158,11 +257,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"56303": {
|
||||
"api": "https://testing.orionprotocol.io/drip-testnet",
|
||||
"api": "https://testing.orion.xyz/drip-testnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -173,17 +275,20 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"staging": {
|
||||
"analyticsAPI": "https://trade.orionprotocol.io/api/stats",
|
||||
"referralAPI": "https://staging.orionprotocol.io/referral-api",
|
||||
"analyticsAPI": "https://trade.orion.xyz/api/stats",
|
||||
"referralAPI": "https://staging.orion.xyz/referral-api",
|
||||
"networks": {
|
||||
"1": {
|
||||
"api": "https://staging.orionprotocol.io/eth-mainnet",
|
||||
"api": "https://staging.orion.xyz/eth-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -194,11 +299,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"56": {
|
||||
"api": "https://staging.orionprotocol.io/bsc-mainnet",
|
||||
"api": "https://staging.orion.xyz/bsc-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -209,11 +317,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"250": {
|
||||
"api": "https://staging.orionprotocol.io/ftm-mainnet",
|
||||
"api": "https://staging.orion.xyz/ftm-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -224,11 +335,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"137": {
|
||||
"api": "https://staging.orionprotocol.io/polygon-mainnet",
|
||||
"api": "https://staging.orion.xyz/polygon-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -239,11 +353,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"66": {
|
||||
"api": "https://staging.orionprotocol.io/okc-mainnet",
|
||||
"api": "https://staging.orion.xyz/okc-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -254,17 +371,92 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"42161": {
|
||||
"api": "https://staging.orion.xyz/arbitrum-one",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"204": {
|
||||
"api": "https://staging.orion.xyz/opbnb-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"2525": {
|
||||
"api": "https://trade.orion.xyz/inevm-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"59144": {
|
||||
"api": "https://trade.orion.xyz/linea-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
"ws": "/v1"
|
||||
},
|
||||
"blockchain": {
|
||||
"http": ""
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"analyticsAPI": "https://trade.orionprotocol.io/api/stats",
|
||||
"referralAPI": "https://testing.orionprotocol.io/referral-api",
|
||||
"analyticsAPI": "https://trade.orion.xyz/api/stats",
|
||||
"referralAPI": "https://testing.orion.xyz/referral-api",
|
||||
"networks": {
|
||||
"97": {
|
||||
"api": "https://dn-dev.orionprotocol.io/bsc-testnet",
|
||||
"api": "https://dn-dev.orion.xyz/bsc-testnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -275,11 +467,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"3": {
|
||||
"api": "https://dn-dev.orionprotocol.io/eth-ropsten",
|
||||
"api": "https://dn-dev.orion.xyz/eth-ropsten",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -290,17 +485,20 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kucoin-production": {
|
||||
"analyticsAPI": "https://trade.orionprotocol.io/api/stats",
|
||||
"referralAPI": "https://trade.orionprotocol.io/referral-api",
|
||||
"analyticsAPI": "https://trade.orion.xyz/api/stats",
|
||||
"referralAPI": "https://trade.orion.xyz/referral-api",
|
||||
"networks": {
|
||||
"1": {
|
||||
"api": "https://trade.orionprotocol.io/eth-mainnet",
|
||||
"api": "https://trade.orion.xyz/eth-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -311,12 +509,15 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
},
|
||||
"liquidityMigratorAddress": "0x23a1820a47BcD022E29f6058a5FD224242F50D1A"
|
||||
},
|
||||
"56": {
|
||||
"api": "https://trade.orionprotocol.io/bsc-mainnet",
|
||||
"api": "https://trade.orion.xyz/bsc-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -327,11 +528,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"250": {
|
||||
"api": "https://trade.orionprotocol.io/ftm-mainnet",
|
||||
"api": "https://trade.orion.xyz/ftm-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -342,11 +546,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"137": {
|
||||
"api": "https://trade.orionprotocol.io/polygon-mainnet",
|
||||
"api": "https://trade.orion.xyz/polygon-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -357,11 +564,14 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
},
|
||||
"66": {
|
||||
"api": "https://trade.orionprotocol.io/okc-mainnet",
|
||||
"api": "https://trade.orion.xyz/okc-mainnet",
|
||||
"services": {
|
||||
"aggregator": {
|
||||
"http": "/backend",
|
||||
@@ -372,9 +582,12 @@
|
||||
},
|
||||
"priceFeed": {
|
||||
"all": "/price-feed"
|
||||
},
|
||||
"indexer": {
|
||||
"http": "/orion-indexer/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ export const pureEnvNetworksSchema = z.object({
|
||||
priceFeed: z.object({
|
||||
all: z.string(),
|
||||
}),
|
||||
indexer: z.object({
|
||||
http: z.string(),
|
||||
}).optional(),
|
||||
}),
|
||||
rpc: z.string().optional(),
|
||||
liquidityMigratorAddress: z.string().optional(),
|
||||
|
||||
@@ -15,4 +15,8 @@ export const productionChains = [
|
||||
SupportedChainId.FANTOM_OPERA,
|
||||
SupportedChainId.POLYGON,
|
||||
SupportedChainId.OKC,
|
||||
SupportedChainId.ARBITRUM,
|
||||
SupportedChainId.OPBNB,
|
||||
SupportedChainId.INEVM,
|
||||
SupportedChainId.LINEA,
|
||||
];
|
||||
|
||||
@@ -23,4 +23,5 @@ export default [
|
||||
'OKXSWAP',
|
||||
'CURVE',
|
||||
'CURVE_FACTORY',
|
||||
'THENA_ALGEBRA_V1',
|
||||
] as const;
|
||||
|
||||
@@ -12,17 +12,14 @@ const mapping: Record<string, string> = {
|
||||
PANCAKESWAP: 'PancakeSwap',
|
||||
UNISWAP: 'Uniswap',
|
||||
QUICKSWAP: 'QuickSwap',
|
||||
ORION_POOL: 'Orion Pool',
|
||||
INTERNAL_POOL_V2: 'Orion Pool V2',
|
||||
INTERNAL_POOL_V3: 'Orion Pool V3',
|
||||
INTERNAL_POOL_V3_0_01: 'Orion Pool V3',
|
||||
INTERNAL_POOL_V3_0_05: 'Orion Pool V3',
|
||||
INTERNAL_POOL_V3_0_3: 'Orion Pool V3',
|
||||
INTERNAL_POOL_V3_1_0: 'Orion Pool V3',
|
||||
ORION_POOL: 'Internal Pool',
|
||||
INTERNAL_POOL_V2: 'Internal Pool V2',
|
||||
INTERNAL_POOL_V3: 'Internal Pool V3',
|
||||
CHERRYSWAP: 'CherrySwap',
|
||||
OKXSWAP: 'OKXSwap',
|
||||
CURVE: 'Curve',
|
||||
CURVE_FACTORY: 'Curve Factory',
|
||||
THENA_ALGEBRA_V1: 'Thena',
|
||||
};
|
||||
|
||||
export default mapping;
|
||||
|
||||
1
src/constants/factories.ts
Normal file
1
src/constants/factories.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3"] as const
|
||||
@@ -9,5 +9,6 @@ export { default as exchangesMap } from './exchangesMap.js';
|
||||
export * from './chains.js';
|
||||
export * from './precisions.js';
|
||||
export * from './gasLimits.js';
|
||||
export * from './timings.js';
|
||||
|
||||
export const SERVICE_TOKEN = 'ORN';
|
||||
|
||||
@@ -1 +1 @@
|
||||
export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'drip'] as const;
|
||||
export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'drip', 'opbnb', 'inevm', 'linea'] as const;
|
||||
|
||||
3
src/constants/timings.ts
Normal file
3
src/constants/timings.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const DAY = 86400
|
||||
export const WEEK_DAYS = 7;
|
||||
export const YEAR = 365 * DAY
|
||||
@@ -1 +1 @@
|
||||
export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB'] as const;
|
||||
export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB', 'OPBNB', 'INEVM', 'LINEA'] as const;
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { ethers, keccak256 } from 'ethers';
|
||||
import type { Order } from '../types.js';
|
||||
|
||||
const hashOrder = (order: Order) => ethers.utils.solidityKeccak256(
|
||||
[
|
||||
'uint8',
|
||||
'address',
|
||||
'address',
|
||||
'address',
|
||||
'address',
|
||||
'address',
|
||||
'uint64',
|
||||
'uint64',
|
||||
'uint64',
|
||||
'uint64',
|
||||
'uint64',
|
||||
'uint8',
|
||||
],
|
||||
[
|
||||
'0x03',
|
||||
order.senderAddress,
|
||||
order.matcherAddress,
|
||||
order.baseAsset,
|
||||
order.quoteAsset,
|
||||
order.matcherFeeAsset,
|
||||
order.amount,
|
||||
order.price,
|
||||
order.matcherFee,
|
||||
order.nonce,
|
||||
order.expiration,
|
||||
order.buySide === 1 ? '0x01' : '0x00',
|
||||
],
|
||||
);
|
||||
const ORDER_TYPEHASH = "0xb5132db62dfceb466f2f8aee7a039db36a99772e5a9771d28388a5f9baad7c54"
|
||||
|
||||
export default hashOrder;
|
||||
export default function getOrderHash(order: Order) {
|
||||
const abiCoder = ethers.AbiCoder.defaultAbiCoder()
|
||||
|
||||
const { senderAddress, matcherAddress, baseAsset, quoteAsset, matcherFeeAsset, amount, price, matcherFee, nonce, expiration, buySide } = order
|
||||
const orderBytes = abiCoder.encode(
|
||||
["bytes32", "address", "address", "address", "address", "address", "uint64", "uint64", "uint64", "uint64", "uint64", "uint8"],
|
||||
[ORDER_TYPEHASH, senderAddress, matcherAddress, baseAsset, quoteAsset, matcherFeeAsset, amount, price, matcherFee, nonce, expiration, buySide]
|
||||
)
|
||||
|
||||
return keccak256(orderBytes)
|
||||
}
|
||||
@@ -1,4 +1,2 @@
|
||||
export { default as signCancelOrder } from './signCancelOrder.js';
|
||||
export { default as signCancelOrderPersonal } from './signCancelOrderPersonal.js';
|
||||
export { default as signOrder } from './signOrder.js';
|
||||
export { default as signOrderPersonal } from './signOrderPersonal.js';
|
||||
|
||||
@@ -1,40 +1,33 @@
|
||||
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
|
||||
import type { ethers } from 'ethers';
|
||||
import { joinSignature, splitSignature } from 'ethers/lib/utils.js';
|
||||
import { ethers } from 'ethers';
|
||||
import CANCEL_ORDER_TYPES from '../constants/cancelOrderTypes.js';
|
||||
import type { CancelOrderRequest, SignedCancelOrderRequest, SupportedChainId } from '../types.js';
|
||||
import getDomainData from './getDomainData.js';
|
||||
import signCancelOrderPersonal from './signCancelOrderPersonal.js';
|
||||
|
||||
type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner;
|
||||
|
||||
const signCancelOrder = async (
|
||||
senderAddress: string,
|
||||
id: string,
|
||||
usePersonalSign: boolean,
|
||||
signer: ethers.Signer,
|
||||
chainId: SupportedChainId,
|
||||
) => {
|
||||
const cancelOrderRequest: CancelOrderRequest = {
|
||||
id,
|
||||
senderAddress,
|
||||
isPersonalSign: usePersonalSign,
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const typedDataSigner = signer as SignerWithTypedDataSign;
|
||||
|
||||
const signature = usePersonalSign
|
||||
? await signCancelOrderPersonal(cancelOrderRequest, signer)
|
||||
// https://docs.ethers.io/v5/api/signer/#Signer-signTypedData
|
||||
: await typedDataSigner._signTypedData(
|
||||
getDomainData(chainId),
|
||||
CANCEL_ORDER_TYPES,
|
||||
cancelOrderRequest,
|
||||
);
|
||||
const signature = await typedDataSigner.signTypedData(
|
||||
getDomainData(chainId),
|
||||
CANCEL_ORDER_TYPES,
|
||||
cancelOrderRequest,
|
||||
);
|
||||
|
||||
// https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265
|
||||
// "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1"
|
||||
const fixedSignature = joinSignature(splitSignature(signature));
|
||||
const fixedSignature = ethers.Signature.from(signature).serialized;
|
||||
|
||||
// if (!fixedSignature) throw new Error("Can't sign order cancel");
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { arrayify, joinSignature, splitSignature } from 'ethers/lib/utils.js';
|
||||
import type { CancelOrderRequest } from '../types.js';
|
||||
|
||||
const signCancelOrderPersonal = async (
|
||||
cancelOrderRequest: CancelOrderRequest,
|
||||
signer: ethers.Signer,
|
||||
) => {
|
||||
const types = ['string', 'string', 'address'];
|
||||
const message = ethers.utils.solidityKeccak256(
|
||||
types,
|
||||
['cancelOrder', cancelOrderRequest.id, cancelOrderRequest.senderAddress],
|
||||
);
|
||||
const signature = await signer.signMessage(arrayify(message));
|
||||
|
||||
// NOTE: metamask broke sig.v value and we fix it in next line
|
||||
return joinSignature(splitSignature(signature));
|
||||
};
|
||||
|
||||
export default signCancelOrderPersonal;
|
||||
@@ -1,14 +1,12 @@
|
||||
import type { TypedDataSigner } from '@ethersproject/abstract-signer';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { ethers } from 'ethers';
|
||||
import { joinSignature, splitSignature } from 'ethers/lib/utils.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { INTERNAL_PROTOCOL_PRECISION } from '../constants/index.js';
|
||||
import ORDER_TYPES from '../constants/orderTypes.js';
|
||||
import type { Order, SignedOrder, SupportedChainId } from '../types.js';
|
||||
import normalizeNumber from '../utils/normalizeNumber.js';
|
||||
import getDomainData from './getDomainData.js';
|
||||
import hashOrder from './hashOrder.js';
|
||||
import signOrderPersonal from './signOrderPersonal.js';
|
||||
|
||||
const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days
|
||||
|
||||
@@ -24,7 +22,6 @@ export const signOrder = async (
|
||||
senderAddress: string,
|
||||
matcherAddress: string,
|
||||
serviceFeeAssetAddr: string,
|
||||
usePersonalSign: boolean,
|
||||
signer: ethers.Signer,
|
||||
chainId: SupportedChainId,
|
||||
) => {
|
||||
@@ -37,41 +34,38 @@ export const signOrder = async (
|
||||
baseAsset: baseAssetAddr,
|
||||
quoteAsset: quoteAssetAddr,
|
||||
matcherFeeAsset: serviceFeeAssetAddr,
|
||||
amount: normalizeNumber(
|
||||
amount: Number(normalizeNumber(
|
||||
amount,
|
||||
INTERNAL_PROTOCOL_PRECISION,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toNumber(),
|
||||
price: normalizeNumber(
|
||||
)),
|
||||
price: Number(normalizeNumber(
|
||||
price,
|
||||
INTERNAL_PROTOCOL_PRECISION,
|
||||
BigNumber.ROUND_FLOOR,
|
||||
).toNumber(),
|
||||
matcherFee: normalizeNumber(
|
||||
)),
|
||||
matcherFee: Number(normalizeNumber(
|
||||
matcherFee,
|
||||
INTERNAL_PROTOCOL_PRECISION,
|
||||
BigNumber.ROUND_CEIL, // ROUND_CEIL because we don't want get "not enough fee" error
|
||||
).toNumber(),
|
||||
)),
|
||||
nonce,
|
||||
expiration,
|
||||
buySide: side === 'BUY' ? 1 : 0,
|
||||
isPersonalSign: usePersonalSign,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const typedDataSigner = signer as SignerWithTypedDataSign;
|
||||
|
||||
const signature = usePersonalSign
|
||||
? await signOrderPersonal(order, signer)
|
||||
: await typedDataSigner._signTypedData(
|
||||
getDomainData(chainId),
|
||||
ORDER_TYPES,
|
||||
order,
|
||||
);
|
||||
const signature = await typedDataSigner.signTypedData(
|
||||
getDomainData(chainId),
|
||||
ORDER_TYPES,
|
||||
order,
|
||||
);
|
||||
|
||||
// https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265
|
||||
// "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1"
|
||||
const fixedSignature = joinSignature(splitSignature(signature));
|
||||
const fixedSignature = ethers.Signature.from(signature).serialized;
|
||||
|
||||
// if (!fixedSignature) throw new Error("Can't sign order");
|
||||
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { ethers } from 'ethers';
|
||||
import type { Order } from '../types.js';
|
||||
|
||||
const { arrayify, joinSignature, splitSignature } = ethers.utils;
|
||||
|
||||
const signOrderPersonal = async (order: Order, signer: ethers.Signer) => {
|
||||
const message = ethers.utils.solidityKeccak256(
|
||||
[
|
||||
'string', 'address', 'address', 'address', 'address',
|
||||
'address', 'uint64', 'uint64', 'uint64', 'uint64', 'uint64', 'uint8',
|
||||
],
|
||||
[
|
||||
'order',
|
||||
order.senderAddress,
|
||||
order.matcherAddress,
|
||||
order.baseAsset,
|
||||
order.quoteAsset,
|
||||
order.matcherFeeAsset,
|
||||
order.amount,
|
||||
order.price,
|
||||
order.matcherFee,
|
||||
order.nonce,
|
||||
order.expiration,
|
||||
order.buySide,
|
||||
],
|
||||
);
|
||||
const signature = await signer.signMessage(arrayify(message));
|
||||
|
||||
// NOTE: metamask broke sig.v value and we fix it in next line
|
||||
return joinSignature(splitSignature(signature));
|
||||
};
|
||||
|
||||
export default signOrderPersonal;
|
||||
@@ -4,6 +4,8 @@ BigNumber.config({ EXPONENTIAL_AT: 1e+9 });
|
||||
export * as config from './config/index.js';
|
||||
export { default as Unit } from './Unit/index.js';
|
||||
export { default as Orion } from './Orion/index.js';
|
||||
export {generateSwapCalldata} from './Unit/Exchange/generateSwapCalldata.js';
|
||||
export { default as factories} from './constants/factories.js';
|
||||
export * as utils from './utils/index.js';
|
||||
export * as services from './services/index.js';
|
||||
export * as crypt from './crypt/index.js';
|
||||
|
||||
@@ -8,7 +8,7 @@ import errorSchema from './schemas/errorSchema.js';
|
||||
import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema.js';
|
||||
import { AggregatorWS } from './ws/index.js';
|
||||
import { atomicSwapHistorySchema } from './schemas/atomicSwapHistorySchema.js';
|
||||
import type { BasicAuthCredentials, SignedCancelOrderRequest, SignedOrder } from '../../types.js';
|
||||
import type { BasicAuthCredentials, OrderSource, SignedCancelOrderRequest, SignedOrder } from '../../types.js';
|
||||
import {
|
||||
pairConfigSchema, aggregatedOrderbookSchema,
|
||||
exchangeOrderbookSchema, poolReservesSchema,
|
||||
@@ -74,13 +74,13 @@ class Aggregator {
|
||||
}
|
||||
|
||||
getOrder = (orderId: string, owner?: string) => {
|
||||
if (!ethers.utils.isHexString(orderId)) {
|
||||
if (!ethers.isHexString(orderId)) {
|
||||
throw new Error(`Invalid order id: ${orderId}. Must be a hex string`);
|
||||
}
|
||||
const url = new URL(`${this.apiUrl}/api/v1/order`);
|
||||
url.searchParams.append('orderId', orderId);
|
||||
if (owner !== undefined) {
|
||||
if (!ethers.utils.isAddress(owner)) {
|
||||
if (!ethers.isAddress(owner)) {
|
||||
throw new Error(`Invalid owner address: ${owner}`);
|
||||
}
|
||||
url.searchParams.append('owner', owner);
|
||||
@@ -196,7 +196,8 @@ class Aggregator {
|
||||
isCreateInternalOrder: boolean,
|
||||
isReversedOrder?: boolean,
|
||||
partnerId?: string,
|
||||
fromWidget?: boolean,
|
||||
source?: OrderSource,
|
||||
rawExchangeRestrictions?: string | undefined,
|
||||
) => {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -205,7 +206,7 @@ class Aggregator {
|
||||
'X-Reverse-Order': isReversedOrder ? 'true' : 'false',
|
||||
},
|
||||
...(partnerId !== undefined) && { 'X-Partner-Id': partnerId },
|
||||
...(fromWidget !== undefined) && { 'X-From-Widget': fromWidget ? 'true' : 'false' },
|
||||
...(source !== undefined) && { 'X-Source': source },
|
||||
...this.basicAuthHeaders,
|
||||
};
|
||||
|
||||
@@ -226,7 +227,7 @@ class Aggregator {
|
||||
{
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: JSON.stringify(signedOrder),
|
||||
body: JSON.stringify({ ...signedOrder, rawExchangeRestrictions }),
|
||||
},
|
||||
errorSchema,
|
||||
);
|
||||
|
||||
@@ -3,22 +3,22 @@ import { z } from 'zod';
|
||||
import { exchanges, orderStatuses, subOrderStatuses } from '../../../constants/index.js';
|
||||
|
||||
const blockchainOrderSchema = z.object({
|
||||
id: z.string().refine(ethers.utils.isHexString, (value) => ({
|
||||
id: z.string().refine(ethers.isHexString, (value) => ({
|
||||
message: `blockchainOrder.id must be a hex string, got ${value}`,
|
||||
})),
|
||||
senderAddress: z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
senderAddress: z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `blockchainOrder.senderAddress must be an address, got ${value}`,
|
||||
})),
|
||||
matcherAddress: z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
matcherAddress: z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `blockchainOrder.matcherAddress must be an address, got ${value}`,
|
||||
})),
|
||||
baseAsset: z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
baseAsset: z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `blockchainOrder.baseAsset must be an address, got ${value}`,
|
||||
})),
|
||||
quoteAsset: z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
quoteAsset: z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `blockchainOrder.quoteAsset must be an address, got ${value}`,
|
||||
})),
|
||||
matcherFeeAsset: z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
matcherFeeAsset: z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `blockchainOrder.matcherFeeAsset must be an address, got ${value}`,
|
||||
})),
|
||||
amount: z.number().int().nonnegative(),
|
||||
@@ -27,7 +27,7 @@ const blockchainOrderSchema = z.object({
|
||||
nonce: z.number(),
|
||||
expiration: z.number(),
|
||||
buySide: z.union([z.literal(1), z.literal(0)]),
|
||||
signature: z.string().refine(ethers.utils.isHexString, (value) => ({
|
||||
signature: z.string().refine(ethers.isHexString, (value) => ({
|
||||
message: `blockchainOrder.signature must be a hex string, got ${value}`,
|
||||
})).nullable(),
|
||||
isPersonalSign: z.boolean(),
|
||||
@@ -43,8 +43,6 @@ const tradeInfoSchema = z.object({
|
||||
updateTime: z.number(),
|
||||
matchedBlockchainOrder: blockchainOrderSchema.optional(),
|
||||
matchedSubOrderId: z.number().int().nonnegative().optional(),
|
||||
exchangeTradeInfo: z.boolean(),
|
||||
poolTradeInfo: z.boolean(),
|
||||
});
|
||||
|
||||
const baseOrderSchema = z.object({
|
||||
@@ -53,7 +51,7 @@ const baseOrderSchema = z.object({
|
||||
amount: z.number().nonnegative(),
|
||||
remainingAmount: z.number().nonnegative(),
|
||||
price: z.number().nonnegative(),
|
||||
sender: z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
sender: z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `order.sender must be an address, got ${value}`,
|
||||
})),
|
||||
filledAmount: z.number().nonnegative(),
|
||||
@@ -77,16 +75,17 @@ const brokerAddressSchema = z.enum([
|
||||
'SELF_BROKER'
|
||||
])
|
||||
.or(selfBrokerSchema)
|
||||
.or(z.string().refine(ethers.utils.isAddress, (value) => ({
|
||||
.or(z.string().refine(ethers.isAddress, (value) => ({
|
||||
message: `subOrder.subOrders.[n].brokerAddress must be an address, got ${value}`,
|
||||
})));
|
||||
const subOrderSchema = baseOrderSchema.extend({
|
||||
price: z.number(),
|
||||
id: z.number(),
|
||||
parentOrderId: z.string().refine(ethers.utils.isHexString, (value) => ({
|
||||
parentOrderId: z.string().refine(ethers.isHexString, (value) => ({
|
||||
message: `subOrder.parentOrderId must be a hex string, got ${value}`,
|
||||
})),
|
||||
exchange: z.string(),
|
||||
exchanges: z.string().array().optional(),
|
||||
brokerAddress: brokerAddressSchema,
|
||||
tradesInfo: z.record(
|
||||
z.string().uuid(),
|
||||
@@ -97,11 +96,11 @@ const subOrderSchema = baseOrderSchema.extend({
|
||||
});
|
||||
|
||||
const orderSchema = z.object({
|
||||
orderId: z.string().refine(ethers.utils.isHexString, (value) => ({
|
||||
orderId: z.string().refine(ethers.isHexString, (value) => ({
|
||||
message: `orderId must be a hex string, got ${value}`,
|
||||
})),
|
||||
order: baseOrderSchema.extend({
|
||||
id: z.string().refine(ethers.utils.isHexString, (value) => ({
|
||||
id: z.string().refine(ethers.isHexString, (value) => ({
|
||||
message: `order.id must be a hex string, got ${value}`,
|
||||
})),
|
||||
fee: z.number().nonnegative(),
|
||||
|
||||
@@ -7,6 +7,13 @@ const orderInfoSchema = z.object({
|
||||
safePrice: z.number(),
|
||||
}).nullable();
|
||||
|
||||
const exchangeContractStep = z.object({
|
||||
pool: z.string(),
|
||||
assetIn: z.string(),
|
||||
assetOut: z.string(),
|
||||
factory: z.string(),
|
||||
});
|
||||
|
||||
const swapInfoBase = z.object({
|
||||
id: z.string(),
|
||||
amountIn: z.number(),
|
||||
@@ -22,12 +29,7 @@ const swapInfoBase = z.object({
|
||||
minAmountOut: z.number(),
|
||||
minAmountIn: z.number(),
|
||||
marketPrice: z.number().nullable(), // spending asset market price
|
||||
exchangeContractPath: z.array(z.object({
|
||||
pool: z.string(),
|
||||
assetIn: z.string(),
|
||||
assetOut: z.string(),
|
||||
factory: z.string(),
|
||||
})),
|
||||
exchangeContractPath: z.array(exchangeContractStep),
|
||||
alternatives: z.object({ // execution alternatives
|
||||
exchanges: z.array(z.string()),
|
||||
path: z.array(z.string()),
|
||||
@@ -40,6 +42,13 @@ const swapInfoBase = z.object({
|
||||
isThroughPoolOrCurve: z.boolean(),
|
||||
}).array(),
|
||||
assetNameMapping: z.record(z.string()).optional(), // address to ERC20 names
|
||||
usd: z.object({ // USD info of this swap, nullable
|
||||
aa: z.number().optional(), // available amount in, USD
|
||||
aao: z.number().optional(), // available amount out, USD
|
||||
mo: z.number().optional(), // market amount out, USD
|
||||
mi: z.number().optional(), // market amount in, USD
|
||||
d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
const swapInfoByAmountIn = swapInfoBase.extend({
|
||||
|
||||
@@ -17,8 +17,6 @@ import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema.js';
|
||||
import assetPairConfigSchema from './schemas/assetPairConfigSchema.js';
|
||||
import type { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema.js';
|
||||
import { objectKeys } from '../../../utils/objectKeys.js';
|
||||
// import assertError from '../../../utils/assertError.js';
|
||||
// import errorSchema from './schemas/errorSchema';
|
||||
|
||||
const UNSUBSCRIBE = 'u';
|
||||
const SERVER_PING_INTERVAL = 30000;
|
||||
@@ -42,7 +40,7 @@ type SwapInfoSubscriptionPayload = {
|
||||
i: string // asset in
|
||||
o: string // asset out
|
||||
a: number // amount IN/OUT
|
||||
es?: string[] | 'cex' | 'pools' // exchange list of all cex or all pools (ORION_POOL, UNISWAP, PANCAKESWAP etc)
|
||||
es?: string // exchange list of all cex or all pools (ORION_POOL, UNISWAP, PANCAKESWAP etc)
|
||||
e?: boolean // is amount IN? Value `false` means a = amount OUT, `true` if omitted
|
||||
is?: boolean // instant settlement
|
||||
}
|
||||
@@ -528,6 +526,13 @@ class AggregatorWS {
|
||||
availableAmountOut: item.aao,
|
||||
})),
|
||||
assetsNameMapping: json.anm,
|
||||
usdInfo: json.usd && {
|
||||
availableAmountIn: json.usd.aa,
|
||||
availableAmountOut: json.usd.aao,
|
||||
marketAmountOut: json.usd.mo,
|
||||
marketAmountIn: json.usd.mi,
|
||||
difference: json.usd.d,
|
||||
},
|
||||
};
|
||||
|
||||
switch (json.k) { // kind
|
||||
|
||||
@@ -23,6 +23,7 @@ const subOrderSchema = z.object({
|
||||
A: z.number(), // settled amount
|
||||
p: z.number(), // avg weighed settlement price
|
||||
e: z.string(), // exchange
|
||||
es: z.string().array().optional(), // exchanges
|
||||
b: z.string(), // broker address
|
||||
S: z.enum(subOrderStatuses), // status
|
||||
o: z.boolean(), // internal only
|
||||
@@ -52,6 +53,7 @@ export const orderUpdateSchema = z.object({
|
||||
subOrders: o.c.map((so) => ({
|
||||
pair: so.P,
|
||||
exchange: so.e,
|
||||
exchanges: so.es,
|
||||
id: so.i,
|
||||
amount: so.a,
|
||||
settledAmount: so.A,
|
||||
@@ -106,6 +108,7 @@ export const fullOrderSchema = z.object({
|
||||
subOrders: o.c.map((so) => ({
|
||||
pair: so.P,
|
||||
exchange: so.e,
|
||||
exchanges: so.es,
|
||||
id: so.i,
|
||||
amount: so.a,
|
||||
settledAmount: so.A,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { z } from 'zod';
|
||||
import MessageType from '../MessageType.js';
|
||||
import baseMessageSchema from './baseMessageSchema.js';
|
||||
import factories from '../../../../constants/factories.js';
|
||||
|
||||
const alternativeSchema = z.object({ // execution alternatives
|
||||
e: z.string().array(), // exchanges
|
||||
@@ -11,6 +12,7 @@ const alternativeSchema = z.object({ // execution alternatives
|
||||
aa: z.number().optional(), // available amount in
|
||||
aao: z.number().optional(), // available amount out
|
||||
});
|
||||
const factorySchema = z.enum(factories);
|
||||
const swapInfoSchemaBase = baseMessageSchema.extend({
|
||||
T: z.literal(MessageType.SWAP_INFO),
|
||||
S: z.string(), // swap request id
|
||||
@@ -37,8 +39,15 @@ const swapInfoSchemaBase = baseMessageSchema.extend({
|
||||
p: z.string(), // pool address
|
||||
ai: z.string().toUpperCase(), // asset in
|
||||
ao: z.string().toUpperCase(), // asset out
|
||||
f: z.string().toUpperCase(), // factory
|
||||
}))
|
||||
f: factorySchema, // factory
|
||||
})),
|
||||
usd: z.object({ // USD info of this swap, nullable
|
||||
aa: z.number().optional(), // available amount in, USD
|
||||
aao: z.number().optional(), // available amount out, USD
|
||||
mo: z.number().optional(), // market amount out, USD
|
||||
mi: z.number().optional(), // market amount in, USD
|
||||
d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage
|
||||
}).optional(),
|
||||
});
|
||||
|
||||
const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
|
||||
|
||||
@@ -10,15 +10,12 @@ import {
|
||||
userEarnedSchema,
|
||||
type PairStatusEnum,
|
||||
pairStatusSchema,
|
||||
governanceContractsSchema,
|
||||
governancePoolsSchema,
|
||||
governancePoolSchema,
|
||||
governanceChainsInfoSchema,
|
||||
pricesWithQuoteAssetSchema,
|
||||
referralDataSchema,
|
||||
} from './schemas/index.js';
|
||||
import type redeemOrderSchema from '../Aggregator/schemas/redeemOrderSchema.js';
|
||||
import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/atomicHistorySchema.js';
|
||||
import { makePartial } from '../../utils/index.js';
|
||||
import { makePartial } from '../../utils';
|
||||
import type { networkCodes } from '../../constants/index.js';
|
||||
import { fetchWithValidation } from 'simple-typed-fetch';
|
||||
import type { BasicAuthCredentials } from '../../types.js';
|
||||
@@ -59,6 +56,14 @@ type AtomicSwapHistoryTargetQuery = AtomicSwapHistoryBaseQuery & {
|
||||
expiredRedeem?: 0 | 1
|
||||
state?: 'REDEEMED' | 'BEFORE-REDEEM'
|
||||
}
|
||||
|
||||
type PlatformFees = {
|
||||
assetIn: string
|
||||
assetOut: string
|
||||
walletAddress?: string | undefined
|
||||
fromWidget?: string | undefined
|
||||
}
|
||||
|
||||
class BlockchainService {
|
||||
private readonly apiUrl: string;
|
||||
|
||||
@@ -106,10 +111,7 @@ class BlockchainService {
|
||||
this.getBlockNumber = this.getBlockNumber.bind(this);
|
||||
this.getRedeemOrderBySecretHash = this.getRedeemOrderBySecretHash.bind(this);
|
||||
this.claimOrder = this.claimOrder.bind(this);
|
||||
this.getGovernanceContracts = this.getGovernanceContracts.bind(this);
|
||||
this.getGovernancePools = this.getGovernancePools.bind(this);
|
||||
this.getGovernancePool = this.getGovernancePool.bind(this);
|
||||
this.getGovernanceChainsInfo = this.getGovernanceChainsInfo.bind(this);
|
||||
this.getGasLimits = this.getGasLimits.bind(this);
|
||||
}
|
||||
|
||||
get basicAuthHeaders() {
|
||||
@@ -223,31 +225,25 @@ class BlockchainService {
|
||||
);
|
||||
|
||||
/**
|
||||
* @deprecated In favor of getPlatformFees
|
||||
*/
|
||||
* @deprecated In favor of getPlatformFees
|
||||
*/
|
||||
getTokensFee = () => fetchWithValidation(
|
||||
`${this.apiUrl}/api/tokensFee`,
|
||||
z.record(z.string()).transform(makePartial),
|
||||
{ headers: this.basicAuthHeaders }
|
||||
);
|
||||
|
||||
getPlatformFees = (
|
||||
{ assetIn, assetOut, walletAddress, fromWidget }: {
|
||||
assetIn?: string | undefined,
|
||||
assetOut?: string | undefined,
|
||||
walletAddress?: string | undefined,
|
||||
fromWidget?: string | undefined
|
||||
}
|
||||
getPlatformFees = ({
|
||||
assetIn,
|
||||
assetOut,
|
||||
walletAddress,
|
||||
fromWidget
|
||||
}: PlatformFees
|
||||
) => {
|
||||
const url = new URL(`${this.apiUrl}/api/platform-fees`);
|
||||
|
||||
if (assetIn !== undefined) {
|
||||
url.searchParams.append('assetIn', assetIn);
|
||||
}
|
||||
|
||||
if (assetOut !== undefined) {
|
||||
url.searchParams.append('assetOut', assetOut);
|
||||
}
|
||||
url.searchParams.append('assetIn', assetIn);
|
||||
url.searchParams.append('assetOut', assetOut);
|
||||
|
||||
if (walletAddress !== undefined) {
|
||||
url.searchParams.append('walletAddress', walletAddress);
|
||||
@@ -264,6 +260,12 @@ class BlockchainService {
|
||||
)
|
||||
};
|
||||
|
||||
getReferralData = (walletAddress: string) => fetchWithValidation(
|
||||
`${this.apiUrl}/api/referral-data/${walletAddress}`,
|
||||
referralDataSchema,
|
||||
{ headers: this.basicAuthHeaders }
|
||||
);
|
||||
|
||||
getGasPriceWei = () => fetchWithValidation(
|
||||
`${this.apiUrl}/api/gasPrice`,
|
||||
z.string(),
|
||||
@@ -427,9 +429,9 @@ class BlockchainService {
|
||||
);
|
||||
|
||||
/**
|
||||
* Sender is user address in source BlockchainService instance \
|
||||
* Receiver is user address in target BlockchainService instance
|
||||
*/
|
||||
* Sender is user address in source BlockchainService instance \
|
||||
* Receiver is user address in target BlockchainService instance
|
||||
*/
|
||||
getAtomicSwapHistory = (query: AtomicSwapHistorySourceQuery | AtomicSwapHistoryTargetQuery) => {
|
||||
const url = new URL(`${this.apiUrl}/api/atomic/history/`);
|
||||
|
||||
@@ -484,28 +486,10 @@ class BlockchainService {
|
||||
},
|
||||
);
|
||||
|
||||
getGovernanceContracts = () => fetchWithValidation(
|
||||
`${this.apiUrl}/api/governance/info`,
|
||||
governanceContractsSchema,
|
||||
{ headers: this.basicAuthHeaders },
|
||||
);
|
||||
|
||||
getGovernancePools = () => fetchWithValidation(
|
||||
`${this.apiUrl}/api/governance/pools`,
|
||||
governancePoolsSchema,
|
||||
{ headers: this.basicAuthHeaders },
|
||||
);
|
||||
|
||||
getGovernancePool = (address: string) => fetchWithValidation(
|
||||
`${this.apiUrl}/api/governance/pools/${address}`,
|
||||
governancePoolSchema,
|
||||
{ headers: this.basicAuthHeaders },
|
||||
);
|
||||
|
||||
getGovernanceChainsInfo = () => fetchWithValidation(
|
||||
`${this.apiUrl}/api/governance/chains-info`,
|
||||
governanceChainsInfoSchema,
|
||||
{ headers: this.basicAuthHeaders },
|
||||
getGasLimits = () => fetchWithValidation(
|
||||
`${this.apiUrl}/api/baseLimits`,
|
||||
z.record(z.number()),
|
||||
{ headers: this.basicAuthHeaders }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@ const baseAtomicHistoryItem = z.object({
|
||||
_id: z.string(),
|
||||
__v: z.number(),
|
||||
asset: z.string(),
|
||||
sender: z.string().refine(ethers.utils.isAddress),
|
||||
secretHash: z.string().refine(ethers.utils.isHexString),
|
||||
receiver: z.string().refine(ethers.utils.isAddress).optional(),
|
||||
sender: z.string().refine(ethers.isAddress),
|
||||
secretHash: z.string().refine(ethers.isHexString),
|
||||
receiver: z.string().refine(ethers.isAddress).optional(),
|
||||
secret: z.string().optional(),
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const governanceChainsInfoSchema = z.object({
|
||||
isChainSupported: z.boolean(),
|
||||
});
|
||||
|
||||
export default governanceChainsInfoSchema;
|
||||
@@ -1,19 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const governanceContractsSchema = z.object({
|
||||
controllerAddress: z.string(),
|
||||
veTOKENAddress: z.string(),
|
||||
veTOKENYieldDistributorV4Address: z.string(),
|
||||
time_total: z.string(),
|
||||
absolute_ve_token_in_voting: z.string(),
|
||||
info: z.record(
|
||||
z.string(),
|
||||
z.object({
|
||||
gaugeAddress: z.string(),
|
||||
gaugeType: z.number(),
|
||||
gaugeName: z.string(),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export default governanceContractsSchema;
|
||||
@@ -1,18 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const governancePoolSchema = z.object({
|
||||
min_apr: z.string(),
|
||||
max_apr: z.string(),
|
||||
tvl: z.string(),
|
||||
lp_supply: z.string(),
|
||||
lp_staked: z.string(),
|
||||
lp_staked_with_boost: z.string(),
|
||||
lp_price_in_usd: z.string(),
|
||||
reward_per_period: z.string(),
|
||||
lock_time_for_max_multiplier: z.string(),
|
||||
lock_max_multiplier: z.string(),
|
||||
veorn_max_multiplier: z.string(),
|
||||
veorn_boost_scale_factor: z.string(),
|
||||
});
|
||||
|
||||
export default governancePoolSchema;
|
||||
@@ -1,28 +0,0 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const governancePoolsSchema = z.array(
|
||||
z.object({
|
||||
slug: z.string(),
|
||||
identifier: z.string(),
|
||||
chain: z.string(),
|
||||
platform: z.string(),
|
||||
logo: z.string(),
|
||||
pair: z.string(),
|
||||
lp_address: z.string(),
|
||||
lp_staked: z.string(),
|
||||
lp_staked_with_boost: z.string(),
|
||||
lp_supply: z.string(),
|
||||
lp_price_in_usd: z.string(),
|
||||
farm_address: z.string(),
|
||||
pool_tokens: z.tuple([z.string(), z.string()]),
|
||||
pool_rewards: z.array(z.string()),
|
||||
tvl: z.string(),
|
||||
min_apr: z.string(),
|
||||
max_apr: z.string(),
|
||||
reward_per_period: z.array(z.string()),
|
||||
weight: z.string(),
|
||||
liquidity: z.string(),
|
||||
})
|
||||
);
|
||||
|
||||
export default governancePoolsSchema;
|
||||
@@ -12,9 +12,6 @@ export { default as atomicSummarySchema } from './atomicSummarySchema.js';
|
||||
export { default as poolsLpAndStakedSchema } from './poolsLpAndStakedSchema.js';
|
||||
export { default as userVotesSchema } from './userVotesSchema.js';
|
||||
export { default as userEarnedSchema } from './userEarnedSchema.js';
|
||||
export { default as governanceContractsSchema } from './governanceContractsSchema.js';
|
||||
export { default as governancePoolsSchema } from './governancePoolsSchema.js';
|
||||
export { default as governancePoolSchema } from './governancePoolSchema.js';
|
||||
export { default as governanceChainsInfoSchema } from './governanceChainsInfoSchema.js';
|
||||
export { default as poolsV3InfoSchema } from './poolsV3InfoSchema.js';
|
||||
export { pricesWithQuoteAssetSchema } from './pricesWithQuoteAssetSchema.js';
|
||||
export { referralDataSchema } from './referralDataSchema.js';
|
||||
@@ -11,7 +11,7 @@ const infoSchema = z.object({
|
||||
chainId: z.number(),
|
||||
chainName: z.string(),
|
||||
exchangeContractAddress: z.string(),
|
||||
swapExecutorContractAddress: z.string().optional(),
|
||||
swapExecutorContractAddress: z.string(),
|
||||
oracleContractAddress: z.string(),
|
||||
matcherAddress: z.string(),
|
||||
orderFeePercent: z.number(),
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const referralDataSchema = z.object({
|
||||
referer: z.string().nullable(),
|
||||
isReferral: z.boolean(),
|
||||
});
|
||||
1
src/services/Indexer/constants.ts
Normal file
1
src/services/Indexer/constants.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const LOCK_START_TIME = 1690848000;// Aug 01 2023 00:00:00 UTC
|
||||
266
src/services/Indexer/index.ts
Normal file
266
src/services/Indexer/index.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
import {
|
||||
environmentResponseSchema,
|
||||
getPoolResponseSchema,
|
||||
listAmountResponseSchema,
|
||||
listNFTOrderResponseSchema,
|
||||
listPoolResponseSchema,
|
||||
listPoolV2ResponseSchema,
|
||||
listPoolV3ResponseSchema,
|
||||
PoolV2InfoResponseSchema,
|
||||
testIncrementorSchema,
|
||||
veORNInfoResponseSchema,
|
||||
votingInfoResponseSchema
|
||||
} from './schemas';
|
||||
import { fetchWithValidation } from 'simple-typed-fetch';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { WEEK_DAYS, YEAR } from '../../constants';
|
||||
import { LOCK_START_TIME } from './constants';
|
||||
|
||||
type BasePayload = {
|
||||
chainId: number
|
||||
jsonrpc: '1.0'
|
||||
};
|
||||
|
||||
type GetEnvironmentPayload = BasePayload & {
|
||||
model: 'Environment'
|
||||
method: 'getEnvironment'
|
||||
params: []
|
||||
};
|
||||
|
||||
type ListNFTOrderPayload = BasePayload & {
|
||||
model: 'OrionV3NFTManager'
|
||||
method: 'listNFTOrder'
|
||||
params: [string]
|
||||
};
|
||||
|
||||
type GetPoolInfoPayload = BasePayload & {
|
||||
model: 'OrionV3Factory' | 'OrionV2Factory'
|
||||
method: 'getPoolInfo'
|
||||
params: [string, string, string]
|
||||
};
|
||||
|
||||
type ListPoolPayload = BasePayload & {
|
||||
model: 'OrionFarmV3'
|
||||
method: 'listPool'
|
||||
params: [string]
|
||||
};
|
||||
|
||||
type VeORNInfoPayload = BasePayload & {
|
||||
model: 'veORN'
|
||||
method: 'info'
|
||||
params: [string]
|
||||
};
|
||||
|
||||
type ListAmountPayload = BasePayload & {
|
||||
model: string
|
||||
method: 'listAmount'
|
||||
params: []
|
||||
};
|
||||
|
||||
type GetAmountByORNPayload = BasePayload & {
|
||||
amountToken: number
|
||||
timeLock: number
|
||||
};
|
||||
|
||||
type Payload =
|
||||
| GetEnvironmentPayload
|
||||
| ListNFTOrderPayload
|
||||
| GetPoolInfoPayload
|
||||
| ListPoolPayload
|
||||
| VeORNInfoPayload
|
||||
| ListAmountPayload
|
||||
| GetAmountByORNPayload;
|
||||
|
||||
class IndexerService {
|
||||
private readonly apiUrl: string;
|
||||
|
||||
private readonly chainId: number;
|
||||
|
||||
get api() {
|
||||
return this.apiUrl;
|
||||
}
|
||||
|
||||
constructor(apiUrl: string, chainId: number) {
|
||||
this.apiUrl = apiUrl;
|
||||
this.chainId = chainId;
|
||||
|
||||
this.getEnvironment = this.getEnvironment.bind(this);
|
||||
this.listNFTOrder = this.listNFTOrder.bind(this);
|
||||
this.getPoolInfo = this.getPoolInfo.bind(this);
|
||||
this.getListPool = this.getListPool.bind(this);
|
||||
this.listPoolV2 = this.listPoolV2.bind(this);
|
||||
this.poolV2Info = this.poolV2Info.bind(this);
|
||||
this.listPoolV3 = this.listPoolV3.bind(this);
|
||||
this.veORNInfo = this.veORNInfo.bind(this);
|
||||
this.listAmount = this.listAmount.bind(this);
|
||||
this.getAmountByORN = this.getAmountByORN.bind(this);
|
||||
this.getAmountAtCurrent = this.getAmountAtCurrent.bind(this);
|
||||
this.getVotingInfo = this.getVotingInfo.bind(this);
|
||||
}
|
||||
|
||||
readonly makeRPCPayload = (payload: Omit<Payload, 'chainId' | 'jsonrpc'>) => {
|
||||
return JSON.stringify({
|
||||
...payload,
|
||||
chainId: this.chainId,
|
||||
jsonrpc: '1.0',
|
||||
});
|
||||
};
|
||||
|
||||
readonly veORNInfo = (address?: string) => {
|
||||
return fetchWithValidation(this.apiUrl, veORNInfoResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'veORN',
|
||||
method: 'info',
|
||||
params: [address],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly getAmountAtCurrent = (amount: number): BigNumber => {
|
||||
const timestamp = Date.now() / 1000;
|
||||
|
||||
// sqrt
|
||||
return BigNumber(amount).dividedBy(this.getK(timestamp));
|
||||
};
|
||||
|
||||
readonly getAmountByORN = (amountToken: string, lockingDays: number) => {
|
||||
const alpha = 730 / (30 - Math.sqrt(730 / 7)) ** (1 / 3);
|
||||
|
||||
const deltaDaysBN = BigNumber(lockingDays);
|
||||
|
||||
if (deltaDaysBN.lte(0)) return BigNumber(0);
|
||||
|
||||
const multSQRT = deltaDaysBN.dividedBy(WEEK_DAYS).sqrt();
|
||||
const multCUBE = deltaDaysBN.dividedBy(alpha).pow(3);
|
||||
|
||||
return BigNumber(amountToken)
|
||||
.multipliedBy(multSQRT.plus(multCUBE));
|
||||
};
|
||||
|
||||
readonly getVotingInfo = (userAddress?: string) => {
|
||||
return fetchWithValidation(this.apiUrl, votingInfoResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionVoting',
|
||||
method: 'info',
|
||||
params: [userAddress],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly getEnvironment = () => {
|
||||
return fetchWithValidation(this.apiUrl, environmentResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'Environment',
|
||||
method: 'getEnvironment',
|
||||
params: [],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly listNFTOrder = (address: string) => {
|
||||
return fetchWithValidation(this.apiUrl, listNFTOrderResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionV3NFTManager',
|
||||
method: 'listNFTOrder',
|
||||
params: [address],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly getListPool = (userAddress?: string) => {
|
||||
return fetchWithValidation(this.apiUrl, listPoolResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionVoting',
|
||||
method: 'listPool',
|
||||
params: [userAddress],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly getPoolInfo = (
|
||||
token0: string,
|
||||
token1: string,
|
||||
poolAddress?: string
|
||||
) => {
|
||||
return fetchWithValidation(this.apiUrl, getPoolResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionV3Factory',
|
||||
method: 'getPoolInfo',
|
||||
params: [token0, token1, poolAddress],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly listPoolV2 = (address?: string) => {
|
||||
return fetchWithValidation(this.apiUrl, listPoolV2ResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionFarmV2',
|
||||
method: 'listPool',
|
||||
params: [address],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly poolV2Info = (token0: string, token1: string, address: string | undefined) => {
|
||||
return fetchWithValidation(this.apiUrl, PoolV2InfoResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionV2Factory',
|
||||
method: 'getPoolInfo',
|
||||
params: [token0, token1, address],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly listPoolV3 = (address?: string) => {
|
||||
return fetchWithValidation(this.apiUrl, listPoolV3ResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'OrionFarmV3',
|
||||
method: 'listPool',
|
||||
params: [address],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly listAmount = (poolKey: string) => {
|
||||
return fetchWithValidation(this.apiUrl, listAmountResponseSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: poolKey,
|
||||
method: 'listAmount',
|
||||
params: [],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
readonly testRetrieve = () => {
|
||||
return fetchWithValidation(this.apiUrl, testIncrementorSchema, {
|
||||
method: 'POST',
|
||||
body: this.makeRPCPayload({
|
||||
model: 'Incrementer',
|
||||
method: 'retrieve',
|
||||
params: [],
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
private readonly getK = (time: number) => {
|
||||
const currentTime = time < LOCK_START_TIME ? LOCK_START_TIME : time;
|
||||
|
||||
const deltaYears = BigNumber(currentTime)
|
||||
.minus(LOCK_START_TIME)
|
||||
.dividedBy(YEAR);
|
||||
return 2 ** BigNumber(deltaYears).multipliedBy(2).toNumber();
|
||||
};
|
||||
}
|
||||
|
||||
export * as schemas from './schemas/index.js';
|
||||
export { IndexerService };
|
||||
26
src/services/Indexer/schemas/basic-pool-info-schema.ts
Normal file
26
src/services/Indexer/schemas/basic-pool-info-schema.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
|
||||
const basicPoolInfo = z.object({
|
||||
poolAddress: evmAddressSchema,
|
||||
isInitialized: z.boolean(),
|
||||
liquidity: z.number().nonnegative(),
|
||||
liquidityInUSD: z.number().nonnegative(),
|
||||
liquidityShare: z.number().nonnegative(),
|
||||
isFarming: z.boolean(),
|
||||
rewardsTotal: z.number().nonnegative(),
|
||||
rewardsPerPeriod: z.number().nonnegative(),
|
||||
rewardsShare: z.number().nonnegative(),
|
||||
feePerPeriod: z.number().nonnegative(),
|
||||
feeTotal: z.number().nonnegative(),
|
||||
feeShare: z.number().nonnegative(),
|
||||
tickMultiplier: z.number().nonnegative(),
|
||||
MAX_TICK: z.number().nonnegative().int(),
|
||||
minAPR: z.number().nonnegative(),
|
||||
maxAPR: z.number().nonnegative(),
|
||||
avgAPR: z.number().nonnegative(),
|
||||
maxBoost: z.number().nonnegative().int(),
|
||||
feeRate: z.array(z.number().nonnegative()),
|
||||
});
|
||||
|
||||
export default basicPoolInfo;
|
||||
23
src/services/Indexer/schemas/environment-response-schema.ts
Normal file
23
src/services/Indexer/schemas/environment-response-schema.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const environmentResponseSchema = z.object({
|
||||
result: z.object({
|
||||
chainId: z.number().int().nonnegative(),
|
||||
nativeToken: z.string(),
|
||||
ORN: evmAddressSchema,
|
||||
WETH9: evmAddressSchema,
|
||||
OrionV3Factory: evmAddressSchema.optional(),
|
||||
OrionV2Factory: evmAddressSchema.optional(),
|
||||
OrionV3NFTManager: evmAddressSchema.optional(),
|
||||
SwapRouterV3: evmAddressSchema.optional(),
|
||||
OrionFarmV3: evmAddressSchema.optional(),
|
||||
OrionFarmV2: evmAddressSchema.optional(),
|
||||
OrionVoting: evmAddressSchema.optional(),
|
||||
veORN: evmAddressSchema.optional(),
|
||||
}),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default environmentResponseSchema;
|
||||
20
src/services/Indexer/schemas/get-pool-response-schema.ts
Normal file
20
src/services/Indexer/schemas/get-pool-response-schema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
import basicPoolInfo from './basic-pool-info-schema.js';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const getPoolResponseSchema = z.object({
|
||||
result: z.object({
|
||||
token0: z.string().nonempty(),
|
||||
token1: z.string().nonempty(),
|
||||
token0Address: evmAddressSchema,
|
||||
token1Address: evmAddressSchema,
|
||||
|
||||
totalLiquidity: z.number().nonnegative(),
|
||||
WETH9: evmAddressSchema,
|
||||
pools: z.record(z.number(), basicPoolInfo.nullable()),
|
||||
}),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default getPoolResponseSchema;
|
||||
11
src/services/Indexer/schemas/index.ts
Normal file
11
src/services/Indexer/schemas/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { default as environmentResponseSchema } from './environment-response-schema';
|
||||
export { default as listNFTOrderResponseSchema } from './list-nft-order-response-schema';
|
||||
export { default as getPoolResponseSchema } from './get-pool-response-schema';
|
||||
export { default as listPoolResponseSchema } from './list-pool-schema';
|
||||
export { default as listPoolV2ResponseSchema } from './list-pool-v2-response-schema';
|
||||
export { default as PoolV2InfoResponseSchema } from './pool-v2-info-schema';
|
||||
export { default as listPoolV3ResponseSchema } from './list-pool-v3-response-schema';
|
||||
export { default as veORNInfoResponseSchema } from './veORN-info-schema';
|
||||
export { default as listAmountResponseSchema } from './list-amount-schema';
|
||||
export { default as votingInfoResponseSchema } from './voting-info-schema';
|
||||
export { default as testIncrementorSchema } from './test-incrementor-schema';
|
||||
14
src/services/Indexer/schemas/info-schema.ts
Normal file
14
src/services/Indexer/schemas/info-schema.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
const infoSchema = z.object({
|
||||
blockNumber: z.number().int().nonnegative(),
|
||||
blockHash: z.string().refine((v) => v.length === 0 || ethers.isHexString(v), {
|
||||
message: 'blockHash must be a valid hex string or empty',
|
||||
}),
|
||||
timeRequest: z.number().int().nonnegative(),
|
||||
timeAnswer: z.number().int().nonnegative(),
|
||||
changes: z.number().int().nonnegative(),
|
||||
});
|
||||
|
||||
export default infoSchema;
|
||||
9
src/services/Indexer/schemas/list-amount-schema.ts
Normal file
9
src/services/Indexer/schemas/list-amount-schema.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const listAmountSchema = z.object({
|
||||
result: z.record(z.number()),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default listAmountSchema;
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import poolSchema from './pool-schema.js';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const listNFTOrderResponseSchema = z.object({
|
||||
result: z.array(poolSchema),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default listNFTOrderResponseSchema;
|
||||
11
src/services/Indexer/schemas/list-pool-schema.ts
Normal file
11
src/services/Indexer/schemas/list-pool-schema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
import infoSchema from './info-schema';
|
||||
import { listPoolV2Schema } from './list-pool-v2-response-schema';
|
||||
import { listPoolV3Schema } from './list-pool-v3-response-schema';
|
||||
|
||||
const listPoolResponseSchema = z.object({
|
||||
result: z.array(listPoolV2Schema.or(listPoolV3Schema)),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default listPoolResponseSchema;
|
||||
62
src/services/Indexer/schemas/list-pool-v2-response-schema.ts
Normal file
62
src/services/Indexer/schemas/list-pool-v2-response-schema.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
import basicPoolInfo from './basic-pool-info-schema.js';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
export const listPoolV2Schema = z.object({
|
||||
pair: z.string(),
|
||||
token0: z.string().nonempty(),
|
||||
token1: z.string().nonempty(),
|
||||
name: z.string(),
|
||||
name0: z.string(),
|
||||
name1: z.string(),
|
||||
token0Address: evmAddressSchema,
|
||||
token1Address: evmAddressSchema,
|
||||
token0Decimals: z.number().int().nonnegative().max(18),
|
||||
token1Decimals: z.number().int().nonnegative().max(18),
|
||||
WETH9: evmAddressSchema,
|
||||
farmAddress: z.string().optional(),
|
||||
weight: z.number(),
|
||||
liquidity0: z.number(),
|
||||
liquidity1: z.number(),
|
||||
token0Price: z.number(),
|
||||
token1Price: z.number(),
|
||||
totalLPSupply: z.number(),
|
||||
totalLPStake: z.number(),
|
||||
totalLPStakeInUSD: z.number(),
|
||||
userLPStaked: z.number(),
|
||||
userLPStakedInUSD: z.number(),
|
||||
lpPriceInUSD: z.number(),
|
||||
lpPriceInORN: z.number(),
|
||||
userReward: z.number(),
|
||||
weeklyReward: z.number(),
|
||||
userAPR: z.number(),
|
||||
lockMaxMultiplier: z.number(),
|
||||
veornMaxMultiplier: z.number(),
|
||||
veornBoostScaleFactor: z.number(),
|
||||
lockTimeForMaxMultiplier: z.number(),
|
||||
userBoost: z.number(),
|
||||
userTimeDeposit: z.number(),
|
||||
userLockTimeStart: z.number(),
|
||||
userLockTimePeriod: z.number(),
|
||||
userVeORN: z.number(),
|
||||
userORN: z.number(),
|
||||
userRewardToPool: z.number(),
|
||||
boostTotalVeORN: z.number(),
|
||||
boostCurrentPoolReward: z.number(),
|
||||
boostTotalLiquidity: z.number(),
|
||||
boostCurrentLiquidity: z.number(),
|
||||
boostCurrentVeORN: z.number(),
|
||||
boostTotalReward: z.number(),
|
||||
|
||||
...basicPoolInfo.shape,
|
||||
|
||||
type: z.string().nonempty(),
|
||||
});
|
||||
|
||||
const listPoolV2ResponseSchema = z.object({
|
||||
result: z.array(listPoolV2Schema),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default listPoolV2ResponseSchema;
|
||||
32
src/services/Indexer/schemas/list-pool-v3-response-schema.ts
Normal file
32
src/services/Indexer/schemas/list-pool-v3-response-schema.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
import basicPoolInfo from './basic-pool-info-schema.js';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
export const listPoolV3Schema = z.object({
|
||||
token0: z.string().nonempty(),
|
||||
token1: z.string().nonempty(),
|
||||
name: z.string(),
|
||||
name0: z.string(),
|
||||
name1: z.string(),
|
||||
token0Address: evmAddressSchema,
|
||||
token1Address: evmAddressSchema,
|
||||
token0Decimals: z.number().int().nonnegative().max(18),
|
||||
token1Decimals: z.number().int().nonnegative().max(18),
|
||||
WETH9: evmAddressSchema,
|
||||
poolFee: z.number(),
|
||||
weeklyReward: z.number(),
|
||||
weight: z.number(),
|
||||
totalLPStakeInUSD: z.number(),
|
||||
|
||||
...basicPoolInfo.shape,
|
||||
|
||||
type: z.literal('v3'),
|
||||
});
|
||||
|
||||
const listPoolV3ResponseSchema = z.object({
|
||||
result: z.array(listPoolV3Schema),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default listPoolV3ResponseSchema;
|
||||
33
src/services/Indexer/schemas/pool-schema.ts
Normal file
33
src/services/Indexer/schemas/pool-schema.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
|
||||
const poolSchema = z.object({
|
||||
tokenId: evmAddressSchema,
|
||||
|
||||
token0: z.string().nonempty(),
|
||||
token1: z.string().nonempty(),
|
||||
token0Address: evmAddressSchema,
|
||||
token1Address: evmAddressSchema,
|
||||
token0Decimals: z.number().int().nonnegative().max(18),
|
||||
token1Decimals: z.number().int().nonnegative().max(18),
|
||||
|
||||
amount: z.number().nonnegative(),
|
||||
amount0: z.number().nonnegative(),
|
||||
amount1: z.number().nonnegative(),
|
||||
from: z.number().nonnegative(),
|
||||
to: z.number().nonnegative(),
|
||||
fee: z.number().nonnegative(),
|
||||
collectFee: z.number().nonnegative(),
|
||||
reward: z.number().nonnegative(),
|
||||
apr: z.number().nonnegative(),
|
||||
boost: z.number().int().nonnegative(),
|
||||
isStaked: z.boolean(),
|
||||
poolFee: z.number().nonnegative(),
|
||||
poolAddress: evmAddressSchema,
|
||||
veOrnForMaxBoost: z.number().nonnegative(),
|
||||
veOrnMaxBoost: z.number().nonnegative(),
|
||||
veORNCurrent: z.number().nonnegative(),
|
||||
time: z.number().int().nonnegative(), // tim
|
||||
});
|
||||
|
||||
export default poolSchema;
|
||||
64
src/services/Indexer/schemas/pool-v2-info-schema.ts
Normal file
64
src/services/Indexer/schemas/pool-v2-info-schema.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
import basicPoolInfo from './basic-pool-info-schema';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const poolInfoSchema = z.object({
|
||||
pair: z.string(),
|
||||
name: z.string(),
|
||||
token0: z.string(),
|
||||
token1: z.string(),
|
||||
name0: z.string(),
|
||||
name1: z.string(),
|
||||
token0Address: evmAddressSchema,
|
||||
token1Address: evmAddressSchema,
|
||||
token0Decimals: z.number().int().nonnegative().max(18),
|
||||
token1Decimals: z.number().int().nonnegative().max(18),
|
||||
WETH9: z.string(),
|
||||
farmAddress: z.string().optional(),
|
||||
weight: z.number(),
|
||||
liquidity0: z.number(),
|
||||
liquidity1: z.number(),
|
||||
token0Price: z.number(),
|
||||
token1Price: z.number(),
|
||||
userLPBalance: z.number(),
|
||||
userLPBalanceStr: z.string(),
|
||||
totalLPSupply: z.number(),
|
||||
totalLPStake: z.number(),
|
||||
totalLPStakeInUSD: z.number(),
|
||||
userLPStaked: z.number(),
|
||||
userLPStakedInUSD: z.number(),
|
||||
lpPriceInUSD: z.number(),
|
||||
lpPriceInORN: z.number(),
|
||||
userReward: z.number(),
|
||||
userWeeklyReward: z.number(),
|
||||
userRewardToPool: z.number(),
|
||||
weeklyReward: z.number(),
|
||||
userAPR: z.number(),
|
||||
lockMaxMultiplier: z.number(),
|
||||
veornMaxMultiplier: z.number(),
|
||||
veornBoostScaleFactor: z.number(),
|
||||
lockTimeForMaxMultiplier: z.number(),
|
||||
userBoost: z.number(),
|
||||
userTimeDeposit: z.number(),
|
||||
userLockTimeStart: z.number(),
|
||||
userLockTimePeriod: z.number(),
|
||||
userVeORN: z.number(),
|
||||
userORN: z.number(),
|
||||
boostTotalVeORN: z.number(),
|
||||
boostCurrentPoolReward: z.number(),
|
||||
boostTotalLiquidity: z.number(),
|
||||
boostCurrentLiquidity: z.number(),
|
||||
boostCurrentVeORN: z.number(),
|
||||
boostTotalReward: z.number(),
|
||||
type: z.literal('v2'),
|
||||
|
||||
...basicPoolInfo.shape,
|
||||
});
|
||||
|
||||
const PoolV2InfoResponseSchema = z.object({
|
||||
result: poolInfoSchema,
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default PoolV2InfoResponseSchema;
|
||||
9
src/services/Indexer/schemas/test-incrementor-schema.ts
Normal file
9
src/services/Indexer/schemas/test-incrementor-schema.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { z } from 'zod';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const testIncrementorSchema = z.object({
|
||||
result: z.number().int(),
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default testIncrementorSchema;
|
||||
14
src/services/Indexer/schemas/util-schemas.ts
Normal file
14
src/services/Indexer/schemas/util-schemas.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { z } from 'zod';
|
||||
|
||||
export const evmAddressSchema = z
|
||||
.string()
|
||||
.refine(ethers.isAddress, (v) => ({
|
||||
message: `${v} is not a valid address`,
|
||||
}));
|
||||
|
||||
export const hexStringSchema = z
|
||||
.string()
|
||||
.refine(ethers.isHexString, (v) => ({
|
||||
message: `${v} is not a valid hex string`,
|
||||
}));
|
||||
27
src/services/Indexer/schemas/veORN-info-schema.ts
Normal file
27
src/services/Indexer/schemas/veORN-info-schema.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { z } from 'zod';
|
||||
import { evmAddressSchema } from './util-schemas.js';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const veORNResultSchema = z.object({
|
||||
avgAPR: z.number(),
|
||||
minAPR: z.number(),
|
||||
maxAPR: z.number(),
|
||||
veTokenAddress: evmAddressSchema,
|
||||
totalORNLocked: z.number(),
|
||||
totalVeORN: z.number(),
|
||||
weeklyReward: z.number(),
|
||||
userAPR: z.number(),
|
||||
userVeORN: z.number(),
|
||||
userORNLocked: z.number(),
|
||||
userLockEndDate: z.number(),
|
||||
userReward: z.number(),
|
||||
userWeeklyReward: z.number(),
|
||||
userMinLockPeriod: z.number(),
|
||||
});
|
||||
|
||||
const veORNInfoSchema = z.object({
|
||||
result: veORNResultSchema,
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default veORNInfoSchema;
|
||||
34
src/services/Indexer/schemas/voting-info-schema.ts
Normal file
34
src/services/Indexer/schemas/voting-info-schema.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { z } from 'zod';
|
||||
import infoSchema from './info-schema.js';
|
||||
|
||||
const poolSchema = z.object({
|
||||
allVote: z.number(),
|
||||
name: z.string(),
|
||||
poolAddress: z.string(),
|
||||
type: z.string(),
|
||||
userVote: z.number(),
|
||||
token0: z.string(), // deprecated
|
||||
token1: z.string(), // deprecated
|
||||
name0: z.string(),
|
||||
name1: z.string(),
|
||||
poolFee: z.number(),
|
||||
userWeight: z.number(),
|
||||
weight: z.number(),
|
||||
});
|
||||
|
||||
const votingResultSchema = z.object({
|
||||
absoluteVeTokenInVoting: z.number(),
|
||||
pools: z.array(poolSchema),
|
||||
userVeTokenBalance: z.number(),
|
||||
userVeTokenInVoting: z.number(),
|
||||
veTokenAddress: z.string(),
|
||||
votingAddress: z.string(),
|
||||
weeklyReward: z.number(),
|
||||
});
|
||||
|
||||
const votingInfoSchema = z.object({
|
||||
result: votingResultSchema,
|
||||
info: infoSchema,
|
||||
});
|
||||
|
||||
export default votingInfoSchema;
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
aggregatedHistorySchema,
|
||||
inviteCodeLinkSchema,
|
||||
contractsAddressesSchema,
|
||||
allTimeLeadersSchema,
|
||||
} from './schemas/index.js';
|
||||
import type { SupportedChainId } from '../../types.js';
|
||||
|
||||
@@ -69,8 +70,9 @@ class ReferralSystem {
|
||||
this.getMiniStats = this.getMiniStats.bind(this);
|
||||
this.getRewardsMapping = this.getRewardsMapping.bind(this);
|
||||
this.claimRewards = this.claimRewards.bind(this);
|
||||
this.getRating = this.getRating.bind(this);
|
||||
this.getRating = this.getRating.bind(this);
|
||||
this.getLeaderboard = this.getLeaderboard.bind(this);
|
||||
this.getLeaderboardSingleChain = this.getLeaderboardSingleChain.bind(this);
|
||||
this.getAllTimeLeaders = this.getAllTimeLeaders.bind(this);
|
||||
this.getContractsAddresses = this.getContractsAddresses.bind(this);
|
||||
this.getClaimInfo = this.getClaimInfo.bind(this);
|
||||
this.getAggregatedHistory = this.getAggregatedHistory.bind(this);
|
||||
@@ -205,7 +207,20 @@ class ReferralSystem {
|
||||
errorSchema
|
||||
);
|
||||
|
||||
getRating = (refererAddress: string | undefined, chainId: SupportedChainId) =>
|
||||
getLeaderboard = (refererAddress: string | undefined) =>
|
||||
fetchWithValidation(
|
||||
`${this.apiUrl}/referer/ve/rating-table-leaderboard?tag=aggregated`,
|
||||
ratingSchema,
|
||||
{
|
||||
headers:
|
||||
refererAddress !== undefined
|
||||
? { 'referer-address': refererAddress }
|
||||
: {},
|
||||
},
|
||||
errorSchema
|
||||
);
|
||||
|
||||
getLeaderboardSingleChain = (refererAddress: string | undefined, chainId: SupportedChainId) =>
|
||||
fetchWithValidation(
|
||||
`${this.apiUrl}/referer/ve/rating-table-leaderboard?chain_id=${chainId}`,
|
||||
ratingSchema,
|
||||
@@ -218,6 +233,19 @@ class ReferralSystem {
|
||||
errorSchema
|
||||
);
|
||||
|
||||
getAllTimeLeaders = (refererAddress: string | undefined) =>
|
||||
fetchWithValidation(
|
||||
`${this.apiUrl}/referer/ve/leaderboard-lifetime`,
|
||||
allTimeLeadersSchema,
|
||||
{
|
||||
headers:
|
||||
refererAddress !== undefined
|
||||
? { 'referer-address': refererAddress }
|
||||
: {},
|
||||
},
|
||||
errorSchema
|
||||
);
|
||||
|
||||
getContractsAddresses = () =>
|
||||
fetchWithValidation(
|
||||
`${this.apiUrl}/referer/view/contracts`,
|
||||
|
||||
11
src/services/ReferralSystem/schemas/allTimeLeadersSchema.ts
Normal file
11
src/services/ReferralSystem/schemas/allTimeLeadersSchema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const allTimeLeadersSchema = z.array(z.object({
|
||||
wallet: z.string(),
|
||||
total_earnings_fmt: z.number(),
|
||||
referrals_count_fmt: z.number(),
|
||||
total_trades_fmt: z.number(),
|
||||
weekly_earnings_fmt: z.number(),
|
||||
}));
|
||||
|
||||
export default allTimeLeadersSchema;
|
||||
@@ -1,10 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
import { SupportedChainId } from '../../../types.js';
|
||||
import { isAddress } from 'ethers/lib/utils.js';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
const contractsAddressesSchema = z.record(
|
||||
z.nativeEnum(SupportedChainId),
|
||||
z.string().refine(isAddress)
|
||||
z.string().refine(ethers.isAddress)
|
||||
);
|
||||
|
||||
export default contractsAddressesSchema;
|
||||
|
||||
@@ -15,8 +15,16 @@ const distinctAnalyticsSchema = z.object({
|
||||
latest_block: z.number(),
|
||||
}),
|
||||
),
|
||||
total_earned: z.number(),
|
||||
total_sent_to_governance: z.number(),
|
||||
total_earned: z.number(),
|
||||
total_volume: z.number(),
|
||||
total_trades: z.number(),
|
||||
all_time_earnings_boost_only: z.number(),
|
||||
all_time_earnings_boost_only_usd: z.number(),
|
||||
all_time_earnings: z.number(),
|
||||
all_time_earnings_usd: z.number(),
|
||||
all_weekly_earnings: z.number(),
|
||||
all_weekly_earnings_usd: z.number(),
|
||||
});
|
||||
|
||||
export default distinctAnalyticsSchema;
|
||||
|
||||
@@ -10,3 +10,4 @@ export { default as claimInfoSchema } from './claimInfoSchema.js';
|
||||
export { default as aggregatedHistorySchema } from './aggregatedHistorySchema.js';
|
||||
export { default as contractsAddressesSchema } from './contractsAddressesSchema.js';
|
||||
export { default as inviteCodeLinkSchema } from './inviteCodeLinkSchema.js';
|
||||
export { default as allTimeLeadersSchema } from './allTimeLeadersSchema.js';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
const linkSchema = z.object({
|
||||
status: z.string(),
|
||||
referer: z.string(),
|
||||
ref_link: z.string(),
|
||||
});
|
||||
|
||||
export default linkSchema;
|
||||
|
||||
@@ -4,6 +4,9 @@ const ratingSchema = z.object({
|
||||
info: z.object({
|
||||
weekly_boost_budget: z.string(),
|
||||
weekly_boost_budget_fmt: z.number(),
|
||||
monthly_boost_budget: z.string(),
|
||||
monthly_boost_budget_fmt: z.number(),
|
||||
displayed_boost_budget_fmt: z.number(),
|
||||
time_left_for_the_reward: z.number(),
|
||||
time_left_for_the_reward_local: z.string(),
|
||||
time_left_for_the_reward_utc: z.string(),
|
||||
@@ -33,6 +36,11 @@ const ratingSchema = z.object({
|
||||
weighted_volume_fmt: z.number(),
|
||||
total_weight: z.string(),
|
||||
total_weight_fmt: z.number(),
|
||||
total_volume_fmt: z.number(),
|
||||
weekly_earnings_fmt: z.number(),
|
||||
total_earnings_fmt: z.number(),
|
||||
referrals_count_fmt: z.number(),
|
||||
total_trades_fmt: z.number(),
|
||||
reward: z.string(),
|
||||
reward_fmt: z.number()
|
||||
})),
|
||||
|
||||
@@ -2,3 +2,4 @@ export * as aggregator from './Aggregator/index.js';
|
||||
export * as blockchainService from './BlockchainService/index.js';
|
||||
export * as priceFeed from './PriceFeed/index.js';
|
||||
export * as referralSystem from './ReferralSystem/index.js';
|
||||
export * as indexer from './Indexer/index.js';
|
||||
|
||||
32
src/types.ts
32
src/types.ts
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-definitions */
|
||||
import type factories from './constants/factories.js';
|
||||
import type { BigNumber } from 'bignumber.js';
|
||||
import type subOrderStatuses from './constants/subOrderStatuses.js';
|
||||
import type positionStatuses from './constants/positionStatuses.js';
|
||||
@@ -47,7 +48,6 @@ export type Order = {
|
||||
nonce: number // uint64
|
||||
expiration: number // uint64
|
||||
buySide: 0 | 1 // uint8, 1=buy, 0=sell
|
||||
isPersonalSign: boolean // bool
|
||||
}
|
||||
|
||||
export type SignedOrder = {
|
||||
@@ -59,7 +59,6 @@ export type SignedOrder = {
|
||||
export type CancelOrderRequest = {
|
||||
id: number | string
|
||||
senderAddress: string
|
||||
isPersonalSign: boolean
|
||||
}
|
||||
|
||||
export type SignedCancelOrderRequest = {
|
||||
@@ -84,10 +83,13 @@ export enum SupportedChainId {
|
||||
MAINNET = '1',
|
||||
ROPSTEN = '3',
|
||||
GOERLI = '5',
|
||||
ARBITRUM_GOERLI = '421613',
|
||||
ARBITRUM = '42161',
|
||||
FANTOM_OPERA = '250',
|
||||
POLYGON = '137',
|
||||
OKC = '66',
|
||||
OPBNB = '204',
|
||||
INEVM = '2525',
|
||||
LINEA = '59144',
|
||||
|
||||
POLYGON_TESTNET = '80001',
|
||||
FANTOM_TESTNET = '4002',
|
||||
@@ -95,6 +97,7 @@ export enum SupportedChainId {
|
||||
BSC_TESTNET = '97',
|
||||
OKC_TESTNET = '65',
|
||||
DRIP_TESTNET = '56303',
|
||||
ARBITRUM_GOERLI = '421613',
|
||||
|
||||
// For testing and debug purpose
|
||||
// BROKEN = '0',
|
||||
@@ -165,11 +168,13 @@ export type SwapInfoAlternative = {
|
||||
availableAmountOut?: number | undefined
|
||||
}
|
||||
|
||||
type ExchangeContractPath = {
|
||||
export type Factory = typeof factories[number]
|
||||
|
||||
export type SingleSwap = {
|
||||
pool: string
|
||||
assetIn: string
|
||||
assetOut: string
|
||||
factory: string
|
||||
factory: Factory
|
||||
}
|
||||
|
||||
export type SwapInfoBase = {
|
||||
@@ -182,7 +187,7 @@ export type SwapInfoBase = {
|
||||
minAmountOut: number
|
||||
|
||||
path: string[]
|
||||
exchangeContractPath: ExchangeContractPath[]
|
||||
exchangeContractPath: SingleSwap[]
|
||||
exchanges?: string[] | undefined
|
||||
poolOptimal: boolean
|
||||
|
||||
@@ -196,6 +201,13 @@ export type SwapInfoBase = {
|
||||
} | undefined
|
||||
alternatives: SwapInfoAlternative[]
|
||||
assetsNameMapping?: Partial<Record<string, string>> | undefined
|
||||
usdInfo: {
|
||||
availableAmountIn: number | undefined
|
||||
availableAmountOut: number | undefined
|
||||
marketAmountOut: number | undefined
|
||||
marketAmountIn: number | undefined
|
||||
difference: string | undefined
|
||||
} | undefined
|
||||
}
|
||||
|
||||
export type SwapInfoByAmountIn = SwapInfoBase & {
|
||||
@@ -252,6 +264,12 @@ export type VerboseUnitConfig = {
|
||||
// http://10.23.5.11:3003/,
|
||||
// https://price-feed:3003/
|
||||
}
|
||||
indexer?: {
|
||||
api: string
|
||||
// For example:
|
||||
// http://localhost:3004/,
|
||||
// http://
|
||||
} | undefined
|
||||
}
|
||||
basicAuth?: BasicAuthCredentials
|
||||
}
|
||||
@@ -438,3 +456,5 @@ export type AtomicSwap = Partial<
|
||||
refundTx?: TransactionInfo | undefined
|
||||
liquidityMigrationTx?: TransactionInfo | undefined
|
||||
}
|
||||
|
||||
export type OrderSource = 'TERMINAL_MARKET' | 'TERMINAL_LIMIT' | 'SWAP_UI' | 'WIDGET';
|
||||
|
||||
9
src/utils/addressLikeToString.ts
Normal file
9
src/utils/addressLikeToString.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { AddressLike } from "ethers";
|
||||
|
||||
export async function addressLikeToString(address: AddressLike): Promise<string> {
|
||||
address = await address
|
||||
if (typeof address !== 'string') {
|
||||
address = await address.getAddress()
|
||||
}
|
||||
return address.toLowerCase()
|
||||
}
|
||||
@@ -8,6 +8,6 @@ export default function calculateNetworkFee(
|
||||
) {
|
||||
const networkFeeGwei = new BigNumber(gasPriceGwei).multipliedBy(gasLimit);
|
||||
|
||||
const bn = new BigNumber(ethers.utils.parseUnits(networkFeeGwei.toString(), 'gwei').toString());
|
||||
const bn = new BigNumber(ethers.parseUnits(networkFeeGwei.toString(), 'gwei').toString());
|
||||
return bn.div(new BigNumber(10).pow(NATIVE_CURRENCY_PRECISION)).toString();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import { ethers } from 'ethers';
|
||||
import invariant from 'tiny-invariant';
|
||||
|
||||
const checkIsToken = async (address: string, provider?: ethers.providers.Provider) => {
|
||||
const checkIsToken = async (address: string, provider?: ethers.Provider) => {
|
||||
invariant(provider, 'No provider for token checking');
|
||||
const tokenContract = ERC20__factory.connect(address, provider);
|
||||
try {
|
||||
@@ -12,7 +12,7 @@ const checkIsToken = async (address: string, provider?: ethers.providers.Provide
|
||||
tokenContract.symbol(),
|
||||
tokenContract.decimals(),
|
||||
tokenContract.totalSupply(),
|
||||
tokenContract.balanceOf(ethers.constants.AddressZero),
|
||||
tokenContract.balanceOf(ethers.ZeroAddress),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import type { ethers } from 'ethers';
|
||||
|
||||
/**
|
||||
* Converts normalized blockchain ("machine-readable") number to denormalized ("human-readable") number.
|
||||
@@ -7,8 +6,8 @@ import type { ethers } from 'ethers';
|
||||
* @param decimals Blockchain asset precision
|
||||
* @returns BigNumber
|
||||
*/
|
||||
export default function denormalizeNumber(input: ethers.BigNumber, decimals: BigNumber.Value) {
|
||||
const decimalsBN = new BigNumber(decimals);
|
||||
export default function denormalizeNumber(input: bigint, decimals: bigint) {
|
||||
const decimalsBN = new BigNumber(decimals.toString());
|
||||
if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`);
|
||||
return new BigNumber(input.toString()).div(new BigNumber(10).pow(decimalsBN));
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ function isomorphicCryptoRandomBytes(size: number): Uint8Array {
|
||||
const generateSecret = () => {
|
||||
const RANDOM_BITS = 256;
|
||||
const rand = isomorphicCryptoRandomBytes(RANDOM_BITS);
|
||||
const secret = ethers.utils.keccak256(rand);
|
||||
const secret = ethers.keccak256(rand);
|
||||
return secret;
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ export default function getAvailableFundsSources(
|
||||
): Source[] {
|
||||
switch (route) {
|
||||
case 'aggregator':
|
||||
if (assetAddress === ethers.constants.AddressZero) return ['exchange']; // We can't take native crypto from wallet
|
||||
if (assetAddress === ethers.ZeroAddress) return ['exchange']; // We can't take native crypto from wallet
|
||||
return ['exchange', 'wallet']; // We can take any token amount from exchange + wallet. Order is important!
|
||||
case 'pool':
|
||||
if (expenseType === 'network_fee') return ['wallet']; // Network fee is always taken from wallet
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type { Exchange } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { ERC20__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import type { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from '../constants/index.js';
|
||||
import type { Aggregator } from '../services/Aggregator/index.js';
|
||||
import denormalizeNumber from './denormalizeNumber.js';
|
||||
import { ERC20__factory, Exchange__factory, type Exchange } from "@orionprotocol/contracts/lib/ethers-v6/index.js";
|
||||
import type { BigNumber } from "bignumber.js";
|
||||
import { ZeroAddress, ethers } from "ethers";
|
||||
import { INTERNAL_PROTOCOL_PRECISION, NATIVE_CURRENCY_PRECISION } from "../constants/index.js";
|
||||
import type { Aggregator } from "../services/Aggregator/index.js";
|
||||
import denormalizeNumber from "./denormalizeNumber.js";
|
||||
import type { AddressLike } from "ethers";
|
||||
import { addressLikeToString } from "./addressLikeToString.js";
|
||||
|
||||
export default async function getBalance(
|
||||
aggregator: Aggregator,
|
||||
@@ -12,11 +13,11 @@ export default async function getBalance(
|
||||
assetAddress: string,
|
||||
walletAddress: string,
|
||||
exchangeContract: Exchange,
|
||||
provider: ethers.providers.Provider,
|
||||
provider: ethers.Provider
|
||||
) {
|
||||
const assetIsNativeCryptocurrency = assetAddress === ethers.constants.AddressZero;
|
||||
const assetIsNativeCryptocurrency = assetAddress === ethers.ZeroAddress;
|
||||
|
||||
let assetWalletBalance: ethers.BigNumber | undefined;
|
||||
let assetWalletBalance: bigint | undefined;
|
||||
|
||||
let denormalizedAssetInWalletBalance: BigNumber | undefined;
|
||||
|
||||
@@ -28,10 +29,13 @@ export default async function getBalance(
|
||||
denormalizedAssetInWalletBalance = denormalizeNumber(assetWalletBalance, assetDecimals);
|
||||
} else {
|
||||
assetWalletBalance = await provider.getBalance(walletAddress);
|
||||
denormalizedAssetInWalletBalance = denormalizeNumber(assetWalletBalance, NATIVE_CURRENCY_PRECISION);
|
||||
denormalizedAssetInWalletBalance = denormalizeNumber(assetWalletBalance, BigInt(NATIVE_CURRENCY_PRECISION));
|
||||
}
|
||||
const assetContractBalance = await exchangeContract.getBalance(assetAddress, walletAddress);
|
||||
const denormalizedAssetInContractBalance = denormalizeNumber(assetContractBalance, INTERNAL_PROTOCOL_PRECISION);
|
||||
const denormalizedAssetInContractBalance = denormalizeNumber(
|
||||
assetContractBalance,
|
||||
BigInt(INTERNAL_PROTOCOL_PRECISION)
|
||||
);
|
||||
const denormalizedAssetLockedBalanceResult = await aggregator.getLockedBalance(walletAddress, assetName);
|
||||
if (denormalizedAssetLockedBalanceResult.isErr()) {
|
||||
throw new Error(denormalizedAssetLockedBalanceResult.error.message);
|
||||
@@ -42,3 +46,154 @@ export default async function getBalance(
|
||||
wallet: denormalizedAssetInWalletBalance,
|
||||
};
|
||||
}
|
||||
|
||||
async function getExchangeBalanceERC20(
|
||||
tokenAddress: AddressLike,
|
||||
walletAddress: AddressLike,
|
||||
exchangeAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToNativeDecimals = true
|
||||
) {
|
||||
walletAddress = await addressLikeToString(walletAddress);
|
||||
exchangeAddress = await addressLikeToString(exchangeAddress);
|
||||
tokenAddress = await addressLikeToString(tokenAddress);
|
||||
|
||||
const exchange = <Exchange>Exchange__factory.connect(exchangeAddress, provider);
|
||||
const exchangeBalance = await exchange.getBalance(tokenAddress, walletAddress);
|
||||
|
||||
if (convertToNativeDecimals) {
|
||||
const tokenContract = ERC20__factory.connect(tokenAddress, provider);
|
||||
const decimals = await tokenContract.decimals();
|
||||
const convertedExchangeBalance = (exchangeBalance * BigInt(10) ** decimals) / BigInt(10) ** 8n;
|
||||
return convertedExchangeBalance;
|
||||
}
|
||||
|
||||
return exchangeBalance;
|
||||
}
|
||||
|
||||
async function getExchangeBalanceNative(
|
||||
walletAddress: AddressLike,
|
||||
exchangeAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToNativeDecimals = true
|
||||
) {
|
||||
walletAddress = await addressLikeToString(walletAddress);
|
||||
exchangeAddress = await addressLikeToString(exchangeAddress);
|
||||
const exchange = <Exchange>Exchange__factory.connect(exchangeAddress, provider);
|
||||
const exchangeBalance = await exchange.getBalance(ZeroAddress, walletAddress);
|
||||
|
||||
if (convertToNativeDecimals) {
|
||||
const convertedExchangeBalance = (exchangeBalance * BigInt(10) ** 18n) / BigInt(10) ** 8n;
|
||||
return convertedExchangeBalance;
|
||||
}
|
||||
|
||||
return exchangeBalance;
|
||||
}
|
||||
|
||||
export async function getExchangeBalance(
|
||||
tokenAddress: AddressLike,
|
||||
walletAddress: AddressLike,
|
||||
exchangeAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToNativeDecimals = true
|
||||
) {
|
||||
walletAddress = await addressLikeToString(walletAddress);
|
||||
tokenAddress = await addressLikeToString(tokenAddress);
|
||||
|
||||
if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) {
|
||||
return getExchangeBalanceNative(walletAddress, exchangeAddress, provider, convertToNativeDecimals);
|
||||
} else {
|
||||
return getExchangeBalanceERC20(tokenAddress, walletAddress, exchangeAddress, provider, convertToNativeDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getExchangeAllowance(
|
||||
tokenAddress: AddressLike,
|
||||
walletAddress: AddressLike,
|
||||
exchangeAddress: AddressLike,
|
||||
provider: ethers.Provider
|
||||
) {
|
||||
if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) {
|
||||
return 0n;
|
||||
} else {
|
||||
walletAddress = await addressLikeToString(walletAddress);
|
||||
tokenAddress = await addressLikeToString(tokenAddress);
|
||||
|
||||
const tokenContract = ERC20__factory.connect(tokenAddress, provider);
|
||||
let allowance = await tokenContract.allowance(walletAddress, exchangeAddress);
|
||||
|
||||
return allowance;
|
||||
}
|
||||
}
|
||||
|
||||
async function getWalletBalanceERC20(
|
||||
tokenAddress: AddressLike,
|
||||
walletAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToExchangeDecimals = false
|
||||
) {
|
||||
walletAddress = await addressLikeToString(walletAddress);
|
||||
tokenAddress = await addressLikeToString(tokenAddress);
|
||||
|
||||
const tokenContract = ERC20__factory.connect(tokenAddress, provider);
|
||||
let walletBalance = await tokenContract.balanceOf(walletAddress);
|
||||
|
||||
if (convertToExchangeDecimals) {
|
||||
const tokenContract = ERC20__factory.connect(tokenAddress, provider);
|
||||
const decimals = await tokenContract.decimals();
|
||||
const convertedNativeBalance = (walletBalance * BigInt(10) ** 8n) / BigInt(10) ** decimals;
|
||||
return convertedNativeBalance;
|
||||
}
|
||||
return walletBalance;
|
||||
}
|
||||
|
||||
async function getWalletBalanceNative(
|
||||
walletAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToExchangeDecimals = false
|
||||
) {
|
||||
walletAddress = await addressLikeToString(walletAddress);
|
||||
const nativeBalance = await provider.getBalance(walletAddress);
|
||||
|
||||
if (convertToExchangeDecimals) {
|
||||
const convertedNativeBalance = (nativeBalance * BigInt(10) ** 8n) / BigInt(10) ** 18n;
|
||||
return convertedNativeBalance;
|
||||
}
|
||||
|
||||
return nativeBalance;
|
||||
}
|
||||
|
||||
export async function getWalletBalance(
|
||||
tokenAddress: AddressLike,
|
||||
walletAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToExchangeDecimals = false
|
||||
) {
|
||||
if (typeof tokenAddress === "string" && tokenAddress === ZeroAddress) {
|
||||
return getWalletBalanceNative(walletAddress, provider, convertToExchangeDecimals);
|
||||
} else {
|
||||
return getWalletBalanceERC20(tokenAddress, walletAddress, provider, convertToExchangeDecimals);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getTotalBalance(
|
||||
tokenAddress: AddressLike,
|
||||
walletAddress: AddressLike,
|
||||
exchangeAddress: AddressLike,
|
||||
provider: ethers.Provider,
|
||||
convertToNativeDecimals = true
|
||||
) {
|
||||
const walletBalance = await getWalletBalance(tokenAddress, walletAddress, provider, !convertToNativeDecimals);
|
||||
const exchangeBalance = await getExchangeBalance(
|
||||
tokenAddress,
|
||||
walletAddress,
|
||||
exchangeAddress,
|
||||
provider,
|
||||
convertToNativeDecimals
|
||||
);
|
||||
return {
|
||||
walletBalance,
|
||||
exchangeBalance,
|
||||
totalBalance: walletBalance + exchangeBalance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Exchange } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import type { Exchange } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import type { BigNumber } from 'bignumber.js';
|
||||
import type { ethers } from 'ethers';
|
||||
import type { Aggregator } from '../services/Aggregator/index.js';
|
||||
@@ -9,7 +9,7 @@ export default async (
|
||||
aggregator: Aggregator,
|
||||
walletAddress: string,
|
||||
exchangeContract: Exchange,
|
||||
provider: ethers.providers.Provider,
|
||||
provider: ethers.Provider,
|
||||
) => {
|
||||
const balances = await Promise.all(
|
||||
Object.entries(balancesRequired)
|
||||
|
||||
@@ -11,7 +11,7 @@ const getNativeCryptocurrencyName = (assetToAddress: Partial<Record<string, stri
|
||||
};
|
||||
}, {});
|
||||
|
||||
const nativeCryptocurrencyName = addressToAssetName[ethers.constants.AddressZero];
|
||||
const nativeCryptocurrencyName = addressToAssetName[ethers.ZeroAddress];
|
||||
if (nativeCryptocurrencyName === undefined) {
|
||||
throw new Error('Native cryptocurrency asset name is not found');
|
||||
}
|
||||
|
||||
8
src/utils/isValidFactory.ts
Normal file
8
src/utils/isValidFactory.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { type Factory } from '../types.js';
|
||||
import { factories } from '../index.js';
|
||||
|
||||
const isValidFactory = (factory: string): factory is Factory => {
|
||||
return factories.some((f) => f === factory);
|
||||
};
|
||||
|
||||
export default isValidFactory;
|
||||
@@ -1,25 +1,25 @@
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { BigNumber } from "bignumber.js";
|
||||
|
||||
/**
|
||||
* Converts denormalized ("human-readable") number to normalized ("machine-readable") number.
|
||||
* @param input Any numeric value
|
||||
* @param decimals Blockchain asset precision
|
||||
* @param roundingMode Rounding mode
|
||||
* @returns ethers.BigNumber
|
||||
* @returns bigint
|
||||
*/
|
||||
export default function normalizeNumber(
|
||||
input: BigNumber.Value,
|
||||
decimals: BigNumber.Value,
|
||||
roundingMode: BigNumber.RoundingMode,
|
||||
roundingMode: BigNumber.RoundingMode
|
||||
) {
|
||||
const decimalsBN = new BigNumber(decimals);
|
||||
if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`);
|
||||
if (!decimalsBN.isInteger())
|
||||
throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`);
|
||||
const inputBN = new BigNumber(input);
|
||||
return ethers.BigNumber.from(
|
||||
return BigInt(
|
||||
inputBN
|
||||
.multipliedBy(new BigNumber(10).pow(decimals))
|
||||
.integerValue(roundingMode)
|
||||
.toString(),
|
||||
.toString()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v5/index.js';
|
||||
import { Exchange__factory } from '@orionprotocol/contracts/lib/ethers-v6/index.js';
|
||||
import { ethers } from 'ethers';
|
||||
import { z } from 'zod';
|
||||
|
||||
const swapThroughOrionPoolSchema = z.object({
|
||||
name: z.literal('swapThroughOrionPool'),
|
||||
args: z.tuple([
|
||||
z.instanceof(ethers.BigNumber), // amount_spend
|
||||
z.instanceof(ethers.BigNumber), // amount_receive
|
||||
z.string().refine(ethers.utils.isAddress).array().nonempty(), // path
|
||||
z.bigint(), // amount_spend
|
||||
z.bigint(), // amount_receive
|
||||
z.string().refine(ethers.isAddress).array().nonempty(), // path
|
||||
z.boolean(), // is_exact_spend
|
||||
]),
|
||||
}).transform((data) => ({
|
||||
@@ -21,34 +21,34 @@ const swapThroughOrionPoolSchema = z.object({
|
||||
}));
|
||||
|
||||
const buyOrderSchema = z.tuple([ // buy order
|
||||
z.string().refine(ethers.utils.isAddress), // senderAddress
|
||||
z.string().refine(ethers.utils.isAddress), // matcherAddress
|
||||
z.string().refine(ethers.utils.isAddress), // baseAsset
|
||||
z.string().refine(ethers.utils.isAddress), // quoteAsset
|
||||
z.string().refine(ethers.utils.isAddress), // matcherFeeAsset
|
||||
z.instanceof(ethers.BigNumber), // amount
|
||||
z.instanceof(ethers.BigNumber), // price
|
||||
z.instanceof(ethers.BigNumber), // matcherFee
|
||||
z.instanceof(ethers.BigNumber), // nonce
|
||||
z.instanceof(ethers.BigNumber), // expiration
|
||||
z.string().refine(ethers.isAddress), // senderAddress
|
||||
z.string().refine(ethers.isAddress), // matcherAddress
|
||||
z.string().refine(ethers.isAddress), // baseAsset
|
||||
z.string().refine(ethers.isAddress), // quoteAsset
|
||||
z.string().refine(ethers.isAddress), // matcherFeeAsset
|
||||
z.bigint(), // amount
|
||||
z.bigint(), // price
|
||||
z.bigint(), // matcherFee
|
||||
z.bigint(), // nonce
|
||||
z.bigint(), // expiration
|
||||
z.literal(1), // buySide
|
||||
z.boolean(), // isPersonalSign
|
||||
z.string().refine(ethers.utils.isHexString), // signature
|
||||
z.string().refine(ethers.isHexString), // signature
|
||||
]);
|
||||
const sellOrderSchema = z.tuple([ // sell orer
|
||||
z.string().refine(ethers.utils.isAddress), // senderAddress
|
||||
z.string().refine(ethers.utils.isAddress), // matcherAddress
|
||||
z.string().refine(ethers.utils.isAddress), // baseAsset
|
||||
z.string().refine(ethers.utils.isAddress), // quoteAsset
|
||||
z.string().refine(ethers.utils.isAddress), // matcherFeeAsset
|
||||
z.instanceof(ethers.BigNumber), // amount
|
||||
z.instanceof(ethers.BigNumber), // price
|
||||
z.instanceof(ethers.BigNumber), // matcherFee
|
||||
z.instanceof(ethers.BigNumber), // nonce
|
||||
z.instanceof(ethers.BigNumber), // expiration
|
||||
z.string().refine(ethers.isAddress), // senderAddress
|
||||
z.string().refine(ethers.isAddress), // matcherAddress
|
||||
z.string().refine(ethers.isAddress), // baseAsset
|
||||
z.string().refine(ethers.isAddress), // quoteAsset
|
||||
z.string().refine(ethers.isAddress), // matcherFeeAsset
|
||||
z.bigint(), // amount
|
||||
z.bigint(), // price
|
||||
z.bigint(), // matcherFee
|
||||
z.bigint(), // nonce
|
||||
z.bigint(), // expiration
|
||||
z.literal(0), // buySide
|
||||
z.boolean(), // isPersonalSign
|
||||
z.string().refine(ethers.utils.isHexString), // signature
|
||||
z.string().refine(ethers.isHexString), // signature
|
||||
]);
|
||||
|
||||
const toOrder = <T extends z.infer<typeof buyOrderSchema> | z.infer<typeof sellOrderSchema>>(data: T) => ({
|
||||
@@ -71,9 +71,9 @@ const fillThroughOrionPoolSchema = z.object({
|
||||
name: z.literal('fillThroughOrionPool'),
|
||||
args: z.tuple([
|
||||
sellOrderSchema,
|
||||
z.instanceof(ethers.BigNumber), // filled amount
|
||||
z.instanceof(ethers.BigNumber), // blockchainFee
|
||||
z.string().refine(ethers.utils.isAddress).array().nonempty(), // path
|
||||
z.bigint(), // filled amount
|
||||
z.bigint(), // blockchainFee
|
||||
z.string().refine(ethers.isAddress).array().nonempty(), // path
|
||||
]),
|
||||
}).transform((data) => ({
|
||||
name: data.name,
|
||||
@@ -90,8 +90,8 @@ const fillOrdersSchema = z.object({
|
||||
args: z.tuple([
|
||||
buyOrderSchema,
|
||||
sellOrderSchema,
|
||||
z.instanceof(ethers.BigNumber), // filledPrice
|
||||
z.instanceof(ethers.BigNumber), // filledAmount
|
||||
z.bigint(), // filledPrice
|
||||
z.bigint(), // filledAmount
|
||||
]),
|
||||
}).transform((data) => ({
|
||||
name: data.name,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export class SafeArray<T> extends Array<T> {
|
||||
|
||||
public static override from<T>(array: ArrayLike<T>): SafeArray<T> {
|
||||
return new SafeArray(array);
|
||||
}
|
||||
@@ -9,9 +8,10 @@ export class SafeArray<T> extends Array<T> {
|
||||
for (const index in array) {
|
||||
const value = array[index]
|
||||
if (value === undefined) {
|
||||
throw new Error("Array passed to constructor has undefined values")
|
||||
throw new Error('Array passed to constructor has undefined values')
|
||||
}
|
||||
this[index] = value
|
||||
|
||||
this[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,11 @@ export class SafeArray<T> extends Array<T> {
|
||||
return [...this];
|
||||
}
|
||||
|
||||
public override map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): SafeArray<U> {
|
||||
public override map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: unknown): SafeArray<U> {
|
||||
return new SafeArray(super.map(callbackfn, thisArg));
|
||||
}
|
||||
|
||||
public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): SafeArray<T> {
|
||||
public override filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: unknown): SafeArray<T> {
|
||||
return new SafeArray(super.filter(callbackfn, thisArg));
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ export function safeGet<V>(obj: Partial<Record<string, V>>, key: string, errorMe
|
||||
const prefix = 'Requirement not met';
|
||||
|
||||
export function must(condition: unknown, message?: string | (() => string)): asserts condition {
|
||||
if (condition) return;
|
||||
const provided = typeof message === 'function' ? message() : message;
|
||||
const value = provided ? `${prefix}: ${provided}` : prefix;
|
||||
throw new Error(value);
|
||||
}
|
||||
if (condition) return;
|
||||
const provided = typeof message === 'function' ? message() : message;
|
||||
const value = provided ? `${prefix}: ${provided}` : prefix;
|
||||
throw new Error(value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user