pull main

This commit is contained in:
Olga Kanishcheva
2024-10-08 15:48:40 +04:00
43 changed files with 903 additions and 479 deletions

1
.gitignore vendored
View File

@@ -132,3 +132,4 @@ lib
.pnp.*
src/artifacts/contracts/
.DS_store

260
PMM-sample.md Normal file
View File

@@ -0,0 +1,260 @@
<!-- Insert logo -->
<div align="center">
<img
src="./logo.svg"
width="300"
alt="Orion Protocol SDK logo"
/>
<h1>PMM example</h1>
</div>
## Overview
This is special example which shows how to use PMM liquidity without using SDK, only using few libraries
```ts
import {ethers, Wallet} from "ethers";
import {z} from "zod";
import hmacSHA256 from "crypto-js/hmac-sha256";
import Hex from "crypto-js/enc-hex";
///////////////////////////////
///////////////////////////////
const pmmOrderQuotationSchema = z.object({
info: z.string().default(''),
makerAsset: z.string().default(''),
takerAsset: z.string().default(''),
maker: z.string().default(''),
allowedSender: z.string().default(''),
makingAmount: z.string().default(''),
takingAmount: z.string().default(''),
});
const pmmOrderSchema = z.object({
order: pmmOrderQuotationSchema.default({}),
signature: z.string().default(''),
success: z.boolean().default(false),
error: z.string().default(''),
});
const orionRFQContractABI =
[
{
"inputs": [
{
"components": [
{
"internalType": "uint256",
"name": "info",
"type": "uint256"
},
{
"internalType": "address",
"name": "makerAsset",
"type": "address"
},
{
"internalType": "address",
"name": "takerAsset",
"type": "address"
},
{
"internalType": "address",
"name": "maker",
"type": "address"
},
{
"internalType": "address",
"name": "allowedSender",
"type": "address"
},
{
"internalType": "uint256",
"name": "makingAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "takingAmount",
"type": "uint256"
}
],
"internalType": "struct OrderRFQLib.OrderRFQ",
"name": "order",
"type": "tuple"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "flagsAndAmount",
"type": "uint256"
}
],
"name": "fillOrderRFQ",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
function encode_utf8(s : string) {
return unescape(encodeURIComponent(s));
}
function sign(message : string, key: string) {
return hmacSHA256(
encode_utf8(message),
encode_utf8(key)
).toString(Hex);
}
function generateHeaders(body : any, method : string, path : string, timestamp : number, apiKey : string, secretKey : string) {
const sortedBody = Object.keys(body)
.sort()
.map((key) => (
`${key}=${body[key]}`
)).join('&');
const payload = timestamp + method.toUpperCase() + path + sortedBody;
const signature = sign(payload, secretKey);
const httpOptions = {
headers: {
'API-KEY': apiKey,
'ACCESS-TIMESTAMP': timestamp.toString(),
'ACCESS-SIGN': signature
}
};
return httpOptions;
}
//
async function RFQOrder(
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
) : Promise<z.infer<typeof pmmOrderSchema>> {
// NB: replace with correct URL for networks
const apiUrl = 'https://testing.orion.xyz/bsc-testnet/backend'
// const apiUrl = 'https://trade.orion.xyz/bsc-mainnet/backend'
, path = '/rfq'
, url = `${apiUrl}/api/v1/integration/pmm`+path
, headers = {
'Content-Type': 'application/json',
}
, data = {
"baseToken":tokenFrom, // USDT
"quoteToken":tokenTo, // ORN
"amount": fromTokenAmount, // 100
"taker": wallet,
"feeBps": 0
}
, method = 'POST'
, timestamp = Date.now()
, signatureHeaders = generateHeaders(data, method, path, timestamp, apiKey, secretKey)
, compiledHeaders = {...headers, ...signatureHeaders.headers, }
, body = JSON.stringify(data)
;
let res = pmmOrderSchema.parse({});
try {
const result = await fetch(url,{
headers: compiledHeaders,
method,
body
});
// const data = await result.text();
// console.log(data);
const json = await result.json();
console.log(json);
const parseResult = pmmOrderSchema.safeParse(json);
if(!parseResult.success) {
// Try to parse error answer
const errorSchema = z.object({error: z.object({code: z.number(), reason: z.string()})});
const errorParseResult = errorSchema.safeParse(json);
if(!errorParseResult.success)
throw Error(`Unrecognized answer from aggregator: ${json}`);
throw Error(errorParseResult.data.error.reason);
}
res.order = parseResult.data.order;
res.signature = parseResult.data.signature;
res.error = '';
res.success = true;
}
catch(err) {
res.error = `${err}`;
}
return res;
}
(async() => {
const apiKey = '958153f1-b8b9-3ec4-84eb-2147429105d9';
const secretKey = 'secretKey';
// BNB testnet tokens and router
const assetORN = '0xf223eca06261145b3287a0fefd8cfad371c7eb34';
const assetUSDT = '0xcb2951e90d8dcf16e1fa84ac0c83f48906d6a744';
const router = '0x89357522C0ed6E557D39dC75290859246077bdfC';
// BNB mainnet tokens and router
// const assetORN = '0xe4ca1f75eca6214393fce1c1b316c237664eaa8e';
// const assetUSDT = '0x55d398326f99059ff775485246999027b3197955';
// const router = '0xcb2D40EabC4f4c92Ee993Eb3D67f7717bE476E76';
const rfqOrder = await RFQOrder(
assetORN, // Spending asset
assetUSDT, // Receiving asset
'1000000000', // Amount in "satoshi" of spending asset
apiKey,
secretKey,
'0x61Eed69c0d112C690fD6f44bB621357B89fBE67F' // Can be any address, ignored for now
);
if(!rfqOrder.success) {
console.log(rfqOrder.error);
return;
}
// ... here you can check order prices, etc.
console.log(rfqOrder);
// Calldata according to provided rfqOrder
const contractInterface = new ethers.Interface(orionRFQContractABI);
const calldata = contractInterface.encodeFunctionData("fillOrderRFQ", [rfqOrder.order, rfqOrder.signature, 0]);
console.log('Calldata = ', calldata);
// Call router with this data
// Replace with your private key
const yourWalletPrivateKey = '0xf1.......';
const tx = {
to: router,
data: calldata
}
const provider = new ethers.JsonRpcProvider("https://data-seed-prebsc-1-s1.binance.org:8545/");
const signer = new ethers.Wallet(yourWalletPrivateKey, provider);
console.log('Address = ', signer.address);
const transactionResponse = await signer.sendTransaction(tx);
console.log("Transaction hash:", transactionResponse.hash);
await transactionResponse.wait();
})();
```

View File

@@ -1,12 +1,8 @@
<!-- Insert logo -->
[//]: # ( <img src="./logo.svg" width="300" alt="Orion Protocol SDK logo"/>)
<div align="center">
<img
src="./logo.svg"
width="300"
alt="Orion Protocol SDK logo"
/>
<h1>Orion Protocol SDK</h1>
<h1>Lumia Stream SDK</h1>
<p>Use CEX and DEX liquidity without KYC.</p>
</div>
@@ -14,16 +10,15 @@
![npm bundle size (version)](https://img.shields.io/bundlephobia/minzip/@orionprotocol/sdk)
[![Downloads](https://img.shields.io/npm/dm/@orionprotocol/sdk.svg)](https://www.npmjs.com/package/@orionprotocol/sdk)
Do you want to integrate the Orion protocol into your application? See [integration guide](./docs/INTEGRATION.md)
Do you want to integrate the Lumia Stream protocol into your application? See [integration guide](./docs/INTEGRATION.md)
## Overview
Orion Software Developer Kit is a set of functions and methods that allow dApp developers connect to the superior aggregated liquidity of Orion Protocol which combines orderbooks of centralized exchanges as well decentralized automatic market makers such as Uniswap or Spookyswap across several supported blockchains.
Through this connection, developers using the SDK can perform a wide range of actions, including swapping selected tokens using Orions aggregated liquidity, obtaining relevant market information through subscriptions, add and remove liquidity to Orions pools.
Lumia Stream Developer Kit, natively built into Lumia, is a set of functions and methods that allow dApp developers to connect to the superior aggregated liquidity of Lumia Stream which combines orderbooks of centralized exchanges as well as decentralized Automatic Market Makers (AMMs) such as Uniswap, PancakeSwap, and Curve, across several supported blockchains. Through this connection, developers using the SDK can perform a wide range of actions, including swapping selected tokens, obtaining relevant market information through subscriptions, and more.
## API Key
Orions SDK is free to use and does not require an API key or registration. Refer to integration examples for more detailed information.
Lumia Streams SDK is free to use and does not require an API key or registration. Refer to integration examples for more detailed information.
- [Overview](#overview)
- [API Key](#api-key)
@@ -33,7 +28,7 @@ Orions SDK is free to use and does not require an API key or registration. Re
- [High level methods](#high-level-methods)
- [Get assets](#get-assets)
- [Get pairs](#get-pairs)
- [Get Orion Bridge history](#get-orion-bridge-history)
- [Get Lumia Stream Bridge history](#get-lumia-stream-bridge-history)
- [Bridge swap](#bridge-swap)
- [Withdraw](#withdraw)
- [Deposit](#deposit)
@@ -145,7 +140,7 @@ const pairs = await orion.getPairs("spot"); // 'spot'
// }
```
### Get Orion Bridge history
### Get Lumia Stream Bridge history
```ts
const bridgeHistory = await orion.bridge.getHistory(
@@ -722,7 +717,7 @@ switch (data.type) {
```
## PMM
PMM allows institutional traders to request RFQ orders from Orion and then fill them.
PMM allows institutional traders to request RFQ orders from Lumia Stream and then fill them.
RFQ order allows trader to fix the price for a certain time interval (up to 90 seconds, including the order settlement time interval on blockchain).

128
package-lock.json generated
View File

@@ -1,25 +1,25 @@
{
"name": "@orionprotocol/sdk",
"version": "0.20.74-rc104",
"version": "0.22.15",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@orionprotocol/sdk",
"version": "0.20.74-rc104",
"version": "0.22.15",
"hasInstallScript": true,
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@orionprotocol/contracts": "1.22.10",
"@orionprotocol/contracts": "^1.23.9",
"@types/lodash.clonedeep": "^4.5.9",
"bignumber.js": "^9.1.1",
"bson-objectid": "^2.0.4",
"buffer": "^6.0.3",
"crypto-js": "^4.2.0",
"ethers": "^6.7.1",
"ethers": "^6.12.0",
"express": "^4.18.2",
"isomorphic-ws": "^5.0.0",
"just-clone": "^6.2.0",
@@ -40,7 +40,6 @@
"@babel/plugin-syntax-import-assertions": "^7.20.0",
"@tsconfig/esm": "^1.0.4",
"@tsconfig/strictest": "^2.0.1",
"@types/crypto-js": "^4.2.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.1",
"@types/node": "^20.5.1",
@@ -82,9 +81,9 @@
}
},
"node_modules/@adraffy/ens-normalize": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz",
"integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg=="
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.1",
@@ -2365,27 +2364,27 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@noble/hashes": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz",
"integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
"node_modules/@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"dependencies": {
"@noble/hashes": "1.3.2"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@noble/secp256k1": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
]
"node_modules/@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
"engines": {
"node": ">= 16"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -2423,9 +2422,9 @@
}
},
"node_modules/@orionprotocol/contracts": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.22.10.tgz",
"integrity": "sha512-c9cUkXs1Nv8p+EVTybwJqeXhecwm7xeycAVauhl6jYAqvKOx7PDCUjzE3Nh0tpi4xP3CLeABgNy8JAFYyvN1VA=="
"version": "1.23.9",
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.23.9.tgz",
"integrity": "sha512-tx21XokSK8kBYmuzfYrXoDattirm6yaG3dslrOKZTHgGP1wC6c6SbQF69pVCCAnPNGvQCt4lw0I/8fxm9Cx23Q=="
},
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
@@ -2547,12 +2546,6 @@
"@types/node": "*"
}
},
"node_modules/@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
"dev": true
},
"node_modules/@types/eslint": {
"version": "8.44.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz",
@@ -5538,9 +5531,9 @@
}
},
"node_modules/ethers": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz",
"integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==",
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.12.1.tgz",
"integrity": "sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==",
"funding": [
{
"type": "individual",
@@ -5552,9 +5545,9 @@
}
],
"dependencies": {
"@adraffy/ens-normalize": "1.9.2",
"@noble/hashes": "1.1.2",
"@noble/secp256k1": "1.7.1",
"@adraffy/ens-normalize": "1.10.1",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.2",
"@types/node": "18.15.13",
"aes-js": "4.0.0-beta.5",
"tslib": "2.4.0",
@@ -11958,9 +11951,9 @@
"dev": true
},
"@adraffy/ens-normalize": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.9.2.tgz",
"integrity": "sha512-0h+FrQDqe2Wn+IIGFkTCd4aAwTJ+7834Ek1COohCyV26AXhwQ7WQaz+4F/nLOeVl/3BtWHOHLPsq46V8YB46Eg=="
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
"integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw=="
},
"@ampproject/remapping": {
"version": "2.2.1",
@@ -13456,15 +13449,18 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"@noble/hashes": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.2.tgz",
"integrity": "sha512-KYRCASVTv6aeUi1tsF8/vpyR7zpfs3FUzy2Jqm+MU+LmUKhQ0y2FpfwqkCcxSg2ua4GALJd8k2R76WxwZGbQpA=="
"@noble/curves": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
"integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
"requires": {
"@noble/hashes": "1.3.2"
}
},
"@noble/secp256k1": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz",
"integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw=="
"@noble/hashes": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
"integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@@ -13493,9 +13489,9 @@
}
},
"@orionprotocol/contracts": {
"version": "1.22.10",
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.22.10.tgz",
"integrity": "sha512-c9cUkXs1Nv8p+EVTybwJqeXhecwm7xeycAVauhl6jYAqvKOx7PDCUjzE3Nh0tpi4xP3CLeABgNy8JAFYyvN1VA=="
"version": "1.23.9",
"resolved": "https://registry.npmjs.org/@orionprotocol/contracts/-/contracts-1.23.9.tgz",
"integrity": "sha512-tx21XokSK8kBYmuzfYrXoDattirm6yaG3dslrOKZTHgGP1wC6c6SbQF69pVCCAnPNGvQCt4lw0I/8fxm9Cx23Q=="
},
"@sinclair/typebox": {
"version": "0.27.8",
@@ -13617,12 +13613,6 @@
"@types/node": "*"
}
},
"@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
"dev": true
},
"@types/eslint": {
"version": "8.44.2",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz",
@@ -15814,13 +15804,13 @@
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
},
"ethers": {
"version": "6.7.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz",
"integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==",
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/ethers/-/ethers-6.12.1.tgz",
"integrity": "sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==",
"requires": {
"@adraffy/ens-normalize": "1.9.2",
"@noble/hashes": "1.1.2",
"@noble/secp256k1": "1.7.1",
"@adraffy/ens-normalize": "1.10.1",
"@noble/curves": "1.2.0",
"@noble/hashes": "1.3.2",
"@types/node": "18.15.13",
"aes-js": "4.0.0-beta.5",
"tslib": "2.4.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@orionprotocol/sdk",
"version": "0.20.74-rc107",
"version": "0.22.15",
"description": "Orion Protocol SDK",
"main": "./lib/index.cjs",
"module": "./lib/index.js",
@@ -57,7 +57,6 @@
"@babel/plugin-syntax-import-assertions": "^7.20.0",
"@tsconfig/esm": "^1.0.4",
"@tsconfig/strictest": "^2.0.1",
"@types/crypto-js": "^4.2.2",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.1",
"@types/node": "^20.5.1",
@@ -89,13 +88,13 @@
"@babel/runtime": "^7.21.0",
"@ethersproject/abstract-signer": "^5.7.0",
"@ethersproject/providers": "^5.7.2",
"@orionprotocol/contracts": "1.22.10",
"@orionprotocol/contracts": "^1.23.9",
"@types/lodash.clonedeep": "^4.5.9",
"bignumber.js": "^9.1.1",
"bson-objectid": "^2.0.4",
"buffer": "^6.0.3",
"crypto-js": "^4.2.0",
"ethers": "^6.7.1",
"ethers": "^6.12.0",
"express": "^4.18.2",
"isomorphic-ws": "^5.0.0",
"just-clone": "^6.2.0",

View File

@@ -31,10 +31,15 @@ export default class Orion {
// TODO: get tradable pairs (aggregated)
public logger: ((message: string) => void) | undefined;
constructor(
envOrConfig: KnownEnv | EnvConfig = 'production',
overrides?: DeepPartial<EnvConfig>
overrides?: DeepPartial<EnvConfig>,
logger?: ((message: string) => void) | undefined
) {
this.logger = logger;
let config: EnvConfig;
if (typeof envOrConfig === 'string') {
const envConfig = envs[envOrConfig];
@@ -104,7 +109,7 @@ export default class Orion {
// api: networkConfig.api,
nodeJsonRpc: networkConfig.nodeJsonRpc,
services: networkConfig.services,
});
}, logger);
return {
...acc,
[chainId]: unit,

View File

@@ -0,0 +1,27 @@
import { SwapExecutor__factory, AeroPool__factory } from "@orionprotocol/contracts/lib/ethers-v6/index.js"
import { type BigNumberish, JsonRpcProvider } from "ethers"
import { SafeArray } from "../../../utils/safeGetters.js"
import { addCallParams } from "./utils.js"
import type { SingleSwap } from "../../../types.js"
export async function generateAeroCalls(
path: SafeArray<SingleSwap>,
amount: BigNumberish,
recipient: string,
provider: JsonRpcProvider
) {
const pools: string[] = [];
const direct: boolean[] = [];
for (const swap of path) {
pools.push(swap.pool);
const token0 = await AeroPool__factory.connect(swap.pool, provider).token0();
direct.push(swap.assetIn.toLowerCase() === token0.toLowerCase());
}
const executorInterface = SwapExecutor__factory.createInterface()
let calldata = executorInterface.encodeFunctionData('swapAeroMulti', [pools, direct, amount, recipient]);
calldata = addCallParams(calldata)
return [calldata]
}

View File

@@ -45,5 +45,5 @@ export async function generateCurveStableSwapCall(
}
calls.push(calldata)
return calls
return calls
}

View File

@@ -15,7 +15,7 @@ export function generateTransferCall(
target,
amount
])
return addCallParams(calldata, callParams)
}
@@ -31,6 +31,6 @@ export function generateApproveCall(
target,
amount
])
return addCallParams(calldata, callParams)
}

View File

@@ -16,6 +16,6 @@ export function generateFeePaymentCall(
token,
amount
])
return addCallParams(calldata, callParams)
}

View File

@@ -3,6 +3,15 @@ import { SafeArray } from "../../../utils/safeGetters.js"
import { type BytesLike, type BigNumberish, concat, ethers, toBeHex } from "ethers"
import { addCallParams } from "./utils.js"
import type { SingleSwap } from "../../../types.js"
import { BigNumber } from 'bignumber.js';
const BILLION = 1000000000;
const TEN_THOUSANDS = 10000;
function countScaledFee(fee: string) {
// The count is needed for the swapUniV2Scaled function, where the denominator is one billion
return new BigNumber(fee).multipliedBy(BILLION).div(TEN_THOUSANDS).toNumber();
}
export async function generateUni2Calls(
path: SafeArray<SingleSwap>,
@@ -19,17 +28,21 @@ export async function generateUni2Calls(
currentSwap.pool,
currentSwap.assetIn,
currentSwap.assetOut,
nextSwap.pool
nextSwap.pool,
currentSwap.fee
)
calls.push(call)
}
}
const lastSwap = path.last();
const calldata = executorInterface.encodeFunctionData('swapUniV2', [
const fee = lastSwap.fee ?? 30;
const scaledFee = countScaledFee(fee.toString());
const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [
lastSwap.pool,
lastSwap.assetIn,
lastSwap.assetOut,
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat(['0x03', recipient])]),
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]),
])
calls.push(addCallParams(calldata))
@@ -41,14 +54,15 @@ export function generateUni2Call(
assetIn: string,
assetOut: string,
recipient: string,
fee: BigNumberish = 3,
fee: BigNumberish = 30,
) {
const executorInterface = SwapExecutor__factory.createInterface()
const calldata = executorInterface.encodeFunctionData('swapUniV2', [
const scaledFee = countScaledFee(fee.toString());
const calldata = executorInterface.encodeFunctionData('swapUniV2Scaled', [
pool,
assetIn,
assetOut,
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(fee), recipient])]),
ethers.AbiCoder.defaultAbiCoder().encode(['uint256'], [concat([toBeHex(scaledFee), recipient])]),
])
return addCallParams(calldata)
}
}

View File

@@ -12,7 +12,7 @@ export function generateWrapAndTransferCall(
const calldata = executorInterface.encodeFunctionData('wrapAndTransfer', [
target,
])
return addCallParams(calldata, callParams)
}
@@ -27,6 +27,6 @@ export function generateUnwrapAndTransferCall(
target,
amount
])
return addCallParams(calldata, callParams)
}

View File

@@ -1,49 +1,51 @@
import type { LibValidator } from "@orionprotocol/contracts/lib/ethers-v6/Exchange.js";
import { ethers, ZeroAddress } from "ethers";
import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from "ethers";
import cloneDeep from "lodash.clonedeep";
import { safeGet, SafeArray } from "../../utils/safeGetters.js";
import { simpleFetch } from "simple-typed-fetch";
import type Unit from "../index.js";
import { generateUni2Calls, generateUni2Call } from "./callGenerators/uniswapV2.js";
import type { LibValidator } from '@orionprotocol/contracts/lib/ethers-v6/Exchange.js';
import { ethers, ZeroAddress } from 'ethers';
import type { AddressLike, JsonRpcProvider, BigNumberish, BytesLike } from 'ethers';
import cloneDeep from 'lodash.clonedeep';
import { safeGet, SafeArray } from '../../utils/safeGetters.js';
import { simpleFetch } from 'simple-typed-fetch';
import type Unit from '../index.js';
import { generateUni2Calls, generateUni2Call } from './callGenerators/uniswapV2.js';
import {
generateUni3Calls,
generateOrion3Calls,
generateUni3Call,
generateOrion3Call,
} from "./callGenerators/uniswapV3.js";
import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from "./callGenerators/utils.js";
import { generateTransferCall } from "./callGenerators/erc20.js";
import { generateCurveStableSwapCall } from "./callGenerators/curve.js";
import type { SingleSwap } from "../../types.js";
import { addressLikeToString } from "../../utils/addressLikeToString.js";
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from "./callGenerators/weth.js";
import { getExchangeAllowance, getTotalBalance } from "../../utils/getBalance.js";
import { generateFeePaymentCall } from "./callGenerators/feePayment.js";
} from './callGenerators/uniswapV3.js';
import { exchangeToNativeDecimals, generateCalls, pathCallWithBalance } from './callGenerators/utils.js';
import { generateTransferCall } from './callGenerators/erc20.js';
import { generateCurveStableSwapCall } from './callGenerators/curve.js';
import type { SingleSwap } from '../../types.js';
import { addressLikeToString } from '../../utils/addressLikeToString.js';
import { generateUnwrapAndTransferCall, generateWrapAndTransferCall } from './callGenerators/weth.js';
import { getExchangeAllowance, getTotalBalance } from '../../utils/getBalance.js';
import { generateFeePaymentCall } from './callGenerators/feePayment.js';
import { generateAeroCalls } from './callGenerators/aero.js';
export type Factory = "UniswapV2" | "UniswapV3" | "Curve" | "OrionV2" | "OrionV3";
export type Factory = 'UniswapV2' | 'UniswapV3' | 'Curve' | 'OrionV2' | 'OrionV3' | 'Aero';
type BaseGenerateSwapCalldataParams = {
amount: BigNumberish;
minReturnAmount: BigNumberish;
initiatorAddress: string;
receiverAddress: string;
path: ArrayLike<SingleSwap>;
matcher?: AddressLike,
feeToken?: AddressLike,
fee?: BigNumberish;
amount: BigNumberish
minReturnAmount: BigNumberish
initiatorAddress: string
receiverAddress: string
path: ArrayLike<SingleSwap>
matcher?: AddressLike
feeToken?: AddressLike
fee?: BigNumberish
}
export type GenerateSwapCalldataWithUnitParams = BaseGenerateSwapCalldataParams & {
unit: Unit;
unit: Unit
};
export type GenerateSwapCalldataParams = BaseGenerateSwapCalldataParams & {
exchangeContractAddress: AddressLike;
wethAddress: AddressLike;
curveRegistryAddress: AddressLike;
swapExecutorContractAddress: AddressLike;
provider: JsonRpcProvider;
exchangeContractAddress: AddressLike
wethAddress: AddressLike
curveRegistryAddress: AddressLike
swapExecutorContractAddress: AddressLike
provider: JsonRpcProvider
logger?: ((message: string) => void) | undefined
};
export async function generateSwapCalldataWithUnit({
@@ -57,29 +59,27 @@ export async function generateSwapCalldataWithUnit({
fee = 0,
unit,
}: GenerateSwapCalldataWithUnitParams): Promise<{
calldata: string;
swapDescription: LibValidator.SwapDescriptionStruct;
value: bigint;
calldata: string
swapDescription: LibValidator.SwapDescriptionStruct
value: bigint
}> {
if (arrayLikePath == undefined || arrayLikePath.length == 0) {
throw new Error("Empty path");
throw new Error('Empty path');
}
const wethAddress = safeGet(unit.contracts, "WETH");
const curveRegistryAddress = safeGet(unit.contracts, "curveRegistry");
const { assetToAddress, swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(
const wethAddress = safeGet(unit.contracts, 'WETH');
const curveRegistryAddress = safeGet(unit.contracts, 'curveRegistry');
const { swapExecutorContractAddress, exchangeContractAddress } = await simpleFetch(
unit.blockchainService.getInfo
)();
const arrayLikePathCopy = cloneDeep(arrayLikePath);
let path = SafeArray.from(arrayLikePathCopy);
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => {
swapInfo.assetIn = assetToAddress[swapInfo.assetIn] ?? swapInfo.assetIn
swapInfo.assetOut = assetToAddress[swapInfo.assetOut] ?? swapInfo.assetOut
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
return swapInfo;
});
path = SafeArray.from(arrayLikePathCopy).map((swapInfo) => ({
...swapInfo,
assetIn: swapInfo.assetAddressIn.toLowerCase(),
assetOut: swapInfo.assetAddressOut.toLowerCase(),
}));
return await generateSwapCalldata({
amount,
@@ -95,6 +95,7 @@ export async function generateSwapCalldataWithUnit({
curveRegistryAddress,
swapExecutorContractAddress,
provider: unit.provider,
logger: unit.logger,
});
}
@@ -112,21 +113,30 @@ export async function generateSwapCalldata({
curveRegistryAddress: curveRegistryAddressLike,
swapExecutorContractAddress: swapExecutorContractAddressLike,
provider,
logger,
}: GenerateSwapCalldataParams): Promise<{
calldata: string;
swapDescription: LibValidator.SwapDescriptionStruct;
value: bigint;
calldata: string
swapDescription: LibValidator.SwapDescriptionStruct
value: bigint
}> {
const wethAddress = await addressLikeToString(wethAddressLike);
logger?.(`wethAddress: ${wethAddress}`);
const curveRegistryAddress = await addressLikeToString(curveRegistryAddressLike);
logger?.(`curveRegistryAddress: ${curveRegistryAddress}`);
const swapExecutorContractAddress = await addressLikeToString(swapExecutorContractAddressLike);
logger?.(`swapExecutorContractAddress, ${swapExecutorContractAddress}`);
const feeToken = await addressLikeToString(feeTokenAddressLike);
logger?.(`feeToken, ${feeToken}`);
const matcher = await addressLikeToString(matcherAddressLike);
logger?.(`matcher: ${matcher}`);
logger?.(`arrayLikePath: ${arrayLikePath}`);
let path = SafeArray.from(arrayLikePath).map((swapInfo) => {
logger?.(`swapInfo: ${swapInfo}`);
swapInfo.assetIn = swapInfo.assetIn.toLowerCase()
swapInfo.assetOut = swapInfo.assetOut.toLowerCase()
return swapInfo;
});
logger?.(`path: ${path}`);
const { assetIn: srcToken } = path.first();
const { assetOut: dstToken } = path.last();
@@ -140,14 +150,18 @@ export async function generateSwapCalldata({
minReturnAmount,
flags: 0,
};
logger?.(`swapDescription: ${swapDescription}`);
const amountNativeDecimals = await exchangeToNativeDecimals(srcToken, amount, provider);
logger?.(`amountNativeDecimals: ${amountNativeDecimals}`);
const feeNativeDecimals = await exchangeToNativeDecimals(feeToken, fee, provider)
logger?.(`feeNativeDecimals: ${feeNativeDecimals}`);
path = SafeArray.from(arrayLikePath).map((singleSwap) => {
if (singleSwap.assetIn == ethers.ZeroAddress) singleSwap.assetIn = wethAddress;
if (singleSwap.assetOut == ethers.ZeroAddress) singleSwap.assetOut = wethAddress;
return singleSwap;
});
logger?.(`path2: ${path}`);
let calls: BytesLike[];
({ swapDescription, calls } = await processSwaps(
@@ -162,19 +176,26 @@ export async function generateSwapCalldata({
curveRegistryAddress,
provider
));
logger?.(`swapDescription: ${swapDescription}`);
logger?.(`calls: ${calls}`);
const calldata = generateCalls(calls);
logger?.(`calldata: ${calldata}`);
const { useExchangeBalance, additionalTransferAmount } = await shouldUseExchangeBalance(
srcToken,
initiatorAddress,
exchangeContractAddress,
amountNativeDecimals,
provider
provider,
logger
);
logger?.(`useExchangeBalance: ${useExchangeBalance}`);
logger?.(`additionalTransferAmount: ${additionalTransferAmount}`);
if (useExchangeBalance) {
swapDescription.flags = 1n << 255n;
}
const value = srcToken == ZeroAddress ? additionalTransferAmount : 0n;
logger?.(`value: ${value}`);
return { swapDescription, calldata, value };
}
@@ -214,7 +235,7 @@ async function processSwaps(
));
}
({swapDescription, calls} = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription));
({ swapDescription, calls } = await payFeeToMatcher(matcher, feeToken, fee, calls, swapDescription));
({ swapDescription, calls } = wrapOrUnwrapIfNeeded(
amount,
@@ -238,27 +259,31 @@ async function processSingleFactorySwaps(
) {
let calls: BytesLike[] = [];
switch (factory) {
case "OrionV2": {
case 'OrionV2': {
swapDescription.srcReceiver = path.first().pool;
calls = await generateUni2Calls(path, swapExecutorContractAddress);
break;
}
case "UniswapV2": {
case 'UniswapV2': {
swapDescription.srcReceiver = path.first().pool;
calls = await generateUni2Calls(path, swapExecutorContractAddress);
break;
}
case "UniswapV3": {
case 'UniswapV3': {
calls = await generateUni3Calls(path, amount, swapExecutorContractAddress, provider);
break;
}
case "OrionV3": {
case 'OrionV3': {
calls = await generateOrion3Calls(path, amount, swapExecutorContractAddress, provider);
break;
}
case "Curve": {
case "Aero": {
calls = await generateAeroCalls(path, amount, swapExecutorContractAddress, provider);
break;
}
case 'Curve': {
if (path.length > 1) {
throw new Error("Supporting only single stable swap on curve");
throw new Error('Supporting only single stable swap on curve');
}
calls = await generateCurveStableSwapCall(
amount,
@@ -285,37 +310,37 @@ async function processMultiFactorySwaps(
curveRegistryAddress: string,
provider: JsonRpcProvider
) {
let calls: BytesLike[] = [];
const calls: BytesLike[] = [];
for (const swap of path) {
switch (swap.factory) {
case "OrionV2": {
case 'OrionV2': {
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress, swap.fee);
calls.push(transferCall, uni2Call);
break;
}
case "UniswapV2": {
case 'UniswapV2': {
let transferCall = generateTransferCall(swap.assetIn, swap.pool, 0);
transferCall = pathCallWithBalance(transferCall, swap.assetIn);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress);
const uni2Call = generateUni2Call(swap.pool, swap.assetIn, swap.assetOut, swapExecutorContractAddress, swap.fee);
calls.push(transferCall, uni2Call);
break;
}
case "UniswapV3": {
case 'UniswapV3': {
let uni3Call = await generateUni3Call(swap, 0, swapExecutorContractAddress, provider);
uni3Call = pathCallWithBalance(uni3Call, swap.assetIn);
calls.push(uni3Call);
break;
}
case "OrionV3": {
case 'OrionV3': {
let orion3Call = await generateOrion3Call(swap, 0, swapExecutorContractAddress, provider);
orion3Call = pathCallWithBalance(orion3Call, swap.assetIn);
calls.push(orion3Call);
break;
}
case "Curve": {
let curveCalls = await generateCurveStableSwapCall(
case 'Curve': {
const curveCalls = await generateCurveStableSwapCall(
amount,
swapExecutorContractAddress,
swap,
@@ -346,7 +371,7 @@ async function payFeeToMatcher(
const feePaymentCall = generateFeePaymentCall(matcher, feeToken, feeAmount)
calls.push(feePaymentCall)
}
return {swapDescription, calls}
return { swapDescription, calls }
}
function wrapOrUnwrapIfNeeded(
@@ -356,7 +381,7 @@ function wrapOrUnwrapIfNeeded(
swapExecutorContractAddress: string,
wethAddress: string
) {
const {dstReceiver, srcReceiver, srcToken, dstToken} = swapDescription;
const { dstReceiver, srcReceiver, srcToken, dstToken } = swapDescription;
if (srcToken === ZeroAddress) {
const wrapCall = generateWrapAndTransferCall(srcReceiver, { value: amount });
swapDescription.srcReceiver = swapExecutorContractAddress;
@@ -379,7 +404,8 @@ async function shouldUseExchangeBalance(
initiatorAddress: AddressLike,
exchangeContractAddress: AddressLike,
amount: bigint,
provider: JsonRpcProvider
provider: JsonRpcProvider,
logger?: ((message: string) => void) | undefined
) {
const { walletBalance, exchangeBalance } = await getTotalBalance(
srcToken,
@@ -387,17 +413,19 @@ async function shouldUseExchangeBalance(
exchangeContractAddress,
provider
);
const exchangeAllowance = await getExchangeAllowance(srcToken, initiatorAddress, exchangeContractAddress, provider);
logger?.('test_123');
if (walletBalance + exchangeBalance < amount) {
throw new Error(
`Not enough balance to make swap, totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}`
`Not enough balance to make swap, walletBalance: ${walletBalance} exchangeBalance: ${exchangeBalance} totalBalance - ${walletBalance + exchangeBalance} swapAmount - ${amount}`
);
}
let useExchangeBalance = true;
let additionalTransferAmount = 0n;
if (exchangeBalance == 0n) {
if (walletBalance >= amount || exchangeBalance == 0n) {
useExchangeBalance = false;
additionalTransferAmount = amount;
} else {

View File

@@ -8,7 +8,6 @@ import type { BlockchainService } from '../../services/BlockchainService/index.j
import { calculateFeeInFeeAsset, denormalizeNumber, getNativeCryptocurrencyName } from '../../utils/index.js';
export type GetSwapInfoParams = {
type: 'exactSpend' | 'exactReceive'
assetIn: string
assetOut: string
amount: BigNumber.Value
@@ -18,12 +17,12 @@ export type GetSwapInfoParams = {
options?: {
instantSettlement?: boolean
poolOnly?: boolean
},
walletAddress?: string,
}
walletAddress?: string
isTradeBuy?: boolean
}
export default async function getSwapInfo({
type,
assetIn,
assetOut,
amount,
@@ -32,6 +31,7 @@ export default async function getSwapInfo({
aggregator,
options,
walletAddress,
isTradeBuy = false,
}: GetSwapInfoParams) {
if (amount === '') throw new Error('Amount can not be empty');
if (assetIn === '') throw new Error('AssetIn can not be empty');
@@ -61,7 +61,6 @@ export default async function getSwapInfo({
}
const swapInfo = await simpleFetch(aggregator.getSwapInfo)(
type,
assetIn,
assetOut,
amountBN.toString(),
@@ -69,6 +68,7 @@ export default async function getSwapInfo({
options?.poolOnly !== undefined && options.poolOnly
? 'pools'
: undefined,
isTradeBuy,
);
const { exchanges: swapExchanges } = swapInfo;
@@ -76,16 +76,6 @@ export default async function getSwapInfo({
const poolExchangesList = factories !== undefined ? Object.keys(factories) : [];
const [firstSwapExchange] = swapExchanges;
// 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);
let route: 'pool' | 'aggregator';
if (options?.poolOnly !== undefined && options.poolOnly) {
route = 'pool';

View File

@@ -47,7 +47,7 @@ export default class Exchange {
public generateSwapCalldata(params: PureGenerateSwapCalldataParams) {
return generateSwapCalldataWithUnit({
...params,
unit: this.unit
unit: this.unit,
})
}

View File

@@ -18,7 +18,6 @@ import type { SingleSwap } from '../../types.js';
import { must, safeGet } from '../../utils/safeGetters.js';
export type SwapLimitParams = {
type: 'exactSpend' | 'exactReceive'
assetIn: string
assetOut: string
price: BigNumber.Value
@@ -35,6 +34,7 @@ export type SwapLimitParams = {
route?: 'aggregator' | 'pool'
}
}
isTradeBuy?: boolean
}
type AggregatorOrder = {
@@ -58,7 +58,6 @@ const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory:
}
export default async function swapLimit({
type,
assetIn,
assetOut,
price,
@@ -67,6 +66,7 @@ export default async function swapLimit({
signer,
unit,
options,
isTradeBuy = false,
}: SwapLimitParams): Promise<Swap> {
if (options?.developer) options.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!');
if (amount === '') throw new Error('Amount can not be empty');
@@ -138,7 +138,6 @@ export default async function swapLimit({
);
const swapInfo = await simpleFetch(aggregator.getSwapInfo)(
type,
assetIn,
assetOut,
amountBN.toString(),
@@ -146,6 +145,7 @@ export default async function swapLimit({
options?.poolOnly !== undefined && options.poolOnly
? 'pools'
: undefined,
isTradeBuy,
);
const { exchanges: swapExchanges, exchangeContractPath } = swapInfo;
@@ -154,11 +154,11 @@ export default async function swapLimit({
if (swapExchanges.length > 0) options?.logger?.(`Swap exchanges: ${swapExchanges.join(', ')}`);
if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
if (swapInfo?.isTradeBuy && 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)) {
if (!(swapInfo?.isTradeBuy) && amountBN.lt(swapInfo.minAmountIn)) {
throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`);
}
@@ -200,9 +200,9 @@ export default async function swapLimit({
options?.logger?.(`Safe price is ${swapInfo.orderInfo.safePrice} ${quoteAssetName}`);
// BTEMP — better than or equal market price
const priceIsBTEMP = type === 'exactSpend'
? priceBN.lte(swapInfo.orderInfo.safePrice)
: priceBN.gte(swapInfo.orderInfo.safePrice);
const priceIsBTEMP = isTradeBuy
? priceBN.gte(swapInfo.orderInfo.safePrice)
: priceBN.lte(swapInfo.orderInfo.safePrice);
options?.logger?.(`Your price ${priceBN.toString()} is ${priceIsBTEMP ? 'better than or equal' : 'worse than'} market price ${swapInfo.orderInfo.safePrice}`);
@@ -246,7 +246,7 @@ export default async function swapLimit({
if (factoryAddress !== undefined) options?.logger?.(`Factory address is ${factoryAddress}. Exchange is ${firstSwapExchange}`);
}
const amountSpend = swapInfo.type === 'exactSpend'
const amountSpend = !(swapInfo?.isTradeBuy)
? swapInfo.amountIn
: new BigNumber(swapInfo.orderInfo.amount).multipliedBy(swapInfo.orderInfo.safePrice)
@@ -261,7 +261,7 @@ export default async function swapLimit({
sources: getAvailableSources('amount', assetInAddress, 'pool'),
});
const amountReceive = swapInfo.type === 'exactReceive'
const amountReceive = swapInfo?.isTradeBuy
? swapInfo.amountOut
: new BigNumber(swapInfo.orderInfo.amount).multipliedBy(swapInfo.orderInfo.safePrice)
const amountSpendBlockchainParam = normalizeNumber(

View File

@@ -37,13 +37,11 @@ type PoolSwap = {
export type Swap = AggregatorOrder | PoolSwap;
const isValidSingleSwap = (singleSwap: Omit<SingleSwap, 'factory'> & { factory: string }): singleSwap is SingleSwap => {
return isValidFactory(singleSwap.factory);
}
export default async function swapMarket({
type,
assetIn,
assetOut,
amount,
@@ -52,6 +50,7 @@ export default async function swapMarket({
signer,
unit,
options,
isTradeBuy = false,
}: SwapMarketParams): Promise<Swap> {
if (options?.developer) options.logger?.('YOU SPECIFIED A DEVELOPER OPTIONS. BE CAREFUL!');
@@ -125,7 +124,6 @@ export default async function swapMarket({
);
const swapInfo = await simpleFetch(aggregator.getSwapInfo)(
type,
assetIn,
assetOut,
amountBN.toString(),
@@ -133,6 +131,7 @@ export default async function swapMarket({
options?.poolOnly !== undefined && options.poolOnly
? 'pools'
: undefined,
isTradeBuy,
);
const { exchanges: swapExchanges, exchangeContractPath } = swapInfo;
@@ -141,11 +140,11 @@ export default async function swapMarket({
if (swapExchanges.length > 0) options?.logger?.(`Swap exchanges: ${swapExchanges.join(', ')}`);
if (swapInfo.type === 'exactReceive' && amountBN.lt(swapInfo.minAmountOut)) {
if (swapInfo?.isTradeBuy && 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)) {
if (!(swapInfo?.isTradeBuy) && amountBN.lt(swapInfo.minAmountIn)) {
throw new Error(`Amount is too low. Min amountIn is ${swapInfo.minAmountIn} ${assetIn}`);
}
@@ -203,7 +202,7 @@ export default async function swapMarket({
.multipliedBy(new BigNumber(1).plus(percent))
.toString();
const amountSpend = swapInfo.type === 'exactSpend' ? swapInfo.amountIn : amountInWithSlippage;
const amountSpend = swapInfo?.isTradeBuy ? amountInWithSlippage : swapInfo.amountIn;
balanceGuard.registerRequirement({
reason: 'Amount spend',
@@ -216,7 +215,7 @@ export default async function swapMarket({
sources: getAvailableSources('amount', assetInAddress, 'pool'),
});
const amountReceive = swapInfo.type === 'exactReceive' ? swapInfo.amountOut : amountOutWithSlippage;
const amountReceive = swapInfo?.isTradeBuy ? amountOutWithSlippage : swapInfo.amountOut;
const amountSpendBlockchainParam = normalizeNumber(
amountSpend,
INTERNAL_PROTOCOL_PRECISION,

View File

@@ -79,6 +79,6 @@ export default class Pmm {
const contract = new ethers.Contract(this.contractAddress, orionRFQContractABI, signer);
// @ts-ignore
return contract.fillOrderRFQ(order.quotation, order.signature, BigInt(0));
return contract.fillOrderRFQ(order.order, order.signature, BigInt(0));
}
}

View File

@@ -11,7 +11,7 @@ export const pmmOrderQuotationSchema = z.object({
});
export const pmmOrderSchema = z.object({
quotation: pmmOrderQuotationSchema.default({}),
order: pmmOrderQuotationSchema.default({}),
signature: z.string().default(''),
success: z.boolean().default(false),
error: z.string().default(''),

View File

@@ -41,7 +41,10 @@ export default class Unit {
public readonly contracts: Record<string, string>;
constructor(config: KnownConfig | VerboseUnitConfig) {
public logger: ((message: string) => void) | undefined;
constructor(config: KnownConfig | VerboseUnitConfig, logger?: ((message: string) => void) | undefined) {
this.logger = logger;
if ('env' in config) {
const staticConfig = envs[config.env];
if (!staticConfig) {
@@ -118,7 +121,8 @@ export default class Unit {
this.aggregator = new Aggregator(
this.config.services.aggregator.http,
this.config.services.aggregator.ws,
this.config.basicAuth
this.config.basicAuth,
logger,
);
this.priceFeed = new PriceFeed(
this.config.services.priceFeed.api,

View File

@@ -81,7 +81,7 @@ describe('Orion', () => {
test('Init Orion testing', () => {
const orion = new Orion('testing');
expect(orion.referralSystem).toBeInstanceOf(ReferralSystem);
expect(orion.unitsArray.length).toBe(4); // eth, bsc, polygon, fantom
expect(orion.unitsArray.length).toBe(2); // eth, bsc
const unitBSC = orion.units[SupportedChainId.BSC_TESTNET];
expect(unitBSC?.chainId).toBe(SupportedChainId.BSC_TESTNET);
@@ -89,23 +89,11 @@ describe('Orion', () => {
expect(orion.getSiblingsOf(SupportedChainId.BSC_TESTNET)).toHaveLength(3);
expect(unitBSC?.networkCode).toBe('bsc');
const unitRopsten = orion.units[SupportedChainId.ROPSTEN]
expect(unitRopsten?.chainId).toBe(SupportedChainId.ROPSTEN);
const unitSepolia = orion.units[SupportedChainId.SEPOLIA]
expect(unitSepolia?.chainId).toBe(SupportedChainId.SEPOLIA);
// expect(unitRopsten?.env).toBe('testing');
expect(orion.getSiblingsOf(SupportedChainId.ROPSTEN)).toHaveLength(3);
expect(unitRopsten?.networkCode).toBe('eth');
const unitPolygon = orion.units[SupportedChainId.POLYGON_TESTNET];
expect(unitPolygon?.chainId).toBe(SupportedChainId.POLYGON_TESTNET);
// expect(unitPolygon?.env).toBe('testing');
expect(orion.getSiblingsOf(SupportedChainId.POLYGON_TESTNET)).toHaveLength(3);
expect(unitPolygon?.networkCode).toBe('polygon');
const unitFantom = orion.units[SupportedChainId.FANTOM_TESTNET];
expect(unitFantom?.chainId).toBe(SupportedChainId.FANTOM_TESTNET);
// expect(unitFantom?.env).toBe('testing');
expect(orion.getSiblingsOf(SupportedChainId.FANTOM_TESTNET)).toHaveLength(3);
expect(unitFantom?.networkCode).toBe('ftm');
expect(orion.getSiblingsOf(SupportedChainId.SEPOLIA)).toHaveLength(3);
expect(unitSepolia?.networkCode).toBe('eth');
});
test('Init Orion production', () => {

View File

@@ -51,42 +51,42 @@
"curveRegistry": ""
}
},
"3": {
"chainId": "3",
"explorer": "https://ropsten.etherscan.io/",
"label": "Ropsten",
"shortName": "ETH-Ropsten",
"11155111": {
"chainId": "11155111",
"explorer": "https://sepolia.etherscan.io/",
"label": "Sepolia",
"shortName": "ETH-Sepolia",
"code": "eth",
"rpc": "https://testing.orion.xyz/eth-ropsten/rpc",
"rpc": "https://gateway.tenderly.co/public/sepolia",
"baseCurrencyName": "ETH",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"5": {
"chainId": "5",
"explorer": "https://goerli.etherscan.io/",
"label": "Goerli",
"shortName": "ETH-Goerli",
"123420000034": {
"chainId": "123420000034",
"explorer": "https://blockscout-123420000034.raas-testnet.gelato.digital/",
"label": "Event Horizon Testnet",
"shortName": "EH-Testnet",
"code": "eth",
"rpc": "https://testing.orion.xyz/eth-goerli/rpc",
"rpc": "https://rpc-123420000034.raas-testnet.gelato.digital/",
"baseCurrencyName": "ETH",
"contracts": {
"WETH": "",
"WETH": "0x4200000000000000000000000000000000000006",
"curveRegistry": ""
}
},
"421613": {
"chainId": "421613",
"explorer": "https://goerli.arbiscan.io/",
"label": "Arbitrum Goerli",
"shortName": "Arbitrum Goerli",
"code": "arb",
"rpc": "https://goerli-rollup.arbitrum.io/rpc",
"baseCurrencyName": "ETH",
"1952959480": {
"chainId": "1952959480",
"explorer": "https://testnet-explorer.lumia.org/",
"label": "Lumia Testnet",
"shortName": "Lumia Testnet",
"code": "lumia",
"rpc": "https://testnet-rpc.lumia.org",
"baseCurrencyName": "LUMIA",
"contracts": {
"WETH": "",
"WETH": "0x1a1aF9C78704D3a0Ab9e031C92E7bd808711A582",
"curveRegistry": ""
}
},
@@ -103,19 +103,6 @@
"curveRegistry": "0x445FE580eF8d70FF569aB36e80c647af338db351"
}
},
"4002": {
"chainId": "4002",
"explorer": "https://testnet.ftmscan.com/",
"label": "Fantom Testnet",
"shortName": "FTM-Testnet",
"code": "ftm",
"rpc": "https://rpc.testnet.fantom.network/",
"baseCurrencyName": "FTM",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"250": {
"chainId": "250",
"explorer": "https://ftmscan.com/",
@@ -142,19 +129,6 @@
"curveRegistry": "0x094d12e5b541784701FD8d65F11fc0598FBC6332"
}
},
"80001": {
"chainId": "80001",
"label": "Polygon Mumbai",
"shortName": "Polygon Mumbai",
"code": "polygon",
"baseCurrencyName": "MATIC",
"rpc": "https://rpc.ankr.com/polygon_mumbai",
"explorer": "https://mumbai.polygonscan.com/",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"66": {
"chainId": "66",
"explorer": "https://www.oklink.com/okc/",
@@ -168,32 +142,6 @@
"curveRegistry": ""
}
},
"65": {
"chainId": "65",
"explorer": "https://www.oklink.com/okc-test/",
"label": "OKC Testnet",
"shortName": "OKC-Testnet",
"code": "okc",
"rpc": "https://exchaintestrpc.okex.org/",
"baseCurrencyName": "OKT",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"56303": {
"chainId": "56303",
"label": "DRIP Chain",
"shortName": "DRIP Chain",
"code": "drip",
"baseCurrencyName": "DRIP",
"rpc": "https://testnet.1d.rip/",
"explorer": "https://explorer-testnet.1d.rip/",
"contracts": {
"WETH": "",
"curveRegistry": ""
}
},
"2525": {
"chainId": "2525",
"label": "inEVM",
@@ -219,5 +167,31 @@
"WETH": "0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f",
"curveRegistry": ""
}
},
"43114": {
"chainId": "43114",
"label": "Avalanche Network",
"shortName": "Avax",
"code": "avax",
"baseCurrencyName": "AVAX",
"rpc": "https://api.avax.network/ext/bc/C/rpc/",
"explorer": "https://snowtrace.io/",
"contracts": {
"WETH": "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7",
"curveRegistry": ""
}
},
"8453": {
"chainId": "8453",
"label": "Base",
"shortName": "BASE",
"code": "base",
"baseCurrencyName": "ETH",
"rpc": "https://mainnet.base.org/",
"explorer": "https://basescan.org/",
"contracts": {
"WETH": "0x4200000000000000000000000000000000000006",
"curveRegistry": ""
}
}
}

View File

@@ -165,6 +165,42 @@
"http": "/orion-indexer/"
}
}
},
"43114": {
"api": "https://trade.orion.xyz/avalanche-c-chain",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"8453": {
"api": "https://trade.orion.xyz/base-mainnet",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
}
}
},
@@ -191,8 +227,8 @@
},
"liquidityMigratorAddress": "0x01b10dds12478C88A5E18e2707E729906bC25CfF6"
},
"5": {
"api": "https://testing.orion.xyz/eth-goerli",
"11155111": {
"api": "https://testing.orion.xyz/eth-sepolia",
"services": {
"aggregator": {
"http": "/backend",
@@ -209,8 +245,8 @@
}
}
},
"421613": {
"api": "https://testing.orion.xyz/arbitrum-goerli",
"123420000034": {
"api": "https://testing.orion.xyz/event-horizon-testnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -227,44 +263,8 @@
}
}
},
"4002": {
"api": "https://testing.orion.xyz/ftm-testnet",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"80001": {
"api": "https://testing.orion.xyz/polygon-mumbai",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"56303": {
"api": "https://testing.orion.xyz/drip-testnet",
"1952959480": {
"api": "https://testing.orion.xyz/lumia-testnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -414,7 +414,7 @@
}
},
"2525": {
"api": "https://trade.orion.xyz/inevm-mainnet",
"api": "https://staging.orion.xyz/inevm-mainnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -432,7 +432,43 @@
}
},
"59144": {
"api": "https://trade.orion.xyz/linea-mainnet",
"api": "https://staging.orion.xyz/linea-mainnet",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"43114": {
"api": "https://staging.orion.xyz/avalanche-c-chain",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
},
"8453": {
"api": "https://staging.orion.xyz/base-mainnet",
"services": {
"aggregator": {
"http": "/backend",
@@ -472,24 +508,6 @@
"http": "/orion-indexer/"
}
}
},
"3": {
"api": "https://dn-dev.orion.xyz/eth-ropsten",
"services": {
"aggregator": {
"http": "/backend",
"ws": "/v1"
},
"blockchain": {
"http": ""
},
"priceFeed": {
"all": "/price-feed"
},
"indexer": {
"http": "/orion-indexer/"
}
}
}
}
},

View File

@@ -16,7 +16,7 @@ export const pureEnvNetworksSchema = z.object({
}),
indexer: z.object({
http: z.string(),
}).optional(),
}).optional()
}),
rpc: z.string().optional(),
liquidityMigratorAddress: z.string().optional(),

View File

@@ -2,12 +2,9 @@ import { SupportedChainId } from '../types.js';
export const developmentChains = [
SupportedChainId.BSC_TESTNET,
SupportedChainId.ROPSTEN,
SupportedChainId.GOERLI,
SupportedChainId.ARBITRUM_GOERLI,
SupportedChainId.FANTOM_TESTNET,
SupportedChainId.POLYGON_TESTNET,
SupportedChainId.OKC_TESTNET,
SupportedChainId.SEPOLIA,
SupportedChainId.EVENT_HORIZON_TESTNET,
SupportedChainId.LUMIA_TESTNET,
];
export const productionChains = [
SupportedChainId.MAINNET,
@@ -19,4 +16,6 @@ export const productionChains = [
SupportedChainId.OPBNB,
SupportedChainId.INEVM,
SupportedChainId.LINEA,
SupportedChainId.AVAX,
SupportedChainId.BASE,
];

View File

@@ -1 +1 @@
export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3"] as const
export default ["UniswapV2", "UniswapV3", "Curve", "OrionV2", "OrionV3", "Aero"] as const

View File

@@ -1 +1 @@
export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'drip', 'opbnb', 'inevm', 'linea'] as const;
export default ['ftm', 'bsc', 'eth', 'polygon', 'okc', 'arb', 'opbnb', 'inevm', 'linea', 'avax', 'base', 'lumia'] as const;

View File

@@ -1 +1 @@
export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB', 'OPBNB', 'INEVM', 'LINEA'] as const;
export default ['FTM', 'BSC', 'ETH', 'POLYGON', 'OKC', 'ARB', 'OPBNB', 'INEVM', 'LINEA', 'AVAX', 'BASE', 'LUMIA'] as const;

View File

@@ -19,9 +19,10 @@ import httpToWS from '../../utils/httpToWS.js';
import { ethers } from 'ethers';
import orderSchema from './schemas/orderSchema.js';
import { fetchWithValidation } from 'simple-typed-fetch';
import hmacSHA256 from "crypto-js/hmac-sha256";
import Hex from "crypto-js/enc-hex";
import {pmmOrderSchema} from "../../Unit/Pmm/schemas/order";
import { pmmOrderSchema } from '../../Unit/Pmm/schemas/order';
// import hmacSHA256 from "crypto-js/hmac-sha256";
// import Hex from "crypto-js/enc-hex";
// const crypto = require('crypto')
class Aggregator {
private readonly apiUrl: string;
@@ -34,11 +35,16 @@ class Aggregator {
return this.apiUrl;
}
public logger: ((message: string) => void) | undefined;
constructor(
httpAPIUrl: string,
wsAPIUrl: string,
basicAuth?: BasicAuthCredentials
basicAuth?: BasicAuthCredentials,
logger?: ((message: string) => void) | undefined
) {
this.logger = logger;
// const oaUrl = new URL(apiUrl);
// const oaWsProtocol = oaUrl.protocol === 'https:' ? 'wss' : 'ws';
// const aggregatorWsUrl = `${oaWsProtocol}://${oaUrl.host + (oaUrl.pathname === '/'
@@ -46,7 +52,7 @@ class Aggregator {
// : oaUrl.pathname)}/v1`;
this.apiUrl = httpAPIUrl;
this.ws = new AggregatorWS(httpToWS(wsAPIUrl));
this.ws = new AggregatorWS(httpToWS(wsAPIUrl), undefined, logger);
this.basicAuth = basicAuth;
this.getHistoryAtomicSwaps = this.getHistoryAtomicSwaps.bind(this);
@@ -55,6 +61,7 @@ class Aggregator {
this.getPairsList = this.getPairsList.bind(this);
this.getSwapInfo = this.getSwapInfo.bind(this);
this.getTradeProfits = this.getTradeProfits.bind(this);
this.getStableCoins = this.getStableCoins.bind(this);
this.placeAtomicSwap = this.placeAtomicSwap.bind(this);
this.placeOrder = this.placeOrder.bind(this);
this.cancelOrder = this.cancelOrder.bind(this);
@@ -65,6 +72,7 @@ class Aggregator {
this.getPoolReserves = this.getPoolReserves.bind(this);
this.getVersion = this.getVersion.bind(this);
this.getPrices = this.getPrices.bind(this);
this.getIsCexLiquidityAvailable = this.getIsCexLiquidityAvailable.bind(this);
}
get basicAuthHeaders() {
@@ -255,21 +263,22 @@ class Aggregator {
);
getSwapInfo = (
type: 'exactSpend' | 'exactReceive',
assetIn: string,
assetOut: string,
amount: string,
instantSettlement?: boolean,
exchanges?: string[] | 'cex' | 'pools',
isTradeBuy?: boolean,
) => {
const url = new URL(`${this.apiUrl}/api/v1/swap`);
url.searchParams.append('assetIn', assetIn);
url.searchParams.append('assetOut', assetOut);
if (type === 'exactSpend') {
if (isTradeBuy !== true) {
url.searchParams.append('amountIn', amount);
} else {
url.searchParams.append('amountOut', amount);
}
if (exchanges !== undefined) {
if (Array.isArray(exchanges)) {
exchanges.forEach((exchange) => {
@@ -334,6 +343,16 @@ class Aggregator {
);
};
getStableCoins = () => {
const url = new URL(`${this.apiUrl}/api/v1/tokens/stable/`);
return fetchWithValidation(
url.toString(),
z.array(z.string()),
{ headers: this.basicAuthHeaders },
errorSchema,
);
};
/**
* Placing atomic swap. Placement must take place on the target chain.
* @param secretHash Secret hash
@@ -373,24 +392,38 @@ class Aggregator {
return fetchWithValidation(url.toString(), atomicSwapHistorySchema, { headers: this.basicAuthHeaders });
};
getIsCexLiquidityAvailable = (
assetIn: string,
assetOut: string,
) => {
const url = new URL(`${this.apiUrl}/api/v1/pairs/cex/liquidity/${assetIn}/${assetOut}`);
private encode_utf8(s : string) {
return unescape(encodeURIComponent(s));
return fetchWithValidation(
url.toString(),
z.boolean(),
{ headers: this.basicAuthHeaders },
errorSchema,
);
};
// private encode_utf8(s: string) {
// return unescape(encodeURIComponent(s));
// }
// @ts-expect-error: TODO: please remove this line!
private sign(message: string, key: string) {
// return crypto.createHmac('sha256', this.encode_utf8(key))
// .update(this.encode_utf8(message))
// .digest('hex');
return '';
}
private sign(message : string, key: string) {
return hmacSHA256(
this.encode_utf8(message),
this.encode_utf8(key)
).toString(Hex);
}
private generateHeaders(body : any, method : string, path : string, timestamp : number, apiKey : string, secretKey : string) {
private generateHeaders(body: any, method: string, path: string, timestamp: number, apiKey: string, secretKey: string) {
const sortedBody = Object.keys(body)
.sort()
.map((key) => (
`${key}=${body[key]}`
)).join('&');
.sort()
.map((key) => (
`${key}=${body[key]}`
)).join('&');
const payload = timestamp + method.toUpperCase() + path + sortedBody;
@@ -407,40 +440,38 @@ class Aggregator {
}
public async RFQOrder(
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
) : Promise<z.infer<typeof pmmOrderSchema>> {
tokenFrom: string,
tokenTo: string,
fromTokenAmount: string,
apiKey: string, //
secretKey: string,
wallet: string
): Promise<z.infer<typeof pmmOrderSchema>> {
// Making the order structure
const
path = '/rfq'
, url = `${this.apiUrl}/api/v1/integration/pmm`+path
, headers = {
'Content-Type': 'application/json',
}
, data = {
"baseToken":tokenFrom, // USDT
"quoteToken":tokenTo, // ORN
"amount": fromTokenAmount, // 100
"taker": wallet,
"feeBps": 0
}
, method = 'POST'
, timestamp = Date.now()
, signatureHeaders = this.generateHeaders(data, method, path, timestamp, apiKey, secretKey)
, compiledHeaders = {...headers, ...signatureHeaders.headers, }
, body = JSON.stringify(data)
path = '/rfq';
const url = `${this.apiUrl}/api/v1/integration/pmm` + path;
const headers = {
'Content-Type': 'application/json',
};
const data = {
baseToken: tokenFrom, // USDT
quoteToken: tokenTo, // ORN
amount: fromTokenAmount, // 100
taker: wallet,
feeBps: 0
};
const method = 'POST';
const timestamp = Date.now();
const signatureHeaders = this.generateHeaders(data, method, path, timestamp, apiKey, secretKey);
const compiledHeaders = { ...headers, ...signatureHeaders.headers, };
const body = JSON.stringify(data)
;
let res = pmmOrderSchema.parse({});
const res = pmmOrderSchema.parse({});
try {
const result = await fetch(url,{
const result = await fetch(url, {
headers: compiledHeaders,
method,
body
@@ -449,25 +480,23 @@ class Aggregator {
const json = await result.json();
const parseResult = pmmOrderSchema.safeParse(json);
if(!parseResult.success) {
if (!parseResult.success) {
// Try to parse error answer
const errorSchema = z.object({error: z.object({code: z.number(), reason: z.string()})});
const errorSchema = z.object({ error: z.object({ code: z.number(), reason: z.string() }) });
const errorParseResult = errorSchema.safeParse(json);
if(!errorParseResult.success)
throw Error(`Unrecognized answer from aggregator: ${json}`);
if (!errorParseResult.success) { throw Error(`Unrecognized answer from aggregator: ${json}`); }
throw Error(errorParseResult.data.error.reason);
}
res.quotation = parseResult.data.quotation;
res.order = parseResult.data.order;
res.signature = parseResult.data.signature;
res.error = '';
res.success = true;
// return result;
}
catch(err) {
} catch (err) {
res.error = `${err}`;
}
return res;

View File

@@ -12,6 +12,8 @@ const exchangeContractStep = z.object({
assetIn: z.string(),
assetOut: z.string(),
factory: z.string(),
assetAddressIn: z.string(),
assetAddressOut: z.string(),
});
const swapInfoBase = z.object({
@@ -49,6 +51,7 @@ const swapInfoBase = z.object({
mi: z.number().optional(), // market amount in, USD
d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage
}).optional(),
autoSlippage: z.number().optional(),
});
const swapInfoByAmountIn = swapInfoBase.extend({
@@ -58,7 +61,7 @@ const swapInfoByAmountIn = swapInfoBase.extend({
marketAmountIn: z.null(),
}).transform((val) => ({
...val,
type: 'exactSpend' as const,
isTradeBuy: false as const,
}));
const swapInfoByAmountOut = swapInfoBase.extend({
@@ -68,7 +71,7 @@ const swapInfoByAmountOut = swapInfoBase.extend({
marketAmountIn: z.number().nullable(),
}).transform((val) => ({
...val,
type: 'exactReceive' as const,
isTradeBuy: true as const,
}));
const swapInfoSchema = swapInfoByAmountIn.or(swapInfoByAmountOut);

View File

@@ -66,10 +66,11 @@ type PairConfigSubscription = {
type AggregatedOrderbookSubscription = {
payload: string
dc?: number
callback: (
asks: OrderbookItem[],
bids: OrderbookItem[],
pair: string
pair: string,
) => void
errorCb?: (message: string) => void
}
@@ -195,9 +196,10 @@ class AggregatorWS {
readonly basicAuth?: BasicAuthCredentials | undefined;
constructor(wsUrl: string, basicAuth?: BasicAuthCredentials) {
constructor(wsUrl: string, basicAuth?: BasicAuthCredentials, logger?: ((message: string) => void) | undefined) {
this.wsUrl = wsUrl;
this.basicAuth = basicAuth;
this.logger = logger;
}
private messageQueue: BufferLike[] = [];
@@ -252,7 +254,7 @@ class AggregatorWS {
subscription: Subscription[T],
prevSubscriptionId?: string
) {
const id = type === 'aobus'
const id = type === SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE
? ((subscription as any).payload as string) // TODO: Refactor!!!
: uuidv4();
@@ -262,6 +264,12 @@ class AggregatorWS {
subRequest['T'] = type;
subRequest['id'] = id;
if ('dc' in subscription) {
if (typeof subscription.dc === 'number') {
subRequest['dc'] = subscription.dc;
}
}
if ('payload' in subscription) {
if (typeof subscription.payload === 'string') {
subRequest['S'] = subscription.payload;
@@ -369,11 +377,11 @@ class AggregatorWS {
delete this.subscriptions[SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]?.[newestSubId];
// !!! swap info subscription is uuid that contains hyphen
} else if (isOrderBooksSubscription(newestSubId)) { // is pair name(AGGREGATED_ORDER_BOOK_UPDATE)
const aobSubscriptions = this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE];
if (aobSubscriptions) {
const targetAobSub = Object.entries(aobSubscriptions).find(([, value]) => value?.payload === newestSubId);
if (targetAobSub) {
const [key] = targetAobSub;
const aobusSubscriptions = this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE];
if (aobusSubscriptions) {
const targetAobusSub = Object.entries(aobusSubscriptions).find(([, value]) => value?.payload === newestSubId);
if (targetAobusSub) {
const [key] = targetAobusSub;
delete this.subscriptions[SubscriptionType.AGGREGATED_ORDER_BOOK_UPDATES_SUBSCRIBE]?.[key];
}
}
@@ -506,6 +514,9 @@ class AggregatorWS {
assetIn: path.ai,
assetOut: path.ao,
factory: path.f,
assetAddressIn: path.aai,
assetAddressOut: path.aao,
fee: path.fee,
})),
poolOptimal: json.po,
...(json.oi) && {
@@ -533,21 +544,22 @@ class AggregatorWS {
marketAmountIn: json.usd.mi,
difference: json.usd.d,
},
autoSlippage: json.sl,
};
switch (json.k) { // kind
case 'exactSpend':
switch (json.tb) { // isTradeBuy
case false:
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[json.S]?.callback({
kind: json.k,
isTradeBuy: false,
marketAmountOut: json.mo,
availableAmountIn: json.aa,
...baseSwapInfo,
});
break;
case 'exactReceive':
case true:
this.subscriptions[SubscriptionType.SWAP_SUBSCRIBE]?.[json.S]?.callback({
kind: json.k,
isTradeBuy: true,
...baseSwapInfo,
marketAmountIn: json.mi,
availableAmountOut: json.aao,

View File

@@ -40,6 +40,9 @@ const swapInfoSchemaBase = baseMessageSchema.extend({
ai: z.string().toUpperCase(), // asset in
ao: z.string().toUpperCase(), // asset out
f: factorySchema, // factory
aai: z.string(), // asset address in
aao: z.string(), // asset address out
fee: z.number().optional(), // fee
})),
usd: z.object({ // USD info of this swap, nullable
aa: z.number().optional(), // available amount in, USD
@@ -48,6 +51,7 @@ const swapInfoSchemaBase = baseMessageSchema.extend({
mi: z.number().optional(), // market amount in, USD
d: z.string().optional(), // difference in available amount in/out (USD) and market amount out/in (USD) in percentage
}).optional(),
sl: z.number().optional(),
});
const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
@@ -55,7 +59,7 @@ const swapInfoSchemaByAmountIn = swapInfoSchemaBase.extend({
aa: z.number(), // available amount in
}).transform((content) => ({
...content,
k: 'exactSpend' as const,
tb: false as const, // isTradeBuy
}));
const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({
@@ -63,7 +67,7 @@ const swapInfoSchemaByAmountOut = swapInfoSchemaBase.extend({
aao: z.number(), // available amount out
}).transform((content) => ({
...content,
k: 'exactReceive' as const,
tb: true as const, // isTradeBuy
}));
const swapInfoSchema = z.union([

View File

@@ -114,6 +114,7 @@ class BlockchainService {
this.getRedeemOrderBySecretHash = this.getRedeemOrderBySecretHash.bind(this);
this.claimOrder = this.claimOrder.bind(this);
this.getGasLimits = this.getGasLimits.bind(this);
this.getExchangeContractWalletBalance = this.getExchangeContractWalletBalance.bind(this);
}
get basicAuthHeaders() {
@@ -495,6 +496,12 @@ class BlockchainService {
z.record(z.number()),
{ headers: this.basicAuthHeaders }
);
getExchangeContractWalletBalance = (exchangeContractAddress: string) => fetchWithValidation(
`${this.apiUrl}/api/broker/getWalletBalance/${exchangeContractAddress}`,
z.record(z.string()),
{ headers: this.basicAuthHeaders }
);
}
export * as schemas from './schemas/index.js';

View File

@@ -1,5 +1,5 @@
import { z } from 'zod';
import { makePartial } from '../../../utils/index.js';
import { makePartial } from '../../../utils';
const internalFeeAssetSchema = z.object({
type: z.enum(['percent', 'plain']),
@@ -10,8 +10,10 @@ const internalFeeAssetSchema = z.object({
const infoSchema = z.object({
chainId: z.number(),
chainName: z.string(),
exchangeContractAddress: z.string(),
swapExecutorContractAddress: z.string(),
libValidatorContractAddress: z.string().optional(),
exchangeContractAddress: z.string(),
spvContractAddress: z.string(),
oracleContractAddress: z.string(),
matcherAddress: z.string(),
orderFeePercent: z.number(),

View File

@@ -0,0 +1,13 @@
import { z } from 'zod';
import { SupportedChainId } from '../../../types';
export const tickerSchema = z.object({
pair: z.string(),
volume24: z.number(),
change24: z.number(),
lastPrice: z.number(),
pricePrecision: z.number(),
networks: z.array(z.nativeEnum(SupportedChainId)),
});
export const tickersSchema = z.array(tickerSchema);

View File

@@ -1,5 +1,6 @@
import {
environmentResponseSchema,
getPointsAtResponseSchema,
getPoolResponseSchema,
listAmountResponseSchema,
listNFTOrderResponseSchema,
@@ -51,6 +52,12 @@ type VeORNInfoPayload = BasePayload & {
params: [string]
};
type GetPointsAtPayload = BasePayload & {
model: 'veORN'
method: 'pointsInfo'
params: [number, number]
};
type ListAmountPayload = BasePayload & {
model: string
method: 'listAmount'
@@ -68,6 +75,7 @@ type Payload =
| GetPoolInfoPayload
| ListPoolPayload
| VeORNInfoPayload
| GetPointsAtPayload
| ListAmountPayload
| GetAmountByORNPayload;
@@ -92,6 +100,7 @@ class IndexerService {
this.poolV2Info = this.poolV2Info.bind(this);
this.listPoolV3 = this.listPoolV3.bind(this);
this.veORNInfo = this.veORNInfo.bind(this);
this.getPointsAt = this.getPointsAt.bind(this);
this.listAmount = this.listAmount.bind(this);
this.getAmountByORN = this.getAmountByORN.bind(this);
this.getAmountAt = this.getAmountAt.bind(this);
@@ -118,6 +127,21 @@ class IndexerService {
});
};
/**
* @param {number} page - current page
* @param {number} [pageSize] - amount of items on one page
*/
readonly getPointsAt = (page = 1, pageSize = 1000) => {
return fetchWithValidation(this.apiUrl, getPointsAtResponseSchema, {
method: 'POST',
body: this.makeRPCPayload({
model: 'veORN',
method: 'pointsAt',
params: [page, pageSize],
}),
});
};
/**
* @param {number} amount - amount
* @param {number} [timestamp = Date.now()] - timestamp, defaults to current time

View File

@@ -0,0 +1,15 @@
import { z } from 'zod';
import infoSchema from './info-schema.js';
const getPointsAtResultSchema = z.object({
pointsObject: z.record(z.string(), z.number()),
currentPage: z.number(),
totalElements: z.number(),
});
const getPointsAtSchema = z.object({
result: getPointsAtResultSchema,
info: infoSchema,
}).nullable();
export default getPointsAtSchema;

View File

@@ -9,3 +9,4 @@ export { default as veORNInfoResponseSchema } from './veORN-info-schema';
export { default as listAmountResponseSchema } from './list-amount-schema';
export { default as votingInfoResponseSchema } from './voting-info-schema';
export { default as testIncrementorSchema } from './test-incrementor-schema';
export { default as getPointsAtResponseSchema } from './get-points-at-schema';

View File

@@ -12,12 +12,15 @@ const veORNResultSchema = z.object({
weeklyReward: z.number(),
userAPR: z.number(),
userVeORN: z.number(),
userVeORNBalance: z.number(),
userORNLocked: z.number(),
userLockEndDate: z.number(),
userReward: z.number(),
userWeeklyReward: z.number(),
userMinLockPeriod: z.number(),
});
dropLock: z.boolean().optional(),
pointsReward: z.number().optional(),
}).passthrough();
const veORNInfoSchema = z.object({
result: veORNResultSchema,

View File

@@ -4,3 +4,4 @@ export * as priceFeed from './PriceFeed/index.js';
export * as referralSystem from './ReferralSystem/index.js';
export * as frontage from './Frontage';
export * as indexer from './Indexer/index.js';
export * as frontage from './Frontage/index.js';

View File

@@ -3,8 +3,9 @@ import type factories from './constants/factories.js';
import type { BigNumber } from 'bignumber.js';
import type subOrderStatuses from './constants/subOrderStatuses.js';
import type positionStatuses from './constants/positionStatuses.js';
import type { knownEnvs } from './config/schemas/index.js';
import type { knownEnvs } from './config/schemas';
import type getHistory from './Orion/bridge/getHistory.js';
import type { networkCodes } from './constants';
export type DeepPartial<T> = T extends object ? {
[P in keyof T]?: DeepPartial<T[P]>;
@@ -81,23 +82,21 @@ export type Pair = {
export enum SupportedChainId {
MAINNET = '1',
ROPSTEN = '3',
GOERLI = '5',
ARBITRUM = '42161',
FANTOM_OPERA = '250',
POLYGON = '137',
BSC = '56',
OKC = '66',
POLYGON = '137',
OPBNB = '204',
FANTOM_OPERA = '250',
INEVM = '2525',
BASE = '8453',
ARBITRUM = '42161',
AVAX = '43114',
LINEA = '59144',
POLYGON_TESTNET = '80001',
FANTOM_TESTNET = '4002',
BSC = '56',
BSC_TESTNET = '97',
OKC_TESTNET = '65',
DRIP_TESTNET = '56303',
ARBITRUM_GOERLI = '421613',
SEPOLIA = '11155111',
EVENT_HORIZON_TESTNET = '123420000034',
LUMIA_TESTNET = '1952959480',
// For testing and debug purpose
// BROKEN = '0',
@@ -175,6 +174,9 @@ export type SingleSwap = {
assetIn: string
assetOut: string
factory: Factory
assetAddressIn: string
assetAddressOut: string
fee?: number | undefined
}
export type SwapInfoBase = {
@@ -208,16 +210,17 @@ export type SwapInfoBase = {
marketAmountIn: number | undefined
difference: string | undefined
} | undefined
autoSlippage: number | undefined
}
export type SwapInfoByAmountIn = SwapInfoBase & {
kind: 'exactSpend'
isTradeBuy: false
availableAmountIn?: number | undefined
marketAmountOut?: number | undefined
}
export type SwapInfoByAmountOut = SwapInfoBase & {
kind: 'exactReceive'
isTradeBuy: true
marketAmountIn?: number | undefined
availableAmountOut?: number | undefined
}
@@ -283,22 +286,22 @@ export type EnvConfig = {
referralAPI: string
frontageAPI: string
networks: Partial<
Record<
SupportedChainId,
VerboseUnitConfig
Record<
SupportedChainId,
VerboseUnitConfig
>
>
>
}
export type AggregatedAssets = Partial<
Record<
string,
Partial<
Record<SupportedChainId, {
address: string
}>
Record<
string,
Partial<
Record<SupportedChainId, {
address: string
}>
>
>
>
>;
>;
export type RedeemOrder = {
sender: string
@@ -438,9 +441,9 @@ type BridgeHistory = Awaited<ReturnType<typeof getHistory>>;
type BridgeHistoryItem = NonNullable<BridgeHistory[string]>;
export type AtomicSwap = Partial<
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
Omit<BridgeHistoryItem, 'creationDate' | 'expiration' | 'secret'>
> & Partial<
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
Omit<AtomicSwapLocal, 'creationDate' | 'expiration' | 'secret'>
> & {
sourceChainId: SupportedChainId
targetChainId: SupportedChainId
@@ -459,3 +462,21 @@ export type AtomicSwap = Partial<
}
export type OrderSource = 'TERMINAL_MARKET' | 'TERMINAL_LIMIT' | 'SWAP_UI' | 'WIDGET';
// Frontage
export type NetworkCode = typeof networkCodes[number];
export type TickersCategories = 'FAVORITES' | 'USD' | 'ORN' | 'NATIVE' | 'ALTS';
export type TickersSortBy = 'PRICE' | 'CHANGE' | 'VOLUME';
export type TickersSortType = 'ASCENDING' | 'DESCENDING';
export type TickersBaseSearchParams = {
currentNetwork?: NetworkCode
targetNetwork?: NetworkCode
sortBy?: TickersSortBy
sortType?: TickersSortType
offset?: number
limit?: number
}

View File

@@ -195,5 +195,5 @@ export async function getTotalBalance(
walletBalance,
exchangeBalance,
totalBalance: walletBalance + exchangeBalance
}
}
}

View File

@@ -8,7 +8,6 @@ const swapThroughOrionPoolSchema = z.object({
z.bigint(), // amount_spend
z.bigint(), // amount_receive
z.string().refine(ethers.isAddress).array().nonempty(), // path
z.boolean(), // is_exact_spend
]),
}).transform((data) => ({
name: data.name,
@@ -16,7 +15,6 @@ const swapThroughOrionPoolSchema = z.object({
amount_spend: data.args[0],
amount_receive: data.args[1],
path: data.args[2],
is_exact_spend: data.args[3],
},
}));