diff --git a/examples/liquidation.ts b/examples/liquidation.ts index b1e9091..cf1f403 100644 --- a/examples/liquidation.ts +++ b/examples/liquidation.ts @@ -2,7 +2,7 @@ import 'dotenv/config'; import { ethers } from 'ethers'; import { simpleFetch } from 'simple-typed-fetch'; -import { CrossMarginCFD__factory, ERC20__factory } from '@electra.finance/contracts/lib/ethers-v5/index.js'; +import { CrossMarginCFD__factory, ERC20__factory } from '@electra.finance/contracts/lib/ethers-v6/index.js'; import type { FuturesTradeInfo } from '../src/index.js'; import { Electra, SupportedChainId, crypt } from '../src/index.js'; @@ -393,10 +393,10 @@ const testLiquidation = async (instrumentName: string) => { const allowance = await collateralContract.allowance(walletAddress, address); const depositAmount = DEPOSIT_AMOUNT + (depositsCount * 10); - const bnAmount = ethers.utils.parseUnits(depositAmount.toString(), decimals); + const bnAmount = ethers.parseUnits(depositAmount.toString(), decimals); if (allowance.lt(bnAmount)) { console.log(`${instrumentName}: Approving ${depositAmount} ${collateralAddress} to ${address}`); - await collateralContract.approve(address, ethers.constants.MaxUint256); // Sometimes before approve you need to call approve(0) + await collateralContract.approve(address, ethers.MaxUint256); // Sometimes before approve you need to call approve(0) await delay(2000); } @@ -450,7 +450,7 @@ const testLiquidation = async (instrumentName: string) => { console.log(`${instrumentName}: Depositing ${dpAmount} ${collateralAddress} to ${address}`); await crossMarginCFDContract.depositAsset( - ethers.utils.parseUnits( + ethers.parseUnits( dpAmount.toString(), decimals ) diff --git a/package-lock.json b/package-lock.json index a194a9a..efbfe51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@electra.finance/sdk", - "version": "0.1.56", + "version": "0.2.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@electra.finance/sdk", - "version": "0.1.56", + "version": "0.2.13", "license": "ISC", "dependencies": { "@babel/runtime": "^7.21.0", @@ -16,7 +16,7 @@ "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", - "ethers": "^5.6.2", + "ethers": "^6.7.1", "express": "^4.18.2", "isomorphic-ws": "^5.0.0", "just-clone": "^6.2.0", @@ -66,6 +66,11 @@ "node": ">=18.0.0" } }, + "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==" + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -713,32 +718,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, "node_modules/@ethersproject/abstract-provider": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", @@ -900,33 +879,6 @@ "@ethersproject/bignumber": "^5.7.0" } }, - "node_modules/@ethersproject/contracts": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", - "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0" - } - }, "node_modules/@ethersproject/hash": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", @@ -953,65 +905,6 @@ "@ethersproject/strings": "^5.7.0" } }, - "node_modules/@ethersproject/hdnode": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", - "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "node_modules/@ethersproject/json-wallets": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", - "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, "node_modules/@ethersproject/keccak256": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", @@ -1064,25 +957,6 @@ "@ethersproject/logger": "^5.7.0" } }, - "node_modules/@ethersproject/pbkdf2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", - "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/sha2": "^5.7.0" - } - }, "node_modules/@ethersproject/properties": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", @@ -1239,29 +1113,6 @@ "hash.js": "1.1.7" } }, - "node_modules/@ethersproject/solidity": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", - "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, "node_modules/@ethersproject/strings": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", @@ -1308,58 +1159,6 @@ "@ethersproject/signing-key": "^5.7.0" } }, - "node_modules/@ethersproject/units": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", - "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/wallet": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", - "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/json-wallets": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, "node_modules/@ethersproject/web": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", @@ -1382,28 +1181,6 @@ "@ethersproject/strings": "^5.7.0" } }, - "node_modules/@ethersproject/wordlists": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", - "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.8", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", @@ -2244,6 +2021,28 @@ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", "dev": true }, + "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/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/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3113,9 +2912,9 @@ } }, "node_modules/aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==" + "version": "4.0.0-beta.5", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", + "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" }, "node_modules/ajv": { "version": "6.12.6", @@ -5208,13 +5007,13 @@ } }, "node_modules/ethers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", - "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.7.1.tgz", + "integrity": "sha512-qX5kxIFMfg1i+epfgb0xF4WM7IqapIIu50pOJ17aebkxxa4BacW5jFrQRmCJpDEg2ZK2oNtR5QjrQ1WDBF29dA==", "funding": [ { "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + "url": "https://github.com/sponsors/ethers-io/" }, { "type": "individual", @@ -5222,36 +5021,46 @@ } ], "dependencies": { - "@ethersproject/abi": "5.7.0", - "@ethersproject/abstract-provider": "5.7.0", - "@ethersproject/abstract-signer": "5.7.0", - "@ethersproject/address": "5.7.0", - "@ethersproject/base64": "5.7.0", - "@ethersproject/basex": "5.7.0", - "@ethersproject/bignumber": "5.7.0", - "@ethersproject/bytes": "5.7.0", - "@ethersproject/constants": "5.7.0", - "@ethersproject/contracts": "5.7.0", - "@ethersproject/hash": "5.7.0", - "@ethersproject/hdnode": "5.7.0", - "@ethersproject/json-wallets": "5.7.0", - "@ethersproject/keccak256": "5.7.0", - "@ethersproject/logger": "5.7.0", - "@ethersproject/networks": "5.7.1", - "@ethersproject/pbkdf2": "5.7.0", - "@ethersproject/properties": "5.7.0", - "@ethersproject/providers": "5.7.2", - "@ethersproject/random": "5.7.0", - "@ethersproject/rlp": "5.7.0", - "@ethersproject/sha2": "5.7.0", - "@ethersproject/signing-key": "5.7.0", - "@ethersproject/solidity": "5.7.0", - "@ethersproject/strings": "5.7.0", - "@ethersproject/transactions": "5.7.0", - "@ethersproject/units": "5.7.0", - "@ethersproject/wallet": "5.7.0", - "@ethersproject/web": "5.7.1", - "@ethersproject/wordlists": "5.7.0" + "@adraffy/ens-normalize": "1.9.2", + "@noble/hashes": "1.1.2", + "@noble/secp256k1": "1.7.1", + "@types/node": "18.15.13", + "aes-js": "4.0.0-beta.5", + "tslib": "2.4.0", + "ws": "8.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/ethers/node_modules/@types/node": { + "version": "18.15.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", + "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==" + }, + "node_modules/ethers/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==" + }, + "node_modules/ethers/node_modules/ws": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/events": { @@ -9738,11 +9547,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==" - }, "node_modules/semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", diff --git a/package.json b/package.json index a0ab816..8c93236 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@electra.finance/sdk", - "version": "0.1.57-rc0", + "version": "0.1.57-rc1", "description": "Electra finance SDK", "main": "./lib/index.cjs", "module": "./lib/index.js", @@ -88,7 +88,7 @@ "bignumber.js": "^9.1.1", "bson-objectid": "^2.0.4", "buffer": "^6.0.3", - "ethers": "^5.6.2", + "ethers": "^6.7.1", "express": "^4.18.2", "isomorphic-ws": "^5.0.0", "just-clone": "^6.2.0", diff --git a/src/BalanceGuard.ts b/src/BalanceGuard.ts index b99e615..34ac434 100644 --- a/src/BalanceGuard.ts +++ b/src/BalanceGuard.ts @@ -1,7 +1,7 @@ import { BigNumber } from 'bignumber.js'; import { ethers } from 'ethers'; import clone from 'just-clone'; -import { ERC20__factory } from '@electra.finance/contracts/lib/ethers-v5/index.js'; +import { ERC20__factory } from '@electra.finance/contracts/lib/ethers-v6'; import { APPROVE_ERC20_GAS_LIMIT, NATIVE_CURRENCY_PRECISION } from './constants/index.js'; import type { AggregatedBalanceRequirement, ApproveFix, Asset, BalanceIssue, BalanceRequirement, Source, @@ -24,7 +24,7 @@ export default class BalanceGuard { private readonly nativeCryptocurrency: Asset; - private readonly provider: ethers.providers.Provider; + private readonly provider: ethers.Provider; private readonly signer: ethers.Signer; @@ -33,7 +33,7 @@ export default class BalanceGuard { constructor( balances: Partial>>, nativeCryptocurrency: Asset, - provider: ethers.providers.Provider, + provider: ethers.Provider, signer: ethers.Signer, logger?: (message: string) => void, ) { @@ -62,10 +62,10 @@ export default class BalanceGuard { const walletAddress = await this.signer.getAddress(); const tokenContract = ERC20__factory .connect(assetAddress, this.provider); - const unsignedTx = await tokenContract.populateTransaction - .approve( + const unsignedTx = await tokenContract.approve + .populateTransaction( spenderAddress, - ethers.constants.MaxUint256, + ethers.MaxUint256, ); unsignedTx.from = walletAddress; let resetRequired = false; @@ -117,21 +117,23 @@ export default class BalanceGuard { const approve = async ({ spenderAddress, targetAmount }: ApproveFix) => { const bnTargetAmount = new BigNumber(targetAmount); const unsignedApproveTx = await tokenContract - .populateTransaction - .approve( + .approve + .populateTransaction( spenderAddress, bnTargetAmount.isZero() ? '0' // Reset - : ethers.constants.MaxUint256, // Infinite approve + : ethers.MaxUint256, // Infinite approve ); const walletAddress = await this.signer.getAddress(); const nonce = await this.provider.getTransactionCount(walletAddress, 'pending'); - const gasPrice = await this.provider.getGasPrice(); + const { gasPrice } = await this.provider.getFeeData(); const network = await this.provider.getNetwork(); unsignedApproveTx.chainId = network.chainId; - unsignedApproveTx.gasPrice = gasPrice; + if (gasPrice != null) { + unsignedApproveTx.gasPrice = gasPrice; + } unsignedApproveTx.nonce = nonce; unsignedApproveTx.from = walletAddress; const gasLimit = await this.provider.estimateGas(unsignedApproveTx); @@ -139,7 +141,7 @@ export default class BalanceGuard { this.logger?.('Approve transaction signing...'); const signedTx = await this.signer.signTransaction(unsignedApproveTx); - const txResponse = await this.provider.sendTransaction(signedTx); + const txResponse = await this.provider.broadcastTransaction(signedTx); this.logger?.(`${issue.asset.name} approve transaction sent ${txResponse.hash}. Waiting for confirmation...`); await txResponse.wait(); this.logger?.(`${issue.asset.name} approve transaction confirmed.`); @@ -230,7 +232,7 @@ export default class BalanceGuard { const lackAmount = remainingBalance.exchange.abs(); // e.g. -435.234234 to 434.234234 let denormalizedAllowance: BigNumber; - if (asset.address === ethers.constants.AddressZero) { + if (asset.address === ethers.ZeroAddress) { denormalizedAllowance = remainingBalance.wallet; } else { if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`); @@ -272,13 +274,11 @@ export default class BalanceGuard { asset.address, spenderAddress, ); - const gasPriceWei = await this.provider.getGasPrice(); - const approveTransactionCost = ethers.BigNumber - .from(APPROVE_ERC20_GAS_LIMIT) - .mul(gasPriceWei); + const { gasPrice: gasPriceWei } = await this.provider.getFeeData(); + const approveTransactionCost = BigInt(APPROVE_ERC20_GAS_LIMIT) * (gasPriceWei ?? 0n); const denormalizedApproveTransactionCost = denormalizeNumber( approveTransactionCost, - NATIVE_CURRENCY_PRECISION + BigInt(NATIVE_CURRENCY_PRECISION) ); requiredApproves.items = { @@ -329,7 +329,7 @@ export default class BalanceGuard { .reduce((p, c) => (c !== undefined ? p.plus(c) : p), new BigNumber(0)); let denormalizedAllowance: BigNumber; - if (asset.address === ethers.constants.AddressZero) { + if (asset.address === ethers.ZeroAddress) { denormalizedAllowance = remainingBalance.wallet; } else { if (spenderAddress === undefined) throw new Error(`Spender address is required for ${asset.name}`); @@ -365,13 +365,11 @@ export default class BalanceGuard { asset.address, spenderAddress, ); - const gasPriceWei = await this.provider.getGasPrice(); - const approveTransactionCost = ethers.BigNumber - .from(APPROVE_ERC20_GAS_LIMIT) - .mul(gasPriceWei); + const { gasPrice: gasPriceWei } = await this.provider.getFeeData(); + const approveTransactionCost = BigInt(APPROVE_ERC20_GAS_LIMIT) * (gasPriceWei ?? 0n); const denormalizedApproveTransactionCost = denormalizeNumber( approveTransactionCost, - NATIVE_CURRENCY_PRECISION + BigInt(NATIVE_CURRENCY_PRECISION) ); requiredApproves.items = { diff --git a/src/Unit/index.ts b/src/Unit/index.ts index c0158bd..f4f8642 100644 --- a/src/Unit/index.ts +++ b/src/Unit/index.ts @@ -21,7 +21,7 @@ export default class Unit { public readonly chainId: SupportedChainId; - public readonly provider: ethers.providers.StaticJsonRpcProvider; + public readonly provider: ethers.JsonRpcProvider; public readonly blockchainService: BlockchainService; @@ -69,7 +69,7 @@ export default class Unit { this.baseCurrencyName = chainInfo.baseCurrencyName; const intNetwork = parseInt(this.chainId, 10); if (Number.isNaN(intNetwork)) throw new Error('Invalid chainId (not a number)' + this.chainId); - this.provider = new ethers.providers.StaticJsonRpcProvider(this.config.nodeJsonRpc, intNetwork); + this.provider = new ethers.JsonRpcProvider(this.config.nodeJsonRpc, intNetwork); this.provider.pollingInterval = 1000; this.blockchainService = new BlockchainService( @@ -157,10 +157,10 @@ export default class Unit { if (symbolPrice === undefined) throw new Error(`Price for ${symbol} not found`); const gasPriceWei = await simpleFetch(this.blockchainService.getGasPriceWei)(); - const gasPriceGwei = ethers.utils.formatUnits(gasPriceWei, 'gwei'); + const gasPriceGwei = ethers.formatUnits(gasPriceWei, 'gwei'); const pricesWithQuoteAsset = await simpleFetch(this.blockchainService.getPricesWithQuoteAsset)(); - const baseCurrencyPriceInQuoteAsset = pricesWithQuoteAsset.prices[ethers.constants.AddressZero]; + const baseCurrencyPriceInQuoteAsset = pricesWithQuoteAsset.prices[ethers.ZeroAddress]; if (baseCurrencyPriceInQuoteAsset === undefined) throw new Error(`Base currency ${this.baseCurrencyName} price not found. Available: ${Object.keys(pricesWithQuoteAsset).join(', ')}`); const feeAssetAddress = assetToAddress[feeAssetName]; if (feeAssetAddress === undefined) throw new Error(`Fee asset ${feeAssetName} address not found`); diff --git a/src/__tests__/basic.test.ts b/src/__tests__/basic.test.ts index 1c47a8e..ce1fc04 100644 --- a/src/__tests__/basic.test.ts +++ b/src/__tests__/basic.test.ts @@ -188,7 +188,7 @@ describe('Electra', () => { expect(unit.aggregator.ws.api).toBe(`ws://localhost:${server1.port}/v1`); expect(unit.blockchainService.api).toBe(blockchainServiceAPI); expect(unit.priceFeed.api).toBe(electraPriceFeedAPI + '/price-feed'); - expect(unit.provider.connection.url).toBe('https://cloudflare-eth.com/'); + expect(unit.provider._getConnection().url).toBe('https://cloudflare-eth.com/'); const info = await simpleFetch(unit.blockchainService.getInfo)(); expect(info).toBeDefined(); @@ -237,7 +237,7 @@ describe('Electra', () => { }); const bscUnit = electra.units[SupportedChainId.BSC_TESTNET] - expect(bscUnit?.provider.connection.url).toBe('https://data-seed-prebsc-1-s1.binance.org:8545/'); + expect(bscUnit?.provider._getConnection().url).toBe('https://data-seed-prebsc-1-s1.binance.org:8545/'); }); test('Electra Responses', async () => { diff --git a/src/addressSchema.ts b/src/addressSchema.ts index 982eda1..2eb9262 100644 --- a/src/addressSchema.ts +++ b/src/addressSchema.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import { z } from 'zod'; -const addressSchema = z.string().refine(ethers.utils.isAddress, (value) => ({ +const addressSchema = z.string().refine(ethers.isAddress, (value) => ({ message: `Should be an address, got ${value}`, })); diff --git a/src/config/chains.json b/src/config/chains.json index d0b5719..846b036 100644 --- a/src/config/chains.json +++ b/src/config/chains.json @@ -113,7 +113,7 @@ "shortName": "DRIP Chain", "code": "drip", "baseCurrencyName": "DRIP", - "rpc": "testnet.1d.rip", + "rpc": "https://testnet.1d.rip/", "explorer": "https://explorer-testnet.1d.rip/" }, "2525": { diff --git a/src/config/envs.json b/src/config/envs.json index 07bc24f..1f3d691 100644 --- a/src/config/envs.json +++ b/src/config/envs.json @@ -108,6 +108,42 @@ } } } + }, + "2525": { + "api": "https://cfd-inevm.electra.finance/", + "services": { + "aggregator": { + "http": "/backend", + "ws": "/v1" + }, + "blockchain": { + "http": "" + }, + "priceFeed": { + "all": "/price-feed" + }, + "indexer": { + "http": "/orion-indexer/" + } + } + }, + "59144": { + "api": "https://cfd-linea.electra.finance/", + "services": { + "aggregator": { + "http": "/backend", + "ws": "/v1" + }, + "blockchain": { + "http": "" + }, + "priceFeed": { + "all": "/price-feed" + }, + "indexer": { + "http": "/orion-indexer/" + } + } } }, "testing": { diff --git a/src/constants/positionStatuses.ts b/src/constants/positionStatuses.ts index 09975f3..96512f8 100644 --- a/src/constants/positionStatuses.ts +++ b/src/constants/positionStatuses.ts @@ -1,6 +1,10 @@ -const positionStatuses = [ - 'SHORT', +export const positionSides = [ 'LONG', + 'SHORT' +] as const + +const positionStatuses = [ + ...positionSides, 'CLOSING', 'LIQUIDATION', 'ZERO', diff --git a/src/crypt/hashCrossMarginCFDOrder.ts b/src/crypt/hashCrossMarginCFDOrder.ts index 127801d..c45deef 100644 --- a/src/crypt/hashCrossMarginCFDOrder.ts +++ b/src/crypt/hashCrossMarginCFDOrder.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import type { CrossMarginCFDOrder } from '../types.js'; -const hashCrossMarginCFDOrder = (order: CrossMarginCFDOrder) => ethers.utils.solidityKeccak256( +const hashCrossMarginCFDOrder = (order: CrossMarginCFDOrder) => ethers.solidityPackedKeccak256( [ 'uint8', 'address', diff --git a/src/crypt/hashIsolatedMarginCFDOrder.ts b/src/crypt/hashIsolatedMarginCFDOrder.ts index 801ab00..dd16cdc 100644 --- a/src/crypt/hashIsolatedMarginCFDOrder.ts +++ b/src/crypt/hashIsolatedMarginCFDOrder.ts @@ -1,7 +1,7 @@ import { ethers } from 'ethers'; import type { IsolatedCFDOrder } from '../types.js'; -const hashIsolatedMarginCFDOrder = (order: IsolatedCFDOrder) => ethers.utils.solidityKeccak256( +const hashIsolatedMarginCFDOrder = (order: IsolatedCFDOrder) => ethers.solidityPackedKeccak256( [ 'uint8', 'address', diff --git a/src/crypt/signCancelOrder.ts b/src/crypt/signCancelOrder.ts index 4f274ab..f501beb 100644 --- a/src/crypt/signCancelOrder.ts +++ b/src/crypt/signCancelOrder.ts @@ -1,13 +1,9 @@ -import type { TypedDataSigner } from '@ethersproject/abstract-signer'; -import type { ethers } from 'ethers'; -import { joinSignature, splitSignature } from 'ethers/lib/utils.js'; +import { ethers } from 'ethers'; import CANCEL_ORDER_TYPES from '../constants/cancelOrderTypes.js'; import type { CancelOrderRequest, SignedCancelOrderRequest, SupportedChainId } from '../types.js'; import getDomainData from './getDomainData.js'; import signCancelOrderPersonal from './signCancelOrderPersonal.js'; -type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; - const signCancelOrder = async ( senderAddress: string, id: string, @@ -22,13 +18,11 @@ const signCancelOrder = async ( isPersonalSign: usePersonalSign, isFromDelegate, }; - // 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( + : await signer.signTypedData( getDomainData(chainId), CANCEL_ORDER_TYPES, cancelOrderRequest, @@ -36,7 +30,7 @@ const signCancelOrder = async ( // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 // "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1" - const fixedSignature = joinSignature(splitSignature(signature)); + const fixedSignature = ethers.Signature.from(signature).serialized; // if (!fixedSignature) throw new Error("Can't sign order cancel"); diff --git a/src/crypt/signCancelOrderPersonal.ts b/src/crypt/signCancelOrderPersonal.ts index c53975a..2837c3b 100644 --- a/src/crypt/signCancelOrderPersonal.ts +++ b/src/crypt/signCancelOrderPersonal.ts @@ -1,5 +1,4 @@ import { ethers } from 'ethers'; -import { arrayify, joinSignature, splitSignature } from 'ethers/lib/utils.js'; import type { CancelOrderRequest } from '../types.js'; const signCancelOrderPersonal = async ( @@ -7,14 +6,14 @@ const signCancelOrderPersonal = async ( signer: ethers.Signer, ) => { const types = ['string', 'string', 'address']; - const message = ethers.utils.solidityKeccak256( + const message = ethers.solidityPackedKeccak256( types, ['cancelOrder', cancelOrderRequest.id, cancelOrderRequest.senderAddress], ); - const signature = await signer.signMessage(arrayify(message)); + const signature = await signer.signMessage(ethers.getBytes(message)); // NOTE: metamask broke sig.v value and we fix it in next line - return joinSignature(splitSignature(signature)); + return ethers.Signature.from(signature).serialized; }; export default signCancelOrderPersonal; diff --git a/src/crypt/signCrossMarginCFDOrder.ts b/src/crypt/signCrossMarginCFDOrder.ts index 28be8ad..973790f 100644 --- a/src/crypt/signCrossMarginCFDOrder.ts +++ b/src/crypt/signCrossMarginCFDOrder.ts @@ -1,8 +1,6 @@ -import type { TypedDataSigner } from '@ethersproject/abstract-signer'; import { BigNumber } from 'bignumber.js'; -import type { ethers } from 'ethers'; -import { joinSignature, splitSignature } from 'ethers/lib/utils.js'; -import { DEFAULT_EXPIRATION, INTERNAL_PROTOCOL_PRECISION } from '../constants/index.js'; +import { ethers } from 'ethers'; +import { DEFAULT_EXPIRATION, INTERNAL_PROTOCOL_PRECISION } from '../constants'; import type { CrossMarginCFDOrder, SignedCrossMarginCFDOrder, SupportedChainId } from '../types.js'; import normalizeNumber from '../utils/normalizeNumber.js'; import getDomainData from './getDomainData.js'; @@ -10,8 +8,6 @@ import { CROSS_MARGIN_CFD_ORDER_TYPES } from '../constants/cfdOrderTypes.js'; import signCrossMarginCFDOrderPersonal from './signCrossMarginCFDOrderPersonal.js'; import hashCrossMarginCFDOrder from './hashCrossMarginCFDOrder.js'; -type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; - export const signCrossMarginCFDOrder = async ( instrumentIndex: number, side: 'BUY' | 'SELL', @@ -24,6 +20,7 @@ export const signCrossMarginCFDOrder = async ( signer: ethers.Signer, chainId: SupportedChainId, stopPrice: BigNumber.Value | undefined, + leverage: string | undefined, isFromDelegate?: boolean, ) => { const nonce = Date.now(); @@ -33,35 +30,36 @@ export const signCrossMarginCFDOrder = async ( senderAddress, matcherAddress, instrumentIndex, - amount: normalizeNumber( + amount: Number(normalizeNumber( amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR, - ).toNumber(), - price: normalizeNumber( + )), + price: Number(normalizeNumber( price, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR, - ).toNumber(), - matcherFee: normalizeNumber( + )), + matcherFee: Number(normalizeNumber( matcherFee, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL, // ROUND_CEIL because we don't want get "not enough fee" error - ).toNumber(), + )), expiration, buySide: side === 'BUY' ? 1 : 0, stopPrice: stopPrice !== undefined ? new BigNumber(stopPrice).toNumber() : undefined, + leverage: leverage !== undefined + ? leverage + : undefined, isPersonalSign: usePersonalSign, isFromDelegate, }; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const typedDataSigner = signer as SignerWithTypedDataSign; const signature = usePersonalSign ? await signCrossMarginCFDOrderPersonal(order, signer) - : await typedDataSigner._signTypedData( + : await signer.signTypedData( getDomainData(chainId), CROSS_MARGIN_CFD_ORDER_TYPES, order, @@ -69,7 +67,7 @@ export const signCrossMarginCFDOrder = async ( // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 // "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1" - const fixedSignature = joinSignature(splitSignature(signature)); + const fixedSignature = ethers.Signature.from(signature).serialized; // if (!fixedSignature) throw new Error("Can't sign order"); diff --git a/src/crypt/signCrossMarginCFDOrderPersonal.ts b/src/crypt/signCrossMarginCFDOrderPersonal.ts index f6750b5..05306bc 100644 --- a/src/crypt/signCrossMarginCFDOrderPersonal.ts +++ b/src/crypt/signCrossMarginCFDOrderPersonal.ts @@ -1,10 +1,8 @@ import { ethers } from 'ethers'; import type { CrossMarginCFDOrder } from '../types.js'; -const { arrayify, joinSignature, splitSignature } = ethers.utils; - const signCrossMarginCFDOrderPersonal = async (order: CrossMarginCFDOrder, signer: ethers.Signer) => { - const message = ethers.utils.solidityKeccak256( + const message = ethers.solidityPackedKeccak256( [ 'string', 'address', 'address', 'uint16', 'uint96', 'uint80', 'uint64', 'uint64', 'uint8', ], @@ -20,10 +18,10 @@ const signCrossMarginCFDOrderPersonal = async (order: CrossMarginCFDOrder, signe order.buySide, ], ); - const signature = await signer.signMessage(arrayify(message)); + const signature = await signer.signMessage(ethers.getBytes(message)); // NOTE: metamask broke sig.v value and we fix it in next line - return joinSignature(splitSignature(signature)); + return ethers.Signature.from(signature).serialized; }; export default signCrossMarginCFDOrderPersonal; diff --git a/src/crypt/signIsolatedMarginCFDOrder.ts b/src/crypt/signIsolatedMarginCFDOrder.ts index fcc87d6..a19078e 100644 --- a/src/crypt/signIsolatedMarginCFDOrder.ts +++ b/src/crypt/signIsolatedMarginCFDOrder.ts @@ -1,7 +1,5 @@ -import type { TypedDataSigner } from '@ethersproject/abstract-signer'; import { BigNumber } from 'bignumber.js'; -import type { ethers } from 'ethers'; -import { joinSignature, splitSignature } from 'ethers/lib/utils.js'; +import { ethers } from 'ethers'; import { DEFAULT_EXPIRATION, INTERNAL_PROTOCOL_PRECISION } from '../constants/index.js'; import type { IsolatedCFDOrder, @@ -14,8 +12,6 @@ import signIsolatedMarginCFDOrderPersonal from './signIsolatedMarginCFDOrderPers import hashIsolatedMarginCFDOrder from './hashIsolatedMarginCFDOrder.js'; import { ISOLATED_MARGIN_CFD_ORDER_TYPES } from '../constants/cfdOrderTypes.js'; -type SignerWithTypedDataSign = ethers.Signer & TypedDataSigner; - export const signIsolatedMarginCFDOrder = async ( instrumentAddress: string, side: 'BUY' | 'SELL', @@ -37,21 +33,21 @@ export const signIsolatedMarginCFDOrder = async ( senderAddress, matcherAddress, instrumentAddress, - amount: normalizeNumber( + amount: Number(normalizeNumber( amount, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR, - ).toNumber(), - price: normalizeNumber( + )), + price: Number(normalizeNumber( price, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_FLOOR, - ).toNumber(), - matcherFee: normalizeNumber( + )), + matcherFee: Number(normalizeNumber( matcherFee, INTERNAL_PROTOCOL_PRECISION, BigNumber.ROUND_CEIL, // ROUND_CEIL because we don't want get "not enough fee" error - ).toNumber(), + )), nonce, expiration, buySide: side === 'BUY' ? 1 : 0, @@ -62,11 +58,9 @@ export const signIsolatedMarginCFDOrder = async ( isFromDelegate, }; - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const typedDataSigner = signer as SignerWithTypedDataSign; const signature = usePersonalSign ? await signIsolatedMarginCFDOrderPersonal(order, signer) - : await typedDataSigner._signTypedData( + : await signer.signTypedData( getDomainData(chainId), ISOLATED_MARGIN_CFD_ORDER_TYPES, order, @@ -74,7 +68,7 @@ export const signIsolatedMarginCFDOrder = async ( // https://github.com/poap-xyz/poap-fun/pull/62#issue-928290265 // "Signature's v was always send as 27 or 28, but from Ledger was 0 or 1" - const fixedSignature = joinSignature(splitSignature(signature)); + const fixedSignature = ethers.Signature.from(signature).serialized; // if (!fixedSignature) throw new Error("Can't sign order"); diff --git a/src/crypt/signIsolatedMarginCFDOrderPersonal.ts b/src/crypt/signIsolatedMarginCFDOrderPersonal.ts index ce7ca7c..4e53a56 100644 --- a/src/crypt/signIsolatedMarginCFDOrderPersonal.ts +++ b/src/crypt/signIsolatedMarginCFDOrderPersonal.ts @@ -1,10 +1,8 @@ import { ethers } from 'ethers'; import type { IsolatedCFDOrder } from '../types.js'; -const { arrayify, joinSignature, splitSignature } = ethers.utils; - const signIsolatedMarginCFDOrderPersonal = async (order: IsolatedCFDOrder, signer: ethers.Signer) => { - const message = ethers.utils.solidityKeccak256( + const message = ethers.solidityPackedKeccak256( [ 'string', 'address', 'address', 'address', 'uint64', 'uint64', 'uint64', 'uint64', 'uint64', 'uint8', ], @@ -21,10 +19,10 @@ const signIsolatedMarginCFDOrderPersonal = async (order: IsolatedCFDOrder, signe order.buySide, ], ); - const signature = await signer.signMessage(arrayify(message)); + const signature = await signer.signMessage(ethers.getBytes(message)); // NOTE: metamask broke sig.v value and we fix it in next line - return joinSignature(splitSignature(signature)); + return ethers.Signature.from(signature).serialized; }; export default signIsolatedMarginCFDOrderPersonal; diff --git a/src/services/Aggregator/index.ts b/src/services/Aggregator/index.ts index 618162f..973831e 100644 --- a/src/services/Aggregator/index.ts +++ b/src/services/Aggregator/index.ts @@ -2,7 +2,7 @@ import { z } from 'zod'; import exchangeInfoSchema from './schemas/exchangeInfoSchema.js'; import cancelOrderSchema from './schemas/cancelOrderSchema.js'; import errorSchema from './schemas/errorSchema.js'; -import { AggregatorWS } from './ws/index.js'; +import { AggregatorWS } from './ws'; import type { BasicAuthCredentials, IsolatedCFDOrder, SignedCancelOrderRequest, SignedCrossMarginCFDOrder, SignedOrder } from '../../types.js'; @@ -56,13 +56,13 @@ class Aggregator { } getOrder = (orderId: string, owner?: string) => { - if (!ethers.utils.isHexString(orderId)) { + if (!ethers.isHexString(orderId)) { throw new Error(`Invalid order id: ${orderId}. Must be a hex string`); } const url = new URL(`${this.apiUrl}/api/v1/order`); url.searchParams.append('orderId', orderId); if (owner !== undefined) { - if (!ethers.utils.isAddress(owner)) { + if (!ethers.isAddress(owner)) { throw new Error(`Invalid owner address: ${owner}`); } url.searchParams.append('owner', owner); @@ -272,6 +272,7 @@ class Aggregator { ); }; } + export * as schemas from './schemas/index.js'; export * as ws from './ws/index.js'; export { Aggregator }; diff --git a/src/services/Aggregator/schemas/orderSchema.ts b/src/services/Aggregator/schemas/orderSchema.ts index 681c64e..a087308 100644 --- a/src/services/Aggregator/schemas/orderSchema.ts +++ b/src/services/Aggregator/schemas/orderSchema.ts @@ -3,22 +3,22 @@ import { z } from 'zod'; import { orderStatuses, subOrderStatuses } from '../../../constants/index.js'; const blockchainOrderSchema = z.object({ - id: z.string().refine(ethers.utils.isHexString, (value) => ({ + id: z.string().refine(ethers.isHexString, (value) => ({ message: `blockchainOrder.id must be a hex string, got ${value}`, })), - senderAddress: z.string().refine(ethers.utils.isAddress, (value) => ({ + senderAddress: z.string().refine(ethers.isAddress, (value) => ({ message: `blockchainOrder.senderAddress must be an address, got ${value}`, })), - matcherAddress: z.string().refine(ethers.utils.isAddress, (value) => ({ + matcherAddress: z.string().refine(ethers.isAddress, (value) => ({ message: `blockchainOrder.matcherAddress must be an address, got ${value}`, })), - baseAsset: z.string().refine(ethers.utils.isAddress, (value) => ({ + baseAsset: z.string().refine(ethers.isAddress, (value) => ({ message: `blockchainOrder.baseAsset must be an address, got ${value}`, })), - quoteAsset: z.string().refine(ethers.utils.isAddress, (value) => ({ + quoteAsset: z.string().refine(ethers.isAddress, (value) => ({ message: `blockchainOrder.quoteAsset must be an address, got ${value}`, })), - matcherFeeAsset: z.string().refine(ethers.utils.isAddress, (value) => ({ + matcherFeeAsset: z.string().refine(ethers.isAddress, (value) => ({ message: `blockchainOrder.matcherFeeAsset must be an address, got ${value}`, })), amount: z.number().int().nonnegative(), @@ -27,7 +27,7 @@ const blockchainOrderSchema = z.object({ nonce: z.number(), expiration: z.number(), buySide: z.union([z.literal(1), z.literal(0)]), - signature: z.string().refine(ethers.utils.isHexString, (value) => ({ + signature: z.string().refine(ethers.isHexString, (value) => ({ message: `blockchainOrder.signature must be a hex string, got ${value}`, })).nullable(), isPersonalSign: z.boolean(), @@ -53,7 +53,7 @@ const baseOrderSchema = z.object({ amount: z.number().nonnegative(), remainingAmount: z.number().nonnegative(), price: z.number().nonnegative(), - sender: z.string().refine(ethers.utils.isAddress, (value) => ({ + sender: z.string().refine(ethers.isAddress, (value) => ({ message: `order.sender must be an address, got ${value}`, })), filledAmount: z.number().nonnegative(), @@ -61,13 +61,13 @@ const baseOrderSchema = z.object({ }) const brokerAddressSchema = z.enum(['INTERNAL_BROKER', 'SELF_BROKER']) - .or(z.string().refine(ethers.utils.isAddress, (value) => ({ + .or(z.string().refine(ethers.isAddress, (value) => ({ message: `subOrder.subOrders.[n].brokerAddress must be an address, got ${value}`, }))); const subOrderSchema = baseOrderSchema.extend({ price: z.number(), id: z.number(), - parentOrderId: z.string().refine(ethers.utils.isHexString, (value) => ({ + parentOrderId: z.string().refine(ethers.isHexString, (value) => ({ message: `subOrder.parentOrderId must be a hex string, got ${value}`, })), brokerAddress: brokerAddressSchema, @@ -80,11 +80,11 @@ const subOrderSchema = baseOrderSchema.extend({ }); const orderSchema = z.object({ - orderId: z.string().refine(ethers.utils.isHexString, (value) => ({ + orderId: z.string().refine(ethers.isHexString, (value) => ({ message: `orderId must be a hex string, got ${value}`, })), order: baseOrderSchema.extend({ - id: z.string().refine(ethers.utils.isHexString, (value) => ({ + id: z.string().refine(ethers.isHexString, (value) => ({ message: `order.id must be a hex string, got ${value}`, })), fee: z.number().nonnegative(), diff --git a/src/services/Aggregator/ws/MessageType.ts b/src/services/Aggregator/ws/MessageType.ts index 13c3342..00f32d9 100644 --- a/src/services/Aggregator/ws/MessageType.ts +++ b/src/services/Aggregator/ws/MessageType.ts @@ -10,6 +10,7 @@ const MessageType = { CFD_ADDRESS_UPDATE: 'auf', ISOLATED_CFD_ADDRESS_UPDATE: 'iauf', FUTURES_TRADE_INFO_UPDATE: 'fti', + FUTURES_TRADES_STREAM_UPDATE: 'ftsu', UNSUBSCRIPTION_DONE: 'ud', } as const; diff --git a/src/services/Aggregator/ws/SubscriptionType.ts b/src/services/Aggregator/ws/SubscriptionType.ts index d487211..d81bba4 100644 --- a/src/services/Aggregator/ws/SubscriptionType.ts +++ b/src/services/Aggregator/ws/SubscriptionType.ts @@ -7,6 +7,7 @@ const SubscriptionType = { CFD_ADDRESS_UPDATES_SUBSCRIBE: 'ausf', ISOLATED_CFD_ADDRESS_UPDATES_SUBSCRIBE: 'iausf', FUTURES_TRADE_INFO_SUBSCRIBE: 'fts', + FUTURES_TRADES_STREAM_SUBSCRIBE: 'ftss' } as const; export default SubscriptionType; diff --git a/src/services/Aggregator/ws/UnsubscriptionType.ts b/src/services/Aggregator/ws/UnsubscriptionType.ts index 5657a9c..8128679 100644 --- a/src/services/Aggregator/ws/UnsubscriptionType.ts +++ b/src/services/Aggregator/ws/UnsubscriptionType.ts @@ -1,4 +1,5 @@ const UnsubscriptionType = { ASSET_PAIRS_CONFIG_UPDATES_UNSUBSCRIBE: 'apcu', + FUTURES_TRADES_STREAM_UNSUBSCRIBE: 'ftsu' } as const; export default UnsubscriptionType; diff --git a/src/services/Aggregator/ws/index.ts b/src/services/Aggregator/ws/index.ts index 470dd50..f8959ef 100644 --- a/src/services/Aggregator/ws/index.ts +++ b/src/services/Aggregator/ws/index.ts @@ -6,12 +6,12 @@ import { pingPongMessageSchema, initMessageSchema, errorSchema, orderBookSchema, assetPairsConfigSchema, addressUpdateSchema, - isolatedAddressUpdateSchema + isolatedAddressUpdateSchema, futuresTradesStreamSchema } from './schemas/index.js'; import UnsubscriptionType from './UnsubscriptionType.js'; import type { AssetPairUpdate, OrderbookItem, Balance, CFDBalance, - FuturesTradeInfo, Json, BasicAuthCredentials, IsolatedCFDBalance, + FuturesTradeInfo, Json, BasicAuthCredentials, IsolatedCFDBalance, FuturesTradesStream, } from '../../../types.js'; import unsubscriptionDoneSchema from './schemas/unsubscriptionDoneSchema.js'; import assetPairConfigSchema from './schemas/assetPairConfigSchema.js'; @@ -42,6 +42,7 @@ const messageSchema = z.union([ assetPairConfigSchema, orderBookSchema, futuresTradeInfoSchema, + futuresTradesStreamSchema, errorSchema, unsubscriptionDoneSchema, ]); @@ -88,6 +89,10 @@ type FuturesTradeInfoSubscription = { errorCb?: (message: string) => void } +type FuturesTradesStreamSubscription = { + callback: (futuresTrades: FuturesTradesStream) => void +} + type IsolatedAddressUpdateUpdate = { kind: 'update' balances: Partial< @@ -187,6 +192,7 @@ type Subscription = { [SubscriptionType.ASSET_PAIRS_CONFIG_UPDATES_SUBSCRIBE]: PairsConfigSubscription [SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]: PairConfigSubscription [SubscriptionType.FUTURES_TRADE_INFO_SUBSCRIBE]: FuturesTradeInfoSubscription + [SubscriptionType.FUTURES_TRADES_STREAM_SUBSCRIBE]: FuturesTradesStreamSubscription } const exclusiveSubscriptions = [ @@ -203,13 +209,17 @@ const nonExistentMessageRegex = /Could not cancel nonexistent subscription: (.*) // resolve: () => void // }; -const FUTURES_SUFFIX = 'USDF'; +const FUTURES_SUFFIX = 'USD'; export type AggregatorWsEvents = { open: WebsocketTransportEvents['open'] close: WebsocketTransportEvents['close'] } +function isAllUpperCase(str: string) { + return str === str.toUpperCase(); +} + class AggregatorWS { private transport?: WebsocketTransport | undefined; @@ -276,6 +286,7 @@ class AggregatorWS { } private hearbeatIntervalId: NodeJS.Timer | undefined; + private setupHeartbeat() { const heartbeat = () => { if (this.isAlive) { @@ -314,7 +325,7 @@ class AggregatorWS { if ('payload' in subscription) { if (typeof subscription.payload === 'string') { subRequest['S'] = subscription.payload; - } else { // SwapInfoSubscriptionPayload | FuturesTradeInfoPayload + } else { // SwapInfoSubscriptionPayload | FuturesTradeInfoPayload | FuturesTradesStreamSubscription subRequest['S'] = { d: id, ...subscription.payload, @@ -399,9 +410,7 @@ class AggregatorWS { }); const isOrderBooksSubscription = (subId: string) => { - const isSpotPairName = subId.includes('-') && subId.split('-').length === 2; - const isFuturesPairName = subId.endsWith(FUTURES_SUFFIX); - return isSpotPairName || isFuturesPairName; + return subId.endsWith(FUTURES_SUFFIX) && !subId.includes('-') && isAllUpperCase(subId); } if (newestSubId.includes('0x')) { // is wallet address (ADDRESS_UPDATE) @@ -426,6 +435,7 @@ class AggregatorWS { // is swap info subscription (contains hyphen) delete this.subscriptions[SubscriptionType.ASSET_PAIR_CONFIG_UPDATES_SUBSCRIBE]?.[newestSubId]; delete this.subscriptions[SubscriptionType.FUTURES_TRADE_INFO_SUBSCRIBE]?.[newestSubId]; + delete this.subscriptions[SubscriptionType.FUTURES_TRADES_STREAM_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]; @@ -576,6 +586,24 @@ class AggregatorWS { minAmount: json.ma, }); break; + case MessageType.FUTURES_TRADES_STREAM_UPDATE: + this.subscriptions[SubscriptionType.FUTURES_TRADES_STREAM_SUBSCRIBE]?.[json.id]?.callback( + { + timestamp: json._, + sender: json.S, + id: json.id, + instrument: json.i, + side: json.s, + amount: json.a, + leverage: json.l, + price: json.p, + txHash: json.h, + network: json.n, + realizedPnL: json.rpnl, + roi: json.r, + } + ); + break; case MessageType.INITIALIZATION: this.onInit?.(); break; diff --git a/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts b/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts index 715e5a5..02615c6 100644 --- a/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts +++ b/src/services/Aggregator/ws/schemas/addressUpdateSchema.ts @@ -52,6 +52,8 @@ export const orderUpdateSchema = z.object({ t: z.number(), // update time E: z.enum(executionTypes).optional(), // execution type C: z.string().optional(), // trigger condition + lv: z.number().optional(), // leverage + roi: z.number().optional(), // ROI% rpnl: z.number().optional(), // realized PnL sltp: z.enum(['STOP_LOSS', 'TAKE_PROFIT']).optional(), // side c: subOrderSchema.array(), // sub orders (content) @@ -67,6 +69,8 @@ export const orderUpdateSchema = z.object({ liquidated: o.l, executionType: o.E, triggerCondition: o.C, + leverage: o.lv, + roi: o.roi, realizedPnL: o.rpnl, sltp: o.sltp, subOrders: getTransformedSubOrders(o.c), @@ -90,6 +94,9 @@ export const fullOrderSchema = z.object({ ro: z.boolean().optional(), // reversed order T: z.number(), // creation time / unix timestamp t: z.number(), // update time + lv: z.number().optional(), // leverage + roi: z.number().optional(), // ROI% + ep: z.number().optional(), // entry price c: subOrderSchema.array(), // sub orders (content) // CFD only @@ -120,6 +127,9 @@ export const fullOrderSchema = z.object({ stopPrice: o.L, liquidated: o.l, realizedPnL: o.rpnl, + leverage: o.lv, + roi: o.roi, + entryPrice: o.ep, subOrders: getTransformedSubOrders(o.c), })); diff --git a/src/services/Aggregator/ws/schemas/futuresTradesStreamSchema.ts b/src/services/Aggregator/ws/schemas/futuresTradesStreamSchema.ts new file mode 100644 index 0000000..bb0968d --- /dev/null +++ b/src/services/Aggregator/ws/schemas/futuresTradesStreamSchema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; +import MessageType from '../MessageType.js'; +import { positionSides } from '../../../../constants/positionStatuses'; +import { networkCodes } from '../../../../constants'; +// import addressSchema from '../../../../addressSchema'; + +export const futuresTradesStreamSchema = z.object({ + T: z.literal(MessageType.FUTURES_TRADES_STREAM_UPDATE), // futures trades stream update + _: z.number(), // timestamp + S: z.string(), // sender // TODO: change to addressSchema + id: z.string(), // request id + i: z.string(), // instrument + s: z.enum(positionSides), // trade side (LONG/SHORT) + a: z.string(), // trade amount + l: z.string(), // leverage + p: z.string(), // price + h: z.string(), // transaction hash + n: z.enum(networkCodes), // network + rpnl: z.string().optional(), // realized PnL, optional + r: z.string().optional(), // ROI in %, optional +}); diff --git a/src/services/Aggregator/ws/schemas/index.ts b/src/services/Aggregator/ws/schemas/index.ts index cbe3fad..64cf23b 100644 --- a/src/services/Aggregator/ws/schemas/index.ts +++ b/src/services/Aggregator/ws/schemas/index.ts @@ -8,5 +8,6 @@ export { default as pingPongMessageSchema } from './pingPongMessageSchema.js'; export { default as balancesSchema } from './balancesSchema.js'; export { default as cfdBalancesSchema } from './cfdBalancesSchema.js'; export { default as isolatedCFDBalancesSchema } from './isolatedCFDBalancesSchema.js'; +export { futuresTradesStreamSchema } from './futuresTradesStreamSchema'; export * from './orderBookSchema.js'; diff --git a/src/services/PriceFeed/index.ts b/src/services/PriceFeed/index.ts index b0fcd5b..2c4a4fb 100644 --- a/src/services/PriceFeed/index.ts +++ b/src/services/PriceFeed/index.ts @@ -1,6 +1,6 @@ import { fetchWithValidation } from 'simple-typed-fetch'; import type { BasicAuthCredentials } from '../../types.js'; -import candlesSchema from './schemas/candlesSchema.js'; +import { allTickersSchema, candlesSchema, leaderboardSchema, statisticsOverviewSchema, topPairsSchema, winnersSchema } from './schemas/'; import { PriceFeedWS } from './ws/index.js'; class PriceFeed { @@ -23,6 +23,11 @@ class PriceFeed { this.basicAuth = basicAuth; this.getCandles = this.getCandles.bind(this); + this.getAllTickers = this.getAllTickers.bind(this); + this.getLeaderboard = this.getLeaderboard.bind(this); + this.getStatisticsOverview = this.getStatisticsOverview.bind(this); + this.getTopPairs = this.getTopPairs.bind(this); + this.getWinners = this.getWinners.bind(this); } get basicAuthHeaders() { @@ -55,6 +60,46 @@ class PriceFeed { ); }; + getAllTickers = () => { + return fetchWithValidation( + `${this.tickersUrl}/all`, + allTickersSchema, + { headers: this.basicAuthHeaders } + ); + } + + getLeaderboard = () => { + return fetchWithValidation( + `${this.statisticsUrl}/futures/addresses`, + leaderboardSchema, + { headers: this.basicAuthHeaders } + ); + } + + getStatisticsOverview = () => { + return fetchWithValidation( + `${this.statisticsUrl}/overview?exchange=ALL`, + statisticsOverviewSchema, + { headers: this.basicAuthHeaders } + ); + } + + getTopPairs = () => { + return fetchWithValidation( + `${this.statisticsUrl}/top-pairs?exchange=ALL`, + topPairsSchema, + { headers: this.basicAuthHeaders } + ); + } + + getWinners = () => { + return fetchWithValidation( + `${this.statisticsUrl}/futures/winners`, + winnersSchema, + { headers: this.basicAuthHeaders } + ); + } + get wsUrl() { const url = new URL(this.apiUrl); const wsProtocol = url.protocol === 'https:' ? 'wss' : 'ws'; @@ -68,6 +113,10 @@ class PriceFeed { get statisticsUrl() { return `${this.apiUrl}/api/v1/statistics`; } + + get tickersUrl() { + return `${this.apiUrl}/api/v1/ticker`; + } } export * as schemas from './schemas/index.js'; diff --git a/src/services/PriceFeed/schemas/allTickersSchema.ts b/src/services/PriceFeed/schemas/allTickersSchema.ts new file mode 100644 index 0000000..5e8039d --- /dev/null +++ b/src/services/PriceFeed/schemas/allTickersSchema.ts @@ -0,0 +1,10 @@ +import { z } from 'zod'; + +export const allTickersSchema = z + .object({ + pair: z.string(), + volume24: z.number(), + change24: z.number(), + lastPrice: z.number(), + }) + .array(); diff --git a/src/services/PriceFeed/schemas/index.ts b/src/services/PriceFeed/schemas/index.ts index aecacd5..6bd2660 100644 --- a/src/services/PriceFeed/schemas/index.ts +++ b/src/services/PriceFeed/schemas/index.ts @@ -1 +1,6 @@ export { default as candlesSchema } from './candlesSchema.js'; +export { allTickersSchema } from './allTickersSchema.js'; +export { leaderboardSchema } from './leaderboardSchema.js' +export { statisticsOverviewSchema } from './statisticsOverviewSchema.js' +export { topPairsSchema } from './topPairsSchema.js'; +export { winnersSchema } from './winnersSchema.js' diff --git a/src/services/PriceFeed/schemas/leaderboardSchema.ts b/src/services/PriceFeed/schemas/leaderboardSchema.ts new file mode 100644 index 0000000..4e619b9 --- /dev/null +++ b/src/services/PriceFeed/schemas/leaderboardSchema.ts @@ -0,0 +1,13 @@ +import { z } from 'zod'; + +export const leaderboardSchema = z + .object({ + address: z.string(), + totalVolume: z.number(), + todayVolume: z.number(), + totalPnl: z.number(), + todayPnl: z.number(), + totalRoi: z.number(), + todayRoi: z.number(), + }) + .array(); diff --git a/src/services/PriceFeed/schemas/statisticsOverviewSchema.ts b/src/services/PriceFeed/schemas/statisticsOverviewSchema.ts new file mode 100644 index 0000000..51e9c8e --- /dev/null +++ b/src/services/PriceFeed/schemas/statisticsOverviewSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const statisticsOverviewSchema = z + .object({ + time: z.number(), + statisticsOverview: z.object({ + volume24h: z.number(), + volume7d: z.number(), + volumeAllTime: z.number() + }) + }); diff --git a/src/services/PriceFeed/schemas/topPairsSchema.ts b/src/services/PriceFeed/schemas/topPairsSchema.ts new file mode 100644 index 0000000..9f7a4d5 --- /dev/null +++ b/src/services/PriceFeed/schemas/topPairsSchema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +export const topPairsSchema = z.object({ + time: z.number(), + topPairs: z.array( + z.object({ + assetPair: z.string(), + statisticsOverview: z.object({ + volume24h: z.number(), + volume7d: z.number() + }) + }) + ) +}); diff --git a/src/services/PriceFeed/schemas/winnersSchema.ts b/src/services/PriceFeed/schemas/winnersSchema.ts new file mode 100644 index 0000000..f75657b --- /dev/null +++ b/src/services/PriceFeed/schemas/winnersSchema.ts @@ -0,0 +1,20 @@ +import { z } from 'zod'; + +export const winnersSchema = z.object({ + bestTrade: z.object({ + address: z.string(), + instrument: z.string(), + leverage: z.number(), + pnl: z.number(), + side: z.string() + }), + worstTrade: z.object({ + address: z.string(), + instrument: z.string(), + leverage: z.number(), + pnl: z.number(), + side: z.string() + }), + bestPnl: z.object({ address: z.string(), pnl: z.number() }), + worstPnl: z.object({ address: z.string(), pnl: z.number() }) +}); diff --git a/src/services/ReferralSystem/index.ts b/src/services/ReferralSystem/index.ts index 445d461..9385b8a 100644 --- a/src/services/ReferralSystem/index.ts +++ b/src/services/ReferralSystem/index.ts @@ -1,37 +1,24 @@ import { fetchWithValidation } from 'simple-typed-fetch'; import { errorSchema, - miniStatsSchema, - rewardsMappingSchema, - distinctAnalyticsSchema, - globalAnalyticsSchema, - rewardsClaimedSchema, linkSchema, - ratingSchema, - claimInfoSchema, aggregatedHistorySchema, + leaderboardSchema, + accountDetailsSchema, + accountReferralsSchema, } from './schemas/index.js'; import type { SupportedChainId } from '../../types.js'; -import contractsAddressesSchema from './schemas/contractsAddressesSchema.js'; -type CreateLinkPayloadType = { - referer: string - link_option: number -}; - -type ClaimRewardsPayload = { - reward_recipient: string - chain_id: number -}; +export type { AccountDetails, AccountReferrals, Leaderboard } from './schemas'; type SubscribePayloadType = { ref_target: string referral: string }; -type SignatureType = { - signature: string -}; +type AddressType = { + address: string +} class ReferralSystem { private readonly apiUrl: string; @@ -43,108 +30,13 @@ class ReferralSystem { constructor(apiUrl: string) { this.apiUrl = apiUrl; - this.getLink = this.getLink.bind(this); - this.getDistinctAnalytics = this.getDistinctAnalytics.bind(this); - this.createReferralLink = this.createReferralLink.bind(this); this.subscribeToReferral = this.subscribeToReferral.bind(this); - this.getMyReferral = this.getMyReferral.bind(this); - this.getGlobalAnalytics = this.getGlobalAnalytics.bind(this); - this.getMiniStats = this.getMiniStats.bind(this); - this.getRewardsMapping = this.getRewardsMapping.bind(this); - this.claimRewards = this.claimRewards.bind(this); - this.getRating = this.getRating.bind(this); - this.getRating = this.getRating.bind(this); - this.getContractsAddresses = this.getContractsAddresses.bind(this); - this.getClaimInfo = this.getClaimInfo.bind(this); this.getAggregatedHistory = this.getAggregatedHistory.bind(this); + this.getLeaderboard = this.getLeaderboard.bind(this); + this.getAccountDetails = this.getAccountDetails.bind(this); + this.getAccountReferrals = this.getAccountReferrals.bind(this); } - getLink = (refererAddress: string) => - fetchWithValidation(`${this.apiUrl}/referer/view/link`, linkSchema, { - headers: { - 'referer-address': refererAddress, - }, - }); - - getMyReferral = (myWalletAddress: string) => - fetchWithValidation(`${this.apiUrl}/referral/view/link`, linkSchema, { - headers: { - referral: myWalletAddress, - }, - }); - - getDistinctAnalytics = (refererAddress: string) => - fetchWithValidation( - `${this.apiUrl}/referer/view/distinct-analytics`, - distinctAnalyticsSchema, - { - headers: { - 'referer-address': refererAddress, - }, - }, - errorSchema - ); - - getGlobalAnalytics = () => - fetchWithValidation( - `${this.apiUrl}/referer/view/global-analytics`, - globalAnalyticsSchema - ); - - /** - * @param refererAddress Address without 0x prefix - */ - getMiniStats = (refererAddress: string) => - fetchWithValidation( - `${this.apiUrl}/referer/view/mini-latest-stats`, - miniStatsSchema, - { - headers: { - 'referer-address': refererAddress, - }, - }, - errorSchema - ); - - getRewardsMapping = ( - referralAddress: string, - page = 1, - positionsPerPage = 10 - ) => - fetchWithValidation( - `${this.apiUrl}/referer/view/rewards-mapping?n_per_page=${positionsPerPage}&page=${page}`, - rewardsMappingSchema, - { - headers: { - referral: referralAddress, - }, - } - ); - - claimRewards = (payload: ClaimRewardsPayload, signature: SignatureType) => - fetchWithValidation( - `${this.apiUrl}/referer/governance/claim-rewards`, - rewardsClaimedSchema, - { - headers: { - 'Content-type': 'application/json', - }, - method: 'POST', - body: JSON.stringify({ payload, signature }), - } - ); - - createReferralLink = ( - payload: CreateLinkPayloadType - ) => - fetchWithValidation(`${this.apiUrl}/referer/create2`, linkSchema, { - headers: { - 'Content-type': 'application/json', - }, - method: 'POST', - body: JSON.stringify(payload), - }); - subscribeToReferral = ( payload: SubscribePayloadType ) => @@ -161,36 +53,6 @@ class ReferralSystem { errorSchema ); - getRating = (refererAddress: string | undefined, chainId: SupportedChainId) => - fetchWithValidation( - `${this.apiUrl}/referer/ve/rating-table-leaderboard?chain_id=${chainId}`, - ratingSchema, - { - headers: refererAddress !== undefined ? { 'referer-address': refererAddress } : {}, - }, - errorSchema - ); - - getContractsAddresses = () => - fetchWithValidation( - `${this.apiUrl}/referer/view/contracts`, - contractsAddressesSchema, - undefined, - errorSchema - ); - - getClaimInfo = (refererAddress: string) => - fetchWithValidation( - `${this.apiUrl}/referer/view/claim-info-with-stats?&suppress_error=1`, - claimInfoSchema, - { - headers: { - 'referer-address': refererAddress, - }, - }, - errorSchema - ); - getAggregatedHistory = ( refererAddress: string, chainId: SupportedChainId | undefined, @@ -225,6 +87,29 @@ class ReferralSystem { errorSchema ); } + + getLeaderboard = ({ + page = 1 + }: { page: number }) => { + return fetchWithValidation( + `${this.apiUrl}/referer/futures/leaderboard?page=${page}`, + leaderboardSchema + ); + } + + getAccountDetails = ({ address }: AddressType) => { + return fetchWithValidation( + `${this.apiUrl}/referer/futures/account-details?address=${address}`, + accountDetailsSchema, + ); + } + + getAccountReferrals = ({ address, page = 1 }: AddressType & { page: number }) => { + return fetchWithValidation( + `${this.apiUrl}/referer/futures/account-referrals?address=${address}&page=${page}`, + accountReferralsSchema, + ); + } } export * as schemas from './schemas/index.js'; diff --git a/src/services/ReferralSystem/schemas/accountDetailsSchema.ts b/src/services/ReferralSystem/schemas/accountDetailsSchema.ts new file mode 100644 index 0000000..66725ff --- /dev/null +++ b/src/services/ReferralSystem/schemas/accountDetailsSchema.ts @@ -0,0 +1,21 @@ +import { z } from 'zod'; + +const pointsAdditionalSchema = z.object({ + event_name: z.string(), + points: z.number(), +}); + +export const accountDetailsSchema = z.object({ + address: z.string(), + ref_link: z.string(), + rank: z.number(), + referrals_count: z.number(), + direct_referrals_count: z.number(), + points_total: z.number(), + points_trading_volume: z.number(), + points_network_trading_volume: z.number(), + network_trading_volume: z.number(), + points_additional: z.array(pointsAdditionalSchema), +}); + +export type AccountDetails = z.infer; diff --git a/src/services/ReferralSystem/schemas/accountReferralsSchema.ts b/src/services/ReferralSystem/schemas/accountReferralsSchema.ts new file mode 100644 index 0000000..226374c --- /dev/null +++ b/src/services/ReferralSystem/schemas/accountReferralsSchema.ts @@ -0,0 +1,16 @@ +import { z } from 'zod'; +import { paginationInfoSchema } from './paginationInfoSchema'; + +const dataSchema = z.object({ + referral: z.string(), + relative_level: z.number(), + volume: z.number(), + points: z.number() +}) + +export const accountReferralsSchema = z.object({ + data: z.array(dataSchema), + pagination_info: paginationInfoSchema +}) + +export type AccountReferrals = z.infer; diff --git a/src/services/ReferralSystem/schemas/aggregatedHistorySchema.ts b/src/services/ReferralSystem/schemas/aggregatedHistorySchema.ts index 9c4f5c3..8591bea 100644 --- a/src/services/ReferralSystem/schemas/aggregatedHistorySchema.ts +++ b/src/services/ReferralSystem/schemas/aggregatedHistorySchema.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; const aggregatedHistorySchema = z.object({ data: z.array(z.object({ history_type: z.string(), + bonus_description: z.string().nullable(), chain_type: z.string(), chain_comp: z.string(), chain_id: z.number(), diff --git a/src/services/ReferralSystem/schemas/claimInfoSchema.ts b/src/services/ReferralSystem/schemas/claimInfoSchema.ts deleted file mode 100644 index 43fbd40..0000000 --- a/src/services/ReferralSystem/schemas/claimInfoSchema.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from 'zod'; - -const claimInfoSchema = z.object({ - global: z.object({ - total_non_accrued: z.number(), - total_non_accrued_token: z.number(), - total_non_accrued_usd: z.number() - }), - chain_to_reward_info: z.record( - z.string(), - z.object({ - total_accrued: z.number(), - total_accrued_token: z.number(), - total_accrued_usd: z.number(), - total_non_accrued: z.number(), - total_non_accrued_token: z.number(), - total_non_accrued_usd: z.number(), - total_earned: z.number() - }) - ), - mini_stats: z.object({ - earned_on_referrals_token: z.number(), - earned_on_referrals_usd: z.number(), - token_usd: z.number(), - registered_via_link_count: z.number(), - earned_in_a_week_token: z.number(), - earned_in_a_week_usd: z.number() - }), -}); - -export default claimInfoSchema; diff --git a/src/services/ReferralSystem/schemas/contractsAddressesSchema.ts b/src/services/ReferralSystem/schemas/contractsAddressesSchema.ts deleted file mode 100644 index caa3a85..0000000 --- a/src/services/ReferralSystem/schemas/contractsAddressesSchema.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { z } from 'zod'; -import { SupportedChainId } from '../../../types.js'; -import { isAddress } from 'ethers/lib/utils.js'; - -const contractsAddressesSchema = z.record( - z.nativeEnum(SupportedChainId), - z.string().refine(isAddress) -); - -export default contractsAddressesSchema; diff --git a/src/services/ReferralSystem/schemas/distinctAnalyticsSchema.ts b/src/services/ReferralSystem/schemas/distinctAnalyticsSchema.ts deleted file mode 100644 index 620aef0..0000000 --- a/src/services/ReferralSystem/schemas/distinctAnalyticsSchema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { z } from 'zod'; - -const distinctAnalyticsSchema = z.object({ - referer: z.string(), - refs_info: z.record( - z.string(), - z.object({ - referral_address: z.string(), - referral_earned_fees: z.number(), - referer_earned_fees: z.number(), - relative_ref_level: z.number(), - reward_record_hash: z.string(), - timestamp: z.number(), - latest_timestamp: z.number(), - latest_block: z.number(), - }), - ), - total_earned: z.number(), - total_sent_to_governance: z.number(), -}); - -export default distinctAnalyticsSchema; diff --git a/src/services/ReferralSystem/schemas/globalAnalyticsSchema.ts b/src/services/ReferralSystem/schemas/globalAnalyticsSchema.ts deleted file mode 100644 index 1c10461..0000000 --- a/src/services/ReferralSystem/schemas/globalAnalyticsSchema.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from 'zod'; - -const globalAnalyticsSchema = z.object({ - ref_to_rewards: z.record(z.string(), z.number()), - total_earned_by_refs: z.number(), - total_sent_to_governance: z.number(), - reward_dist_count_in_general: z.record(z.string(), z.number()), - total_ref_system_actors: z.number(), -}); - -export default globalAnalyticsSchema; diff --git a/src/services/ReferralSystem/schemas/index.ts b/src/services/ReferralSystem/schemas/index.ts index c181860..f1bc007 100644 --- a/src/services/ReferralSystem/schemas/index.ts +++ b/src/services/ReferralSystem/schemas/index.ts @@ -1,11 +1,8 @@ -export { default as linkSchema } from './linkSchema.js'; -export { default as distinctAnalyticsSchema } from './distinctAnalyticsSchema.js'; -export { default as errorSchema } from './errorSchema.js'; -export { default as miniStatsSchema } from './miniStatsSchema.js'; -export { default as rewardsMappingSchema } from './rewardsMappingSchema.js'; -export { default as rewardsClaimedSchema } from './rewardsClaimedSchema.js'; -export { default as globalAnalyticsSchema } from './globalAnalyticsSchema.js'; -export { default as ratingSchema } from './ratingSchema.js'; -export { default as claimInfoSchema } from './claimInfoSchema.js'; -export { default as aggregatedHistorySchema } from './aggregatedHistorySchema.js'; -export { default as contractsAddressesSchema } from './contractsAddressesSchema.js'; +export { default as linkSchema } from './linkSchema'; +export { default as errorSchema } from './errorSchema'; +export { default as aggregatedHistorySchema } from './aggregatedHistorySchema'; +export { + type AccountDetails, accountDetailsSchema +} from './accountDetailsSchema'; +export { type AccountReferrals, accountReferralsSchema } from './accountReferralsSchema'; +export { leaderboardSchema, type Leaderboard } from './leaderboardSchema'; diff --git a/src/services/ReferralSystem/schemas/leaderboardSchema.ts b/src/services/ReferralSystem/schemas/leaderboardSchema.ts new file mode 100644 index 0000000..0bbff81 --- /dev/null +++ b/src/services/ReferralSystem/schemas/leaderboardSchema.ts @@ -0,0 +1,17 @@ +import { z } from 'zod'; +import { paginationInfoSchema } from './paginationInfoSchema'; + +const dataSchema = z.object({ + referral: z.string(), + referrals_count: z.number(), + points_total: z.number(), + points_referrals: z.number(), + points_additional: z.number(), +}); + +export const leaderboardSchema = z.object({ + pagination_info: paginationInfoSchema, + data: z.array(dataSchema), +}); + +export type Leaderboard = z.infer; diff --git a/src/services/ReferralSystem/schemas/miniStatsSchema.ts b/src/services/ReferralSystem/schemas/miniStatsSchema.ts deleted file mode 100644 index cf79838..0000000 --- a/src/services/ReferralSystem/schemas/miniStatsSchema.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { z } from 'zod'; - -const miniStatsSchema = z.object({ - earned_on_referrals_token: z.number(), - earned_on_referrals_usd: z.number(), - token_usd: z.number(), - registered_via_link_count: z.number(), - earned_in_a_week_token: z.number(), - earned_in_a_week_usd: z.number(), -}); - -export default miniStatsSchema; diff --git a/src/services/ReferralSystem/schemas/paginationInfoSchema.ts b/src/services/ReferralSystem/schemas/paginationInfoSchema.ts new file mode 100644 index 0000000..d17695f --- /dev/null +++ b/src/services/ReferralSystem/schemas/paginationInfoSchema.ts @@ -0,0 +1,6 @@ +import { z } from 'zod'; + +export const paginationInfoSchema = z.object({ + c_page: z.number(), + t_pages: z.number(), +}); diff --git a/src/services/ReferralSystem/schemas/ratingSchema.ts b/src/services/ReferralSystem/schemas/ratingSchema.ts deleted file mode 100644 index b67128a..0000000 --- a/src/services/ReferralSystem/schemas/ratingSchema.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { z } from 'zod'; - -const ratingSchema = z.object({ - info: z.object({ - weekly_boost_budget: z.string(), - weekly_boost_budget_fmt: z.number(), - time_left_for_the_reward: z.number(), - time_left_for_the_reward_local: z.string(), - time_left_for_the_reward_utc: z.string(), - personal_info: z.object({ - rank_id: z.number(), - wallet: z.string(), - staked_ve_token: z.string(), - staked_ve_token_fmt: z.number(), - weighted_volume: z.string(), - weighted_volume_fmt: z.number(), - total_weight: z.string(), - total_weight_fmt: z.number(), - reward: z.string(), - reward_fmt: z.number() - }).nullable(), - }), - list: z.array(z.object({ - rank_id: z.number(), - wallet: z.string(), - staked_ve_token: z.string(), - staked_ve_token_fmt: z.number(), - weighted_volume: z.string(), - weighted_volume_fmt: z.number(), - total_weight: z.string(), - total_weight_fmt: z.number(), - reward: z.string(), - reward_fmt: z.number() - })), -}); - -export default ratingSchema; diff --git a/src/services/ReferralSystem/schemas/rewardsClaimedSchema.ts b/src/services/ReferralSystem/schemas/rewardsClaimedSchema.ts deleted file mode 100644 index ef47199..0000000 --- a/src/services/ReferralSystem/schemas/rewardsClaimedSchema.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from 'zod'; - -const rewardsClaimedSchema = z.object({ - referer: z.string(), - amount: z.string(), - signature: z.string(), -}); - -export default rewardsClaimedSchema; diff --git a/src/services/ReferralSystem/schemas/rewardsMappingSchema.ts b/src/services/ReferralSystem/schemas/rewardsMappingSchema.ts deleted file mode 100644 index 66b3dba..0000000 --- a/src/services/ReferralSystem/schemas/rewardsMappingSchema.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { z } from 'zod'; - -const rewardsMappingSchema = z.object({ - data: z.array( - z.object({ - distribution: z.object({ - dist: z.object({ - underlying_token: z.number(), - referers_list: z.array(z.number()), - }), - address_to_reward_mapping: z.record(z.string(), z.number()), - ref_offset_to_rewarded_actors: z.record(z.string(), z.string()), - governance_reward_only: z.number(), - total_reward: z.number(), - trade_initiator: z.string(), - }), - timestamp_ms: z.number(), - block_height: z.number(), - tx_hash: z.string(), - price_feed_meta_info: z - .record(z.string(), z.record(z.string(), z.number())) - .nullable(), - }) - ), - pagination_info: z.object({ - c_page: z.number().int().nonnegative(), - t_pages: z.number().int().nonnegative(), - }), -}); - -export default rewardsMappingSchema; diff --git a/src/types.ts b/src/types.ts index 9099714..b74d8e4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,7 +2,9 @@ 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 { positionSides } from './constants/positionStatuses.js'; +import type { knownEnvs } from './config/schemas'; +import type { networkCodes } from './constants'; export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; @@ -32,6 +34,7 @@ export type Balance = { allowance: string } +export type PositionSide = typeof positionSides[number]; export type PositionStatus = typeof positionStatuses[number]; type StatesByInstrument = { @@ -118,6 +121,7 @@ type BaseFuturesOrder = { expiration: number // uint64 buySide: 0 | 1 // uint8, 1=buy, 0=sell stopPrice?: number | undefined // uint64 + leverage?: string | undefined // string isPersonalSign: boolean // bool isFromDelegate?: boolean | undefined // bool } @@ -188,6 +192,8 @@ export enum SupportedChainId { // BROKEN = '0', } +export type NetworkShortName = typeof networkCodes[number]; + const balanceTypes = ['exchange', 'wallet'] as const; export type Source = typeof balanceTypes[number]; @@ -251,6 +257,21 @@ export type FuturesTradeInfo = { minAmount: number } +export type FuturesTradesStream = { + timestamp: number + sender: string + id: string + instrument: string + side: PositionSide + amount: string + leverage: string + price: string + txHash: string + network: NetworkShortName + realizedPnL: string | undefined + roi: string | undefined +} + export enum HistoryTransactionStatus { PENDING = 'Pending', DONE = 'Done', diff --git a/src/utils/calculateNetworkFee.ts b/src/utils/calculateNetworkFee.ts index 8f348e9..5a84809 100644 --- a/src/utils/calculateNetworkFee.ts +++ b/src/utils/calculateNetworkFee.ts @@ -8,6 +8,6 @@ export default function calculateNetworkFee( ) { const networkFeeGwei = new BigNumber(gasPriceGwei).multipliedBy(gasLimit); - const bn = new BigNumber(ethers.utils.parseUnits(networkFeeGwei.toString(), 'gwei').toString()); + const bn = new BigNumber(ethers.parseUnits(networkFeeGwei.toString(), 'gwei').toString()); return bn.div(new BigNumber(10).pow(NATIVE_CURRENCY_PRECISION)).toString(); } diff --git a/src/utils/checkIsToken.ts b/src/utils/checkIsToken.ts index e8ce34a..e73e584 100644 --- a/src/utils/checkIsToken.ts +++ b/src/utils/checkIsToken.ts @@ -1,8 +1,8 @@ -import { ERC20__factory } from '@electra.finance/contracts/lib/ethers-v5/index.js'; +import { ERC20__factory } from '@electra.finance/contracts/lib/ethers-v6/index.js'; import { ethers } from 'ethers'; import invariant from 'tiny-invariant'; -const checkIsToken = async (address: string, provider?: ethers.providers.Provider) => { +const checkIsToken = async (address: string, provider?: ethers.Provider) => { invariant(provider, 'No provider for token checking'); const tokenContract = ERC20__factory.connect(address, provider); try { @@ -12,7 +12,7 @@ const checkIsToken = async (address: string, provider?: ethers.providers.Provide tokenContract.symbol(), tokenContract.decimals(), tokenContract.totalSupply(), - tokenContract.balanceOf(ethers.constants.AddressZero), + tokenContract.balanceOf(ethers.ZeroAddress), ], ); diff --git a/src/utils/denormalizeNumber.ts b/src/utils/denormalizeNumber.ts index 6ebffd9..86bce3c 100644 --- a/src/utils/denormalizeNumber.ts +++ b/src/utils/denormalizeNumber.ts @@ -1,5 +1,4 @@ import { BigNumber } from 'bignumber.js'; -import type { ethers } from 'ethers'; /** * Converts normalized blockchain ("machine-readable") number to denormalized ("human-readable") number. @@ -7,8 +6,8 @@ import type { ethers } from 'ethers'; * @param decimals Blockchain asset precision * @returns BigNumber */ -export default function denormalizeNumber(input: ethers.BigNumber, decimals: BigNumber.Value) { - const decimalsBN = new BigNumber(decimals); +export default function denormalizeNumber(input: bigint, decimals: bigint) { + const decimalsBN = new BigNumber(decimals.toString()); if (!decimalsBN.isInteger()) throw new Error(`Decimals '${decimalsBN.toString()}' is not an integer`); return new BigNumber(input.toString()).div(new BigNumber(10).pow(decimalsBN)); } diff --git a/src/utils/generateSecret.ts b/src/utils/generateSecret.ts index d1ff475..7caabca 100644 --- a/src/utils/generateSecret.ts +++ b/src/utils/generateSecret.ts @@ -45,7 +45,7 @@ function isomorphicCryptoRandomBytes(size: number): Uint8Array { const generateSecret = () => { const RANDOM_BITS = 256; const rand = isomorphicCryptoRandomBytes(RANDOM_BITS); - const secret = ethers.utils.keccak256(rand); + const secret = ethers.keccak256(rand); return secret; }; diff --git a/src/utils/getAvailableFundsSources.ts b/src/utils/getAvailableFundsSources.ts index e552576..4b490ec 100644 --- a/src/utils/getAvailableFundsSources.ts +++ b/src/utils/getAvailableFundsSources.ts @@ -8,7 +8,7 @@ export default function getAvailableFundsSources( ): Source[] { switch (route) { case 'aggregator': - if (assetAddress === ethers.constants.AddressZero) return ['exchange']; // We can't take native crypto from wallet + if (assetAddress === ethers.ZeroAddress) return ['exchange']; // We can't take native crypto from wallet return ['exchange', 'wallet']; // We can take any token amount from exchange + wallet. Order is important! case 'pool': if (expenseType === 'network_fee') return ['wallet']; // Network fee is always taken from wallet diff --git a/src/utils/getNativeCryptocurrencyName.ts b/src/utils/getNativeCryptocurrencyName.ts index 67c3fec..fa12251 100644 --- a/src/utils/getNativeCryptocurrencyName.ts +++ b/src/utils/getNativeCryptocurrencyName.ts @@ -11,7 +11,7 @@ const getNativeCryptocurrencyName = (assetToAddress: Partial