Initial commit

This commit is contained in:
Aleksandr Kraiz
2022-04-20 23:41:04 +04:00
commit 106b702d21
118 changed files with 25394 additions and 0 deletions

67
.eslintrc.js Normal file
View File

@@ -0,0 +1,67 @@
module.exports = {
ignorePatterns: ['.eslintrc.js'],
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'airbnb-base',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: "./",
project: [
"./tsconfig.json"
],
ecmaVersion: 12,
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
],
rules: {
'@typescript-eslint/consistent-type-assertions': [
'error',
{
assertionStyle: 'never',
},
],
'import/max-dependencies': [
'error',
{
max: 20,
ignoreTypeImports: false,
},
],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': ['error'],
'max-len': [
1,
140,
2,
],
'import/extensions': [
'error',
'ignorePackages',
{
js: 'never',
jsx: 'never',
ts: 'never',
tsx: 'never',
},
],
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
};

34
.github/workflows/release-package.yml vendored Normal file
View File

@@ -0,0 +1,34 @@
name: Node.js Package
on:
release:
types: [created]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
- run: npm ci
- run: npm test
publish-gpr:
needs: build
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: 12
registry-url: https://npm.pkg.github.com/
- run: npm ci
- run: npm run build-ts
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

134
.gitignore vendored Normal file
View File

@@ -0,0 +1,134 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
lib
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
#idea service files
.idea
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
src/artifacts/contracts/

6
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,6 @@
{
"cSpell.words": [
"bignumber",
"denormalized"
]
}

185
README.md Normal file
View File

@@ -0,0 +1,185 @@
# Orion Protocol SDK
## Install
Before install SDK you need create Personal Access Token.
1. Create PAT [here](https://github.com/settings/tokens):
1. type any name (for example `READ_PACKAGES`)
2. select scope `read:packages`)
2. At your machine go to `~` (your home directory, **not project!**)
3. Create or modify `.npmrc` file with content:
```
//npm.pkg.github.com/:_authToken=YOUR_PAT
@orionprotocol:registry=https://npm.pkg.github.com/
```
4. Save `.npmrc` file
5. Now you can install `@orionprotocol/sdk` as dependency in your package
# Usage
## High level methods
### Easy start
```ts
import "dotenv/config";
import { initOrionUnit } from "@orionprotocol/sdk";
import { Wallet } from "ethers";
const chain = process.env.CHAINID; // 0x56
const env = process.env.ENV; // production
const privateKey = process.env.PRIVATE_KEY; // 0x...
if (!chain) throw new Error("CHAINID is required");
if (!env) throw new Error("ENV is required");
if (!privateKey) throw new Error("PRIVATE_KEY is required");
const wallet = new Wallet(privateKey);
// OrionUnit is chain-in-environment abstraction
const orionUnit = initOrionUnit(chain, env);
// Make market swap
orionUnit
.swapMarket({
type: "exactSpend",
assetIn: "ORN",
assetOut: "USDT",
feeAsset: "ORN",
amount: 23.89045345,
slippagePercent: 1,
signer: wallet,
options: {
logger: console.log,
// Set it to true if you want the issues associated with
// the lack of allowance to be automatically corrected
autoApprove: true,
},
})
.then(console.log);
```
## Low level methods
### Get historical price
```ts
const candles = await orionUnit.priceFeed.getCandles(
"ORN-USDT",
1650287678, // interval start
1650374078, // interval end
"5m", // interval
"all" // exchange
);
```
### Using contracts
```ts
import { contracts } from "@orionprotocol/sdk";
const exchangeContract = contracts.Exchange__factory.connect(
exchangeContractAddress,
orionUnit.provider
);
const erc20Contract = contracts.ERC20__factory.connect(
tokenAddress,
orionUnit.provider
);
const governanceContract = contracts.OrionGovernance__factory.connect(
governanceAddress,
orionUnit.provider
);
const orionVoting = contracts.OrionVoting__factory.connect(
votingContractAddress,
orionUnit.provider
);
```
### Get tradable pairs
```ts
const pairsList = await orionUnit.orionAggregator.getPairsList();
```
### Get swap info
```ts
const swapInfo = await orionUnit.orionAggregator.getSwapInfo(
// Use 'exactSpend' when 'amount' is how much you want spend. Use 'exactReceive' otherwise
type: 'exactSpend',
assetIn: 'ORN',
assetOut: 'USDT',
amount: 6.23453457,
);
```
### Place order in Orion Aggregator
```ts
const { orderId } = await orionUnit.orionAggregator.placeOrder(
{
senderAddress: '0x61eed69c0d112c690fd6f44bb621357b89fbe67f',
matcherAddress: '0xfbcad2c3a90fbd94c335fbdf8e22573456da7f68',
baseAsset: '0xf223eca06261145b3287a0fefd8cfad371c7eb34',
quoteAsset: '0xcb2951e90d8dcf16e1fa84ac0c83f48906d6a744',
matcherFeeAsset: '0xf223eca06261145b3287a0fefd8cfad371c7eb34',
amount: 500000000
price: 334600000,
matcherFee: '29296395', // Orion Fee + Network Fee
nonce: 1650345051276
expiration: 1652850651276
buySide: 0,
isPersonalSign: false, // https://docs.metamask.io/guide/signing-data.html#a-brief-history
},
false // Place in internal orderbook
)
```
### Orion Aggregator WebSocket
Available subscriptions:
```ts
ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE = 'apcus',
AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE = 'aobus',
ADDRESS_UPDATES_SUBSCRIBE = 'aus', // Orders history, balances info
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE = 'btasabus',
SWAP_SUBSCRIBE = 'ss', // Swap info updates
```
Example:
```ts
import { services } from "@orionprotocol/sdk";
import { v4 as uuidv4 } from "uuid";
const swapRequestId = uuidv4();
orionUnit.orionAggregator.ws.subscribe(
services.orionAggregator.ws.SubscriptionType.SWAP_SUBSCRIBE,
{
payload: {
d: swapRequestId,
i: assetIn, // asset in
o: assetOut, // asset out
e: true, // true when type of swap is exactSpend, can be omitted (true bu default)
a: 5.62345343,
},
// Handle data update in your way
callback: (swapInfo) => {
switch (swapInfo.kind) {
case "exactSpend":
console.log(swapInfo.availableAmountOut);
break;
case "exactReceive":
console.log(swapInfo.availableAmountOut);
break;
}
},
}
);
```

15892
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

77
package.json Normal file
View File

@@ -0,0 +1,77 @@
{
"name": "@orionprotocol/sdk",
"version": "0.1.0",
"description": "Orion Protocol SDK",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"scripts": {
"start": "npm run build-ts && node lib/index.js",
"develop": "concurrently -i -k -p \"[{name}]\" -n \"Node,TypeScript\" -c \"yellow.bold,cyan.bold\" \"yarn watch-js\" \"yarn watch-ts\"",
"clean": "rimraf lib/*",
"build-ts": "npm run typechain:generate-types && tsc --skipLibCheck",
"watch-ts": "npm run typechain:generate-types && tsc -w --skipLibCheck",
"watch-js": "nodemon lib/index.js",
"test": "exit 0",
"coverage": "jest --coverage",
"lint:eslint": "eslint ./src --ext .ts,.js,.tsx,.jsx",
"lint:eslint:fix": "eslint ./src --ext .ts,.js,.tsx,.jsx --fix",
"typechain:generate-types": "typechain --target=ethers-v5 ./src/abis/*.json --out-dir ./src/artifacts/contracts"
},
"repository": {
"type": "git",
"url": "git+https://github.com/orionprotocol/sdk.git"
},
"publishConfig": {
"@orionprotocol:registry": "https://npm.pkg.github.com"
},
"keywords": [
"sdk",
"orion",
"orionprotocol",
"trading"
],
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/orionprotocol/sdk/issues"
},
"devDependencies": {
"@typechain/ethers-v5": "^10.0.0",
"@types/csprng": "^0.1.2",
"@types/node": "^17.0.23",
"@types/node-fetch": "^2.6.1",
"@types/socket.io-client": "1.4.33",
"@types/uuid": "^8.3.4",
"@types/ws": "^8.5.3",
"@typescript-eslint/eslint-plugin": "^5.16.0",
"@typescript-eslint/parser": "^5.16.0",
"concurrently": "^7.0.0",
"eslint": "^8.12.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.25.4",
"husky": "^7.0.4",
"jest": "^27.5.1",
"typechain": "^8.0.0",
"typescript": "^4.5.3"
},
"dependencies": {
"@ethersproject/abstract-signer": "^5.6.0",
"@ethersproject/providers": "^5.6.2",
"bignumber.js": "^9.0.2",
"csprng": "^0.1.2",
"ethers": "^5.6.2",
"isomorphic-ws": "^4.0.1",
"just-clone": "^5.0.1",
"node-fetch": "^2.6.7",
"socket.io-client": "2.4.0",
"tiny-invariant": "^1.2.0",
"uuid": "^8.3.2",
"websocket-heartbeat-js": "^1.1.0",
"ws": "^8.5.0",
"zod": "^3.14.4"
},
"homepage": "https://github.com/orionprotocol/sdk#readme",
"files": [
"lib/**/*"
]
}

337
src/BalanceGuard.ts Normal file
View File

@@ -0,0 +1,337 @@
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import clone from 'just-clone';
import { contracts, utils } from '.';
import { APPROVE_ERC20_GAS_LIMIT, NATIVE_CURRENCY_PRECISION } from './constants';
import {
AggregatedBalanceRequirement, Asset, BalanceIssue, BalanceRequirement, Source,
} from './types';
const arrayEquals = (a: unknown[], b: unknown[]) => a.length === b.length
&& a.every((value, index) => value === b[index]);
// By asset + sources + spender
const aggregateBalanceRequirements = (requirements: BalanceRequirement[]) => requirements
.reduce<AggregatedBalanceRequirement[]>((prev, curr) => {
const aggregatedBalanceRequirement = prev.find(
(item) => item.asset.address === curr.asset.address
&& arrayEquals(item.sources, curr.sources)
&& item.spenderAddress === curr.spenderAddress,
);
if (aggregatedBalanceRequirement) {
aggregatedBalanceRequirement.items = {
...aggregatedBalanceRequirement.items,
[curr.reason]: curr.amount,
};
return prev;
}
return [
...prev,
{
asset: curr.asset,
sources: curr.sources,
spenderAddress: curr.spenderAddress,
items: {
[curr.reason]: curr.amount,
},
},
];
}, []);
export default class BalanceGuard {
private readonly balances: Partial<
Record<
string,
Record<
'exchange' | 'wallet' | 'allowance',
BigNumber>
>
>;
public readonly requirements: BalanceRequirement[] = [];
private readonly nativeCryptocurrency: Asset;
private readonly provider: ethers.providers.Provider;
private readonly walletAddress: string;
constructor(
balances: Partial<Record<string, Record<'exchange' | 'wallet' | 'allowance', BigNumber>>>,
nativeCryptocurrency: Asset,
provider: ethers.providers.Provider,
walletAddress: string,
) {
this.balances = balances;
this.nativeCryptocurrency = nativeCryptocurrency;
this.provider = provider;
this.walletAddress = walletAddress;
}
registerRequirement(expense: BalanceRequirement) {
this.requirements.push(expense);
}
// Used for case feeAsset === assetOut
setExtraBalance(assetName: string, amount: BigNumber.Value, source: Source) {
const assetBalance = this.balances[assetName];
if (!assetBalance) throw Error(`Can't set extra balance. Asset ${assetName} not found`);
assetBalance[source] = assetBalance[source].plus(amount);
}
private async checkResetRequired(
assetAddress: string,
spenderAddress: string,
walletAddress: string,
) {
const tokenContract = contracts.ERC20__factory
.connect(assetAddress, this.provider);
const unsignedTx = await tokenContract.populateTransaction
.approve(
spenderAddress,
ethers.constants.MaxUint256,
);
unsignedTx.from = walletAddress;
let resetRequired = false;
try {
await this.provider.estimateGas(unsignedTx);
} catch {
resetRequired = true;
}
return resetRequired;
}
async check() {
const remainingBalances = clone(this.balances);
const aggregatedRequirements = aggregateBalanceRequirements(this.requirements);
// Balance absorption order is important!
// 1. Exchange-contract only
// 2. Exchange + wallet (can produce approves requirements)
// 3. Wallet balance (tokens) (can produce approves requirements)
// 4. Wallet balance: native cryptocurrency
const requiredApproves: AggregatedBalanceRequirement = {
asset: this.nativeCryptocurrency,
sources: ['wallet'],
items: {},
};
const balanceIssues: BalanceIssue[] = [];
const flattedAggregatedRequirements = Object
.values(aggregatedRequirements)
.flatMap((item) => item);
const exchangeOnlyAggregatedRequirements = aggregatedRequirements
.filter(({ sources }) => sources.length === 1 && sources[0] === 'exchange');
exchangeOnlyAggregatedRequirements.forEach(({ asset, items }) => {
const remainingBalance = remainingBalances[asset.name];
if (!remainingBalance) throw new Error(`No ${asset.name} balance`);
const itemsAmountSum = Object.values(items)
.reduce<BigNumber>((p, c) => (c ? p.plus(c) : p), new BigNumber(0));
remainingBalance.exchange = remainingBalance.exchange.minus(itemsAmountSum);
if (remainingBalance.exchange.lt(0)) {
const lackAmount = remainingBalance.exchange.abs();
const exchangeBalance = this.balances?.[asset.name]?.exchange;
balanceIssues.push({
asset,
sources: ['exchange'],
message: `Not enough ${asset.name} on exchange balance. `
+ `Needed: ${itemsAmountSum.toString()}, available: ${exchangeBalance?.toString()}. `
+ `You need to deposit at least ${lackAmount.toString()} ${asset.name} into exchange contract`,
});
}
});
const exchangePlusWalletAggregatedRequirements = aggregatedRequirements
.filter(({ sources }) => sources[0] === 'exchange' && sources[1] === 'wallet');
// This requirements can be fulfilled by exchange + wallet
await Promise.all(exchangePlusWalletAggregatedRequirements
.map(async ({ asset, spenderAddress, items }) => {
const remainingBalance = remainingBalances[asset.name];
if (!remainingBalance) throw new Error(`No ${asset.name} balance`);
const itemsAmountSum = Object.values(items)
.reduce<BigNumber>((p, c) => (c ? p.plus(c) : p), new BigNumber(0));
remainingBalance.exchange = remainingBalance.exchange.minus(itemsAmountSum);
if (remainingBalance.exchange.lt(0)) {
const lackAmount = remainingBalance.exchange.abs(); // e.g. -435.234234 to 434.234234
// Try to take lack amount from wallet
const approvedWalletBalance = BigNumber
.min(
remainingBalance.wallet,
remainingBalance.allowance,
// For native cryptocurrency allowance is always just current balance
);
if (lackAmount.lte(approvedWalletBalance)) { // We can take lack amount from wallet
remainingBalance.wallet = remainingBalance.wallet.minus(lackAmount);
} else {
// We can't take lack amount from wallet. Is approve helpful?
const approveAvailable = remainingBalance.wallet.gt(approvedWalletBalance)
? remainingBalance.wallet.minus(approvedWalletBalance)
: new BigNumber(0);
const approveIsHelpful = approveAvailable.gte(lackAmount);
const targetApprove = approvedWalletBalance.plus(lackAmount);
const exchangeBalance = this.balances?.[asset.name]?.exchange;
const available = exchangeBalance?.plus(approvedWalletBalance);
const issueMessage = `Not enough ${asset.name} on exchange + wallet balance. `
+ `Needed: ${itemsAmountSum.toString()}, available: ${available?.toString()} `
+ `(exchange: ${exchangeBalance?.toString()}, wallet: ${approvedWalletBalance.toString()}). ${approveIsHelpful
? `You need to be allowed to spend another ${lackAmount.toString()} ${asset.name} more`
: 'Approve is not helpful'}`;
if (approveIsHelpful) {
if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`);
const resetRequired = await this.checkResetRequired(
asset.address,
spenderAddress,
this.walletAddress,
);
const gasPriceWei = await this.provider.getGasPrice();
const approveTransactionCost = ethers.BigNumber
.from(APPROVE_ERC20_GAS_LIMIT)
.mul(gasPriceWei);
const denormalizedApproveTransactionCost = utils
.denormalizeNumber(approveTransactionCost, NATIVE_CURRENCY_PRECISION);
requiredApproves.items = {
...requiredApproves.items,
...resetRequired && {
[`Reset ${asset.name} from 'wallet' to ${spenderAddress}`]: denormalizedApproveTransactionCost.toString(),
},
[`Approve ${asset.name} from 'wallet' to ${spenderAddress}`]: denormalizedApproveTransactionCost.toString(),
};
balanceIssues.push({
asset,
sources: ['exchange', 'wallet'],
approves: [
...resetRequired ? [{
targetAmount: 0,
spenderAddress,
}] : [],
{
targetAmount: targetApprove,
spenderAddress,
},
],
message: issueMessage,
});
} else {
balanceIssues.push({
asset,
sources: ['exchange', 'wallet'],
message: issueMessage,
});
}
}
}
}));
const walletTokensAggregatedRequirements = flattedAggregatedRequirements
.filter(({ sources, asset }) => sources[0] === 'wallet' && asset.name !== this.nativeCryptocurrency.name);
await Promise.all(walletTokensAggregatedRequirements
.map(async ({ asset, spenderAddress, items }) => {
const remainingBalance = remainingBalances[asset.name];
if (!remainingBalance) throw new Error(`No ${asset.name} balance`);
const itemsAmountSum = Object.values(items)
.reduce<BigNumber>((p, c) => (c ? p.plus(c) : p), new BigNumber(0));
const approvedWalletBalance = BigNumber
.min(
remainingBalance.wallet,
remainingBalance.allowance,
);
if (itemsAmountSum.lte(approvedWalletBalance)) { // Approved wallet balance is enough
remainingBalance.wallet = remainingBalance.wallet.minus(itemsAmountSum);
} else {
// We can't take lack amount from wallet. Is approve helpful?
const lackAmount = itemsAmountSum.minus(approvedWalletBalance).abs();
const approveAvailable = remainingBalance.wallet.gt(approvedWalletBalance)
? remainingBalance.wallet.minus(approvedWalletBalance)
: new BigNumber(0);
const approveIsHelpful = approveAvailable.gte(lackAmount);
const targetApprove = approvedWalletBalance.plus(lackAmount);
const issueMessage = `Not enough ${asset.name} on wallet balance. `
+ `Needed: ${itemsAmountSum.toString()}, available: ${approvedWalletBalance.toString()}. ${approveIsHelpful
? `You need to be allowed to spend another ${lackAmount.toString()} ${asset.name} more`
: 'Approve is not helpful'}`;
if (approveIsHelpful) {
if (!spenderAddress) throw new Error(`Spender address is required for ${asset.name}`);
const resetRequired = await this.checkResetRequired(
asset.address,
spenderAddress,
this.walletAddress,
);
const gasPriceWei = await this.provider.getGasPrice();
const approveTransactionCost = ethers.BigNumber
.from(APPROVE_ERC20_GAS_LIMIT)
.mul(gasPriceWei);
const denormalizedApproveTransactionCost = utils
.denormalizeNumber(approveTransactionCost, NATIVE_CURRENCY_PRECISION);
requiredApproves.items = {
...requiredApproves.items,
...resetRequired && {
[`Reset ${asset.name} from 'wallet' to ${spenderAddress}`]: denormalizedApproveTransactionCost.toString(),
},
[`Approve ${asset.name} from 'wallet' to ${spenderAddress}`]: denormalizedApproveTransactionCost.toString(),
};
balanceIssues.push({
asset,
sources: ['wallet'],
approves: [
...resetRequired ? [{
targetAmount: 0,
spenderAddress,
}] : [],
{
targetAmount: targetApprove,
spenderAddress,
},
],
message: issueMessage,
});
} else {
balanceIssues.push({
asset,
sources: ['wallet'],
message: issueMessage,
});
}
}
}));
const walletNativeAggregatedRequirements = flattedAggregatedRequirements
.filter(({ sources, asset }) => sources[0] === 'wallet' && asset.name === this.nativeCryptocurrency.name);
walletNativeAggregatedRequirements.forEach(({ asset, items }) => {
const remainingBalance = remainingBalances[asset.name];
if (!remainingBalance) throw new Error(`No ${asset.name} balance`);
const itemsAmountSum = Object.values({ ...items, ...requiredApproves.items })
.reduce<BigNumber>((p, c) => (c ? p.plus(c) : p), new BigNumber(0));
remainingBalance.wallet = remainingBalance.wallet.minus(itemsAmountSum);
if (remainingBalance.wallet.lt(0)) {
const lackAmount = remainingBalance.wallet.abs();
balanceIssues.push({
asset,
sources: ['wallet'],
message: `Not enough ${asset.name} on wallet balance. `
+ `You need to deposit at least ${lackAmount.toString()} ${asset.name} into wallet contract`,
});
}
});
return balanceIssues;
}
}

46
src/OrionUnit/index.ts Normal file
View File

@@ -0,0 +1,46 @@
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';
type PureSwapMarketParams= Omit<SwapMarketParams, 'orionUnit'>
export default class OrionUnit {
public readonly env: string;
public readonly chainId: SupportedChainId;
public readonly provider: ethers.providers.StaticJsonRpcProvider;
public readonly orionBlockchain: OrionBlockchain;
public readonly orionAggregator: OrionAggregator;
public readonly priceFeed: PriceFeed;
public readonly apiUrl: string;
constructor(
chainId: SupportedChainId,
rpc: string,
env: string,
apiUrl: string,
) {
this.chainId = chainId;
this.provider = new ethers.providers.StaticJsonRpcProvider(rpc);
this.env = env;
this.apiUrl = apiUrl;
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,
});
}
}

419
src/OrionUnit/swapMarket.ts Normal file
View File

@@ -0,0 +1,419 @@
/* 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';
export type SwapMarketParams = {
type: 'exactSpend' | 'exactReceive',
assetIn: string,
assetOut: string,
amount: BigNumber.Value,
feeAsset: string,
slippagePercent: BigNumber.Value,
signer: ethers.Signer,
orionUnit: OrionUnit,
options?: {
logger?: (message: string) => void,
// route?: 'pool' | 'aggregator',
autoApprove?: boolean,
}
}
type AggregatorOrder = {
through: 'aggregator'
id: string,
}
type PoolSwap = {
through: 'orion_pool'
txHash: string,
}
type Swap = AggregatorOrder | PoolSwap;
export default async function swapMarket({
type,
assetIn,
assetOut,
amount,
feeAsset,
slippagePercent,
signer,
orionUnit,
options,
}: SwapMarketParams): Promise<Swap> {
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');
if (feeAsset === '') throw new Error('Fee asset can not be empty');
if (slippagePercent === '') throw new Error('Slippage percent can not be empty');
const amountBN = new BigNumber(amount);
if (amountBN.isNaN()) throw new Error(`Amount '${amount.toString()}' is not a number`);
const slippagePercentBN = new BigNumber(slippagePercent);
if (slippagePercentBN.isNaN()) throw new Error(`Slippage percent '${slippagePercent.toString()}' is not a number`);
if (slippagePercentBN.lte(0)) throw new Error('Slippage percent should be greater than 0');
if (slippagePercentBN.gte(50)) throw new Error('Slippage percent should be less than 50');
const walletAddress = await signer.getAddress();
options?.logger?.(`Wallet address is ${walletAddress}`);
const {
orionBlockchain, orionAggregator, provider, chainId,
} = orionUnit;
const {
exchangeContractAddress,
matcherAddress,
assetToAddress,
} = await orionBlockchain.getInfo();
const addressToAsset = Object
.entries(assetToAddress)
.reduce<Partial<Record<string, string>>>((prev, [asset, address]) => {
if (!address) return prev;
return {
...prev,
[address]: asset,
};
}, {});
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 feeAssets = await orionBlockchain.getTokensFee();
const pricesInOrn = await orionBlockchain.getPrices();
const gasPriceWei = await orionBlockchain.getGasPriceWei();
const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei').toString();
const assetInAddress = assetToAddress[assetIn];
if (!assetInAddress) throw new Error(`Asset '${assetIn}' not found`);
const feeAssetAddress = assetToAddress[feeAsset];
if (!feeAssetAddress) throw new Error(`Fee asset '${feeAsset}' not found. Available assets: ${Object.keys(feeAssets).join(', ')}`);
const balances = await getBalances(
{
[assetIn]: assetInAddress,
[feeAsset]: feeAssetAddress,
[nativeCryptocurrency]: ethers.constants.AddressZero,
},
orionAggregator,
walletAddress,
exchangeContract,
provider,
);
const balanceGuard = new BalanceGuard(
balances,
{
name: nativeCryptocurrency,
address: ethers.constants.AddressZero,
},
provider,
walletAddress,
);
const swapInfo = await orionAggregator.getSwapInfo(type, assetIn, assetOut, amount.toString());
if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
throw new Error(`Amount is too low. Min amountOut is ${swapInfo.minAmountOut} ${assetOut}`);
}
if (swapInfo.type === 'exactSpend' && amountBN.lt(swapInfo.minAmountIn)) {
throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`);
}
if (swapInfo.orderInfo === null) throw new Error(swapInfo.executionInfo);
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) => {
const assetAddress = assetToAddress?.[name];
if (!assetAddress) throw new Error(`No asset address for ${name}`);
return assetAddress;
});
const amountOutWithSlippage = new BigNumber(swapInfo.amountOut)
.multipliedBy(new BigNumber(1).minus(percent))
.toString();
const amountInWithSlippage = new BigNumber(swapInfo.amountIn)
.multipliedBy(new BigNumber(1).plus(percent))
.toString();
const amountSpend = swapInfo.type === 'exactSpend' ? swapInfo.amountIn : amountInWithSlippage;
balanceGuard.registerRequirement({
reason: 'Amount spend',
asset: {
name: assetIn,
address: assetInAddress,
},
amount: amountSpend.toString(),
spenderAddress: exchangeContractAddress,
sources: getAvailableSources('amount', assetInAddress, 'orion_pool'),
});
const amountReceive = swapInfo.type === 'exactReceive' ? swapInfo.amountOut : amountOutWithSlippage;
const unsignedSwapThroughOrionPoolTx = await exchangeContract.populateTransaction.swapThroughOrionPool(
utils.normalizeNumber(
amountSpend,
INTERNAL_ORION_PRECISION,
BigNumber.ROUND_CEIL,
),
utils.normalizeNumber(
amountReceive,
INTERNAL_ORION_PRECISION,
BigNumber.ROUND_FLOOR,
),
pathAddresses,
type === 'exactSpend',
);
unsignedSwapThroughOrionPoolTx.chainId = parseInt(chainId, 16);
unsignedSwapThroughOrionPoolTx.gasPrice = ethers.BigNumber.from(gasPriceWei);
unsignedSwapThroughOrionPoolTx.from = walletAddress;
const amountSpendBN = new BigNumber(amountSpend);
let value = new BigNumber(0);
const denormalizedAssetInExchangeBalance = balances[assetIn]?.exchange;
if (!denormalizedAssetInExchangeBalance) throw new Error(`Asset '${assetIn}' exchange balance is not found`);
if (assetIn === nativeCryptocurrency && amountSpendBN.gt(denormalizedAssetInExchangeBalance)) {
value = amountSpendBN.minus(denormalizedAssetInExchangeBalance);
}
unsignedSwapThroughOrionPoolTx.value = utils.normalizeNumber(value, NATIVE_CURRENCY_PRECISION, BigNumber.ROUND_CEIL);
unsignedSwapThroughOrionPoolTx.gasLimit = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT);
const transactionCost = ethers.BigNumber.from(SWAP_THROUGH_ORION_POOL_GAS_LIMIT).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: getAvailableSources('network_fee', ethers.constants.AddressZero, 'orion_pool'),
});
if (value.gt(0)) {
balanceGuard.registerRequirement({
reason: 'Transaction value (extra amount)',
asset: {
name: nativeCryptocurrency,
address: ethers.constants.AddressZero,
},
amount: value.toString(),
sources: getAvailableSources('amount', ethers.constants.AddressZero, 'orion_pool'),
});
}
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());
const nonce = await provider.getTransactionCount(walletAddress, 'pending');
unsignedSwapThroughOrionPoolTx.nonce = nonce;
const signedSwapThroughOrionPoolTx = await signer.signTransaction(unsignedSwapThroughOrionPoolTx);
const swapThroughOrionPoolTxResponse = await provider.sendTransaction(signedSwapThroughOrionPoolTx);
return {
through: 'orion_pool',
txHash: swapThroughOrionPoolTxResponse.hash,
};
}
options?.logger?.('Swap through aggregator');
const slippageMultiplier = new BigNumber(1).plus(
swapInfo.orderInfo.side === 'SELL'
? percent.negated() // e.g. -0.01
: percent, // e.g. 0.01
);
const safePriceWithDeviation = percent.isZero()
? swapInfo.orderInfo.safePrice
: new BigNumber(swapInfo.orderInfo.safePrice)
.multipliedBy(slippageMultiplier)
.toString();
const [baseAssetName, quoteAssetName] = swapInfo.orderInfo.assetPair.split('-');
const pairConfig = await orionAggregator.getPairConfig(`${baseAssetName}-${quoteAssetName}`);
if (!pairConfig) throw new Error(`Pair config ${baseAssetName}-${quoteAssetName} not found`);
const baseAssetAddress = assetToAddress[baseAssetName];
if (!baseAssetAddress) throw new Error(`No asset address for ${baseAssetName}`);
const quoteAssetAddress = assetToAddress[quoteAssetName];
if (!quoteAssetAddress) throw new Error(`No asset address for ${quoteAssetName}`);
const safePriceWithAppliedPrecision = new BigNumber(safePriceWithDeviation)
.decimalPlaces(
pairConfig.pricePrecision,
swapInfo.orderInfo.side === 'BUY'
? BigNumber.ROUND_CEIL
: BigNumber.ROUND_FLOOR,
);
balanceGuard.registerRequirement({
reason: 'Amount',
asset: {
name: assetIn,
address: assetInAddress,
},
amount: swapInfo.orderInfo.side === 'SELL'
? swapInfo.orderInfo.amount.toString()
: safePriceWithAppliedPrecision.multipliedBy(swapInfo.orderInfo.amount).toString(),
spenderAddress: exchangeContractAddress,
sources: getAvailableSources('amount', assetInAddress, 'aggregator'),
});
// Fee calculation
const baseAssetPriceInOrn = pricesInOrn?.[baseAssetAddress];
if (!baseAssetPriceInOrn) throw new Error(`Base asset price ${baseAssetName} in ORN not found`);
const baseCurrencyPriceInOrn = pricesInOrn[ethers.constants.AddressZero];
if (!baseCurrencyPriceInOrn) throw new Error('Base currency price in ORN not found');
const feeAssetPriceInOrn = pricesInOrn[feeAssetAddress];
if (!feeAssetPriceInOrn) throw new Error(`Fee asset price ${feeAsset} in ORN not found`);
const feePercent = feeAssets?.[feeAsset];
if (!feePercent) throw new Error(`Fee asset ${feeAsset} not available`);
const { orionFeeInFeeAsset, networkFeeInFeeAsset, totalFeeInFeeAsset } = utils.calculateFeeInFeeAsset(
swapInfo.orderInfo.amount,
feeAssetPriceInOrn,
baseAssetPriceInOrn,
baseCurrencyPriceInOrn,
gasPriceGwei,
feePercent,
);
if (feeAsset === assetOut) {
options?.logger?.('Fee asset equals received asset. The fee can be paid from the amount received');
options?.logger?.(`Set extra balance: + ${swapInfo.amountOut} ${assetOut} to exchange`);
balanceGuard.setExtraBalance(feeAsset, swapInfo.amountOut, 'exchange');
}
balanceGuard.registerRequirement({
reason: 'Network fee',
asset: {
name: feeAsset,
address: feeAssetAddress,
},
amount: networkFeeInFeeAsset,
spenderAddress: exchangeContractAddress,
sources: getAvailableSources('network_fee', feeAssetAddress, 'aggregator'),
});
balanceGuard.registerRequirement({
reason: 'Orion fee',
asset: {
name: feeAsset,
address: feeAssetAddress,
},
amount: orionFeeInFeeAsset,
spenderAddress: exchangeContractAddress,
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());
const signedOrder = await crypt.signOrder(
baseAssetAddress,
quoteAssetAddress,
swapInfo.orderInfo.side,
safePriceWithAppliedPrecision.toString(),
swapInfo.orderInfo.amount,
totalFeeInFeeAsset,
walletAddress,
matcherAddress,
feeAssetAddress,
false,
signer,
chainId,
);
const orderIsOk = await exchangeContract.validateOrder(signedOrder);
if (!orderIsOk) throw new Error('Order is not valid');
const { orderId } = await orionAggregator.placeOrder(signedOrder, false);
return {
through: 'aggregator',
id: orderId,
};
}

View File

@@ -0,0 +1,494 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "reward",
"type": "uint256"
}
],
"name": "RewardAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "reward",
"type": "uint256"
}
],
"name": "RewardPaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Staked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdrawn",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "_balances",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "earned",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "emergencyAssetWithdrawal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "exit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getReward",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_stakingToken",
"type": "address"
},
{
"internalType": "address",
"name": "_rewardsToken",
"type": "address"
},
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "lastTimeRewardApplicable",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastUpdateTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_reward",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_rewardsDuration",
"type": "uint256"
}
],
"name": "notifyRewardAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "periodFinish",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerToken",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerTokenStored",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardRate",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "rewards",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardsDuration",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardsToken",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "stakeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "stakeWithPermit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "stakingToken",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "userRewardPerTokenPaid",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

222
src/abis/ERC20.json Normal file
View File

@@ -0,0 +1,222 @@
[
{
"constant": true,
"inputs": [],
"name": "name",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_spender",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_from",
"type": "address"
},
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [
{
"name": "",
"type": "uint8"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "symbol",
"outputs": [
{
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{
"name": "_to",
"type": "address"
},
{
"name": "_value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "_owner",
"type": "address"
},
{
"name": "_spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"payable": true,
"stateMutability": "payable",
"type": "fallback"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "owner",
"type": "address"
},
{
"indexed": true,
"name": "spender",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"name": "from",
"type": "address"
},
{
"indexed": true,
"name": "to",
"type": "address"
},
{
"indexed": false,
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
}
]

1687
src/abis/Exchange.json Normal file

File diff suppressed because it is too large Load Diff

524
src/abis/IDOCollector.json Normal file
View File

@@ -0,0 +1,524 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "previousAdminRole",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "bytes32",
"name": "newAdminRole",
"type": "bytes32"
}
],
"name": "RoleAdminChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleGranted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "account",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
}
],
"name": "RoleRevoked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "time",
"type": "uint256"
}
],
"name": "TokensClaimed",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "participant",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "time",
"type": "uint256"
}
],
"name": "UserParticipated",
"type": "event"
},
{
"inputs": [],
"name": "DEFAULT_ADMIN_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ORNToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "OWNER_ROLE",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "addOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "allocation",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "claimTokens",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "address",
"name": "wallet",
"type": "address"
}
],
"name": "emergencyAssetWithdrawal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "finishTime",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleAdmin",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "getRoleMember",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
}
],
"name": "getRoleMemberCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "grantRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "hasRole",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "idoToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "participate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "renounceRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "role",
"type": "bytes32"
},
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "revokeRole",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_ornToken",
"type": "address"
},
{
"internalType": "address",
"name": "_idoToken",
"type": "address"
},
{
"internalType": "uint32",
"name": "_startTime",
"type": "uint32"
},
{
"internalType": "uint32",
"name": "_finishTime",
"type": "uint32"
},
{
"internalType": "uint256",
"name": "_allocation",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "_startClaimTime",
"type": "uint32"
}
],
"name": "setIDOParams",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "startClaimTime",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startTime",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes4",
"name": "interfaceId",
"type": "bytes4"
}
],
"name": "supportsInterface",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalORN",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "userBalances",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@@ -0,0 +1,362 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "NewTokenDistribution",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"indexed": false,
"internalType": "uint192",
"name": "amount",
"type": "uint192"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "orderHash",
"type": "bytes32"
}
],
"name": "TokensClaimed",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "accrued",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
},
{
"internalType": "address",
"name": "asset",
"type": "address"
},
{
"internalType": "uint192",
"name": "amount",
"type": "uint192"
},
{
"internalType": "uint64",
"name": "startTime",
"type": "uint64"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"internalType": "struct IDOAccruedDistributor.Order",
"name": "order_",
"type": "tuple"
}
],
"name": "claimTokens",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "claimedOrders",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint32",
"name": "finishClaimTime_",
"type": "uint32"
}
],
"name": "distibuteNewTokens",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "emergencyAssetWithdrawal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "finishClaimTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "idoToken",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "idoToken_",
"type": "address"
},
{
"internalType": "address",
"name": "verifier_",
"type": "address"
},
{
"internalType": "uint32",
"name": "startClaimTime_",
"type": "uint32"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "lastTimeRewardApplicable",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "payouts",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "startClaimTime",
"outputs": [
{
"internalType": "uint32",
"name": "",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "term",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokensAllocation",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "idoToken_",
"type": "address"
},
{
"internalType": "address",
"name": "verifier_",
"type": "address"
},
{
"internalType": "uint32",
"name": "startClaimTime_",
"type": "uint32"
}
],
"name": "updateParams",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "verifier",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,159 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [],
"name": "WETH9",
"outputs": [
{
"internalType": "contract IWETH9",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "exchange",
"outputs": [
{
"internalType": "contract IExchangeWithAtomic",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "exchangeAllowances",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_exchange",
"type": "address"
},
{
"internalType": "address",
"name": "_WETH9",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "pairAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokensToMigrate",
"type": "uint256"
},
{
"internalType": "bytes32",
"name": "secretHash0",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "secretHash1",
"type": "bytes32"
},
{
"internalType": "uint64",
"name": "expiration",
"type": "uint64"
},
{
"internalType": "uint24",
"name": "targetChainId",
"type": "uint24"
}
],
"name": "migrate",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"stateMutability": "payable",
"type": "receive"
}
]

View File

@@ -0,0 +1,851 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "reward",
"type": "uint256"
}
],
"name": "RewardAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "reward",
"type": "uint256"
}
],
"name": "RewardPaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Staked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdrawn",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint56",
"name": "lock_increase_amount",
"type": "uint56"
}
],
"name": "acceptLock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint56",
"name": "new_lock_amount",
"type": "uint56"
}
],
"name": "acceptNewLockAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
},
{
"internalType": "uint56",
"name": "lock_decrease_amount",
"type": "uint56"
}
],
"name": "acceptUnlock",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "balances_",
"outputs": [
{
"internalType": "uint56",
"name": "balance",
"type": "uint56"
},
{
"internalType": "uint56",
"name": "locked_balance",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "basic_fee_percent",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint56",
"name": "burn_size",
"type": "uint56"
}
],
"name": "burn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "burn_vote_end_",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "earned",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "emergencyAssetWithdrawal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "exit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "extra_fee_percent",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "extra_fee_seconds",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fee_total",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getAvailableWithdrawBalance",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getBalance",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getLockedBalance",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getReward",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getRewardForDuration",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTotalBalance",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "getTotalLockedBalance",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "wallet",
"type": "address"
}
],
"name": "getVaults",
"outputs": [
{
"components": [
{
"internalType": "uint56",
"name": "amount",
"type": "uint56"
},
{
"internalType": "uint64",
"name": "created_time",
"type": "uint64"
}
],
"internalType": "struct OrionGovernance.UserVault[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "staking_token",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "lastTimeRewardApplicable",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lastUpdateTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "reward",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_rewardsDuration",
"type": "uint256"
}
],
"name": "notifyRewardAmount",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "periodFinish",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerToken",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerTokenStored",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardRate",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "rewards",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardsDuration",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint64",
"name": "burn_vote_end",
"type": "uint64"
}
],
"name": "setBurnVoteEnd",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint16",
"name": "extra_fee_percent_",
"type": "uint16"
},
{
"internalType": "uint64",
"name": "extra_fee_seconds_",
"type": "uint64"
},
{
"internalType": "uint16",
"name": "basic_fee_percent_",
"type": "uint16"
}
],
"name": "setVaultParameters",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "voting_contract_address",
"type": "address"
}
],
"name": "setVotingContractAddress",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint56",
"name": "adding_amount",
"type": "uint56"
}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "staking_token_",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "total_balance_",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "total_votes_burn_",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "total_votes_dont_burn_",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "userRewardPerTokenPaid",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "user_burn_votes_",
"outputs": [
{
"internalType": "uint56",
"name": "",
"type": "uint56"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "index",
"type": "uint256"
}
],
"name": "vaultWithdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "vaults_",
"outputs": [
{
"internalType": "uint56",
"name": "amount",
"type": "uint56"
},
{
"internalType": "uint64",
"name": "created_time",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint56",
"name": "voting_amount",
"type": "uint56"
},
{
"internalType": "bool",
"name": "vote_for_burn",
"type": "bool"
}
],
"name": "voteBurn",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "voteBurnAvailable",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "voting_contract_address_",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint56",
"name": "removing_amount",
"type": "uint56"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"_pair","type":"address"},{"internalType":"address","name":"_router","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"},{"internalType":"address","name":"_stakingRewards","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount0V1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1V1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0V2","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1V2","type":"uint256"}],"name":"TestCalc","type":"event"},{"inputs":[{"internalType":"uint256","name":"tokensToMigrate","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"}]

View File

@@ -0,0 +1 @@
[{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":false,"internalType":"address","name":"pair","type":"address"},{"indexed":false,"internalType":"uint256","name":"","type":"uint256"}],"name":"PairCreated","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"allPairs","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allPairsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"value","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"}],"name":"createPair","outputs":[{"internalType":"address","name":"pair","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeToSetter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_feeTo","type":"address"}],"name":"setFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_feeToSetter","type":"address"}],"name":"setFeeToSetter","outputs":[],"stateMutability":"nonpayable","type":"function"}]

View File

@@ -0,0 +1,507 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "reward",
"type": "uint256"
}
],
"name": "RewardAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "reward",
"type": "uint256"
}
],
"name": "RewardPaid",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Staked",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "balances_account",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "rewardPerToken",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "userRewardPerTokenPaid_account",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "rewards_account",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "voting_contract_getPoolRewards",
"type": "uint256"
}
],
"name": "TestEarnedCalc",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "_rewardPerToken",
"type": "uint256"
}
],
"name": "TestRewardPerToken",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "_rewardPerTokenStored",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "_voting_pool_accumulator_stored",
"type": "uint256"
}
],
"name": "TestUpdateReward",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "rewards",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "userRewardPerTokenPaid",
"type": "uint256"
}
],
"name": "TestUpdateRewardUser",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "user",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "Withdrawn",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "_balances",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "account",
"type": "address"
}
],
"name": "earned",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "asset",
"type": "address"
}
],
"name": "emergencyAssetWithdrawal",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "exit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getReward",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_stakingToken",
"type": "address"
},
{
"internalType": "address",
"name": "voting_contract_address",
"type": "address"
}
],
"name": "initialize",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerToken",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rewardPerTokenStored",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "rewards",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "stake",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "to",
"type": "address"
}
],
"name": "stakeTo",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "stakeWithPermit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "stakingToken",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "userRewardPerTokenPaid",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "voting_contract_",
"outputs": [
{
"internalType": "contract IOrionVoting",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "voting_pool_accumulator_stored_",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
}
],
"name": "withdraw",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View File

@@ -0,0 +1 @@
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"uint56","name":"amount","type":"uint56"}],"name":"cease","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint56","name":"amount","type":"uint56"},{"internalType":"address","name":"to","type":"address"}],"name":"claimRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pool_address","type":"address"}],"name":"getPoolRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRewardPerVotingToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"rewards_token","type":"address"},{"internalType":"address","name":"governance_contract_address","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"pool_states_","outputs":[{"internalType":"uint8","name":"state","type":"uint8"},{"internalType":"uint56","name":"votes","type":"uint56"},{"internalType":"uint256","name":"last_acc_reward_per_voting_token","type":"uint256"},{"internalType":"uint256","name":"acc_reward","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reward_rate_","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rewards_token_","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool_address","type":"address"},{"internalType":"uint8","name":"new_state","type":"uint8"}],"name":"setPoolState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"rewards","type":"uint64"},{"internalType":"uint64","name":"duration","type":"uint64"}],"name":"setRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint56","name":"","type":"uint56"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"total_supply_","outputs":[{"internalType":"uint56","name":"","type":"uint56"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"user_votes_","outputs":[{"internalType":"uint56","name":"voted_amount","type":"uint56"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"poolAddress","type":"address"},{"internalType":"uint56","name":"amount","type":"uint56"}],"name":"vote","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pool_address","type":"address"}],"name":"votes","outputs":[{"internalType":"uint56","name":"","type":"uint56"}],"stateMutability":"view","type":"function"}]

273
src/abis/PriceOracle.json Normal file
View File

@@ -0,0 +1,273 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "publicKey",
"type": "address"
},
{
"internalType": "address",
"name": "_baseAsset",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "previousOwner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "assetPrices",
"outputs": [
{
"internalType": "uint64",
"name": "price",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "timestamp",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "baseAsset",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "chainLinkETHAggregator",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "added",
"type": "address[]"
},
{
"internalType": "address[]",
"name": "removed",
"type": "address[]"
}
],
"name": "changePriceProviderAuthorization",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "assets",
"type": "address[]"
}
],
"name": "getChainLinkPriceData",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "assetAddresses",
"type": "address[]"
}
],
"name": "givePrices",
"outputs": [
{
"components": [
{
"internalType": "uint64",
"name": "price",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "timestamp",
"type": "uint64"
}
],
"internalType": "struct PriceOracle.PriceDataOut[]",
"name": "",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "oraclePublicKey",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "priceProviderAuthorization",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address[]",
"name": "assetAddresses",
"type": "address[]"
},
{
"internalType": "uint64[]",
"name": "prices",
"type": "uint64[]"
},
{
"internalType": "uint64",
"name": "timestamp",
"type": "uint64"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
}
],
"internalType": "struct PriceOracle.Prices",
"name": "priceFeed",
"type": "tuple"
}
],
"name": "provideDataAddressAuthorization",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address[]",
"name": "assets",
"type": "address[]"
},
{
"internalType": "address[]",
"name": "aggregatorAddresses",
"type": "address[]"
}
],
"name": "setChainLinkAggregators",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

File diff suppressed because one or more lines are too long

58
src/config/chains.json Normal file
View File

@@ -0,0 +1,58 @@
{
"0x1": {
"chainId": "0x1",
"explorer": "https://etherscan.io/",
"label": "Ethereum",
"code": "eth",
"rpc": "https://trade.orionprotocol.io/rpc",
"baseCurrencyName": "ETH"
},
"0x38": {
"chainId": "0x38",
"explorer": "https://bscscan.com/",
"label": "Binance Smart Chain",
"code": "bsc",
"rpc": "https://bsc-dataseed.binance.org/",
"baseCurrencyName": "BNB"
},
"0x61": {
"chainId": "0x61",
"explorer": "https://testnet.bscscan.com/",
"label": "Binance Smart Chain Testnet",
"code": "bsc",
"rpc": "https://bsc-stage.node.orionprotocol.io/",
"baseCurrencyName": "BNB"
},
"0x3": {
"chainId": "0x3",
"explorer": "https://ropsten.etherscan.io/",
"label": "Ropsten",
"code": "eth",
"rpc": "https://testing.orionprotocol.io/eth-ropsten/rpc",
"baseCurrencyName": "ETH"
},
"0xfa2": {
"chainId": "0xfa2",
"explorer": "https://testnet.ftmscan.com/",
"label": "Fantom Testnet",
"code": "ftm",
"rpc": "https://testing.orionprotocol.io/ftm-testnet/rpc",
"baseCurrencyName": "FTM"
},
"0xfa": {
"chainId": "0xfa",
"explorer": "https://ftmscan.com/",
"label": "Fantom",
"code": "ftm",
"rpc": "https://rpcapi.fantom.network/",
"baseCurrencyName": "FTM"
},
"0x0": {
"chainId": "0x0",
"explorer": "https://brokenscan.io/",
"label": "BrokenChain",
"code": "bkn",
"rpc": "https://brokenscan.io/rpc",
"baseCurrencyName": "BKN"
}
}

View File

@@ -0,0 +1,5 @@
{
"name": "Orion Exchange",
"version": "1",
"salt": "0xf2d857f4a3edcb9b78b4d503bfe733db1e3f6cdc2b7971ee739626c97e86a557"
}

69
src/config/envs.json Normal file
View File

@@ -0,0 +1,69 @@
{
"production": {
"networks": {
"0x1": {
"api": "trade.orionprotocol.io",
"liquidityMigratorAddress": "0x23a1820a47BcD022E29f6058a5FD224242F50D1A"
},
"0x38": {
"api": "trade-exp.orionprotocol.io"
}
}
},
"testing": {
"networks": {
"0x61": {
"api": "testing.orionprotocol.io/bsc-testnet",
"liquidityMigratorAddress": "0x01b10dd12478C88A5E18e2707E729906bC25CfF6"
},
"0x3": {
"api": "testing.orionprotocol.io/eth-ropsten",
"liquidityMigratorAddress": "0x36969a25622AE31bA9946e0c8151f0dc08b3A1c8"
},
"0xfa2": {
"api": "testing.orionprotocol.io/ftm-testnet"
}
}
},
"staging": {
"networks": {
"0x1": {
"api": "staging.orionprotocol.io"
},
"0x38": {
"api": "staging-bsc.orionprotocol.io"
},
"0xfa": {
"api": "staging-ftm.orionprotocol.io"
}
}
},
"broken": {
"networks": {
"0x0": {
"api": "broken0.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
},
"0x61": {
"api": "broken1.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
}
}
},
"partially-broken": {
"networks": {
"0x61": {
"api": "testing.orionprotocol.io/bsc-testnet",
"liquidityMigratorAddress": "0x01b10dd12478C88A5E18e2707E729906bC25CfF6"
},
"0x3": {
"api": "testing.orionprotocol.io/eth-ropsten",
"liquidityMigratorAddress": "0x36969a25622AE31bA9946e0c8151f0dc08b3A1c8"
},
"0xfa2": {
"api": "testing.orionprotocol.io/ftm-testnet"
},
"0x0": {
"api": "broken.orionprotocol.io/everything-is-fine/this-url-was-created-for-tests-of-a-partially-broken-environment"
}
}
}
}

13
src/config/index.ts Normal file
View File

@@ -0,0 +1,13 @@
import jsonChains from './chains.json';
import jsonEnvs from './envs.json';
import { pureEnvSchema, pureChainInfoSchema } from './schemas';
const chains = pureChainInfoSchema.parse(jsonChains);
const envs = pureEnvSchema.parse(jsonEnvs);
export {
chains,
envs,
};
export * as schemas from './schemas';

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
const eip712DomainSchema = z.object({
name: z.string(),
version: z.string(),
chainId: z.string(),
verifyingContract: z.string(),
salt: z.string(),
})
.partial()
.refine(
(data) => Object.keys(data).length > 0,
'At least one property should be filled in.',
);
export default eip712DomainSchema;

View File

@@ -0,0 +1,3 @@
export { default as eip712DomainSchema } from './eip712DomainSchema';
export * from './pureEnvSchema';
export * from './pureChainSchema';

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
import { SupportedChainId } from '../../types';
export const pureChainInfoPayloadSchema = z.object({
chainId: z.nativeEnum(SupportedChainId),
label: z.string(),
code: z.string(),
explorer: z.string(),
rpc: z.string(),
baseCurrencyName: z.string(),
});
export const pureChainInfoSchema = z.record(
z.nativeEnum(SupportedChainId),
pureChainInfoPayloadSchema,
);

View File

@@ -0,0 +1,18 @@
import { z } from 'zod';
import { SupportedChainId } from '../../types';
export const pureEnvPayloadSchema = z.object({
networks: z.record(
z.nativeEnum(SupportedChainId),
z.object({
api: z.string(),
rpc: z.string().optional(),
liquidityMigratorAddress: z.string().optional(),
}),
),
});
export const pureEnvSchema = z.record(
z.string(),
pureEnvPayloadSchema,
);

View File

@@ -0,0 +1,8 @@
const CANCEL_ORDER_TYPES = {
DeleteOrder: [
{ name: 'senderAddress', type: 'address' },
{ name: 'id', type: 'string' },
],
};
export default CANCEL_ORDER_TYPES;

4
src/constants/chains.ts Normal file
View File

@@ -0,0 +1,4 @@
import { SupportedChainId } from '../types';
export const developmentChains = [SupportedChainId.BSC_TESTNET, SupportedChainId.ROPSTEN, SupportedChainId.FANTOM_TESTNET];
export const productionChains = [SupportedChainId.MAINNET, SupportedChainId.BSC, SupportedChainId.FANTOM_OPERA];

View File

@@ -0,0 +1,30 @@
export const DEPOSIT_ETH_GAS_LIMIT = 70000;
export const DEPOSIT_ERC20_GAS_LIMIT = 150000;
export const WITHDRAW_GAS_LIMIT = DEPOSIT_ERC20_GAS_LIMIT;
export const APPROVE_ERC20_GAS_LIMIT = 70000;
export const STAKE_ERC20_GAS_LIMIT = 150000;
export const VOTE_ERC20_GAS_LIMIT = 150000;
export const FILL_ORDERS_GAS_LIMIT = 220000;
export const SWAP_THROUGH_ORION_POOL_GAS_LIMIT = 600000;
export const ADD_LIQUIDITY_GAS_LIMIT = 600000;
export const FARMING_STAKE_GAS_LIMIT = 350000;
export const FARMING_CLAIM_GAS_LIMIT = 350000;
export const FARMING_EXIT_GAS_LIMIT = 500000;
export const FARMING_WITHDRAW_GAS_LIMIT = 350000;
export const GOVERNANCE_GET_REWARD_GAS_LIMIT = 250000;
export const GOVERNANCE_STAKE_GAS_LIMIT = 300000;
export const GOVERNANCE_UNSTAKE_GAS_LIMIT = 250000;
export const GOVERNANCE_VOTE_GAS_LIMIT = 200000;
export const MIGRATE_GAS_LIMIT = 800000;
export const LOCKATOMIC_GAS_LIMIT = 200000;
export const REDEEMATOMIC_GAS_LIMIT = 200000;
export const LIQUIDITY_MIGRATE_GAS_LIMIT = 600000;
export const DEFAULT_GAS_LIMIT = 700000;
export const TOKEN_EXCEPTIONS: Record<string, Record<string, number>> = {
CUMMIES: {
deposit: 300000,
withdraw: 300000,
},
};

8
src/constants/index.ts Normal file
View File

@@ -0,0 +1,8 @@
export { default as cancelOrderTypes } from './cancelOrderTypes';
export { default as orderStatuses } from './orderStatuses';
export { default as orderTypes } from './orderTypes';
export { default as subOrderStatuses } from './subOrderStatuses';
export * from './chains';
export * from './precisions';
export * from './gasLimits';

View File

@@ -0,0 +1,9 @@
import subOrderStatuses from './subOrderStatuses';
// https://github.com/orionprotocol/orion-aggregator/blob/develop/src/main/java/io/orionprotocol/aggregator/model/order/status/OrderStatus.java
const orderStatuses = [
...subOrderStatuses,
'ROUTING', // order got sub orders, but not all of them have status ACCEPTED
] as const;
export default orderStatuses;

View File

@@ -0,0 +1,17 @@
const ORDER_TYPES = {
Order: [
{ name: 'senderAddress', type: 'address' },
{ name: 'matcherAddress', type: 'address' },
{ name: 'baseAsset', type: 'address' },
{ name: 'quoteAsset', type: 'address' },
{ name: 'matcherFeeAsset', type: 'address' },
{ name: 'amount', type: 'uint64' },
{ name: 'price', type: 'uint64' },
{ name: 'matcherFee', type: 'uint64' },
{ name: 'nonce', type: 'uint64' },
{ name: 'expiration', type: 'uint64' },
{ name: 'buySide', type: 'uint8' },
],
};
export default ORDER_TYPES;

View File

@@ -0,0 +1,2 @@
export const INTERNAL_ORION_PRECISION = 8;
export const NATIVE_CURRENCY_PRECISION = 18;

View File

@@ -0,0 +1,15 @@
// https://github.com/orionprotocol/orion-aggregator/blob/develop/src/main/java/io/orionprotocol/aggregator/model/order/status/SubOrderStatus.java
const subOrderStatuses = [
'NEW', // created, wasn't added to IOB or wasn't accepted by the broker
'ACCEPTED', // added to IOB or accepted by the broker
'PARTIALLY_FILLED', // partially filled
'FILLED', // fully filled
'TX_PENDING', // sub order was filled and at least one of its trades is pending
'CANCELED', // canceled by user or by expiration
'REJECTED', // rejected by broker
'FAILED', // at least one trade failed
'SETTLED', // all trades successfully settled
'NOT_FOUND', // broker not processed sub order yet
] as const;
export default subOrderStatuses;

151
src/crypt.ts Normal file
View File

@@ -0,0 +1,151 @@
/* eslint-disable no-underscore-dangle */
import { TypedDataSigner } from '@ethersproject/abstract-signer';
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import {
CancelOrderRequest, Order, SignedCancelOrderRequest, SignedOrder, SupportedChainId,
} from './types';
import eip712DomainData from './config/eip712DomainData.json';
import eip712DomainSchema from './config/schemas/eip712DomainSchema';
import { hashOrder, normalizeNumber } from './utils';
import { INTERNAL_ORION_PRECISION } from './constants/precisions';
import ORDER_TYPES from './constants/orderTypes';
import CANCEL_ORDER_TYPES from './constants/cancelOrderTypes';
import signOrderPersonal from './utils/signOrderPersonal';
const DEFAULT_EXPIRATION = 29 * 24 * 60 * 60 * 1000; // 29 days
type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner;
const EIP712Domain = eip712DomainSchema.parse(eip712DomainData);
const { arrayify, joinSignature, splitSignature } = ethers.utils;
/**
* See {@link https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator}
*/
const getDomainData = (chainId: SupportedChainId) => ({
...EIP712Domain,
chainId,
});
export const signOrder = async (
baseAssetAddr: string,
quoteAssetAddr: string,
side: 'BUY' | 'SELL',
price: BigNumber.Value,
amount: BigNumber.Value,
matcherFee: BigNumber.Value,
senderAddress: string,
matcherAddress: string,
orionFeeAssetAddr: string,
usePersonalSign: boolean,
signer: ethers.Signer,
chainId: SupportedChainId,
) => {
const nonce = Date.now();
const expiration = nonce + DEFAULT_EXPIRATION;
const order: Order = {
senderAddress,
matcherAddress,
baseAsset: baseAssetAddr,
quoteAsset: quoteAssetAddr,
matcherFeeAsset: orionFeeAssetAddr,
amount: normalizeNumber(
amount,
INTERNAL_ORION_PRECISION,
BigNumber.ROUND_FLOOR,
).toNumber(),
price: normalizeNumber(
price,
INTERNAL_ORION_PRECISION,
BigNumber.ROUND_FLOOR,
).toNumber(),
matcherFee: normalizeNumber(
matcherFee,
INTERNAL_ORION_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,
);
// 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));
if (!fixedSignature) throw new Error("Can't sign order");
const signedOrder: SignedOrder = {
...order,
id: hashOrder(order),
signature: fixedSignature,
};
return signedOrder;
};
export 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 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,
);
// 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));
if (!fixedSignature) throw new Error("Can't sign order cancel");
const signedCancelOrderReqeust: SignedCancelOrderRequest = {
...cancelOrderRequest,
signature: fixedSignature,
};
return signedCancelOrderReqeust;
};

109
src/entities/Exchange.ts Normal file
View File

@@ -0,0 +1,109 @@
/* eslint-disable camelcase */
import { Provider } from '@ethersproject/providers';
import { BytesLike, ethers, Signer } from 'ethers';
import {
Exchange as ExchangeContract,
Exchange__factory as ExchangeContract__factory,
} from '../artifacts/contracts';
import { LibAtomic } from '../artifacts/contracts/Exchange';
import {
DEPOSIT_ERC20_GAS_LIMIT, DEPOSIT_ETH_GAS_LIMIT, LOCKATOMIC_GAS_LIMIT,
REDEEMATOMIC_GAS_LIMIT, SWAP_THROUGH_ORION_POOL_GAS_LIMIT, WITHDRAW_GAS_LIMIT,
} from '../constants';
import { SupportedChainId } from '../types';
export default class Exchange {
chainId: SupportedChainId;
private exchangeContract: ExchangeContract;
constructor(chainId: SupportedChainId, signerOrProvider: Signer | Provider, address: string) {
this.chainId = chainId;
this.exchangeContract = ExchangeContract__factory.connect(address, signerOrProvider);
}
swapThroughOrionPool(
amount_spend: ethers.BigNumberish,
amount_receive: ethers.BigNumberish,
path: string[],
is_exact_spend: boolean,
value?: ethers.BigNumberish,
) {
return this.exchangeContract.populateTransaction.swapThroughOrionPool(
amount_spend,
amount_receive,
path,
is_exact_spend,
{
gasLimit: SWAP_THROUGH_ORION_POOL_GAS_LIMIT,
value,
},
);
}
depositNativeCurrency(value: ethers.BigNumberish) {
return this.exchangeContract.populateTransaction.deposit({
gasLimit: DEPOSIT_ETH_GAS_LIMIT,
value,
});
}
depositERC20(assetAddress: string, amount: ethers.BigNumberish) {
return this.exchangeContract.populateTransaction.depositAsset(
assetAddress,
amount,
{
gasLimit: DEPOSIT_ERC20_GAS_LIMIT,
},
);
}
withdraw(assetAddress: string, amount: ethers.BigNumberish) {
return this.exchangeContract.populateTransaction.withdraw(
assetAddress,
amount,
{
gasLimit: WITHDRAW_GAS_LIMIT,
},
);
}
lockAtomic(lockOrder: LibAtomic.LockOrderStruct) {
return this.exchangeContract.populateTransaction.lockAtomic(
lockOrder,
{
gasLimit: LOCKATOMIC_GAS_LIMIT,
},
);
}
redeemAtomic(redeemOrder: LibAtomic.RedeemOrderStruct, secret: BytesLike) {
return this.exchangeContract.populateTransaction.redeemAtomic(
redeemOrder,
secret,
{
gasLimit: REDEEMATOMIC_GAS_LIMIT,
},
);
}
redeem2Atomics(
order1: LibAtomic.RedeemOrderStruct,
secret1: BytesLike,
order2: LibAtomic.RedeemOrderStruct,
secret2: BytesLike,
) {
return this.exchangeContract.populateTransaction.redeem2Atomics(
order1,
secret1,
order2,
secret2,
);
}
refundAtomic(secretHash: BytesLike) {
return this.exchangeContract.populateTransaction.refundAtomic(
secretHash,
);
}
}

1
src/entities/index.ts Normal file
View File

@@ -0,0 +1 @@
export { default as Exchange } from './Exchange';

View File

@@ -0,0 +1,55 @@
import { Schema, z } from 'zod';
import fetch, { RequestInit } from 'node-fetch';
import { isWithError, isWithReason, HttpError } from './utils';
export class ExtendedError extends Error {
public url: string;
public status: number | null;
constructor(url: string, status: number | null, message: string) {
super();
this.url = url;
this.status = status;
this.message = message;
}
}
export const fetchJsonWithValidation = async <DataOut, DataIn, ErrorOut, ErrorIn>(
url: string,
schema: Schema<DataOut, z.ZodTypeDef, DataIn>,
options?: RequestInit,
errorSchema?: Schema<ErrorOut, z.ZodTypeDef, ErrorIn>,
) => {
const response = await fetch(url, {
...options || {},
headers: {
'Cache-Control': 'no-store, max-age=0',
...(options ? options.headers : {}),
},
});
const text = await response.text();
// The ok read-only property of the Response interface contains a Boolean
// stating whether the response was successful (status in the range 200 - 299) or not.
if (!response.ok) {
throw new HttpError(response.status, text, 'HTTP', response.statusText);
}
const payload: unknown = JSON.parse(text);
try {
const data = schema.parse(payload);
return data;
} catch (e) {
if (errorSchema) {
const errorObj = errorSchema.parse(payload);
if (isWithError(errorObj) && isWithReason(errorObj.error)) {
throw new ExtendedError(url, response.status, errorObj.error.reason);
}
}
if (e instanceof Error) throw new ExtendedError(url, response.status, e.message);
throw e;
}
};

11
src/index.ts Normal file
View File

@@ -0,0 +1,11 @@
export * as config from './config';
// export * from './entities';
export { default as OrionUnit } from './OrionUnit';
export { default as initOrionUnit } from './initOrionUnit';
export * from './fetchWithValidation';
export * as utils from './utils';
export * as services from './services';
export * as contracts from './artifacts/contracts';
export * as crypt from './crypt';
export * from './constants';
export * from './types';

30
src/initOrionUnit.ts Normal file
View File

@@ -0,0 +1,30 @@
import { config, OrionUnit } from '.';
import { isValidChainId } from './utils';
const { chains, envs } = config;
export default function initOrionUnit(chain: string, env: string) {
if (!isValidChainId(chain)) throw new Error(`Chain '${chain}' is not valid.`);
if (!(env in envs)) throw new Error(`Env '${env}' not found. Available environments is: ${Object.keys(envs).join(', ')}`);
const envInfo = envs[env];
const envNetworks = envInfo?.networks;
if (!(chain in envNetworks)) {
throw new Error(`Chain '${chain}' not found. `
+ `Available chains in selected environment (${env}) is: ${Object.keys(envNetworks).join(', ')}`);
}
const envNetworkInfo = envNetworks[chain];
const chainInfo = chains[chain];
if (!envNetworkInfo) throw new Error('Env network info is required');
if (!chainInfo) throw new Error('Chain info is required');
return new OrionUnit(
chainInfo.chainId,
envNetworkInfo.rpc ?? chainInfo.rpc,
env,
envNetworkInfo.api,
);
}

View File

@@ -0,0 +1,11 @@
import { z } from 'zod';
const errorSchema = z.object({
error: z.object({
code: z.number(),
reason: z.string(),
}),
timestamp: z.string(),
});
export default errorSchema;

View File

@@ -0,0 +1,212 @@
import BigNumber from 'bignumber.js';
import { z } from 'zod';
import { fetchJsonWithValidation } from '../../fetchWithValidation';
import swapInfoSchema from './schemas/swapInfoSchema';
import exchangeInfoSchema from './schemas/exchangeInfoSchema';
import cancelOrderSchema from './schemas/cancelOrderSchema';
import orderBenefitsSchema from './schemas/orderBenefitsSchema';
import errorSchema from './errorSchema';
import placeAtomicSwapSchema from './schemas/placeAtomicSwapSchema';
import { OrionAggregatorWS } from './ws';
import atomicSwapHistorySchema from './schemas/atomicSwapHistorySchema';
import { SignedCancelOrderRequest, SignedOrder, SupportedChainId } from '../../types';
import { pairConfigSchema } from './schemas';
class OrionAggregator {
private readonly apiUrl: string;
readonly ws: OrionAggregatorWS;
constructor(apiUrl: string, chainId: SupportedChainId) {
this.apiUrl = apiUrl;
this.ws = new OrionAggregatorWS(this.aggregatorWSUrl, chainId);
}
get aggregatorWSUrl() { return `wss://${this.apiUrl}/v1`; }
get aggregatorUrl() {
return `https://${this.apiUrl}/backend`;
}
getPairsList() {
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/pairs/list`,
z.array(z.string()),
);
}
getPairConfigs() {
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/pairs/exchangeInfo`,
exchangeInfoSchema,
undefined,
errorSchema,
);
}
getPairConfig(assetPair: string) {
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/pairs/exchangeInfo/${assetPair}`,
pairConfigSchema,
undefined,
errorSchema,
);
}
checkWhitelisted(address: string) {
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/whitelist/check?address=${address}`,
z.boolean(),
undefined,
errorSchema,
);
}
placeOrder(
signedOrder: SignedOrder,
isCreateInternalOrder: boolean,
partnerId?: string,
) {
const headers = {
'Content-Type': 'application/json',
Accept: 'application/json',
...partnerId && { 'X-Partner-Id': partnerId },
};
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/order/${isCreateInternalOrder ? 'internal' : ''}`,
z.object({
orderId: z.string(),
placementRequests: z.array(
z.object({
amount: z.number(),
brokerAddress: z.string(),
exchange: z.string(),
}),
).optional(),
}),
{
headers,
method: 'POST',
body: JSON.stringify(signedOrder),
},
errorSchema,
);
}
cancelOrder(signedCancelOrderRequest: SignedCancelOrderRequest) {
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/order`,
cancelOrderSchema,
{
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
body: JSON.stringify({
...signedCancelOrderRequest,
sender: signedCancelOrderRequest.senderAddress,
}),
},
errorSchema,
);
}
getSwapInfo(
type: 'exactSpend' | 'exactReceive',
assetIn: string,
assetOut: string,
amount: string,
) {
const url = new URL(`${this.aggregatorUrl}/api/v1/swap`);
url.searchParams.append('assetIn', assetIn);
url.searchParams.append('assetOut', assetOut);
if (type === 'exactSpend') {
url.searchParams.append('amountIn', amount);
} else {
url.searchParams.append('amountOut', amount);
}
return fetchJsonWithValidation(
url.toString(),
swapInfoSchema,
undefined,
errorSchema,
);
}
getLockedBalance(address: string, currency: string) {
const url = new URL(`${this.aggregatorUrl}/api/v1/address/balance/reserved/${currency}`);
url.searchParams.append('address', address);
return fetchJsonWithValidation(
url.toString(),
z.object({
[currency]: z.number(),
}).partial(),
undefined,
errorSchema,
);
}
getTradeProfits(
symbol: string,
amount: BigNumber,
isBuy: boolean,
) {
const url = new URL(`${this.aggregatorUrl}/api/v1/orderBenefits`);
url.searchParams.append('symbol', symbol);
url.searchParams.append('amount', amount.toString());
url.searchParams.append('side', isBuy ? 'buy' : 'sell');
return fetchJsonWithValidation(
url.toString(),
orderBenefitsSchema,
undefined,
errorSchema,
);
}
/**
* Placing atomic swap. Placement must take place on the target chain.
* @param secretHash Secret hash
* @param sourceNetworkCode uppercase, e.g. BSC, ETH
* @returns Fetch promise
*/
placeAtomicSwap(
secretHash: string,
sourceNetworkCode: string,
) {
return fetchJsonWithValidation(
`${this.aggregatorUrl}/api/v1/atomic-swap`,
placeAtomicSwapSchema,
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'POST',
body: JSON.stringify({
secretHash,
sourceNetworkCode,
}),
},
errorSchema,
);
}
/**
* Get placed atomic swaps. Each atomic swap received from this list has a target chain corresponding to this Orion Aggregator
* @param sender Sender address
* @returns Fetch promise
*/
getHistoryAtomicSwaps(sender: string, limit = 1000) {
const url = new URL(`${this.aggregatorUrl}/api/v1/atomic-swap/history/all`);
url.searchParams.append('sender', sender);
url.searchParams.append('limit', limit.toString());
return fetchJsonWithValidation(url.toString(), atomicSwapHistorySchema);
}
}
export * as schemas from './schemas';
export * as ws from './ws';
export { OrionAggregator };

View File

@@ -0,0 +1,21 @@
import { z } from 'zod';
import redeemOrderSchema from './redeemOrderSchema';
export const atomicSwapHistorySchema = z.array(z.object({
id: z.string(),
sender: z.string(),
lockOrder: z.object({
sender: z.string(),
asset: z.string(),
amount: z.number(),
expiration: z.number(),
secretHash: z.string(),
used: z.boolean(),
sourceNetworkCode: z.string(),
}),
redeemOrder: redeemOrderSchema,
status: z.enum(['SETTLED', 'EXPIRED', 'ACTIVE']),
creationTime: z.number(),
}));
export default atomicSwapHistorySchema;

View File

@@ -0,0 +1,13 @@
import { z } from 'zod';
const cancelOrderSchema = z.object({
orderId: z.union([z.number(), z.string()]),
cancellationRequests: z.array(z.object({
amount: z.number(),
brokerAddress: z.string(),
exchange: z.string(),
})).optional(),
remainingAmount: z.number().optional(),
});
export default cancelOrderSchema;

View File

@@ -0,0 +1,6 @@
import { z } from 'zod';
import pairConfigSchema from './pairConfigSchema';
const exchangeInfoSchema = z.array(pairConfigSchema);
export default exchangeInfoSchema;

View File

@@ -0,0 +1,8 @@
export { default as atomicSwapHistorySchema } from './atomicSwapHistorySchema';
export { default as cancelOrderSchema } from './cancelOrderSchema';
export { default as exchangeInfoSchema } from './exchangeInfoSchema';
export { default as orderBenefitsSchema } from './orderBenefitsSchema';
export { default as pairConfigSchema } from './pairConfigSchema';
export { default as placeAtomicSwapSchema } from './placeAtomicSwapSchema';
export { default as redeemOrderSchema } from './redeemOrderSchema';
export { default as swapInfoSchema } from './swapInfoSchema';

View File

@@ -0,0 +1,8 @@
import { z } from 'zod';
const orderBenefitsSchema = z.record(z.object({
benefitBtc: z.string(),
benefitPct: z.string(),
}));
export default orderBenefitsSchema;

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
const pairConfigSchema = z.object({
baseAssetPrecision: z.number().int(),
executableOnBrokersPriceDeviation: z.number().nullable(),
maxPrice: z.number(),
maxQty: z.number(),
minPrice: z.number(),
minQty: z.number(),
name: z.string(),
pricePrecision: z.number().int(),
qtyPrecision: z.number().int(),
quoteAssetPrecision: z.number().int(),
});
export default pairConfigSchema;

View File

@@ -0,0 +1,18 @@
import { z } from 'zod';
const placeAtomicSwapSchema = z.object({
redeemOrder: z.object({
amount: z.number(),
asset: z.string(),
expiration: z.number(),
receiver: z.string(),
secretHash: z.string(),
sender: z.string(),
signature: z.string(),
claimReceiver: z.string(),
}),
secretHash: z.string(),
sender: z.string(),
});
export default placeAtomicSwapSchema;

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
const redeemOrderSchema = z.object({
asset: z.string(),
amount: z.number(),
secretHash: z.string(),
sender: z.string(),
receiver: z.string(),
expiration: z.number(),
signature: z.string(),
claimReceiver: z.string(),
});
export default redeemOrderSchema;

View File

@@ -0,0 +1,46 @@
import { z } from 'zod';
const swapInfoBase = z.object({
id: z.string(),
amountIn: z.number(),
amountOut: z.number(),
assetIn: z.string(),
assetOut: z.string(),
path: z.array(z.string()),
isThroughPoolOptimal: z.boolean(),
executionInfo: z.string(),
orderInfo: z.object({
assetPair: z.string(),
side: z.enum(['BUY', 'SELL']),
amount: z.number(),
safePrice: z.number(),
}).nullable(),
price: z.number().nullable(), // spending asset price
minAmountOut: z.number(),
minAmountIn: z.number(),
marketPrice: z.number().nullable(), // spending asset market price
});
const swapInfoByAmountIn = swapInfoBase.extend({
availableAmountOut: z.null(),
availableAmountIn: z.number(),
marketAmountOut: z.number().nullable(),
marketAmountIn: z.null(),
}).transform((val) => ({
...val,
type: 'exactSpend' as const,
}));
const swapInfoByAmountOut = swapInfoBase.extend({
availableAmountOut: z.number(),
availableAmountIn: z.null(),
marketAmountOut: z.null(),
marketAmountIn: z.number().nullable(),
}).transform((val) => ({
...val,
type: 'exactReceive' as const,
}));
const swapInfoSchema = swapInfoByAmountIn.or(swapInfoByAmountOut);
export default swapInfoSchema;

View File

@@ -0,0 +1,12 @@
enum MessageType {
ERROR = 'e',
PING_PONG = 'pp',
SWAP_INFO = 'si',
INITIALIZATION = 'i',
AGGREGATED_ORDER_BOOK_UPDATE = 'aobu',
ASSET_PAIRS_CONFIG_UPDATE = 'apcu',
ADDRESS_UPDATE = 'au',
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE = 'btasabu'
}
export default MessageType;

View File

@@ -0,0 +1,9 @@
enum SubscriptionType {
ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE = 'apcus',
AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE = 'aobus',
ADDRESS_UPDATES_SUBSCRIBE = 'aus',
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE = 'btasabus',
SWAP_SUBSCRIBE = 'ss',
}
export default SubscriptionType;

View File

@@ -0,0 +1,5 @@
enum UnsubscriptionType {
ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE = 'apcu',
BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_UNSUBSCRIBE = 'btasabu',
}
export default UnsubscriptionType;

View File

@@ -0,0 +1,318 @@
import { z } from 'zod';
import WebSocket from 'isomorphic-ws';
import { validate as uuidValidate } from 'uuid';
import { fullOrderSchema, orderUpdateSchema } from './schemas/addressUpdateSchema';
import MessageType from './MessageType';
import SubscriptionType from './SubscriptionType';
import {
pingPongMessageSchema, initMessageSchema,
errorSchema, brokerMessageSchema, orderBookSchema,
assetPairsConfigSchema, addressUpdateSchema, swapInfoSchema,
} from './schemas';
import UnsubscriptionType from './UnsubscriptionType';
import { SwapInfoByAmountIn, SwapInfoByAmountOut } from '../../..';
import { SupportedChainId } from '../../../types';
// import errorSchema from './schemas/errorSchema';
const UNSUBSCRIBE = 'u';
// https://github.com/orionprotocol/orion-aggregator/tree/feature/OP-1752-symmetric-swap#swap-info-subscribe
type SwapSubscriptionRequest = {
d: string, // swap request UUID, set by client side
i: string, // asset in
o: string, // asset out
a: number // amount IN/OUT
e?: boolean; // is amount IN? Value `false` means a = amount OUT, `true` if omitted
}
type BrokerTradableAtomicSwapBalanceSubscription = {
callback: (balances: {
asset: string;
balance: number;
}[]) => void,
}
type PairConfigSubscription = {
callback: (
chainId: SupportedChainId,
data: z.infer<typeof assetPairsConfigSchema>['u'],
) => void,
}
type AggregatedOrderbookSubscription = {
payload: string,
callback: (
asks: z.infer<typeof orderBookSchema>['ob']['a'],
bids: z.infer<typeof orderBookSchema>['ob']['b'],
pair: string
) => void,
}
type SwapInfoSubscription = {
payload: SwapSubscriptionRequest,
callback: (swapInfo: SwapInfoByAmountIn | SwapInfoByAmountOut) => void,
}
type AddressUpdateSubscription = {
payload: string,
callback: ({ fullOrders, orderUpdate, balances } : {
fullOrders?: z.infer<typeof fullOrderSchema>[],
orderUpdate?: z.infer<typeof orderUpdateSchema> | z.infer<typeof fullOrderSchema>,
balances?: Partial<
Record<
string,
[
string,
string,
string,
string,
string
]>
>,
}) => void,
}
type Subscription = {
[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE]: AddressUpdateSubscription,
[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]: AggregatedOrderbookSubscription,
[SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]: PairConfigSubscription,
[SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE]: BrokerTradableAtomicSwapBalanceSubscription,
[SubscriptionType.SWAP_SUBSCRIBE]: SwapInfoSubscription
}
type Subscriptions<T extends SubscriptionType> = { [K in T]: Subscription[K] }
class OrionAggregatorWS {
private ws: WebSocket | undefined;
private chainId: SupportedChainId;
private subscriptions: Partial<Subscriptions<SubscriptionType>> = {};
private onError?: (err: string) => void;
constructor(url: string, chainId: SupportedChainId, onError?: (err: string) => void) {
this.chainId = chainId;
this.onError = onError;
this.init(url);
}
sendRaw(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
if (this.ws?.readyState === 1) {
this.ws.send(data);
} else if (this.ws?.readyState === 0) {
setTimeout(() => {
this.sendRaw(data);
}, 50);
}
}
send(data: unknown) {
if (this.ws?.readyState === 1) {
this.ws.send(JSON.stringify(data));
} else {
setTimeout(() => {
this.send(data);
}, 50);
}
}
subscribe<T extends SubscriptionType>(
type: T,
subscription: Subscription[T],
) {
this.send({
T: type,
...('payload' in subscription) && {
S: subscription.payload,
},
});
this.subscriptions[type] = subscription;
}
unsubscribe(subscription: UnsubscriptionType | string) {
this.send({
T: UNSUBSCRIBE,
S: subscription,
});
if (subscription.includes('0x')) { // is wallet address (ADDRESS_UPDATE)
delete this.subscriptions[SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE];
} else if (uuidValidate(subscription)) { // is swap info subscription (contains hyphen)
delete this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE];
// !!! swap info subscription is uuid that contains hyphen
} else if (subscription.includes('-') && subscription.split('-').length === 2) { // is pair name(AGGREGATED_ORDER_BOOK_UPDATE)
delete this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE];
} else if (subscription === UnsubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE) {
delete this.subscriptions[SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE];
} else if (subscription === UnsubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_UNSUBSCRIBE) {
delete this.subscriptions[SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE];
}
}
init(url: string) {
this.ws = new WebSocket(url);
this.ws.onclose = () => {
console.log(`Orion Aggregator ${this.chainId} WS Connection closed`);
this.init(url);
};
this.ws.onopen = () => {
console.log(`Orion Aggregator ${this.chainId} WS Connection established`);
Object.entries(this.subscriptions).forEach(([type, subscription]) => {
this.send({
T: type,
...('payload' in subscription) && {
S: subscription.payload,
},
});
});
};
this.ws.onmessage = (e) => {
const { data } = e;
const rawJson: unknown = JSON.parse(data.toString());
const messageSchema = z.union([
initMessageSchema,
pingPongMessageSchema,
addressUpdateSchema,
assetPairsConfigSchema,
brokerMessageSchema,
orderBookSchema,
swapInfoSchema,
errorSchema,
]);
const json = messageSchema.parse(rawJson);
switch (json.T) {
case MessageType.ERROR: {
const { m: errorMessage } = errorSchema.parse(json);
this.onError?.(errorMessage);
}
break;
case MessageType.PING_PONG:
this.sendRaw(data.toString());
break;
case MessageType.SWAP_INFO:
switch (json.k) { // kind
case 'exactSpend':
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.callback({
kind: json.k,
swapRequestId: json.S,
assetIn: json.ai,
assetOut: json.ao,
amountIn: json.a,
amountOut: json.o,
price: json.p,
marketAmountOut: json.mo,
marketPrice: json.mp,
minAmounOut: json.mao,
minAmounIn: json.ma,
availableAmountIn: json.aa,
...json.oi && {
orderInfo: {
pair: json.oi.p,
side: json.oi.s,
amount: json.oi.a,
safePrice: json.oi.sp,
},
},
path: json.ps,
poolOptimal: json.po,
});
break;
case 'exactReceive':
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.callback({
kind: json.k,
swapRequestId: json.S,
assetIn: json.ai,
assetOut: json.ao,
amountIn: json.a,
amountOut: json.o,
price: json.p,
marketAmountIn: json.mi,
marketPrice: json.mp,
minAmounOut: json.mao,
minAmounIn: json.ma,
availableAmountOut: json.aao,
...json.oi && {
orderInfo: {
pair: json.oi.p,
side: json.oi.s,
amount: json.oi.a,
safePrice: json.oi.sp,
},
},
path: json.ps,
poolOptimal: json.po,
});
break;
default:
break;
}
break;
// case MessageType.INITIALIZATION:
// break;
case MessageType.AGGREGATED_ORDER_BOOK_UPDATE: {
const { ob, S } = json;
this.subscriptions[
SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE
]?.callback(ob.a, ob.b, S);
}
break;
case MessageType.ASSET_PAIRS_CONFIG_UPDATE: {
const pairs = json;
this.subscriptions[
SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE
]?.callback(this.chainId, pairs.u);
}
break;
case MessageType.ADDRESS_UPDATE:
switch (json.k) { // kind
case 'i': // initial
this.subscriptions[
SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE
]?.callback({
fullOrders: json.o,
balances: json.b,
});
break;
case 'u': // update
this.subscriptions[
SubscriptionType.ADDRESS_UPDATES_SUBSCRIBE
]?.callback({
orderUpdate: json.o?.[0],
balances: json.b,
});
break;
default:
break;
}
break;
case MessageType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE: {
const updatedBrokerBalances = json.bb.map((bb) => {
const [asset, balance] = bb;
return { asset, balance };
});
this.subscriptions[
SubscriptionType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATES_SUBSCRIBE
]?.callback(updatedBrokerBalances);
}
break;
default:
break;
}
};
}
}
export * as schemas from './schemas';
export {
OrionAggregatorWS,
SubscriptionType,
UnsubscriptionType,
MessageType,
};

View File

@@ -0,0 +1,79 @@
import { z } from 'zod';
import orderStatuses from '../../../../constants/orderStatuses';
import subOrderStatuses from '../../../../constants/subOrderStatuses';
import MessageType from '../MessageType';
import balancesSchema from './balancesSchema';
import baseMessageSchema from './baseMessageSchema';
const baseAddressUpdate = baseMessageSchema.extend({
T: z.literal(MessageType.ADDRESS_UPDATE),
S: z.string(), // subscription
uc: z.array(z.enum(['b', 'o'])), // update content
});
const subOrderSchema = z.object({
i: z.number(), // id
I: z.string(), // parent order id
O: z.string(), // sender (owner)
P: z.string(), // asset pair
s: z.enum(['BUY', 'SELL']), // side
a: z.number(), // amount
A: z.number(), // settled amount
p: z.number(), // avg weighed settlement price
e: z.string(), // exchange
b: z.string(), // broker address
S: z.enum(subOrderStatuses), // status
o: z.boolean(), // internal only
});
export const orderUpdateSchema = z.object({
I: z.string(), // id
A: z.number(), // settled amount
S: z.enum(orderStatuses), // status
t: z.number(), // update time
c: subOrderSchema.array(),
})
.transform((val) => ({
...val,
k: 'update' as const,
}));
export const fullOrderSchema = z.object({
I: z.string(), // id
O: z.string(), // sender (owner)
P: z.string(), // asset pair
s: z.enum(['BUY', 'SELL']), // side
a: z.number(), // amount
A: z.number(), // settled amount
p: z.number(), // price
F: z.string(), // fee asset
f: z.number(), // fee
o: z.boolean(), // internal only
S: z.enum(orderStatuses), // status
T: z.number(), // creation time / unix timestamp
t: z.number(), // update time
c: subOrderSchema.array(),
}).transform((val) => ({
...val,
k: 'full' as const,
}));
const updateMessageSchema = baseAddressUpdate.extend({
k: z.literal('u'), // kind of message: "u" - updates
uc: z.array(z.enum(['b', 'o'])), // update content: "o" - orders updates, "b" - balance updates
b: balancesSchema.optional(),
o: z.tuple([fullOrderSchema.or(orderUpdateSchema)]).optional(),
});
const initialMessageSchema = baseAddressUpdate.extend({
k: z.literal('i'), // kind of message: "i" - initial
b: balancesSchema,
o: z.array(fullOrderSchema).optional(), // When no orders — no field
});
const addressUpdateSchema = z.union([
initialMessageSchema,
updateMessageSchema,
]);
export default addressUpdateSchema;

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const assetPairsConfigSchema = baseMessageSchema.extend({
T: z.literal(MessageType.ASSET_PAIRS_CONFIG_UPDATE),
u: z.array(
z.tuple([
z.string(), // pairName
z.number(), // minQty
z.number(), // pricePrecision
]),
),
});
export default assetPairsConfigSchema;

View File

@@ -0,0 +1,14 @@
import { z } from 'zod';
import { makePartial } from '../../../../utils';
const balancesSchema = z.record( // changed balances in format
z.string(), // asset
z.tuple([
z.string(), // tradable balance
z.string(), // reserved balance
z.string(), // contract balance
z.string(), // wallet balance
z.string(), // allowance
]),
).transform(makePartial);
export default balancesSchema;

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
import MessageType from '../MessageType';
const baseMessageSchema = z.object({
T: z.nativeEnum(MessageType),
_: z.number(),
});
export default baseMessageSchema;

View File

@@ -0,0 +1,15 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const brokerMessageSchema = baseMessageSchema.extend({
T: z.literal(MessageType.BROKER_TRADABLE_ATOMIC_SWAP_ASSETS_BALANCE_UPDATE),
bb: z.array(
z.tuple([
z.string(), // Asset name
z.number(), // limit
]),
),
});
export default brokerMessageSchema;

View File

@@ -0,0 +1,11 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const errorSchema = baseMessageSchema.extend({
T: z.literal(MessageType.ERROR),
c: z.number(), // code
m: z.string(), // error message,
});
export default errorSchema;

View File

@@ -0,0 +1,11 @@
export { default as addressUpdateSchema } from './addressUpdateSchema';
export { default as assetPairsConfigSchema } from './assetPairsConfigSchema';
export { default as baseMessageSchema } from './baseMessageSchema';
export { default as brokerMessageSchema } from './brokerMessageSchema';
export { default as errorSchema } from './errorSchema';
export { default as initMessageSchema } from './initMessageSchema';
export { default as pingPongMessageSchema } from './pingPongMessageSchema';
export { default as swapInfoSchema } from './swapInfoSchema';
export { default as balancesSchema } from './balancesSchema';
export * from './orderBookSchema';

View File

@@ -0,0 +1,10 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const initMessageSchema = baseMessageSchema.extend({
T: z.literal(MessageType.INITIALIZATION),
i: z.string(),
});
export default initMessageSchema;

View File

@@ -0,0 +1,22 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
export const orderBookItemSchema = z.tuple([
z.string(), // price
z.string(), // size
z.array(z.string()), // exchanges
z.array(z.tuple([
z.enum(['SELL', 'BUY']), // side
z.string(), // pairname
])),
]);
export const orderBookSchema = baseMessageSchema.extend({
T: z.literal(MessageType.AGGREGATED_ORDER_BOOK_UPDATE),
S: z.string(),
ob: z.object({
a: z.array(orderBookItemSchema),
b: z.array(orderBookItemSchema),
}),
});

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const pingPongMessageSchema = baseMessageSchema.extend({
T: z.literal(MessageType.PING_PONG),
});
export default pingPongMessageSchema;

View File

@@ -0,0 +1,48 @@
import { z } from 'zod';
import MessageType from '../MessageType';
import baseMessageSchema from './baseMessageSchema';
const swapInfoSchemaBase = baseMessageSchema.extend({
T: z.literal(MessageType.SWAP_INFO),
S: z.string(), // swap request id
ai: z.string(), // asset in,
ao: z.string(), // asset out
a: z.number(), // amount in
o: z.number(), // amount out
ma: z.number(), // min amount in
mao: z.number(), // min amount out
ps: z.string().array(), // path
po: z.boolean(), // is swap through pool optimal
p: z.number().optional(), // price
mp: z.number().optional(), // market price
oi: z.object({ // info about order equivalent to this swap
p: z.string(), // asset pair
s: z.enum(['SELL', 'BUY']), // side
a: z.number(), // amount
sp: z.number(), // safe price (with safe deviation but without slippage)
}).optional(),
});
const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
mo: z.number().optional(), // market amount out
aa: z.number(), // available amount in
}).transform((content) => ({
...content,
k: 'exactSpend' as const,
}));
const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({
mi: z.number().optional(), // market amount in
aao: z.number(), // available amount out
}).transform((content) => ({
...content,
k: 'exactReceive' as const,
}));
const swapInfoSchema = z.union([
swapInfoSchemaByAmountIn,
swapInfoSchemaByAmountOut,
]);
export default swapInfoSchema;

View File

@@ -0,0 +1,276 @@
import { z } from 'zod';
import { fetchJsonWithValidation } from '../../fetchWithValidation';
import { PairStatusEnum, pairStatusSchema } from './schemas/adminPoolsListSchema';
import {
IDOSchema, atomicHistorySchema,
poolsConfigSchema, poolsInfoSchema, infoSchema, historySchema,
addPoolSchema, adminPoolsListSchema,
} from './schemas';
import { OrionBlockchainSocketIO } from './ws';
import redeemOrderSchema from '../OrionAggregator/schemas/redeemOrderSchema';
import { sourceAtomicHistorySchema, targetAtomicHistorySchema } from './schemas/atomicHistorySchema';
import { SupportedChainId } from '../../types';
import { utils } from '../..';
interface IAdminAuthHeaders {
auth: string;
[key: string]: string
}
export interface IEditPool {
tokenAIcon?: string;
tokenBIcon?: string;
symbol?: string;
status: PairStatusEnum;
qtyPrecision?: number;
pricePrecision?: number;
minQty?: number;
tokenASymbol?: string;
tokenBSymbol?: string;
tokensReversed?: boolean;
}
type AtomicSwapHistoryBaseQuery = {
limit?: number
sender?: string,
receiver?: string,
used?: 0 | 1,
page?: number,
}
type AtomicSwapHistorySourceQuery = AtomicSwapHistoryBaseQuery & {
type?: 'source',
expiredLock?: 0 | 1,
state?: 'LOCKED' | 'CLAIMED' |'REFUNDED',
}
type AtomicSwapHistoryTargetQuery = AtomicSwapHistoryBaseQuery & {
type?: 'target',
expiredRedeem?: 0 | 1,
state?: 'REDEEMED' | 'BEFORE-REDEEM',
}
class OrionBlockchain {
private readonly apiUrl: string;
readonly ws: OrionBlockchainSocketIO;
constructor(
apiUrl: string,
chainId: SupportedChainId,
) {
this.apiUrl = apiUrl;
this.ws = new OrionBlockchainSocketIO(`https://${apiUrl}/`);
}
get orionBlockchainWsUrl() {
return `https://${this.apiUrl}/`;
}
getAuthToken() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/auth/token`, z.object({ token: z.string() }));
}
getCirculatingSupply() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/circulating-supply`, z.number());
}
getInfo() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/info`, infoSchema);
}
getPoolsConfig() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/config`, poolsConfigSchema);
}
getPoolsInfo() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/info`, poolsInfoSchema);
}
getHistory(address: string) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/history/${address}`, historySchema);
}
getPrices() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/prices`, z.record(z.string()).transform(utils.makePartial));
}
getTokensFee() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/tokensFee`, z.record(z.string()).transform(utils.makePartial));
}
getGasPriceWei() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/gasPrice`, z.string());
}
checkFreeRedeemAvailable(walletAddress: string) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/atomic/has-free-redeem/${walletAddress}`, z.boolean());
}
redeemAtomicSwap(
redeemOrder: z.infer<typeof redeemOrderSchema>,
secret: string,
sourceNetwork: string,
) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem`,
z.string(),
{
method: 'POST',
body: JSON.stringify({
order: redeemOrder,
secret,
sourceNetwork,
}),
headers: {
'Content-Type': 'application/json',
},
},
);
}
redeem2AtomicSwaps(
redeemOrder1: z.infer<typeof redeemOrderSchema>,
secret1: string,
redeemOrder2: z.infer<typeof redeemOrderSchema>,
secret2: string,
sourceNetwork: string,
) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem2atomics`,
z.string(),
{
method: 'POST',
body: JSON.stringify({
order1: redeemOrder1,
secret1,
order2: redeemOrder2,
secret2,
sourceNetwork,
}),
headers: {
'Content-Type': 'application/json',
},
},
);
}
checkRedeem(secretHash: string) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem/${secretHash}`,
z.enum(['OK', 'FAIL']).nullable(),
);
}
checkRedeem2Atomics(firstSecretHash: string, secondSecretHash: string) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/atomic/matcher-redeem/${firstSecretHash}-${secondSecretHash}`,
z.enum(['OK', 'FAIL']).nullable(),
);
}
getIDOInfo() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/solarflare`, IDOSchema);
}
checkAuth(headers: IAdminAuthHeaders) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/auth/check`, z.object({
auth: z.boolean(),
}), { headers });
}
getPoolsList(headers: IAdminAuthHeaders) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/pools/list`,
adminPoolsListSchema,
{ headers },
);
}
editPool(address: string, data: IEditPool, headers: IAdminAuthHeaders) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/pools/edit/${address}`,
pairStatusSchema,
{
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
...headers,
},
},
);
}
addPool(data: z.infer<typeof addPoolSchema>) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/add`, z.number(), {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
});
}
checkPoolInformation(poolAddress: string) {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/pools/check/${poolAddress}`, pairStatusSchema);
}
getAtomicSwapAssets() {
return fetchJsonWithValidation(`https://${this.apiUrl}/api/atomic/swap-assets`, z.array(z.string()));
}
/**
* Sender is user address in source Orion Blockchain instance \
* Receiver is user address in target Orion Blockchain instance
*/
getAtomicSwapHistory(query: AtomicSwapHistorySourceQuery | AtomicSwapHistoryTargetQuery) {
const url = new URL(`https://${this.apiUrl}/api/atomic/history/`);
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
return fetchJsonWithValidation(url.toString(), atomicHistorySchema);
}
getSourceAtomicSwapHistory(query: AtomicSwapHistorySourceQuery) {
const url = new URL(`https://${this.apiUrl}/api/atomic/history/`);
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
if (!query.type) url.searchParams.append('type', 'source');
return fetchJsonWithValidation(url.toString(), sourceAtomicHistorySchema);
}
getTargetAtomicSwapHistory(query: AtomicSwapHistoryTargetQuery) {
const url = new URL(`https://${this.apiUrl}/api/atomic/history/`);
Object.entries(query)
.forEach(([key, value]) => url.searchParams.append(key, value.toString()));
if (!query.type) url.searchParams.append('type', 'target');
return fetchJsonWithValidation(url.toString(), targetAtomicHistorySchema);
}
checkIfHashUsed(secretHashes: string[]) {
return fetchJsonWithValidation(
`https://${this.apiUrl}/api/atomic/is-hash-used`,
z.record(z.boolean()).transform(utils.makePartial),
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
method: 'POST',
body: JSON.stringify(secretHashes),
},
);
}
}
export * as ws from './ws';
export * as schemas from './schemas';
export { OrionBlockchain };

View File

@@ -0,0 +1,10 @@
import { z } from 'zod';
const IDOSchema = z.object({
amount: z.number().or(z.null()),
amountInWei: z.number().or(z.null()),
amountInUSDT: z.number().or(z.null()),
address: z.string(),
});
export default IDOSchema;

View File

@@ -0,0 +1,11 @@
import { z } from 'zod';
const addPoolSchema = z.object({
poolAddress: z.string(),
tokenAIcon: z.string().optional(),
tokenAName: z.string().optional(),
tokenBIcon: z.string().optional(),
tokenBName: z.string().optional(),
});
export default addPoolSchema;

View File

@@ -0,0 +1,38 @@
import { z } from 'zod';
export enum PairStatusEnum {
DOESNT_EXIST = -1,
REVIEW = 0,
ACCEPTED = 1,
REJECTED = 2,
}
export const pairStatusSchema = z.nativeEnum(PairStatusEnum);
const tokenSchema = z.object({
symbol: z.string(),
icon: z.string().optional(),
address: z.string(),
decimals: z.number().optional(),
isUser: z.boolean().optional(),
});
const poolOnVerificationSchema = z.object({
tokenA: tokenSchema,
tokenB: tokenSchema,
_id: z.string().optional(),
address: z.string(),
symbol: z.string(),
isUser: z.boolean(),
minQty: z.number().optional(),
tokensReversed: z.boolean(),
status: pairStatusSchema,
updatedAt: z.number(),
createdAt: z.number(),
qtyPrecision: z.number().optional(),
pricePrecision: z.number().optional(),
});
export type adminPoolType = z.infer<typeof poolOnVerificationSchema>;
export const adminPoolsListSchema = z.array(poolOnVerificationSchema);

View File

@@ -0,0 +1,72 @@
import { z } from 'zod';
const baseAtomicHistorySchema = z.object({
success: z.boolean(),
count: z.number(),
total: z.number(),
pagination: z.object({}),
});
const baseAtomicHistoryItem = z.object({
used: z.boolean(),
claimed: z.boolean(),
isAggApplied: z.boolean(),
_id: z.string(),
__v: z.number(),
asset: z.string(),
sender: z.string(),
secretHash: z.string(),
receiver: z.string().optional(),
secret: z.string().optional(),
});
const sourceAtomicHistorySchemaItem = baseAtomicHistoryItem.extend({
type: z.literal('source'),
amountToReceive: z.number().optional(),
amountToSpend: z.number().optional(),
timestamp: z.object({
lock: z.number().optional(),
claim: z.number().optional(),
refund: z.number().optional(),
}).optional(),
expiration: z.object({
lock: z.number().optional(),
}).optional(),
state: z.enum(['LOCKED', 'REFUNDED', 'CLAIMED']),
targetChainId: z.number(),
transactions: z.object({
lock: z.string().optional(),
claim: z.string().optional(),
refund: z.string().optional(),
}).optional(),
});
const targetAtomicHistorySchemaItem = baseAtomicHistoryItem.extend({
type: z.literal('target'),
timestamp: z.object({
redeem: z.number().optional(),
}).optional(),
expiration: z.object({
redeem: z.number().optional(),
}).optional(),
state: z.enum(['REDEEMED', 'BEFORE-REDEEM']),
transactions: z.object({
redeem: z.string().optional(),
}).optional(),
});
export const sourceAtomicHistorySchema = baseAtomicHistorySchema.extend({
data: z.array(sourceAtomicHistorySchemaItem),
});
export const targetAtomicHistorySchema = baseAtomicHistorySchema.extend({
data: z.array(targetAtomicHistorySchemaItem),
});
const atomicHistorySchema = baseAtomicHistorySchema.extend({
data: z.array(
z.discriminatedUnion('type', [sourceAtomicHistorySchemaItem, targetAtomicHistorySchemaItem]),
),
});
export default atomicHistorySchema;

View File

@@ -0,0 +1,9 @@
import { z } from 'zod';
const checkRedeemOrderSchema = z.object({
redeemTxHash: z.string(),
secret: z.string().nullable(),
secretHash: z.string(),
});
export default checkRedeemOrderSchema;

View File

@@ -0,0 +1,20 @@
import { z } from 'zod';
const historySchema = z.array(z.object(
{
amount: z.string(),
amountNumber: z.string(),
asset: z.string(),
assetAddress: z.string(),
contractBalance: z.string().optional(),
createdAt: z.number(),
transactionHash: z.string(),
type: z.string(),
user: z.string(),
walletBalance: z.string().optional(),
__v: z.number(),
_id: z.string(),
},
));
export default historySchema;

View File

@@ -0,0 +1,9 @@
export * from './adminPoolsListSchema';
export { default as addPoolSchema } from './addPoolSchema';
export { default as atomicHistorySchema } from './atomicHistorySchema';
export { default as checkRedeemOrderSchema } from './checkRedeemOrderSchema';
export { default as historySchema } from './historySchema';
export { default as IDOSchema } from './IDOSchema';
export { default as infoSchema } from './infoSchema';
export { default as poolsConfigSchema } from './poolsConfigSchema';
export { default as poolsInfoSchema } from './poolsInfoSchema';

View File

@@ -0,0 +1,16 @@
import { z } from 'zod';
import { makePartial } from '../../../utils';
const infoSchema = z.object({
chainId: z.number(),
chainName: z.string(),
exchangeContractAddress: z.string(),
oracleContractAddress: z.string(),
matcherAddress: z.string(),
orderFeePercent: z.number(),
assetToAddress: z.record(z.string()).transform(makePartial),
assetToDecimals: z.record(z.number()).transform(makePartial),
assetToIcons: z.record(z.string()).transform(makePartial).optional(),
});
export default infoSchema;

View File

@@ -0,0 +1,28 @@
import { z } from 'zod';
import { makePartial } from '../../../utils';
const poolsConfigSchema = z.object({
WETHAddress: z.string().optional(),
factoryAddress: z.string(),
governanceAddress: z.string(),
routerAddress: z.string(),
votingAddress: z.string(),
pools: z.record(
z.string(),
z.object({
lpTokenAddress: z.string(),
minQty: z.number().optional(),
pricePrecision: z.number().int().optional(),
qtyPrecision: z.number().int().optional(),
reverted: z.boolean().optional(),
rewardToken: z.string().nullable().optional(),
state: z.number().int().optional(),
rewardTokenDecimals: z.number().int().optional(),
stakingRewardFinish: z.number().optional(),
stakingRewardAddress: z.string(),
vote_rewards_disabled: z.boolean().optional(),
}),
).transform(makePartial),
});
export default poolsConfigSchema;

View File

@@ -0,0 +1,25 @@
import { z } from 'zod';
const poolsInfoSchema = z.object({
governance: z.object({
apr: z.string(),
rewardRate: z.string(),
totalBalance: z.string(),
}),
totalRewardRatePerWeek: z.string(),
pools: z.record(
z.object({
currentAPR: z.string(),
isUser: z.boolean().optional(),
price: z.string(),
reserves: z.record(z.string()),
totalLiquidityInDollars: z.string(),
totalRewardRatePerWeek: z.string(),
totalStakedAmountInDollars: z.string(),
totalVoted: z.string(),
weight: z.string(),
}),
),
});
export default poolsInfoSchema;

View File

@@ -0,0 +1,63 @@
import io from 'socket.io-client';
import { z } from 'zod';
import balancesSchema from './schemas/balancesSchema';
const handleBalancesMessage = (
rawData: unknown,
updateData: (balancesData: z.infer<typeof balancesSchema>) => void,
) => {
const data = balancesSchema.parse(rawData);
updateData(data);
};
type UpdateBalanceDataHandler = (balancesData: z.infer<typeof balancesSchema>) => void;
export class OrionBlockchainSocketIO {
private socket: typeof io.Socket;
constructor(orionBlockchainWSUrl: string) {
const url = new URL(orionBlockchainWSUrl);
// https://stackoverflow.com/questions/29511404/connect-to-socket-io-server-with-specific-path-and-namespace
this.socket = io(url.origin, {
path: `${url.pathname}socket.io`,
transports: ['websocket'],
autoConnect: false,
});
}
connect(updateDataHandler: UpdateBalanceDataHandler) {
if (updateDataHandler) {
this.socket.on('balanceChange', (data: unknown) => handleBalancesMessage(data, updateDataHandler));
this.socket.on('balances', (data: unknown) => handleBalancesMessage(data, updateDataHandler));
}
this.socket.connect();
}
close() {
this.socket.removeAllListeners();
this.socket.close();
}
resetConnection() {
// Because Orion Blockchain does not have a subscription / unsubscribe system
// Only way to "unsubscribe" is reset connection
this.socket.disconnect();
this.socket.open();
}
subscribeBalancesUpdate(walletAddress: string) {
if (this.socket.connected) {
this.socket.emit('clientAddress', walletAddress);
} else {
this.socket.on('connect', () => {
this.socket.emit('clientAddress', walletAddress);
});
}
}
updateAllBalances(walletAddress: string) {
this.socket.emit('getAllBalances', walletAddress);
}
}
export * as schemas from './schemas';

View File

@@ -0,0 +1,10 @@
import { z } from 'zod';
import { makePartial } from '../../../../utils';
const balancesSchema = z.object({
contractBalances: z.record(z.string()).transform(makePartial).optional(),
walletBalances: z.record(z.string()).transform(makePartial),
allowances: z.record(z.string()).transform(makePartial).optional(),
});
export default balancesSchema;

View File

@@ -0,0 +1 @@
export { default as balancesSchema } from './balancesSchema';

View File

@@ -0,0 +1,45 @@
import { fetchJsonWithValidation } from '../../fetchWithValidation';
import candlesSchema from './schemas/candlesSchema';
class PriceFeed {
private apiUrl: string;
constructor(apiUrl: string) {
this.apiUrl = apiUrl;
}
getCandles(
symbol: string,
timeStart: number,
timeEnd: number,
interval: '5m' | '30m' | '1h' | '1d',
exchange: string,
) {
const url = new URL(`https://${this.apiUrl}/candles/candles`);
url.searchParams.append('symbol', symbol);
url.searchParams.append('timeStart', timeStart.toString());
url.searchParams.append('timeEnd', timeEnd.toString());
url.searchParams.append('interval', interval);
url.searchParams.append('exchange', exchange);
return fetchJsonWithValidation(
url.toString(),
candlesSchema,
);
}
get candlesUrl() { return `https://${this.apiUrl}/candles/candles`; }
get allTickersWSUrl() { return `wss://${this.apiUrl}/ws2/allTickers`; }
get tickerWSUrl() { return `wss://${this.apiUrl}/ws2/ticker/`; }
get lastPriceWSUrl() { return `wss://${this.apiUrl}/ws2/lastPrice/`; }
}
export * as schemas from './schemas';
export * as ws from './ws';
export {
PriceFeed,
};

View File

@@ -0,0 +1,18 @@
import { z } from 'zod';
const candlesSchema = z.object({
candles: z.array(z.object({
close: z.string(),
high: z.string(),
low: z.string(),
open: z.string(),
time: z.number(),
timeEnd: z.number(),
timeStart: z.number(),
volume: z.string(),
})),
timeStart: z.number(),
timeEnd: z.number(),
});
export default candlesSchema;

View File

@@ -0,0 +1 @@
export { default as candlesSchema } from './candlesSchema';

View File

@@ -0,0 +1,32 @@
import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
import { z } from 'zod';
import tickerInfoSchema from './schemas/tickerInfoSchema';
const schema = z.array(z.union([
z.number(),
tickerInfoSchema,
]));
export default class PriceFeedAllTickersWS {
private pairsWebSocket: WebsocketHeartbeatJs;
constructor(
url: string,
updateData: (pairs: z.infer<typeof tickerInfoSchema>[]) => void,
) {
this.pairsWebSocket = new WebsocketHeartbeatJs({ url });
this.pairsWebSocket.onmessage = (e) => {
if (e.data === 'pong') return;
const json = JSON.parse(e.data);
const data = schema.parse(json);
data.shift(); // Unnecessary timestamp
const tickersData = z.array(tickerInfoSchema).parse(data);
updateData(tickersData);
};
}
kill() {
this.pairsWebSocket.close();
}
}

View File

@@ -0,0 +1,31 @@
import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
import { z } from 'zod';
const schema = z.tuple([
z.number(), // unix timestamp
z.string(), // pair
z.number(), // price
]);
export default class PriceFeedLastPriceWS {
private pairsWebSocket: WebsocketHeartbeatJs;
constructor(
url: string,
pair: string,
updateData: (price: number) => void,
) {
this.pairsWebSocket = new WebsocketHeartbeatJs({ url: url + pair });
this.pairsWebSocket.onmessage = (e) => {
if (e.data === 'pong') return;
const json = JSON.parse(e.data);
const [,, price] = schema.parse(json);
updateData(price);
};
}
kill() {
this.pairsWebSocket.close();
}
}

View File

@@ -0,0 +1,35 @@
import WebsocketHeartbeatJs from 'websocket-heartbeat-js';
import { z } from 'zod';
import tickerInfoSchema from './schemas/tickerInfoSchema';
const schema = z.tuple([
z.number(), // timestamp
tickerInfoSchema,
]);
export default class PriceFeedTickerWS {
priceWebSocket: WebsocketHeartbeatJs;
constructor(
symbol: string,
url: string,
updateData: (pair: z.infer<typeof tickerInfoSchema>) => void,
) {
this.priceWebSocket = new WebsocketHeartbeatJs({
url: `${url}${symbol}`,
});
this.priceWebSocket.onmessage = (e) => {
if (e.data === 'pong') return;
const data = JSON.parse(e.data);
const [, tickerData] = schema.parse(data);
if (tickerData === undefined) return;
updateData(tickerData);
};
}
kill() {
this.priceWebSocket.close();
}
}

View File

@@ -0,0 +1,5 @@
export { default as PriceFeedAllTickersWS } from './PriceFeedAllTickersWS';
export { default as PriceFeedTickerWS } from './PriceFeedTickerWS';
export { default as PriceFeedLastPriceWS } from './PriceFeedLastPriceWS';
export * as schemas from './schemas';

View File

@@ -0,0 +1 @@
export { default as tickerInfoSchema } from './tickerInfoSchema';

View File

@@ -0,0 +1,12 @@
import { z } from 'zod';
const tickerInfoSchema = z.tuple([
z.string(), // pair name
z.string(), // lastPrice
z.string(), // openPrice
z.string(), // high price
z.string(), // low price
z.string(), // volume 24h
]);
export default tickerInfoSchema;

3
src/services/index.ts Normal file
View File

@@ -0,0 +1,3 @@
export * as orionAggregator from './OrionAggregator';
export * as orionBlockchain from './OrionBlockchain';
export * as priceFeed from './PriceFeed';

129
src/types.ts Normal file
View File

@@ -0,0 +1,129 @@
import BigNumber from 'bignumber.js';
export interface Order {
senderAddress: string; // address
matcherAddress: string; // address
baseAsset: string; // address
quoteAsset: string; // address
matcherFeeAsset: string; // address
amount: number; // uint64
price: number; // uint64
matcherFee: number; // uint64
nonce: number; // uint64
expiration: number; // uint64
buySide: number; // uint8, 1=buy, 0=sell
isPersonalSign: boolean; // bool
}
export interface SignedOrder extends Order {
id: string; // hash of Order (it's not part of order structure in smart-contract)
signature: string; // bytes
needWithdraw?: boolean; // bool (not supported yet by smart-contract)
}
export interface CancelOrderRequest {
id: number | string;
senderAddress: string;
isPersonalSign: boolean;
}
export interface SignedCancelOrderRequest extends CancelOrderRequest {
id: number | string;
senderAddress: string;
signature: string;
}
export interface Pair {
name: string;
baseCurrency: string;
quoteCurrency: string;
lastPrice: string;
openPrice: string;
change24h: string;
high: string;
low: string;
vol24h: string;
}
type SwapInfoBase = {
swapRequestId: string,
assetIn: string,
assetOut: string,
amountIn: number,
amountOut: number,
minAmounIn: number,
minAmounOut: number,
path: string[],
poolOptimal: boolean,
price?: number,
marketPrice?: number,
orderInfo?: {
pair: string,
side: 'BUY' | 'SELL',
amount: number,
safePrice: number,
}
}
export type SwapInfoByAmountIn = SwapInfoBase & {
kind: 'exactSpend',
availableAmountIn?: number,
marketAmountOut?: number,
}
export type SwapInfoByAmountOut = SwapInfoBase & {
kind: 'exactReceive',
marketAmountIn?: number,
availableAmountOut?: number,
}
export type SwapInfo = SwapInfoByAmountIn | SwapInfoByAmountOut;
export enum SupportedChainId {
MAINNET = '0x1',
ROPSTEN = '0x3',
FANTOM_OPERA = '0xfa',
FANTOM_TESTNET = '0xfa2',
BSC = '0x38',
BSC_TESTNET = '0x61',
// For testing and debug purpose
BROKEN = '0x0',
}
const balanceTypes = ['exchange', 'wallet'] as const;
export type Source = typeof balanceTypes[number];
export type Asset = {
name: string;
address: string;
}
export type BalanceRequirement = {
readonly reason: string,
readonly asset: Asset,
readonly amount: string,
readonly sources: Source[],
readonly spenderAddress?: string;
}
export type AggregatedBalanceRequirement = {
readonly asset: Asset,
readonly sources: Source[],
readonly spenderAddress?: string;
items: Partial<Record<string, string>>,
}
export type Approve = {
readonly targetAmount: BigNumber.Value,
readonly spenderAddress: string
}
export type BalanceIssue = {
readonly asset: Asset,
readonly message: string;
readonly sources: Source[],
readonly resetRequired?: boolean;
readonly approves?: Approve[];
}

View File

@@ -0,0 +1,36 @@
import BigNumber from 'bignumber.js';
import { FILL_ORDERS_GAS_LIMIT } from '../constants';
import calculateNetworkFeeInFeeAsset from './calculateNetworkFeeInFeeAsset';
import calculateOrionFeeInFeeAsset from './calculateOrionFeeInFeeAsset';
const calculateFeeInFeeAsset = (
amount: BigNumber.Value,
feeAssetPriceInOrn: BigNumber.Value,
baseAssetPriceInOrn: BigNumber.Value,
baseCurrencyPriceInOrn: BigNumber.Value,
gasPriceGwei: BigNumber.Value,
feePercent: BigNumber.Value,
) => {
const orionFeeInFeeAsset = calculateOrionFeeInFeeAsset(
amount,
feeAssetPriceInOrn,
baseAssetPriceInOrn,
feePercent,
);
const networkFeeInFeeAsset = calculateNetworkFeeInFeeAsset(
gasPriceGwei,
FILL_ORDERS_GAS_LIMIT,
baseCurrencyPriceInOrn,
feeAssetPriceInOrn,
);
return {
orionFeeInFeeAsset,
networkFeeInFeeAsset,
totalFeeInFeeAsset: new BigNumber(orionFeeInFeeAsset)
.plus(networkFeeInFeeAsset)
.toString(),
};
};
export default calculateFeeInFeeAsset;

View File

@@ -0,0 +1,13 @@
import BigNumber from 'bignumber.js';
import { ethers } from 'ethers';
import { NATIVE_CURRENCY_PRECISION } from '../constants/precisions';
export default function calculateNetworkFee(
gasPriceGwei: BigNumber.Value,
gasLimit: BigNumber.Value,
) {
const networkFeeGwei = new BigNumber(gasPriceGwei).multipliedBy(gasLimit);
const bn = new BigNumber(ethers.utils.parseUnits(networkFeeGwei.toString(), 'gwei').toString());
return bn.div(new BigNumber(10).pow(NATIVE_CURRENCY_PRECISION)).toString();
}

Some files were not shown because too many files have changed in this diff Show More