Added deposit

This commit is contained in:
Aleksandr Kraiz
2022-04-22 01:27:32 +04:00
parent c2e21d8e7e
commit 945ef60a5f
9 changed files with 289 additions and 137 deletions

View File

@@ -0,0 +1,136 @@
/* eslint-disable max-len */
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import getBalances from '../../utils/getBalances';
import BalanceGuard from '../../BalanceGuard';
import OrionUnit from '..';
import { contracts, utils } from '../..';
import {
DEPOSIT_ERC20_GAS_LIMIT, DEPOSIT_ETH_GAS_LIMIT, INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION,
} from '../../constants';
import { normalizeNumber } from '../../utils';
export type DepositParams = {
asset: string,
amount: BigNumber.Value,
signer: ethers.Signer,
orionUnit: OrionUnit,
}
export default async function deposit({
asset,
amount,
signer,
orionUnit,
}: DepositParams) {
if (asset === '') throw new Error('Asset can not be empty');
const amountBN = new BigNumber(amount);
if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`);
if (amountBN.lte(0)) throw new Error(`Amount '${amount.toString()}' should be greater than 0`);
const walletAddress = await signer.getAddress();
const {
orionBlockchain, orionAggregator, provider, chainId,
} = orionUnit;
const {
exchangeContractAddress,
assetToAddress,
} = await orionBlockchain.getInfo();
const addressToAsset = Object
.entries(assetToAddress)
.reduce<Partial<Record<string, string>>>((prev, [assetName, address]) => {
if (!address) return prev;
return {
...prev,
[address]: assetName,
};
}, {});
const nativeCryptocurrency = addressToAsset[ethers.constants.AddressZero];
if (!nativeCryptocurrency) throw new Error('Native cryptocurrency asset is not found');
const exchangeContract = contracts.Exchange__factory.connect(exchangeContractAddress, provider);
const gasPriceWei = await orionBlockchain.getGasPriceWei();
const assetAddress = assetToAddress[asset];
if (!assetAddress) throw new Error(`Asset '${asset}' not found`);
const balances = await getBalances(
{
[asset]: assetAddress,
[nativeCryptocurrency]: ethers.constants.AddressZero,
},
orionAggregator,
walletAddress,
exchangeContract,
provider,
);
const balanceGuard = new BalanceGuard(
balances,
{
name: nativeCryptocurrency,
address: ethers.constants.AddressZero,
},
provider,
signer,
);
balanceGuard.registerRequirement({
reason: 'Amount',
asset: {
name: asset,
address: assetAddress,
},
amount: amount.toString(),
spenderAddress: exchangeContractAddress,
sources: ['wallet'],
});
let unsignedTx: ethers.PopulatedTransaction;
if (asset === nativeCryptocurrency) {
unsignedTx = await exchangeContract.populateTransaction.deposit();
unsignedTx.value = normalizeNumber(amount, NATIVE_CURRENCY_PRECISION, BigNumber.ROUND_CEIL);
unsignedTx.gasLimit = ethers.BigNumber.from(DEPOSIT_ETH_GAS_LIMIT);
} else {
unsignedTx = await exchangeContract.populateTransaction.depositAsset(
assetAddress,
normalizeNumber(amount, INTERNAL_ORION_PRECISION, BigNumber.ROUND_CEIL),
);
unsignedTx.gasLimit = ethers.BigNumber.from(DEPOSIT_ERC20_GAS_LIMIT);
}
const transactionCost = ethers.BigNumber.from(unsignedTx.gasLimit).mul(gasPriceWei);
const denormalizedTransactionCost = utils.denormalizeNumber(transactionCost, NATIVE_CURRENCY_PRECISION);
balanceGuard.registerRequirement({
reason: 'Network fee',
asset: {
name: nativeCryptocurrency,
address: ethers.constants.AddressZero,
},
amount: denormalizedTransactionCost.toString(),
sources: ['wallet'],
});
unsignedTx.chainId = parseInt(chainId, 16);
unsignedTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
unsignedTx.from = walletAddress;
await balanceGuard.check(true);
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
unsignedTx.nonce = nonce;
const signedTx = await signer.signTransaction(unsignedTx);
const txResponse = await provider.sendTransaction(signedTx);
console.log(`Deposit tx sent: ${txResponse.hash}. Waiting for confirmation...`);
const txReceipt = await txResponse.wait();
if (txReceipt.status) {
console.log('Deposit tx confirmed');
} else {
console.log('Deposit tx failed');
}
}

View File

@@ -0,0 +1,28 @@
import OrionUnit from '..';
import deposit, { DepositParams } from './deposit';
import swapMarket, { SwapMarketParams } from './swapMarket';
type PureSwapMarketParams= Omit<SwapMarketParams, 'orionUnit'>
type PureDepositParams= Omit<DepositParams, 'orionUnit'>
export default class Exchange {
private readonly orionUnit: OrionUnit;
constructor(orionUnit: OrionUnit) {
this.orionUnit = orionUnit;
}
public swapMarket(params: PureSwapMarketParams) {
return swapMarket({
...params,
orionUnit: this.orionUnit,
});
}
public deposit(params: PureDepositParams) {
return deposit({
...params,
orionUnit: this.orionUnit,
});
}
}

View File

@@ -1,13 +1,12 @@
/* eslint-disable max-len */
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import getBalances from '../utils/getBalances';
import BalanceGuard from '../BalanceGuard';
import getAvailableSources from '../utils/getAvailableFundsSources';
import { Approve, BalanceIssue } from '../types';
import OrionUnit from '.';
import { contracts, crypt, utils } from '..';
import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../constants';
import getBalances from '../../utils/getBalances';
import BalanceGuard from '../../BalanceGuard';
import getAvailableSources from '../../utils/getAvailableFundsSources';
import OrionUnit from '..';
import { contracts, crypt, utils } from '../..';
import { INTERNAL_ORION_PRECISION, NATIVE_CURRENCY_PRECISION, SWAP_THROUGH_ORION_POOL_GAS_LIMIT } from '../../constants';
export type SwapMarketParams = {
type: 'exactSpend' | 'exactReceive',
@@ -56,6 +55,7 @@ export default async function swapMarket({
const amountBN = new BigNumber(amount);
if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`);
if (amountBN.lte(0)) throw new Error(`Amount '${amount.toString()}' should be greater than 0`);
const slippagePercentBN = new BigNumber(slippagePercent);
if (slippagePercentBN.isNaN()) throw new Error(`Slippage percent '${slippagePercent.toString()}' is not a number`);
@@ -117,7 +117,7 @@ export default async function swapMarket({
address: ethers.constants.AddressZero,
},
provider,
walletAddress,
signer,
);
const swapInfo = await orionAggregator.getSwapInfo(type, assetIn, assetOut, amount.toString());
@@ -134,40 +134,6 @@ export default async function swapMarket({
const percent = new BigNumber(slippagePercent).div(100);
const fixBalanceIssue = async (issue: BalanceIssue) => {
const tokenContract = contracts.ERC20__factory.connect(issue.asset.address, provider);
const approve = async ({ spenderAddress, targetAmount }: Approve) => {
const bnTargetAmount = new BigNumber(targetAmount);
const unsignedApproveTx = await tokenContract
.populateTransaction
.approve(
spenderAddress,
bnTargetAmount.isZero()
? '0' // Reset
: ethers.constants.MaxUint256, // Infinite approve
);
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
unsignedApproveTx.chainId = parseInt(chainId, 16);
unsignedApproveTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
unsignedApproveTx.nonce = nonce;
unsignedApproveTx.from = walletAddress;
const gasLimit = await provider.estimateGas(unsignedApproveTx);
unsignedApproveTx.gasLimit = gasLimit;
const signedTx = await signer.signTransaction(unsignedApproveTx);
const txResponse = await provider.sendTransaction(signedTx);
options?.logger?.(`${issue.asset.name} approve transaction sent ${txResponse.hash}. Waiting for confirmation...`);
await txResponse.wait();
options?.logger?.(`${issue.asset.name} approve transaction confirmed.`);
};
await issue.approves?.reduce(async (promise, item) => {
await promise;
return approve(item);
}, Promise.resolve());
};
if (swapInfo.isThroughPoolOptimal) {
options?.logger?.('Swap through pool');
const pathAddresses = swapInfo.path.map((name) => {
@@ -252,25 +218,7 @@ export default async function swapMarket({
});
}
options?.logger?.(`Balance requirements: ${balanceGuard.requirements
.map((requirement) => `${requirement.amount} ${requirement.asset.name} `
+ `for '${requirement.reason}' `
+ `from [${requirement.sources.join(' + ')}]`)
.join(', ')}`);
const balanceIssues = await balanceGuard.check();
const autofixableBalanceIssues = balanceIssues.filter((balanceIssue) => balanceIssue.approves);
const allBalanceIssuesIsAutofixable = autofixableBalanceIssues.length === balanceIssues.length;
if (!allBalanceIssuesIsAutofixable) options?.logger?.('Some balance issues is not autofixable');
if (!allBalanceIssuesIsAutofixable || (options !== undefined && !options.autoApprove)) {
throw new Error(`Balance issues: ${balanceIssues.map((issue, i) => `${i + 1}. ${issue.message}`).join('\n')}`);
}
await autofixableBalanceIssues.reduce(async (promise, item) => {
await promise;
return fixBalanceIssue(item);
}, Promise.resolve());
await balanceGuard.check(options?.autoApprove);
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
unsignedSwapThroughOrionPoolTx.nonce = nonce;
@@ -374,25 +322,7 @@ export default async function swapMarket({
sources: getAvailableSources('orion_fee', feeAssetAddress, 'aggregator'),
});
options?.logger?.(`Balance requirements: ${balanceGuard.requirements
.map((requirement) => `${requirement.amount} ${requirement.asset.name} `
+ `for '${requirement.reason}' `
+ `from [${requirement.sources.join(' + ')}]`)
.join(', ')}`);
const balanceIssues = await balanceGuard.check();
const autofixableBalanceIssues = balanceIssues.filter((balanceIssue) => balanceIssue.approves);
const allBalanceIssuesIsAutofixable = autofixableBalanceIssues.length === balanceIssues.length;
if (!allBalanceIssuesIsAutofixable) options?.logger?.('Some balance issues is not autofixable');
if (!allBalanceIssuesIsAutofixable || (options !== undefined && !options.autoApprove)) {
throw new Error(`Balance issues: ${balanceIssues.map((issue, i) => `${i + 1}. ${issue.message}`).join('\n')}`);
}
await autofixableBalanceIssues.reduce(async (promise, item) => {
await promise;
return fixBalanceIssue(item);
}, Promise.resolve());
await balanceGuard.check(options?.autoApprove);
const signedOrder = await crypt.signOrder(
baseAssetAddress,

View File

@@ -2,10 +2,9 @@ import { ethers } from 'ethers';
import { OrionAggregator } from '../services/OrionAggregator';
import { OrionBlockchain } from '../services/OrionBlockchain';
import { PriceFeed } from '../services/PriceFeed';
import swapMarket, { SwapMarketParams } from './swapMarket';
import { SupportedChainId } from '../types';
import Exchange from './Exchange';
type PureSwapMarketParams= Omit<SwapMarketParams, 'orionUnit'>
export default class OrionUnit {
public readonly env: string;
@@ -19,6 +18,8 @@ export default class OrionUnit {
public readonly priceFeed: PriceFeed;
public readonly exchange: Exchange;
public readonly apiUrl: string;
constructor(
@@ -35,12 +36,6 @@ export default class OrionUnit {
this.orionBlockchain = new OrionBlockchain(apiUrl, chainId);
this.orionAggregator = new OrionAggregator(apiUrl, chainId);
this.priceFeed = new PriceFeed(apiUrl);
}
public swapMarket(params: PureSwapMarketParams) {
return swapMarket({
...params,
orionUnit: this,
});
this.exchange = new Exchange(this);
}
}