Repository: Loopring/loopring_sdk
Branch: master
Commit: 852e9f6a127e
Files: 165
Total size: 1.1 MB
Directory structure:
gitextract_m37m1v1z/
├── .babelrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .gitpod.yml
├── .prettierignore
├── .prettierrc.json
├── Changelog.md
├── LICENSE
├── README.md
├── book.json
├── docs/
│ ├── .bookignore
│ ├── Changelog.md
│ ├── README.md
│ ├── SUMMARY.md
│ ├── about_us.md
│ └── js_sdk/
│ ├── Deposit.md
│ ├── NFTAction/
│ │ ├── collectionNFT.md
│ │ ├── deployNFT.md
│ │ ├── metaNFT.md
│ │ ├── mintNFT.md
│ │ ├── tradeNFT.md
│ │ └── validateNFTOrder.md
│ ├── README.md
│ ├── account/
│ │ ├── activeAccount.md
│ │ ├── fee.md
│ │ ├── historyRecord.md
│ │ ├── signature.md
│ │ ├── wallet_api.md
│ │ └── whitelisted_user_api.md
│ ├── deposit/
│ │ ├── depositERC20.md
│ │ └── depositNFT.md
│ ├── erc20Trade/
│ │ └── orderERC20.md
│ ├── exchange/
│ │ ├── ammpool_api.md
│ │ ├── exchange.md
│ │ └── webSocket.md
│ ├── transfer/
│ │ ├── transferERC20.md
│ │ └── transferNFT.md
│ └── withdraw/
│ ├── withdrawERC20.md
│ └── withdrawNFT.md
├── jest.config.cjs
├── package.json
├── rollup.config.mjs
├── src/
│ ├── api/
│ │ ├── ammpool_api.ts
│ │ ├── base_api.ts
│ │ ├── config/
│ │ │ ├── abis/
│ │ │ │ ├── contractWallet.ts
│ │ │ │ ├── erc1155.ts
│ │ │ │ ├── erc20.ts
│ │ │ │ ├── erc721.ts
│ │ │ │ ├── exchange_3_6.ts
│ │ │ │ ├── hebao.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── smartWallet.ts
│ │ │ ├── guardianTypeData.ts
│ │ │ └── index.ts
│ │ ├── contacts_api.ts
│ │ ├── contract_api.ts
│ │ ├── defi_api.ts
│ │ ├── delegate_api.ts
│ │ ├── ethereum/
│ │ │ └── contracts/
│ │ │ ├── AbiFunction.ts
│ │ │ ├── Contract.ts
│ │ │ ├── Contracts.ts
│ │ │ └── index.ts
│ │ ├── exchange_api.ts
│ │ ├── global_api.ts
│ │ ├── index.ts
│ │ ├── luckToken_api.ts
│ │ ├── nft_api.ts
│ │ ├── rabbitWithdraw_api.ts
│ │ ├── request.ts
│ │ ├── sign/
│ │ │ ├── poseidon/
│ │ │ │ ├── EDDSAUtil.ts
│ │ │ │ ├── TestsEDDSAUtil_test.ts
│ │ │ │ ├── babyJub.ts
│ │ │ │ ├── eddsa.ts
│ │ │ │ ├── eddsa_test.ts
│ │ │ │ ├── field.ts
│ │ │ │ ├── field_test.ts
│ │ │ │ ├── jubjub.ts
│ │ │ │ ├── jubjub_test.ts
│ │ │ │ ├── permutation.ts
│ │ │ │ └── permutation_test.ts
│ │ │ └── sign_tools.ts
│ │ ├── user_api.ts
│ │ ├── vault_api.ts
│ │ ├── wallet_api.ts
│ │ ├── whitelisted_user_api.ts
│ │ └── ws_api.ts
│ ├── defs/
│ │ ├── account_defs.ts
│ │ ├── error_codes.ts
│ │ ├── index.ts
│ │ ├── loopring_constants.ts
│ │ ├── loopring_defs.ts
│ │ ├── loopring_enums.ts
│ │ ├── nft_defs.ts
│ │ ├── url_defs.ts
│ │ ├── web3_defs.ts
│ │ └── ws_defs.ts
│ ├── index.ts
│ ├── tests/
│ │ ├── .eslintrc
│ │ ├── .gitignore
│ │ ├── MockData.ts
│ │ ├── MockSwapData.ts
│ │ ├── README.md
│ │ ├── UTC--2021-02-04T02-55-36.490219109Z--ef439044717c3af35f4f46e52aa99280217a7114
│ │ ├── demo/
│ │ │ ├── NFTAction/
│ │ │ │ ├── collectionNFT.md
│ │ │ │ ├── collectionNFT.test.ts
│ │ │ │ ├── deployNFT.md
│ │ │ │ ├── deployNFT.test.ts
│ │ │ │ ├── metaNFT.md
│ │ │ │ ├── metaNFT.test.ts
│ │ │ │ ├── mintNFT.md
│ │ │ │ ├── mintNFT.test.ts
│ │ │ │ ├── tradeNFT.md
│ │ │ │ ├── tradeNFT.test.ts
│ │ │ │ ├── validateNFTOrder.md
│ │ │ │ └── validateNFTOrder.test.ts
│ │ │ ├── account/
│ │ │ │ ├── account.test.ts
│ │ │ │ ├── activeAccount.md
│ │ │ │ ├── activeAccount.test.ts
│ │ │ │ ├── fee.md
│ │ │ │ ├── fee.test.ts
│ │ │ │ ├── historyRecord.md
│ │ │ │ ├── historyRecord.test.ts
│ │ │ │ ├── signature.md
│ │ │ │ └── signature.test.ts
│ │ │ ├── deposit/
│ │ │ │ ├── deposit.md
│ │ │ │ ├── deposit.test.ts
│ │ │ │ ├── depositNFT.md
│ │ │ │ └── depositNFT.test.ts
│ │ │ ├── erc20Trade/
│ │ │ │ ├── orderERC20.md
│ │ │ │ └── orderERC20.test.ts
│ │ │ ├── exchange/
│ │ │ │ ├── exchange.md
│ │ │ │ ├── exchange.test.ts
│ │ │ │ ├── webSocket.md
│ │ │ │ └── webSocket.test.ts
│ │ │ ├── transfer/
│ │ │ │ ├── transferERC20.md
│ │ │ │ ├── transferERC20.test.ts
│ │ │ │ ├── transferNFT.md
│ │ │ │ └── transferNFT.test.ts
│ │ │ └── withdraw/
│ │ │ ├── withdrawERC20.md
│ │ │ ├── withdrawERC20.test.ts
│ │ │ ├── withdrawNFT.md
│ │ │ └── withdrawNFT.test.ts
│ │ ├── formatter.test.ts
│ │ └── unitTest/
│ │ ├── account/
│ │ │ ├── account.test.ts
│ │ │ └── sign_tools.test.ts
│ │ ├── appWallet/
│ │ │ └── wallet.test.ts
│ │ ├── erc20Trade/
│ │ │ ├── amm.test.ts
│ │ │ ├── amm_calc.test.ts
│ │ │ └── defi.test.ts
│ │ ├── exchange/
│ │ │ └── exchange.test.ts
│ │ ├── transfer/
│ │ │ └── transferUT.test.ts
│ │ └── withdraw/
│ │ ├── forceWithdrawls.test.ts
│ │ └── withdrawUT.test.ts
│ ├── types/
│ │ └── eddsa.d.ts
│ ├── types.d.ts
│ └── utils/
│ ├── formatter.ts
│ ├── index.ts
│ ├── log_tools.ts
│ ├── network_tools.ts
│ ├── obj_tools.ts
│ ├── swap_calc_utils.ts
│ ├── symbol_tools.ts
│ └── window_utils.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .babelrc
================================================
{
"env": {
"esmBundled": {
"presets": [
["@babel/env", {
"targets": "> 0.25%, not dead"
}], "@babel/typescript"]
},
"cjs": {
"presets": [
["@babel/env", {
"modules":"commonjs"
}], "@babel/typescript"]
},
"prod": {
"presets": [
[
"@babel/env",
{
"targets": { "node": "18" },
"useBuiltIns": "usage",
"corejs": 3
}
],
"@babel/typescript"
]
}
},
"plugins": [
"@babel/plugin-transform-optional-chaining",
"@babel/plugin-transform-typescript",
"transform-class-properties"
]
}
================================================
FILE: .eslintignore
================================================
# Ignore artifacts:
build
coverage
_book
dist
node_modules
dist
================================================
FILE: .eslintrc
================================================
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"no-console": 2 ,
// Remember, this means error!
"camelcase": ["warn"]
}
}
================================================
FILE: .gitignore
================================================
yarn-error.log
*.tgz
*testfile*
.idea
_book
dist
node_modules
coverage
./docs
.DS_Store
================================================
FILE: .gitpod.yml
================================================
# This configuration file was automatically generated by Gitpod.
# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
# and commit this file to your remote git repository to share the goodness with others.
tasks:
- init: yarn install && yarn run build
command: yarn run start
================================================
FILE: .prettierignore
================================================
# Ignore artifacts:
build
coverage
_book
dist
node_modules
================================================
FILE: .prettierrc.json
================================================
{}
================================================
FILE: Changelog.md
================================================
# CHANGELOG
## JS SDK Version ChangeLog
#### 3.6.14
1) Guardian approve
#### v3.5.0
1)ecdsaSignature update to `eth_signTypedData_v4` use method `sendAsync`
#### v3.4.15
1)Internal browser contract signarue support
2)referral signature at active account
3)referral reword
4)Explorer Collection related Entry and feature optimization
5)insufficient quota reminder for Dual Invest
6)Add tutorial on Block trade
#### v3.3.16
1)Change Ecrecove function
2)Stop limit
3) Red Packet
4) Block trade
#### v3.3.0
1) GLOBAL information update
#### v3.2.6
1) BlindBox Redpacket
#### v3.2.1
1) LRC staking
2) signature bug fix
#### v3.1.5
1) hardware wallet last digital match / hard-code
generateKeyPair(_, publicKey: { x: string; y: string } | undefined = undefined)
#### v3.1.0
1) dual API update (support for Partial order)
2) red Packet feature
3) amm redeem mini order
#### v3.0.1 - v3.0.8
1) reformate Error throw code states
2) new feature for collection group
3) method split for localStorage
#### v2.0.24
1)collection feature
2)investment feature
#### v2.0.17
1) submitForceWithdrawals
2) return a result hash
3) order/swap demo
#### v2.0.12
1) ipfs cid to nftId startWith 0
2) server-side retturn nftId startWith 0
#### v2.0.11
1) add deposit to
2) get Account by account id
3) add HEBAO_META_TYPE deposit_wallet
#### v2.0.7
1) Fix isMobile
#### v2.0.6
1) mintNFT nftData
#### v2.0.5 (If you use v2 please move to >= v2.0.4 )
1) payPayeeUpdateAccount
2) fix generateApiKey error
#### v2.0.0
1) update poseidon
***
#### v1.10.3
1) Demo update
#### v1.10.1
1) getAllowances formatter update
2) getTokenBalances formatter update
3) unit test update
#### v1.9.6
1) add is Mobile in personalSign
#### v1.9.4
1) remove Authereum & walletLink
2) DEPLOYMENT_STATUS
#### v1.8.13
1)Update web3_defs.ts CREATION_CODE
2)submitDeployNFT transfer.maxFee tokenId: transfer.token.tokenId,
#### v1.8.7
1)getAmmPoolTxs V3
#### v1.8.5
1)validateNftOrder
#### v1.8.1
1)validateNftOrder
2)NFTCounterFactualInfo
#### v1.7.9
1) nftMint NFTCounterFactualInfo
####v1.7.6
1) Doc update,UT update
####v1.7.2
1) NFT ACTION
2) AMM Bug
####v1.6.6
1) NFT MINT IPFS
####v1.6.5
1) The eddsaKey is generated by a keyseed, default format is 'Sign this message to access Loopring
####v1.6.3
1) Error format bug fix {code:number,message:string}
####v1.6.0
1) Error format `{code:number, msg|message: string}`
####v1.5.7
1) 721 NFT URI function
#### v1.5.6
1) submitDeployNFT
#### v1.5.2
1) NFT deposit
#### v1.4.9
1) Counter Factual check
2) updateAccount
#### v1.4.7
1) NFT META
2) HEBAO
#### v1.4.5
1) CF wallet action sign update
#### v1.4.4 (alpha)
1) fix unlock FC wallet
#### v1.4.3 (alpha)
1) bug fix for NFT get contractMeta
2) FC wallet connect signature
3) Error code
#### v1.4.2
1) update activity
#### v1.3.10
1) Add recognizing header for web
#### v1.3.9
1) Update getLatestTokenPrice url
#### v1.3.8
1) SDK update for error type
#### v1.3.4 - v1.3.7 destroy
#### v1.3.3
1) add computeNFTAddress
2) add eslint and prettie for the code format
#### v1.3.2
1) Fix some bug
2) Update withdraw NFT unit test
#### v1.3.0 && v1.3.1 -- !!! revert
formatter Uint8Array support
# !!! revert
================================================
FILE: LICENSE
================================================
Licensor: Loopring Technology Limited
Licensed Work: Loopring-web/loopring-sdk
The Licensed Work is (c) 2022 Loopring Technology Limited
-----------------------------------------------------------------------------
Terms
The Licensor hereby grants you the right to copy, modify, create derivative
works, redistribute, and make limited-production use of the Licensed Work, on condition that Loopring L2 protocol/technology is supported on your platform that this Licensed Work is being used.
If your use of the Licensed Work does not comply with the requirements
currently in effect as described in this License, you must acquire a
full licensing agreement from the Licensor, or its affiliated entities,
or you must refrain from using the Licensed Work.
All copies of the original and modified Licensed Work, and derivative works
of the Licensed Work, are subject to this License. This License applies
separately for each version of the Licensed Work.
You must conspicuously display this License on each original or modified copy
of the Licensed Work. If you receive the Licensed Work in original or
modified form from a third party, the terms and conditions set forth in this
License apply to your use of that work.
Any use of the Licensed Work in violation of this License will automatically
terminate your rights under this License for the current and all other
versions of the Licensed Work.
This License does not grant you any right in any trademark or logo of
Licensor or its affiliates (provided that you may use a trademark or logo of
Licensor as expressly required by this License).
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
TITLE.
-----------------------------------------------------------------------------
================================================
FILE: README.md
================================================
# Loopring SDK
[](https://raw.githubusercontent.com/Loopring/loopring_sdk/master/LICENSE)
[](https://www.npmjs.com/package/react-data-grid)
[](https://discord.com/invite/KkYccYp)
## 🚀 Quick Start
```shell
# Using npm
npm i @loopring-web/loopring-sdk --save
# Using yarn
yarn add @loopring-web/loopring-sdk
```
when using for browser make sure set up `NODE_ENV=production|development`
> Make sure you are using the original npm registry.
> `npm config set registry http://registry.npmjs.org`
## 📒 Documentation
Please see the [documentation page](https://loopring.github.io/loopring_sdk/) for information about getting started and developing with the Loopring SDK.
- [JS SDK](https://loopring.github.io/loopring_sdk)
- [APIs](https://docs-protocol.loopring.io)
## ✨ Changelog
- [Changelog](https://loopring.github.io/loopring_sdk/ChangeLog.html)
## 🫂 Community
- [Loopring Website](https://loopring.org/)
- [Loopring Exchange](https://loopring.io/#/layer2)
- [Loopring Reddit](https://www.reddit.com/r/loopringorg/)
- [Loopring Medium](https://medium.com/loopring-protocol)
- [Loopring Twitter](https://twitter.com/loopringorg)
- [Loopring Telegram](https://t.me/loopring_en)
## 🎒 Getting Started
Please see our [introduction page](https://loopring.github.io/loopring_sdk/) for details on integrating the SDK into your application.
## 🙋 Protocol & Architecture
- [Whitepaper](https://loopring.org/resources/en_whitepaper.pdf)
- [Design Docs](https://github.com/LoopringSecondary/docs/wiki/Loopring3_Design)
## ❓[Help](https://desk.zoho.com/portal/loopring/en/home)
## 👉 [What is Loopring?](https://loopring.org/#/)
## 🔑 Security
- [Wallet](https://security.loopring.io/)
- [Protocol Audit](https://loopring.org/resources/loopring1.0_audit.pdf)
================================================
FILE: book.json
================================================
{
"root": "./docs",
"description": "loopring sdk",
"author": "Loopring Dev Team",
"title": "Loopring SDK",
"gitbook": ">=3.0.0",
"links": {
"sidebar": {
"Home": "https://loopring.io"
}
}
}
================================================
FILE: docs/.bookignore
================================================
docs/detail
docs/README.md
docs/Changelog.mdv
================================================
FILE: docs/Changelog.md
================================================
# CHANGELOG
## JS SDK Version ChangeLog
#### v3.6.0
1) dual Invest autoReinvest
2) guardina singature typeData
3) remove signature 02/03 ended
#### v3.2.1
1) LRC staking
2) signature bug fix
#### v3.1.5
1) hardware wallet last digital match / hard-code
generateKeyPair(_, publicKey: { x: string; y: string } | undefined = undefined)
#### v3.1.0
1) dual API update (support for Partial order)
2) red Packet feature
3) amm redeem mini order
#### v3.0.1 - v3.0.8
1) reformate Error throw code states
2) new feature for collection group
3) method split for localStorage
#### v2.0.24
1)collection feature
2)investment feature
#### v2.0.17
1) submitForceWithdrawals
2) return a result hash
3) order/swap demo
#### v2.0.12
1) ipfs cid to nftId startWith 0
2) server-side retturn nftId startWith 0
#### v2.0.11
1) add deposit to
2) get Account by account id
3) add HEBAO_META_TYPE deposit_wallet
#### v2.0.7
1) Fix isMobile
#### v2.0.6
1) mintNFT nftData
#### v2.0.5 (If you use v2 please move to >= v2.0.4 )
1) payPayeeUpdateAccount
2) fix generateApiKey error
#### v2.0.0
1) update poseidon
***
#### v1.10.3
1) Demo update
#### v1.10.1
1) getAllowances formatter update
2) getTokenBalances formatter update
3) unit test update
#### v1.9.6
1) add is Mobile in personalSign
#### v1.9.4
1) remove Authereum & walletLink
2) DEPLOYMENT_STATUS
#### v1.8.13
1)Update web3_defs.ts CREATION_CODE
2)submitDeployNFT transfer.maxFee tokenId: transfer.token.tokenId,
#### v1.8.7
1)getAmmPoolTxs V3
#### v1.8.5
1)validateNftOrder
#### v1.8.1
1)validateNftOrder
2)NFTCounterFactualInfo
#### v1.7.9
1) nftMint NFTCounterFactualInfo
####v1.7.6
1) Doc update,UT update
####v1.7.2
1) NFT ACTION
2) AMM Bug
####v1.6.6
1) NFT MINT IPFS
####v1.6.5
1) The eddsaKey is generated by a keyseed, default format is 'Sign this message to access Loopring
####v1.6.3
1) Error format bug fix {code:number,message:string}
####v1.6.0
1) Error format `{code:number, msg|message: string}`
####v1.5.7
1) 721 NFT URI function
#### v1.5.6
1) submitDeployNFT
#### v1.5.2
1) NFT deposit
#### v1.4.9
1) Counter Factual check
2) updateAccount
#### v1.4.7
1) NFT META
2) HEBAO
#### v1.4.5
1) CF wallet action sign update
#### v1.4.4 (alpha)
1) fix unlock FC wallet
#### v1.4.3 (alpha)
1) bug fix for NFT get contractMeta
2) FC wallet connect signature
3) Error code
#### v1.4.2
1) update activity
#### v1.3.10
1) Add recognizing header for web
#### v1.3.9
1) Update getLatestTokenPrice url
#### v1.3.8
1) SDK update for error type
#### v1.3.4 - v1.3.7 destroy
#### v1.3.3
1) add computeNFTAddress
2) add eslint and prettie for the code format
#### v1.3.2
1) Fix some bug
2) Update withdraw NFT unit test
#### v1.3.0 && v1.3.1 -- !!! revert
formatter Uint8Array support
# !!! revert
================================================
FILE: docs/README.md
================================================
# Loopring SDK
[](https://raw.githubusercontent.com/Loopring/loopring_sdk/master/LICENSE)
[](https://www.npmjs.com/package/react-data-grid)
[](https://discord.com/invite/KkYccYp)
## 🚀 Quick Start
```shell
# Using npm
npm i @loopring-web/loopring-sdk --save
# Using yarn
yarn add @loopring-web/loopring-sdk
```
> Make sure you are using the original npm registry.
> `npm config set registry http://registry.npmjs.org`
## 📒 Documentation
Please see the [documentation page](https://loopring.github.io/loopring_sdk/) for information about getting started and developing with the Loopring SDK.
- [JS SDK](https://loopring.github.io/loopring_sdk)
- [Python](https://github.com/Loopring/hello_loopring)
- [APIs](https://docs.loopring.io/en/)
## ✨ Changelog
- [Changelog](https://loopring.github.io/loopring_sdk/ChangeLog.html)
## 🫂 Community
- [Loopring Website](https://loopring.org/)
- [Loopring Exchange](https://loopring.io/#/layer2)
- [Loopring Reddit](https://www.reddit.com/r/loopringorg/)
- [Loopring Medium](https://medium.com/loopring-protocol)
- [Loopring Twitter](https://twitter.com/loopringorg)
- [Loopring Telegram](https://t.me/loopring_en)
## 🎒 Getting Started
Please see our [introduction page](https://loopring.github.io/loopring_sdk/) for details on integrating the SDK into your application.
## 🙋 Protocol & Architecture
- [Whitepaper](https://loopring.org/resources/en_whitepaper.pdf)
- [Design Docs](https://github.com/LoopringSecondary/docs/wiki/Loopring3_Design)
## ❓[Help](https://desk.zoho.com/portal/loopring/en/home)
## 👉 [What is Loopring?](https://loopring.org/#/)
## 🔑 Security
- [Wallet](https://security.loopring.io/)
- [Protocol Audit](https://loopring.org/resources/loopring1.0_audit.pdf)
================================================
FILE: docs/SUMMARY.md
================================================
# Summary
### Overview
- [Introduction](README.md)
- [Changelog](Changelog.md)
- [GitHub](https://github.com/Loopring/loopring_sdk)
### SDKs demo
- [JS SDK](./js_sdk/README.md)
- [Exchange](./js_sdk/exchange/exchange.md)
- [Web Socket](./js_sdk/exchange/webSocket.md)
- [Amm](./js_sdk/exchange/ammpool_api.md)
- [Account](./js_sdk/README.md#mock-account)
- [Active Account](./js_sdk/account/activeAccount.md)
- [Fees](./js_sdk/account/fee.md)
- [Signature](./js_sdk/account/signature.md)
- [Transaction Recorder](./js_sdk/account/historyRecord.md)
- [ERC20](./js_sdk/README.md#mock-erc20-token-map)
- [Deposit](./js_sdk/deposit/depositERC20.md)
- [Transfer](./js_sdk/transfer/transferERC20.md)
- [Withdraw](./js_sdk/withdraw/withdrawERC20.md)
- [Order](./js_sdk/erc20Trade/orderERC20.md)
- [NFT](./js_sdk/README.md#mock-account)
- [Deposit](./js_sdk/deposit/depositNFT.md)
- [Transfer](./js_sdk/transfer/transferNFT.md)
- [Withdraw](./js_sdk/withdraw/withdrawNFT.md)
- [Mint](./js_sdk/NFTAction/mintNFT.md)
- [MetaData](./js_sdk/NFTAction/metaNFT.md)
- [Collection](./js_sdk/NFTAction/collectionNFT.md)
- [Deploy](./js_sdk/NFTAction/deployNFT.md)
- [Trade](./js_sdk/NFTAction/tradeNFT.md)
- [Validate Order](./js_sdk/NFTAction/validateNFTOrder.md)
### Links
- [Python](https://github.com/Loopring/hello_loopring)
- [APIs](https://docs.loopring.io/en/)
- [About us](https://loopring.org/#/)
- [Submit a Request](https://desk.zoho.com/portal/loopring/en/newticket)
================================================
FILE: docs/about_us.md
================================================
##### written by loopring dev team.
================================================
FILE: docs/js_sdk/Deposit.md
================================================
# Deposit from Ethereum L1 to Loopring, First Step for Start L2
- [Step1: getUser Layer1 ETH balance](#step1️⃣-getuser-layer1-eth-balance)
- [ETH](#1-let-start-from-eth)
- [ERC20](#2-erc20-token-such-as-lrc)
- [NFT](#3-nft)
- [Step2: Allow and Approve Loopring to get transaction those Token access](#step2️⃣-allow-and-approve-loopring-to-get-transaction-those-token-access)
- [ETH](#1-eth-skip-this-step)
- [ERC20](#2-erc20-token-such-as-lrc-1)
- [NFT](#3-nft-1)
- [Step3: Deposit](#step3️⃣-deposit)
- [ETH](#1-eth-same-as-erc20-only-can-not-deposit-all-for-gas-cost)
- [ERC20](#2-erc20-token-such-as-lrc-2)
- [NFT](#3-nft-2)
- [Additional & Reference](#additional--reference)
- [Approve Simple Signature Demo](#approve-simple-signature-demo)
- [Deposit Signature Demo](#deposit-signature-demo-fee-is-pay-by-eth-only-current-fee-is-0)
>**All Deposit Method, User should have enough `ETH` pay for the >Ethereum Gas (Loopring have no charge, no fee for Deposit).**
Before start read this Doc, make sure you understand how to create an We3 with an Ethereum provider
Let's start step by step how to Deposit from Ethereum L1 to Loopring ( Demo as [Loopring JS SDK](https://loopring.github.io/loopring_sdk) ):
Connect Wallet processing...
When you connect with Wallet (EOA or Loopring Wallet), you will know your `accAddress = ${Account_Address}` and have a `web3` instance
User has Three chooses Deposit `ETH`; `ERC20 Token (Such as LRC )`; `NFT (ERC721 & ERC1155)`;
***
### Step1️⃣: getUser Layer1 ETH balance
#### 1. Let start from ETH
```ts
const { ethBalance } = await LoopringAPI.exchangeAPI.getEthBalances({owner: accAddress});
```
SDK: [getEthBalances](https://github.com/Loopring/loopring_sdk/blob/master/src/api/exchange_api.ts#L514)
API: [/api/v3/eth/balances](https://uat2.loopring.io/api/v3/eth/balances?owner=0xfF7d59D9316EBA168837E3eF924BCDFd64b237D8)
Choose deposit amount from UI
>tips: user should keep some ETH pay for Ethereum Gas
#### 2. ERC20 Token (Such as LRC)
```ts
const { ethBalance } = await LoopringAPI.exchangeAPI.getEthBalances({owner: accAddress});
//tokenArr is Loopring supprot ERC20 TokenId Array.jion(',')
const { tokenBalances } = await LoopringAPI.exchangeAPI.getTokenBalances({owner: accAddress, token: tokenArr.join()})
```
API: [/api/v3/eth/tokenBalances](https://uat2.loopring.io/api/v3/eth/tokenBalances?owner=0xfF7d59D9316EBA168837E3eF924BCDFd64b237D8&token=0xfc28028d9b1f6966fe74710653232972f50673be%2C0x0000000000000000000000000000000000000000%2C0xd4e71c4bb48850f5971ce40aa428b09f242d3e8a%2C0xcd2c81b322a5b530b5fa3432e57da6803b0317f7%2C0x47525e6a5def04c9a56706e93f54cc70c2e8f165)
> {tokenArr} = getMixMarkets()
> SDK:[getMixMarkets](https://github.com/Loopring/loopring_sdk/blob/master/src/api/exchange_api.ts#L409)
> API:[/api/v3/mix/markets](https://api.loopring.network/api/v3/mix/markets) Market pair reduce to Unique Token Name ['ETH','LRC','USDT',...]
#### 3. NFT
- Prepare Token Address `nftTokenAddress`
- NFT ID `nftId`
- Know NFT Type `ERC721` or `ERC1155`
```ts
const response = await nft.getNFTBalance({
web3,
account: accAddress,
tokenAddress: nftTokenAddress,
nftId: nftId,
nftType: NFTType.ERC1155,
});
```
SDK: [getNFTBalance](https://github.com/Loopring/loopring_sdk/blob/master/src/api/nft_api.ts#L100)
***
### Step2️⃣: Allow and Approve Loopring to get transaction those Token access
#### 1. ETH Skip this step
#### 2. ERC20 Token (Such as LRC )
- check Allowances
- getNonce web3.eth.getTransactionCount
- contract.approveMax
```ts
import {getTradeArg} from "./ws_defs";
const {tokenAllowances} = await exchangeAPI.getAllowances({
owner: accAddress,
token: "LRC",
})
if (tokenAllowances["LRC"] === undefined || tokenAllowances["LRC"] < getTradeArgValue){
const nonce = await web3.eth.getTransactionCount(accAddress);
const response = await contract.approveMax(
web3,
accAddress,
tokenAddress, // LRC address {tokenIdMap} = getTokens(); tokenIdMap['LRC']
depositAddress, //{exchangeInfo} = getExchangeInfo() exchangeInfo.depositAddress
gasPrice,
gasLimit,
ChainId.GOERLI,
nonce,
true
);
}
```
SDK:[getAllowances](https://github.com/Loopring/loopring_sdk/blob/master/src/api/exchange_api.ts#L583)
API:[/api/v3/eth/allowances](https://uat2.loopring.io/api/v3/eth/allowances?owner=0xfF7d59D9316EBA168837E3eF924BCDFd64b237D8&token=0xfc28028d9b1f6966fe74710653232972f50673be)
SDK:[getExchangeInfo](https://github.com/Loopring/loopring_sdk/blob/master/src/api/exchange_api.ts#L624)
API:[/api/v3/exchange/info](https://uat2.loopring.io/api/v3/exchange/info)
SDK:[getTokens](https://github.com/Loopring/loopring_sdk/blob/master/src/api/exchange_api.ts#L416)
API:[/apiv3/exchange/tokens](https://uat2.loopring.io/api/v3/exchange/tokens)
SDK:[approveMax](https://github.com/Loopring/loopring_sdk/blob/master/src/api/contract_api.ts#L271)
[Approve Signature Demo](#approve-simple-signature-demo)
#### 3. NFT
- check nft isApprovedForAll
- getNonce web3.eth.getTransactionCount
- contract.approveNFT (ALL) by nftTokenAddress
```ts
const isApproved = await nft.isApprovedForAll({
web3,
from: accAddress,
exchangeAddress: exchangeAddr, //{exchangeInfo} = getExchangeInfo() exchangeInfo.exchangeAddr
nftType: NFTType.ERC1155,
tokenAddress: nftTokenAddress,
});
if(!isApproved){
const nonce = await web3.eth.getTransactionCount(accAddress);
const response = await nft.approveNFT({
web3,
from: accAddress,
depositAddress,
tokenAddress: nftTokenAddress,
tokenId: nftId,
nftType: NFTType.ERC1155,
gasPrice,
gasLimit,
chainId: ChainId.GOERLI,
nonce,
approved: true,
sendByMetaMask: true,
});
}
```
SDK:[isApprovedForAll](https://github.com/Loopring/loopring_sdk/blob/master/src/api/nft_api.ts#L312)
SDK:[approveNFT](https://github.com/Loopring/loopring_sdk/blob/master/src/api/nft_api.ts#L235)
[Approve Signature Demo](#approve-simple-signature-demo)
***
### Step3️⃣: Deposit
#### 1. ETH (Same as ERC20, only can not deposit all for Gas cost)
#### 2. ERC20 Token (Such as LRC )
```ts
const nonce = await web3.eth.getTransactionCount(accAddress);
const response = await contract.deposit(
web3,
accAddress,
exchangeAddr, // {exchangeInfo} = getExchangeInfo() exchangeInfo.exchangeAddr
tokenInfo,
10, // tradeValue
0, // fee 0
gasPrice,
gasLimit,
ChainId.GOERLI,
nonce,
true
);
```
SDK:[deposit](https://github.com/Loopring/loopring_sdk/blob/master/src/api/contract.ts#L300)
[Deposit Signature Demo](#deposit-signature-demo-fee-is-pay-by-eth-only-current-fee-is-0)
#### 3. NFT
```ts
const nonce = await web3.eth.getTransactionCount(accAddress);
const response = await nft.depositNFT({
web3,
from:accAddress,
exchangeAddress::exchangeAddr, //{exchangeInfo} = getExchangeInfo() exchangeInfo.exchangeAddr
nftType: NFTType.ERC1155,
tokenAddress: nftTokenAddress,
amount: 1,
gasPrice,
gasLimit,
chainId: ChainId.GOERLI,
nonce,
sendByMetaMask:true}
);
```
SDK:[depositNFT](https://github.com/Loopring/loopring_sdk/blob/master/src/api/nft_api.ts#L354)
[Deposit Signature Demo](#deposit-signature-demo-fee-is-pay-by-eth-only-current-fee-is-0)
***
### Additional & Reference
#### Approve Simple Signature Demo
For more detail about genERC{XXX}Data please read [Contract ABI Specification](//https://docs.soliditylang.org/en/develop/abi-spec.html#)
```ts
/* ERC20 Approve Data structure */
const data = genERC20Data(ERC20Method.Approve, {
_spender: depositAddress,
_value:"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
});
/* NFT setApprovalForAll Data structure */
// if (nftType === NFTType.ERC1155) {
// data = this._genERC1155Data(NFTMethod.setApprovalForAll, {
// operator: depositAddress,
// approved,
// });
// } else if (nftType === NFTType.ERC721) {
// //TODO list not support now
// data = this._genERC721Data(NFTMethod.setApprovalForAll, {
// operator: depositAddress,
// approved,
// });
// }
const gasPrice = fm.fromGWEI(gasPrice).toNumber();
web3.eth.sendTransaction({
from:accAddress,
to: depositAddress, //{exchangeInfo} = getExchangeInfo() exchangeInfo.depositAddress
value:"0",
data,
chainId,
nonce,
gasPrice,
gasLimit,
}).then((_error,transactionHash: string) =>{
if (!err) {
resolve({ result: transactionHash });
} else {
resolve({ error: { message: err.message } });
}
});
```
### Deposit Signature Demo (fee is pay by ETH only current fee is 0)
- tokenSymbol is ETH, sendTransaction value should be `amount + fee`
- Other tokenSymbol as LRC sendTransaction value is `fee`
- `fee` is pay by ETH only (current fee is 0)
```ts
const tokenAddress = "0x?????????";
const tokenSymbol = "LRC";
const fee = 0;
/* tokenSymbol is ETH, sendTransaction value should be `amount + fee` (current fee is 0)
* other tokenSymbol as LRC addresss valueC is fee (current fee is 0)
*/
const value = tokenSymbol === "ETH"? amount + fee : fee;
const data = genExchangeData(ERC20Method.Deposit, {
tokenAddress,
amount,
from,
to: from,
extraData: "",
});
/* NFT deposit Data structure */
// const data = genExchangeData(NFTMethod.depositNFT, {
// from,
// to: from,
// nftType,
// tokenAddress,
// nftId,
// amount,
// extraData: extraData ? extraData : "",
// });
web3.eth.sendTransaction({
from:accAddress,
to:exchangeAddress, //{exchangeInfo} = getExchangeInfo() exchangeInfo.depositAddress
value,
data,
chainId,
nonce,
gasPrice,
gasLimit,
}).then((_error,transactionHash: string) =>{
if (!err) {
resolve({ result: transactionHash });
} else {
resolve({ error: { message: err.message } });
}
});
return await sendRawTx(
web3,
from,
exchangeAddress,
valueC.toFixed(),
data,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask
);
```
last but not least, waiting and check your layer 2 account Balance... start Layer2
#### Reference
- @Loopring-web/web3-provider Package & [DEMO](https://github.com/Loopring/web3-provider)
- Loopring allow you directory dev with our [APIs](https://docs.loopring.io/en/)
- For your Web-app & Dapp welcome use our [JS SDK](https://loopring.github.io/loopring_sdk)
================================================
FILE: docs/js_sdk/NFTAction/collectionNFT.md
================================================
# Collection NFT
Definition: allow user create a NFT Collection with different Contract(token) address.
- allow user mint the NFTs with collection
- when collection deployed, NFTs will have different Contract(token) address in layer1
- Loopring own collection on L2, allow user to view/edit their Collection information.
- will soon enable the "Import Collection" to manage legacy NFTs
***
## Step 1. get Account
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
```
***
## Step 2. get eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. get apiKey
```ts
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
```
***
## Step 4. get storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: TOKEN_INFO.tokenMap[ "LRC" ].tokenId, // same as maxFee tokenId
},
apiKey
);
```
***
## Step 5. get collection Information(tokenAddress)
```ts
const collectionRes = await LoopringAPI.userAPI
.getUserOwenCollection({
owner: accInfo.owner,
tokenAddress: mockData.nftTokenAddress,
isMintable: true
},
apiKey
)
if ((collectionRes &&
((collectionRes as sdk.RESULT_INFO).code ||
(collectionRes as sdk.RESULT_INFO).message)) || !collectionRes.collections.length
) {
console.log("Collection is disable to mint ");
throw "Collection is disable to mint ";
}
const collectionMeta = (collectionRes as any).collections[ 0 ] as CollectionMeta;
const counterFactualNftInfo: NFTCounterFactualInfo = {
nftOwner: accInfo.owner,
nftFactory: collectionMeta.nftFactory ?? sdk.NFTFactory_Collection[ sdk.ChainId.GOERLI ],
nftBaseUri: collectionMeta.baseUri,
};
```
***
## Step 6. fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
tokenAddress: collectionMeta.contractAddress,
requestType: sdk.OffchainNFTFeeReqType.NFT_MINT,
},
apiKey
);
```
***
## Step7. Mint
```ts
const response = await LoopringAPI.userAPI.submitNFTMint({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
minterId: accInfo.accountId,
minterAddress: accInfo.owner,
toAccountId: accInfo.accountId,
toAddress: accInfo.owner,
nftType: 0,
tokenAddress: collectionMeta.contractAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId, //nftId.toString(16),
amount: "1",
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
storageId: storageId.offchainId ?? 9,
maxFee: {
tokenId: TOKEN_INFO.tokenMap[ "LRC" ].tokenId,
amount: fee.fees[ "LRC" ].fee ?? "9400000000000000000",
},
counterFactualNftInfo,
royaltyPercentage: 5,
forceToMint: true, // suggest use as false, for here is just for run test
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
```
____
### ps: Mint with legacy nftFactory
! Mint NFT from this way has no collection information at deploy contract(tokenAdress is
unique)
***
#### Step 1,2,3,4 is same logic
#### Step 5. get tokenAddress
```ts
const counterFactualNftInfo = {
nftOwner: accInfo.owner,
nftFactory: sdk.NFTFactory[ sdk.ChainId.GOERLI ],
nftBaseUri: "",
};
const nftTokenAddress =
LoopringAPI.nftAPI.computeNFTAddress(counterFactualNftInfo)
.tokenAddress || "";
console.log("nftTokenAddress", nftTokenAddress);
```
***
#### Step 6. get fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
tokenAddress: nftTokenAddress,
requestType: sdk.OffchainNFTFeeReqType.NFT_MINT,
},
apiKey
);
```
#### Step 7. Mint
```ts
const response = await LoopringAPI.userAPI.submitNFTMint({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
minterId: accInfo.accountId,
minterAddress: accInfo.owner,
toAccountId: accInfo.accountId,
toAddress: accInfo.owner,
nftType: 0,
tokenAddress: nftTokenAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId, //nftId.toString(16),
amount: "1",
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
storageId: storageId.offchainId ?? 9,
maxFee: {
tokenId: TOKEN_INFO.tokenMap[ "LRC" ].tokenId,
amount: fee.fees[ "LRC" ].fee ?? "9400000000000000000",
},
royaltyPercentage: 5,
counterFactualNftInfo,
forceToMint: true, // suggest use as false, for here is just for run test
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
```
================================================
FILE: docs/js_sdk/NFTAction/deployNFT.md
================================================
# Deploy NFT
Definition: Only nft minter can deploy NFT
***
## Step 1. get Account
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
```
***
## Step 2. get eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. get apiKey
```ts
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
```
***
## Step 4. get fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainNFTFeeReqType.NFT_DEPLOY,
amount: "0",
},
apiKey
);
console.log(fee);
```
***
## Step 5. get storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: TOKEN_INFO.tokenMap["LRC"].tokenId, // same as Step 7. transfer->token->tokenId
},
apiKey
);
```
***
## Step 6. broker
```ts
const {broker} = await LoopringAPI.exchangeAPI.getAvailableBroker();
```
***
## Step 7. Build transfer & Deploy
```ts
const transfer = {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
payerAddr: LOOPRING_EXPORTED_ACCOUNT.address,
payerId: LOOPRING_EXPORTED_ACCOUNT.accountId,
payeeAddr: broker,
// payeeAddr: LOOPRING_EXPORTED_ACCOUNT.address2,
storageId: storageId.offchainId,
token: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
volume: fee.fees["LRC"].fee ?? "9400000000000000000",
},
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
};
const response = await LoopringAPI.userAPI.submitDeployNFT({
request: {
transfer,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
console.log(response);
```
================================================
FILE: docs/js_sdk/NFTAction/metaNFT.md
================================================
# NFT META METHODS
***
## getContractNFTMeta
```ts
const result = await LoopringAPI.nftAPI.getContractNFTMeta({
web3,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId,
nftType: sdk.NFTType.ERC1155,
});
console.log(result);
```
***
## getInfoForNFTTokens
```ts
const response = await LoopringAPI.nftAPI.getInfoForNFTTokens({
nftDatas: [LOOPRING_EXPORTED_ACCOUNT.nftData],
});
console.log(`getInfoForNFTTokens: response: `, JSON.stringify(response));
```
***
## computeNFTAddress
```ts
const response = LoopringAPI.nftAPI.computeNFTAddress({
nftOwner: "0xE20cF871f1646d8651ee9dC95AAB1d93160b3467",
nftFactory: "0x40F2C1770E11c5bbA3A26aEeF89616D209705C5D",
});
console.log(
`computeNFTAddress:`,
response,
"0xee354d81778a4c5a08fd9dbeb5cfd01a840a746d"
);
```
***
## ipfsCid0ToNftID
```ts
const ipfs = "QmNuqdeWUJ9iEiw5qZfJ2pJ9onqAS45ZffvV8JQSUzp7DQ";
const nftID =
"0x0880847b7587968f32ba6c741f9d797d9dc64971979922a80c4e590453b8dc2f";
console.log(
`ipfsCid0ToNftID: ipfs: `,
ipfs,
LoopringAPI.nftAPI.ipfsCid0ToNftID(ipfs)
);
```
***
## ipfsNftIDToCid
```ts
const ipfs = "QmNuqdeWUJ9iEiw5qZfJ2pJ9onqAS45ZffvV8JQSUzp7DQ";
const nftID =
"0x0880847b7587968f32ba6c741f9d797d9dc64971979922a80c4e590453b8dc2f";
console.log(
`ipfsCid0ToNftID: nftID: `,
nftID,
LoopringAPI.nftAPI.ipfsNftIDToCid(nftID)
);
```
================================================
FILE: docs/js_sdk/NFTAction/mintNFT.md
================================================
# Mint NFT
Definition: Mint Layer2 NFT, Loopring follow the ipfs NFT format, IPFS CID will convert to nftId, please view MetaNFT.md
>
!!! important describe
>
Follow mehod is the simple way for mint NTF, but this kind of NFT will using the same contact & with no Contract metadata forever on L1
> New Version of NFT will has it isolate Contract/colletion with metadata inforamtion
> From Step 3. nftTokenAddress please follow create `collectionNFT` step create collection(contract), the api will return follow info for mint NFT
>
```ts
tokenAddress: collectionMeta.contractAddress
counterFactualNftInfo: {
nftOwner: ccInfo.owner,
nftFactory: collectionMeta.nftFactory ?? sdk.NFTFactory_Collection[chainId],
nftBaseUri: collectionMeta?.baseUri ?? "",
},
```
## Step 1. get Account
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
```
***
## Step 2. get eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. get apiKey
```ts
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey({
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
```
***
## Step 4. get storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: TOKEN_INFO.tokenMap[ "LRC" ].tokenId, // same as maxFee tokenId
},
apiKey
);
```
***
## Step 5. get collection Information(tokenAddress)
```ts
const collectionRes = await LoopringAPI.userAPI
.getUserOwenCollection({
owner: accInfo.owner,
tokenAddress: mockData.nftTokenAddress,
isMintable: true
},
apiKey
)
if ((collectionRes &&
((collectionRes as sdk.RESULT_INFO).code ||
(collectionRes as sdk.RESULT_INFO).message)) || !collectionRes.collections.length
) {
console.log("Collection is disable to mint ");
throw "Collection is disable to mint ";
}
const collectionMeta = (collectionRes as any).collections[ 0 ] as CollectionMeta;
const counterFactualNftInfo: NFTCounterFactualInfo = {
nftOwner: accInfo.owner,
nftFactory: collectionMeta.nftFactory ?? sdk.NFTFactory_Collection[ sdk.ChainId.GOERLI ],
nftBaseUri: collectionMeta.baseUri,
};
```
***
## Step 6. fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
tokenAddress: collectionMeta.contractAddress,
requestType: sdk.OffchainNFTFeeReqType.NFT_MINT,
},
apiKey
);
```
***
## Step7. Mint
```ts
const response = await LoopringAPI.userAPI.submitNFTMint({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
minterId: accInfo.accountId,
minterAddress: accInfo.owner,
toAccountId: accInfo.accountId,
toAddress: accInfo.owner,
nftType: 0,
tokenAddress: nftTokenAddress, // please read the description -> tokenAddress: collectionMeta.contractAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId, //nftId.toString(16),
amount: "1",
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
storageId: storageId.offchainId ?? 9,
maxFee: {
tokenId: TOKEN_INFO.tokenMap[ "LRC" ].tokenId,
amount: fee.fees[ "LRC" ].fee ?? "9400000000000000000",
},
counterFactualNftInfo,
royaltyPercentage: 5,
forceToMint: true, // suggest use as false, for here is just for run test
// please read the description
// counterFactualNftInfo: {
// nftOwner: ccInfo.owner,
// nftFactory: collectionMeta.nftFactory ?? sdk.NFTFactory_Collection[chainId],
// nftBaseUri: collectionMeta?.baseUri ?? "",
// },
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
```
____
### ps: Mint with legacy nftFactory
! Mint NFT from this way has no collection information at deploy contract(tokenAdress is
unique)
***
#### Step 1,2,3,4 is same logic
#### Step 5. get tokenAddress
```ts
const counterFactualNftInfo = {
nftOwner: accInfo.owner,
nftFactory: sdk.NFTFactory[ sdk.ChainId.GOERLI ],
nftBaseUri: "",
};
const nftTokenAddress =
LoopringAPI.nftAPI.computeNFTAddress(counterFactualNftInfo)
.tokenAddress || "";
console.log("nftTokenAddress", nftTokenAddress);
```
***
#### Step 6. get fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
tokenAddress: nftTokenAddress,
requestType: sdk.OffchainNFTFeeReqType.NFT_MINT,
},
apiKey
);
```
#### Step 7. Mint
```ts
const response = await LoopringAPI.userAPI.submitNFTMint({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
minterId: accInfo.accountId,
minterAddress: accInfo.owner,
toAccountId: accInfo.accountId,
toAddress: accInfo.owner,
nftType: 0,
tokenAddress: nftTokenAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId, //nftId.toString(16),
amount: "1",
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
storageId: storageId.offchainId ?? 9,
maxFee: {
tokenId: TOKEN_INFO.tokenMap[ "LRC" ].tokenId,
amount: fee.fees[ "LRC" ].fee ?? "9400000000000000000",
},
royaltyPercentage: 5,
counterFactualNftInfo,
forceToMint: true, // suggest use as false, for here is just for run test
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
```
================================================
FILE: docs/js_sdk/NFTAction/tradeNFT.md
================================================
# Trade NFT
Definition: This method is help for understand how to match a maker with a taker order
***
## tradeNFT
> Private or third account can signature and approve this order
[mock order](#MockOrder)
```ts
// Step 1. getAccount
const accInfoC = (
await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
})
).accInfo;
// Step 1. eddsaKeyC
const eddsaKeyC = await signatureKeyPairMock(accInfoC);
// Step 3. apiKey
const apiKeyC = (
await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfoC.accountId,
},
eddsaKeyC.sk
)
).apiKey;
// NFT Trade
const response = await LoopringAPI.userAPI.submitNFTTrade({
request: {
maker: {
...mockData.makerOrder,
eddsaSignature: mockData.makerOrderEddsaSignature,
},
makerFeeBips: 1000,
taker: {
...mockData.takerOrder,
eddsaSignature: mockData.takerOrderEddsaSignature,
},
takerFeeBips: 100,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
apiKey: apiKeyC,
eddsaKey: eddsaKeyC.sk,
});
console.log(response);
```
***
## MockOrder
> Validate NFT Order please reader @Validate NFT Order
```ts
// Step 1. getAccount
const accInfo = (
await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
})
).accInfo;
const accInfo2 = (
await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address2,
})
).accInfo;
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
const eddsaKey2 = await signatureKeyPairMock(accInfo2, web3_2);
// Step 3. apiKey
const apiKey = (
await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
)
).apiKey;
const apiKey2 = (
await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo2.accountId,
},
eddsaKey2.sk
)
).apiKey;
// Step 4. storageId
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
},
apiKey
);
const storageId2 = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo2.accountId,
sellTokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
},
apiKey2
);
// Step 5. generate Order, please read validateNFTOrder
const makerOrder: sdk.NFTOrderRequestV3 = {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: accInfo.accountId,
storageId: storageId.orderId,
sellToken: {
tokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
amount: "1",
},
buyToken: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
amount: LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
},
allOrNone: false,
fillAmountBOrS: false,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
maxFeeBips: 1000,
};
const makerOrderEddsaSignature = sdk.get_EddsaSig_NFT_Order(
makerOrder,
eddsaKey.sk
);
const takerOrder: sdk.NFTOrderRequestV3 = {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: accInfo2.accountId,
storageId: storageId2.orderId,
sellToken: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
amount: LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
},
buyToken: {
tokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
amount: "1",
},
allOrNone: false,
fillAmountBOrS: true,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
maxFeeBips: 100,
};
const takerOrderEddsaSignature = sdk.get_EddsaSig_NFT_Order(
takerOrder,
eddsaKey2.sk
);
mockData = {
takerOrder,
takerOrderEddsaSignature,
makerOrder,
makerOrderEddsaSignature,
makerFeeBips: 1000,
maxFeeBips: 100,
};
```
================================================
FILE: docs/js_sdk/NFTAction/validateNFTOrder.md
================================================
# Validate NFT Order
Definition: Loopring L2 support a method help for Validate NFT one side Order, validate NFT Order is not required for
Loopring, but when make NFT Trade, it should pass this validation
## SellNFTByERC20
```ts
// Step 1. getAccount
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. apiKey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
// Step 4. storageId
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
},
apiKey
);
console.log("storageId:", storageId);
// let hash: any = new BN(nftId,'hex')
// hash = toHex(hash);//new BigInteger(sha256(nftId.toString()).toString(), 16)
// Step 5. submitNFTValidateOrder
const response = await LoopringAPI.userAPI.submitNFTValidateOrder({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: accInfo.accountId,
storageId: storageId.orderId,
sellToken: {
tokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
amount: "1",
},
buyToken: {
tokenId: 1,
amount: "10000000000000",
},
allOrNone: false,
fillAmountBOrS: false,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
maxFeeBips: 1000,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
console.log("sellNFT NFTOrderRequestV3:", response);
```
***
## BuyNFTByERC20
```ts
// Step 1. getAccount
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. apiKey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
// Step 5. submitNFTValidateOrder
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
},
apiKey
);
console.log("storageId:", storageId);
const response = await LoopringAPI.userAPI.submitNFTValidateOrder({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: accInfo.accountId,
storageId: storageId.orderId,
sellToken: {
tokenId: 1,
amount: "10000000000000",
},
buyToken: {
tokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
amount: "1",
},
fillAmountBOrS: true,
allOrNone: false,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
maxFeeBips: 100,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
```
================================================
FILE: docs/js_sdk/README.md
================================================
# Loopring SDK
Loopring SDK Initialize and Mock Data structure
***
## SDK Initialize
```ts
import * as sdk from "../index";
export class LoopringAPIClass {
public static userAPI: UserAPI;
public static exchangeAPI: ExchangeAPI;
public static ammpoolAPI: AmmpoolAPI;
public static walletAPI: WalletAPI;
public static wsAPI: WsAPI;
public static nftAPI: NFTAPI;
public static delegate: DelegateAPI;
public static globalAPI: GlobalAPI;
public static contractAPI: typeof ContractAPI;
public static __chainId__: sdk.ChainId;
public static InitApi = (chainId: sdk.ChainId) => {
LoopringAPI.userAPI = new UserAPI({ chainId });
LoopringAPI.exchangeAPI = new ExchangeAPI({ chainId });
LoopringAPI.globalAPI = new GlobalAPI({ chainId });
LoopringAPI.ammpoolAPI = new AmmpoolAPI({ chainId });
LoopringAPI.walletAPI = new WalletAPI({ chainId });
LoopringAPI.wsAPI = new WsAPI({ chainId });
LoopringAPI.nftAPI = new NFTAPI({ chainId });
LoopringAPI.delegate = new DelegateAPI({ chainId });
LoopringAPI.__chainId__ = chainId;
LoopringAPI.contractAPI = ContractAPI;
};
}
/* env:
* test: sdk.ChainId.GOERLI
* eth: sdk.ChainId.MAINNET
*/
LoopringAPIClass.InitApi({sdk.ChainId.MAINNET});
```
***
## Connect Wallet
Suggestion web3 provider
```shell
# Using npm
npm i @loopring-web/web3-provider --save
# Using yarn
yarn add @loopring-web/web3-provider
```
- [Demo Vue](https://codesandbox.io/s/vue-8nco78)
- [Demo React](https://codesandbox.io/s/react-4v50ft)
[Mock Provider](#mock-provider)
***
## Loopring ERC20 Data Structure
```ts
const {tokensMap, idIndex, addressIndex} = LoopringAPI.exchangeAPI.getTokens();
```
[Mock ERC20](#mock-erc20-token-map)
***
## Token Decimal
```ts
const uiValue = 100;
const tradeValue = sdk.toBig(uiValue).times("1e" + TOKEN_INFO.tokenMap.LRC.decimals);
```
***
## Test Mock Data
##### Mock Account
```ts
export const LOOPRING_EXPORTED_ACCOUNT = {
address: "0x727e0fa09389156fc803eaf9c7017338efd76e7f",
privateKey:
"491aecdb1d5f6400a6b62fd12a41a86715bbab675c37a4060ba115fecf94083c",
accountId: 12454,
address2: "0xb6d8c39D5528357dBCe6BEd82aC71c74e9D19079",
privateKey2:
"e020ed769032ba95d9a5207687a663d6198fe2f5cedf28a250f7cbd8c81a5263",
accountId2: 10488,
addressCF: "0x23dE4Da688c94a66E8bbE9BCc95CB03b4e209C15",
accountIdCF: 11632,
addressContractWallet: "0xD4BD7c71B6d4A09217ccc713f740d6ed8f4EA0cd",
depositAddress: "0xb684B265f650a77afd27Ce0D95252a7329B5bD72",
exchangeAddress: "0x2e76EBd1c7c0C8e7c2B875b6d505a260C525d25e",
whitelistedAddress: "0x35405E1349658BcA12810d0f879Bf6c5d89B512C",
whitelistedEddkey:
"0x27a5b716c7309a30703ede3f1a218cdec857e424a31543f8a658e7d2208db33",
// const eddkeyWhitelisted =
// "0x27a5b716c7309a30703ede3f1a218cdec857e424a31543f8a658e7d2208db33";
// apiKey: "2PYgTOZwXHkPXtJMlOMG06ZX1QKJInpoky6iYIbtMgmkbfdL4PvxyEOj0LPOfgYX",
chainId: 5,
nftTokenAddress: "0x8394cB7e768070217592572582228f62CdDE4FCE",
nftTokenId: 32768,
nftId: "0xa0ce8990402955e559799af24ea765b14ffecc32dfa1cce2dadaf20016b074e6",
nftData: "0x1a2001aac7a1fd00cef07889cdb67b1355f86e5bc9df71cfa44fa1c7b49f598f",
testNotOx: "727e0fa09389156fc803eaf9c7017338efd76e7f",
tradeLRCValue: 1000000000000000000,
tradeETHValue: 0.0001, //same as UI
gasPrice: 20, // for test
gasLimit: 200000, // for test
validUntil: Math.round(Date.now() / 1000) + 30 * 86400,
};
export const CUSTOMER_KEY_SEED = "XXXXXX" + " with key nonce: " + "${nonce}";
```
##### Mock provider
```ts
const provider = new PrivateKeyProvider(
LOOPRING_EXPORTED_ACCOUNT.privateKey,
"https://goerli.infura.io/v3/a06ed9c6b5424b61beafff27ecc3abf3"
);
const provider2 = new PrivateKeyProvider(
LOOPRING_EXPORTED_ACCOUNT.privateKey2,
"https://goerli.infura.io/v3/a06ed9c6b5424b61beafff27ecc3abf3"
);
export const web3 = new Web3(provider);
export const web3_2 = new Web3(provider2);
```
##### Mock ERC20 Token Map
```ts
export let TOKEN_INFO = {
addressIndex: {
"0x0000000000000000000000000000000000000000": "ETH",
"0xfc28028d9b1f6966fe74710653232972f50673be": "LRC",
"0xd4e71c4bb48850f5971ce40aa428b09f242d3e8a": "USDT",
"0xfeb069407df0e1e4b365c10992f1bc16c078e34b": "LP-LRC-ETH",
"0x049a02fa9bc6bd54a2937e67d174cc69a9194f8e": "LP-ETH-USDT",
"0xcd2c81b322a5b530b5fa3432e57da6803b0317f7": "DAI",
"0x47525e6a5def04c9a56706e93f54cc70c2e8f165": "USDC",
"0xf37cf4ced77b985708d591acc6bfd08586ab3409": "LP-USDC-ETH",
},
tokenMap: {
ETH: {
type: "ETH",
tokenId: 0,
symbol: "ETH",
name: "Ethereum",
address: "0x0000000000000000000000000000000000000000",
decimals: 18,
precision: 7,
precisionForOrder: 3,
orderAmounts: {
minimum: "5000000000000000",
maximum: "1000000000000000000000",
dust: "200000000000000",
},
luckyTokenAmounts: {
minimum: "50000000000000",
maximum: "1000000000000000000000",
dust: "50000000000000",
},
fastWithdrawLimit: "100000000000000000000",
gasAmounts: {
distribution: "85000",
deposit: "100000",
},
enabled: true,
isLpToken: false,
tradePairs: ["LRC", "USDT", "USDC"],
},
LRC: {
type: "erc20Trade",
tokenId: 1,
symbol: "LRC",
name: "Loopring",
address: "0xfc28028d9b1f6966fe74710653232972f50673be",
decimals: 18,
precision: 3,
precisionForOrder: 3,
orderAmounts: {
minimum: "5000000000000000000",
maximum: "5000000000000000000000000",
dust: "5000000000000000000",
},
luckyTokenAmounts: {
minimum: "50000000000000000",
maximum: "5000000000000000000000000",
dust: "50000000000000000",
},
fastWithdrawLimit: "750000000000000000000000",
gasAmounts: {
distribution: "101827",
deposit: "200000",
},
enabled: true,
isLpToken: false,
tradePairs: ["ETH"],
},
USDT: {
type: "erc20Trade",
tokenId: 2,
symbol: "USDT",
name: "USDT",
address: "0xd4e71c4bb48850f5971ce40aa428b09f242d3e8a",
decimals: 6,
precision: 2,
precisionForOrder: 3,
orderAmounts: {
minimum: "5000000",
maximum: "2000000000000",
dust: "250000",
},
luckyTokenAmounts: {
minimum: "50000",
maximum: "200000000000",
dust: "50000",
},
fastWithdrawLimit: "250000000000",
gasAmounts: {
distribution: "106233",
deposit: "200000",
},
enabled: true,
isLpToken: false,
tradePairs: ["ETH", "DAI"],
},
"LP-LRC-ETH": {
type: "erc20Trade",
tokenId: 4,
symbol: "LP-LRC-ETH",
name: "AMM-LRC-ETH",
address: "0xfeb069407df0e1e4b365c10992f1bc16c078e34b",
decimals: 8,
precision: 6,
precisionForOrder: 3,
orderAmounts: {
minimum: "100000000",
maximum: "10000000000000000000",
dust: "100000000",
},
luckyTokenAmounts: {
minimum: "100000000",
maximum: "10000000000000000000",
dust: "100000000",
},
fastWithdrawLimit: "20000000000",
gasAmounts: {
distribution: "150000",
deposit: "200000",
},
enabled: true,
isLpToken: true,
},
"LP-ETH-USDT": {
type: "erc20Trade",
tokenId: 7,
symbol: "LP-ETH-USDT",
name: "LP-ETH-USDT",
address: "0x049a02fa9bc6bd54a2937e67d174cc69a9194f8e",
decimals: 8,
precision: 6,
precisionForOrder: 3,
orderAmounts: {
minimum: "100000000",
maximum: "10000000000000",
dust: "100000000",
},
luckyTokenAmounts: {
minimum: "100000000",
maximum: "10000000000000",
dust: "100000000",
},
fastWithdrawLimit: "20000000000",
gasAmounts: {
distribution: "150000",
deposit: "200000",
},
enabled: true,
isLpToken: true,
},
DAI: {
type: "erc20Trade",
tokenId: 6,
symbol: "DAI",
name: "dai",
address: "0xcd2c81b322a5b530b5fa3432e57da6803b0317f7",
decimals: 18,
precision: 6,
precisionForOrder: 3,
orderAmounts: {
minimum: "10000000000000000000",
maximum: "100000000000000000000000",
dust: "10000000000000000",
},
luckyTokenAmounts: {
minimum: "10000000000000000000",
maximum: "100000000000000000000000",
dust: "10000000000000000000",
},
fastWithdrawLimit: "10000000000000000000000",
gasAmounts: {
distribution: "150000",
deposit: "200000",
},
enabled: true,
isLpToken: false,
tradePairs: ["USDT"],
},
USDC: {
type: "USDC",
tokenId: 8,
symbol: "USDC",
name: "USDC",
address: "0x47525e6a5def04c9a56706e93f54cc70c2e8f165",
decimals: 6,
precision: 6,
precisionForOrder: 3,
orderAmounts: {
minimum: "1000",
maximum: "10000000000000000000",
dust: "100",
},
luckyTokenAmounts: {
minimum: "1000000",
maximum: "10000000000",
dust: "1000000",
},
fastWithdrawLimit: "20000000000000000000",
gasAmounts: {
distribution: "150000",
deposit: "200000",
},
enabled: true,
isLpToken: false,
tradePairs: ["ETH"],
},
"LP-USDC-ETH": {
type: "LP-USDC-ETH",
tokenId: 9,
symbol: "LP-USDC-ETH",
name: "LP-USDC-ETH",
address: "0xf37cf4ced77b985708d591acc6bfd08586ab3409",
decimals: 8,
precision: 7,
precisionForOrder: 3,
orderAmounts: {
minimum: "100000",
maximum: "1000000000000000000000000000000000000000",
dust: "10000",
},
luckyTokenAmounts: {
minimum: "1000000000000000",
maximum: "10000000000000000000",
dust: "1000000000000000",
},
fastWithdrawLimit: "20000000000000000000",
gasAmounts: {
distribution: "150000",
deposit: "200000",
},
enabled: true,
isLpToken: true,
},
},
idIndex: {
"0": "ETH",
"1": "LRC",
"2": "USDT",
"4": "LP-LRC-ETH",
"6": "DAI",
"7": "LP-ETH-USDT",
"8": "USDC",
"9": "LP-USDC-ETH",
},
marketMap: {
"LRC-ETH": {
baseTokenId: 1,
enabled: true,
market: "LRC-ETH",
orderbookAggLevels: 5,
precisionForPrice: 6,
quoteTokenId: 0,
status: 3,
isSwapEnabled: true,
createdAt: 1617967800000,
},
"ETH-USDT": {
baseTokenId: 0,
enabled: true,
market: "ETH-USDT",
orderbookAggLevels: 3,
precisionForPrice: 3,
quoteTokenId: 2,
status: 3,
isSwapEnabled: true,
createdAt: 1617972300000,
},
"DAI-USDT": {
baseTokenId: 6,
enabled: true,
market: "DAI-USDT",
orderbookAggLevels: 2,
precisionForPrice: 4,
quoteTokenId: 2,
status: 3,
isSwapEnabled: true,
createdAt: 0,
},
"USDC-ETH": {
baseTokenId: 8,
enabled: true,
market: "USDC-ETH",
orderbookAggLevels: 3,
precisionForPrice: 3,
quoteTokenId: 0,
status: 3,
isSwapEnabled: true,
createdAt: 1636974420000,
},
},
};
```
##### Mock AMM MAP
```ts
export let AMM_MAP = {
"AMM-LRC-ETH": {
name: "LRCETH-Pool",
market: "AMM-LRC-ETH",
address: "0xfEB069407df0e1e4B365C10992F1bc16c078E34b",
version: "1.0.0",
tokens: { pooled: [1, 0], lp: 4 },
feeBips: 20,
precisions: { price: 6, amount: 5 },
createdAt: "1617967800000",
status: 31,
},
"AMM-ETH-USDT": {
name: "AMM-ETH-USDT",
market: "AMM-ETH-USDT",
address: "0x049a02FA9bc6bd54a2937E67D174cc69a9194f8e",
version: "1.0.0",
tokens: { pooled: [0, 2], lp: 7 },
feeBips: 20,
precisions: { price: 3, amount: 3 },
createdAt: "1617972300000",
status: 31,
},
"AMM-USDC-ETH": {
name: "AMM-USDC-ETH",
market: "AMM-USDC-ETH",
address: "0xf37cf4CEd77b985708D591AcC6BfD08586Ab3409",
version: "1.0.0",
tokens: {
pooled: [8, 0],
lp: 9,
},
feeBips: 20,
precisions: {
price: 3,
amount: 4,
},
createdAt: "1636974420000",
status: 0,
},
};
```
##### Mock EIP712 Typed Data
```ts
export const testTypedData: EIP712TypedData = {
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" },
],
TestTypedData: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "tokenID", type: "uint16" },
],
},
primaryType: "TestTypedData",
domain: {
name: "Loopring Protocol",
version: "3.6.0",
chainId: sdk.ChainId.GOERLI,
verifyingContract: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
},
message: {
from: LOOPRING_EXPORTED_ACCOUNT.address,
to: LOOPRING_EXPORTED_ACCOUNT.address2,
tokenID: TOKEN_INFO.tokenMap.LRC.tokenId,
},
};
```
##### Mock generate eddsaKey
```ts
export async function signatureKeyPairMock(
accInfo: sdk.AccountInfo,
_web3: Web3 = web3
) {
const eddsaKey = await sdk.generateKeyPair({
web3: _web3,
address: accInfo.owner,
keySeed:
accInfo.keySeed ??
sdk.GlobalAPI.KEY_MESSAGE.replace(
"${exchangeAddress}",
LOOPRING_EXPORTED_ACCOUNT.exchangeAddress
).replace("${nonce}", (accInfo.nonce - 1).toString()),
walletType: sdk.ConnectorNames.MetaMask,
chainId: sdk.ChainId.GOERLI,
});
return eddsaKey;
}
```
================================================
FILE: docs/js_sdk/account/activeAccount.md
================================================
# Active Account
Definition: After user Deposit or (Third-Part Transfer), how to active Loopring L2 account.
***
## Step 1. get account info
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
```
***
## Step 2. use keySeed or CUSTOMER_KEY_SEED generateKeyPair
```ts
const keySeed = sdk.BaseAPI.KEY_MESSAGE.replace(
"${exchangeAddress}",
LOOPRING_EXPORTED_ACCOUNT.exchangeAddress
).replace("${nonce}", accInfo.nonce.toString());
const eddsaKey = await sdk.generateKeyPair({
web3,
address: accInfo.owner,
keySeed,
walletType: sdk.ConnectorNames.MetaMask,
chainId: sdk.ChainId.GOERLI,
});
console.log("eddsakey:", eddsaKey.sk);
```
Or
```ts
// CUSTOMER_KEY_SEED = "XXXXXX" + " with key nonce: " + "${nonce}";
const keySeed = CUSTOMER_KEY_SEED.replace(
"${nonce}",
accInfo.nonce.toString()
const eddsaKey = await sdk.generateKeyPair({
web3,
address: accInfo.owner,
keySeed,
walletType: sdk.ConnectorNames.MetaMask,
chainId: sdk.ChainId.GOERLI,
});
console.log("eddsakey:", eddsaKey.sk);
```
***
## Step 3. get fee
```ts
const fee = await LoopringAPI.globalAPI.getActiveFeeInfo({
accountId: accInfo.accountId,
});
console.log("fee:", fee);
```
***
## Step 4. updateAccount (active or rest)
```ts
const result = await LoopringAPI.userAPI.updateAccount({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
owner: accInfo.owner,
accountId: accInfo.accountId,
publicKey: {x: eddsaKey.formatedPx, y: eddsaKey.formatedPy},
maxFee: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
volume: fee.fees["LRC"].fee ?? "9400000000000000000",
},
keySeed,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
nonce: accInfo.nonce as number,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
isHWAddr: false,
});
const {accInfo: updateAccountInfo} =
await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log(
"updateAccount Result: ",
result,
"updateAccountInfo:",
updateAccountInfo
);
```
================================================
FILE: docs/js_sdk/account/fee.md
================================================
# Fee
Loopring have 2 get Fee api:
- getOffchainFeeAmt:
- `ORDER`,
- `FFCHAIN_WITHDRAWAL`,
- `UPDATE_ACCOUNT`,
- `TRANSFER`,
- `FAST_OFFCHAIN_WITHDRAWAL`,
- `OPEN_ACCOUNT`,
- `AMM_EXIT`,
- `DEPOSIT`,
- `AMM_JOIN`,
- getNFTOffchainFeeAmt:
- `NFT_MINT`,
- `NFT_WITHDRAWAL`,
- `NFT_TRANSFER`,
- `NFT_DEPLOY`,
***
## Fee: updateAccount
```ts
// Step 1. get account info
console.log(LoopringAPI.exchangeAPI.getAccount);
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.UPDATE_ACCOUNT,
},
apiKey
);
console.log("updateAccount:", response);
```
***
## Fee: transfer
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.TRANSFER,
},
apiKey
);
console.log("transfer:", response);
```
***
## Fee: withdraw
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
tokenSymbol: TOKEN_INFO.tokenMap.LRC.symbol,
requestType: sdk.OffchainFeeReqType.OFFCHAIN_WITHDRAWAL,
},
apiKey
);
console.log("withdraw:", response);
```
***
## Fee: fastWithdraw
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.FAST_OFFCHAIN_WITHDRAWAL,
tokenSymbol: TOKEN_INFO.tokenMap.LRC.symbol,
},
apiKey
);
console.log("fastWithdraw:", response);
```
***
## Fee: order
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.ORDER,
tokenSymbol: TOKEN_INFO.tokenMap.LRC.symbol,
},
apiKey
);
console.log("order:", response);
```
***
## Fee: amm_exit
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.AMM_EXIT,
},
apiKey
);
console.log("amm_exit:", response);
```
***
## Fee: amm_join
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.AMM_JOIN,
},
apiKey
);
console.log("amm_join:", response);
```
***
## Fee: NFT Transfer
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainNFTFeeReqType.NFT_TRANSFER,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
},
apiKey
);
console.log("NFTTransfer:", response);
```
***
## Fee: NFT Withdrawal
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
return {accInfo, eddsaKey, apiKey};
const response = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
requestType: sdk.OffchainNFTFeeReqType.NFT_WITHDRAWAL,
},
apiKey
);
console.log("NFTWithdrawal:", response);
```
## Fee: NFT Mint
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
const response = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainNFTFeeReqType.NFT_MINT,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress
},
apiKey
);
console.log("NFTWithdrawal:", response);
```
## Fee: NFT Deploy
```ts
// Step 1. get account info
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount(
{owner: LOOPRING_EXPORTED_ACCOUNT.address,});
console.log("accInfo:", accInfo);
// Step 2. eddsaKey
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
// Step 3. get apikey
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
const response = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainNFTFeeReqType.NFT_DEPLOY,
},
apiKey
);
console.log("NFTWithdrawal:", response);
```
***
## getActiveFeeInfo without apikey & accountId
```ts
const response = await LoopringAPI.globalAPI.getActiveFeeInfo({});
```
***
## getActiveFeeInfo without apikey with accountId
```ts
const response = await
LoopringAPI.globalAPI.getActiveFeeInfo({accountId: LOOPRING_EXPORTED_ACCOUNT.accountId});
```
================================================
FILE: docs/js_sdk/account/historyRecord.md
================================================
# User Actions History
For check account Actions, more detail such as filters please read SDK interface or API .
***
## getUserTrades
```ts
async () => {
const result = await LoopringAPI.userAPI.getUserTrades(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
offset: 0,
limit: 20,
fillTypes: sdk.TradesFillTypes.dex,
},
apiKey
);
console.log("getUserTrades:", result);
```
***
## getUserTxs
```ts
async () => {
const result = await LoopringAPI.userAPI.getUserTxs(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
types: [
sdk.UserTxTypes.DEPOSIT,
sdk.UserTxTypes.TRANSFER,
sdk.UserTxTypes.ONCHAIN_WITHDRAWAL,
],
},
apiKey
);
console.log("getUserTxs:", result);
```
***
## getUserNFTTransactionHistory
```ts
async () => {
const result = await LoopringAPI.userAPI.getUserNFTTransactionHistory(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
types: [
sdk.UserNFTTxTypes.DEPOSIT,
sdk.UserNFTTxTypes.TRANSFER,
sdk.UserNFTTxTypes.WITHDRAW,
sdk.UserNFTTxTypes.MINT,
],
},
apiKey
);
console.log("getUserNFTTransactionHistory:", result);
```
***
## getOrders
```ts
async () => {
const result = await LoopringAPI.userAPI.getOrders(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
orderTypes: sdk.OrderType.LimitOrder,
},
apiKey
);
console.log("getOrders:", result);
```
================================================
FILE: docs/js_sdk/account/signature.md
================================================
# Signature
Loopring SDK support EOA (EOA hardware wallet) & Loopring Smart wallet Signature
- For Browser extension, Dapp, Hardware wallet we only support for EOA
- For Loopring Smart wallet (App), Provider gateway only can be walletConnect
## Follow is the provider gateway we inject & test:
- MetaMask (Ledger, Trezor)
- WalletConnect (Authereum, Loopring Smart wallet )
- Coinbase
- [Coming soon...](https://desk.zoho.com/portal/loopring/en/newticket)
## For signature:
For eth_sign signing types (eth_sign, personal_sign, v1, v3, v4)
### EOA:
- For Browser
extension ([More information: signing-data](https://docs.metamask.io/guide/signing-data.html#a-brief-history))
+ common EOA we use the `v4` signature and `web3.eth.personal.ecRecover` validate signature
+ when `v4` signature is failed for any step, we will try `personal_sign` and `web3.eth.personal.ecRecover` validate
signature
- For Dapp
+ when loopring Dex is inside Dapp Webview & connect by `window.ethereum`, we remove the `web3.eth.personal.ecRecover`
validate
### Loopring Smart wallet:
- For Smart wallet we send `eth_signTypedData` by walletConnect & validate
ABI.Contracts.ContractWallet.encodeInputs `isValidSignature(bytes32,bytes)`
### Loopring Counterfactual wallet:
- signature is same as Smart wallet
- But ecRecover is by
walletOwner, `const {walletOwner} = await LoopringAPI.exchangeAPI.getCounterFactualInfo({ accountId: LOOPRING_EXPORTED_ACCOUNT.accountIdCF, });`
> ❗ when add `SigSuffix` `02|03` ( follow EIP712 + `02`, personal_sign + `03`)
>- for `v4` ecdsaSignature the result signature should + `SigSuffix.Suffix02`;
>- for `personal_sign` ecdsaSignature the result signature should + `SigSuffix.Suffix03`;
***
## generateKeyPair
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
const result = await signatureKeyPairMock(accInfo);
console.log(result.sk);
```
***
## getEcDSASig: eth_signTypedData_v4
```ts
// test case is not allow brock by Mock provider
const result = await sdk.getEcDSASig(
web3,
testTypedData,
LOOPRING_EXPORTED_ACCOUNT.address,
sdk.GetEcDSASigType.HasDataStruct,
sdk.ChainId.GOERLI,
LOOPRING_EXPORTED_ACCOUNT.accountId,
"",
sdk.ConnectorNames.Unknown
);
console.log("getEcDSASig:eth_signTypedData_v4",
result,
"ecdsaSig+sdk.SigSuffix.Suffix02",
result.ecdsaSig + sdk.SigSuffix.Suffix02
);
```
***
## getEcDSASig: personalSign(WithoutDataStruct--Hardware wallet)
```ts
const result = await sdk.getEcDSASig(
web3,
testTypedData,
LOOPRING_EXPORTED_ACCOUNT.address,
sdk.GetEcDSASigType.WithoutDataStruct,
sdk.ChainId.GOERLI,
LOOPRING_EXPORTED_ACCOUNT.accountId,
"",
sdk.ConnectorNames.Unknown
);
console.log(
"getEcDSASig:WithoutDataStruct(personalSign)",
result,
"ecdsaSig+sdk.SigSuffix.Suffix03",
result.ecdsaSig + sdk.SigSuffix.Suffix03
);
```
***
## getEcDSASig: personalSign(Contract)
```ts
// test case is not allow brock by Mock provider
const result = await sdk.getEcDSASig(
web3,
testTypedData,
LOOPRING_EXPORTED_ACCOUNT.address,
sdk.GetEcDSASigType.Contract,
sdk.ChainId.GOERLI,
LOOPRING_EXPORTED_ACCOUNT.accountId,
"",
sdk.ConnectorNames.Unknown
);
console.log(
"getEcDSASig:personalSign(Contract)",
result
);
```
## Validate signature
[github: src/api/base_api.ts#personalSign](https://github.com/Loopring/loopring_sdk/blob/2c79c1837114f4f383e2d292de3da4b2dac02252/src/api/base_api.ts#L549)
```
export async function personalSign(
web3: any,
account: string | undefined,
pwd: string,
msg: string,
walletType: ConnectorNames,
chainId: ChainId,
accountId?: number,
counterFactualInfo?: CounterFactualInfo,
isMobile?: boolean
) {
if (!account) {
return { error: "personalSign got no account" };
}
return new Promise((resolve) => {
try {
web3.eth.personal.sign(
msg,
account,
pwd,
async function (err: any, result: any) {
if (!err) {
// Valid:1. counter Factual signature Valid
if (counterFactualInfo && accountId) {
myLog("fcWalletValid counterFactualInfo accountId:");
const fcValid = await fcWalletValid(
web3,
account,
msg,
result,
accountId,
chainId,
counterFactualInfo
);
if (fcValid.result) {
resolve({
sig: result,
counterFactualInfo: fcValid.counterFactualInfo,
});
return;
}
}
// Valid: 2. webview directory signature Valid
if (
(window?.ethereum?.isImToken || window?.ethereum?.isMetaMask) &&
isMobile &&
// Mobile directory connect will sign ConnectorNames as MetaMask only
walletType === ConnectorNames.MetaMask
) {
const address: string[] = await window.ethereum?.request({
method: "eth_requestAccounts",
});
if (
address?.find(
(item) => item.toLowerCase() === account.toLowerCase()
)
) {
return resolve({ sig: result });
}
}
// Valid: 3. EOA signature Valid by ecRecover
const valid: any = await ecRecover(web3, account, msg, result);
if (valid.result) {
return resolve({ sig: result });
}
// Valid: 4. contractWallet signature Valid `isValidSignature(bytes32,bytes)`
const walletValid2: any = await contractWalletValidate32(
web3,
account,
msg,
result
);
if (walletValid2.result) {
return resolve({ sig: result });
}
// Valid: 5. counter Factual signature Valid when no counterFactualInfo
if (accountId) {
const fcValid = await fcWalletValid(
web3,
account,
msg,
result,
accountId,
chainId
);
if (fcValid.result) {
return resolve({
sig: result,
counterFactualInfo: fcValid.counterFactualInfo,
});
}
}
// Valid: 6. myKeyValid Valid again
const myKeyValid: any = await mykeyWalletValid(
web3,
account,
msg,
result
);
if (myKeyValid.result) {
return resolve({ sig: result });
}
// Valid: Error cannot pass personalSign Valid
// eslint-disable-next-line no-console
console.log(
"web3.eth.personal.sign Valid, valid 5 ways, all failed!"
);
return resolve({
error: "web3.eth.personal.sign Valid, valid 5 ways, all failed!",
});
} else {
return resolve({
error: "personalSign err before Validate:" + err,
});
}
}
);
} catch (reason) {
resolve({ error: reason });
}
});
}
```
================================================
FILE: docs/js_sdk/account/wallet_api.md
================================================
# Whitelisted User Part
##### 1) getUserAssets
```javascript
// step 1. get account info
const request: GetUserAssetsRequest = {
wallet: "0xeF041462825bFdF79b2f1f02A70b2753cB5b1516",
offset: 10,
limit: 10,
};
const response = await api.getUserAssets(request);
```
##### 2) getTokenPrices
```javascript
// step 1. get account info
const request: GetTokenPricesRequest = {
token: "0xdac17f958d2ee523a2206206994597c13d831ec7",
};
const response = await api.getTokenPrices(request);
```
##### 3) getLatestTokenPrices
```javascript
// step 1. get account info
const response = await api.getLatestTokenPrices();
```
================================================
FILE: docs/js_sdk/account/whitelisted_user_api.md
================================================
# Whitelisted User Part
## submitInternalTransfer
```javascript
// step 1. get account info
let addressWhitlisted = "0x35405E1349658BcA12810d0f879Bf6c5d89B512C";
let eddkeyWhitelisted =
"0x27a5b716c7309a30703ede3f1a218cdec857e424a31543f8a658e7d2208db33";
const { accInfo } = await exchange.getAccount({ owner: addressWhitlisted });
console.log("accInfo:", accInfo);
const { exchangeInfo } = await exchange.getExchangeInfo();
// step 2 get apikey
const request: GetUserApiKeyRequest = {
accountId: accInfo.accountId,
};
const { apiKey } = await userApi.getUserApiKey(request, eddkeyWhitelisted);
console.log("apiKey:", apiKey);
// step 3 get storageId
const request2: GetNextStorageIdRequest = {
accountId: accInfo.accountId,
sellTokenId: 1,
};
const storageId = await userApi.getNextStorageId(request2, apiKey);
// step 4 transfer
const request3: OriginTransferRequestV3 = {
exchange: exchangeInfo.exchangeAddress,
payerAddr: addressWhitlisted,
payerId: accInfo.accountId,
payeeAddr: "0xb6AdaC3e924B4985Ad74646FEa3610f14cDFB79c",
payeeId: 0,
storageId: storageId.offchainId,
token: {
tokenId: 1,
volume: "100000000000000000000",
},
maxFee: {
tokenId: 1,
volume: "9400000000000000000",
},
validUntil: VALID_UNTIL,
};
console.log("request3:", request3);
const response = await whitelistedUserApi.submitInternalTransfer(
request3,
eddkeyWhitelisted,
apiKey
);
console.log(response);
```
## submitOffchainWithdraw is unavailable.
TODO: submitOffchainWithdraw example
================================================
FILE: docs/js_sdk/deposit/depositERC20.md
================================================
# Deposit ERC20
Definition: Move user L1 ERC20 assets to Loopring L2
> **All Deposit Method, User should have enough `ETH` pay for the Ethereum Gas (Loopring have no charge, no fee for Deposit).**
Provider will give the Gas Price & Limit, sdk also have a method get gasPrice:
`const gasPrice = (await LoopringAPI.exchangeAPI.getGasPrice() ).gasPrice;`
# ETH
***
## Step 1. getNonce
```ts
const nonce = await sdk.getNonce(web3, LOOPRING_EXPORTED_ACCOUNT.address);
console.log(
`deposit: ${TOKEN_INFO.tokenMap.ETH.symbol}-${LOOPRING_EXPORTED_ACCOUNT.tradeETHValue}, gasPrice: ${LOOPRING_EXPORTED_ACCOUNT.gasPrice}, `
);
```
***
## Step 2. deposit
```ts
const response = await sdk.deposit(
web3,
LOOPRING_EXPORTED_ACCOUNT.address,
LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
TOKEN_INFO.tokenMap.ETH,
LOOPRING_EXPORTED_ACCOUNT.tradeETHValue,
0,
LOOPRING_EXPORTED_ACCOUNT.gasPrice,
LOOPRING_EXPORTED_ACCOUNT.gasLimit,
sdk.ChainId.GOERLI,
nonce,
true
);
console.log(`nonce: ${nonce} deposit_ETH: `, response);
```
# ERC20
***
## Step 1. getAllowances
```ts
const {tokenAllowances} = await LoopringAPI.exchangeAPI.getAllowances({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
token: [TOKEN_INFO.tokenMap.LRC.address],
});
if (
tokenAllowances.has(TOKEN_INFO.tokenMap.LRC.address) &&
Number(tokenAllowances.get(TOKEN_INFO.tokenMap.LRC.address)) <
LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue
) {
const nonce = await web3.eth.getTransactionCount(
LOOPRING_EXPORTED_ACCOUNT.address
);
await sdk.approveMax(
web3,
LOOPRING_EXPORTED_ACCOUNT.address,
TOKEN_INFO.tokenMap.LRC.address, // LRC address {tokenIdMap} = getTokens(); tokenIdMap['LRC']
LOOPRING_EXPORTED_ACCOUNT.depositAddress, //{exchangeInfo} = getExchangeInfo() exchangeInfo.depositAddress
LOOPRING_EXPORTED_ACCOUNT.gasPrice,
LOOPRING_EXPORTED_ACCOUNT.gasLimit,
sdk.ChainId.GOERLI,
nonce,
true
);
}
```
***
## Step 2. getNonce
```ts
const nonce = await sdk.getNonce(web3, LOOPRING_EXPORTED_ACCOUNT.address);
console.log(
`deposit: ${TOKEN_INFO.tokenMap.LRC.symbol}-${LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue}, gasPrice: ${LOOPRING_EXPORTED_ACCOUNT.gasPrice}, `
);
```
***
## Step 3. deposit
```ts
const response = await sdk.deposit(
web3,
LOOPRING_EXPORTED_ACCOUNT.address,
LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
TOKEN_INFO.tokenMap.LRC,
sdk
.toBig(LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue)
.div("1e" + TOKEN_INFO.tokenMap.LRC.decimals)
.toNumber(),
0,
LOOPRING_EXPORTED_ACCOUNT.gasPrice,
LOOPRING_EXPORTED_ACCOUNT.gasLimit,
sdk.ChainId.GOERLI,
nonce,
true
);
console.log(`nonce: ${nonce} deposit_LRC: `, response);
```
================================================
FILE: docs/js_sdk/deposit/depositNFT.md
================================================
# Deposit NFT
Definition: Move user L1 NFT assets to Loopring L2
> **All Deposit Method, User should have enough `ETH` pay for the Ethereum Gas (Loopring have no charge, no fee for Deposit).**
***
## Step 1. getNFTBalance & getEthBalances
```ts
const {ethBalance} = await LoopringAPI.exchangeAPI.getEthBalances({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
const nftBalance = await LoopringAPI.nftAPI.getNFTBalance({
web3,
account: LOOPRING_EXPORTED_ACCOUNT.address,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId,
nftType: sdk.NFTType.ERC1155,
});
```
***
## Step 2. isApprovedForAll
```ts
const isApprovedForAll = await LoopringAPI.nftAPI.isApprovedForAll({
web3,
from: LOOPRING_EXPORTED_ACCOUNT.address,
exchangeAddress: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
nftType: sdk.NFTType.ERC1155, // todo: sdk.NFTType.ERC721
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
});
console.log(`check is approveNFT`, isApprovedForAll);
```
***
## Step 3. approveNFT All
```ts
if (!isApprovedForAll) {
const nonce = await sdk.getNonce(
web3,
LOOPRING_EXPORTED_ACCOUNT.address
);
const approveNFT = await LoopringAPI.nftAPI.approveNFT({
web3,
from: LOOPRING_EXPORTED_ACCOUNT.address,
depositAddress: LOOPRING_EXPORTED_ACCOUNT.depositAddress,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
nftType: sdk.NFTType.ERC1155, // todo: sdk.NFTType.ERC721
gasPrice: LOOPRING_EXPORTED_ACCOUNT.gasPrice,
gasLimit: LOOPRING_EXPORTED_ACCOUNT.gasLimit,
chainId: sdk.ChainId.GOERLI,
nonce,
sendByMetaMask: true,
});
console.log(`nonce: ${nonce} approveNFT: ${approveNFT?.result}`);
}
```
***
## Step 3. nonce
```ts
const nonce = await sdk.getNonce(web3, LOOPRING_EXPORTED_ACCOUNT.address);
console.log(
`deposit: NFT, gasPrice: ${LOOPRING_EXPORTED_ACCOUNT.gasPrice}, `
);
```
***
## Step 4. depositNFT
```ts
const response = await LoopringAPI.nftAPI.depositNFT({
web3,
from: LOOPRING_EXPORTED_ACCOUNT.address,
exchangeAddress: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
nftType: sdk.NFTType.ERC1155, // todo: sdk.NFTType.ERC721
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
nftId: LOOPRING_EXPORTED_ACCOUNT.nftId,
amount: 2, // todo:when sdk.NFTType.ERC721 amount: 1,
gasPrice: LOOPRING_EXPORTED_ACCOUNT.gasPrice,
gasLimit: LOOPRING_EXPORTED_ACCOUNT.gasLimit + 100000,
chainId: sdk.ChainId.GOERLI,
nonce,
sendByMetaMask: true,
});
console.log(`nonce: ${nonce} deposit NFT ERC1155: `, response);
```
================================================
FILE: docs/js_sdk/erc20Trade/orderERC20.md
================================================
# Order ERC20
***
> ### mini-order
To support small quantity trading, we introduce an additional concept "tradeCost", which is the minimum gas fee when a trade transaction is uplink to Ethereum.
Let's take LRC-ETH trading as an example.
Below are the steps -
**1) Query api/v3/exchange/tokens to get the dust value of orderAmounts for both LRC and ETH.**
The dust value is the minimum value to pass Relayer check. Any amount less than "dust" can't be traded. In this case, we will get both minTOkenLRC and minTokenETH after getting dust value. If user wants to convert LRC to ETH, the set LRC amount can't be less than minTokenLRC and the converted ETH amount can't be less than minTokenETH.
**2) Query api/v3/user/orderUserRateAmount to get the tradeCost value.**
The parameters to call this interface are "accountId" and "market=LRC-ETH". In this example, we will get two tradeCost values for LRC and ETH as tradeCostLRC and tradeCostETH.
**3) Set maxAllowBips = 50% as the maxFeeBips can't exceed 50%**
**4) Set slippage as the slippage value user configured in UI (for example 0.1%)**
**5)Caculate minCostLRC and minCostETH as below**
minCostLRC = max(minTokenLRC, tradeCostLRC/maxAllowBips)
minCostETH = max(minTokenETH, tradeCostETH/maxAllowBips)
**6) Caculate the cost by considering slippage**
minCostLRCSlip = minCostLRC/(1-slippage)
minCostETHSlip = minCostETH/(1-slippage)
**7) Cacluate the minimum quantity user has to set**
tradeCostSellLRC = max(tradeCostSellLRC, minTokenLRC) * 1.1
tradeCostSellETH = max(tradeCostSellETH, minTokenETH) * 1.1
Here we add additonally 10% tolerance.
**8) Caculate the previous minimum token amount per calling api/v3/user/orderUserRateAmount (existing logic)**
This is the threshold to distinguish small quantity trading and normal trading
We will get two values (configSellLRC and configSellETH) which are used for previous trading quantity limit (Per USD 100) caculation
**9)Caculate the new maxFeeBips and start trading**
Let's take LRC->ETH as the example
User inputs the amount of LRC to convert, amount = sellLRC
if sellLRC >= configSellLRC then
// Normal trading case, stay with previous logic
maxFeeBips=63 (the default value for maxFeeBips)
Trade
else if sellLRC < tradeCostSellLRC then
// Really too small to support
Prompt user the amount is too small to support
Exit
else
// This is what we call as small quantity
costRate = Ceil(tradeCostETH/minbuyETH)
maxFeeBips = max(costRate, takerRate)
Trade
End If
> ### price impact update
> 1. sellTokenMinAmount = baseOrderInfo.minAmount from LoopringAPI.userAPI.getMinimumTokenAmt({accountId,marke}, apiKey);
> 2. {output} from sdk.getOutputAmount(input: sellTokenMinAmount, isAtoB: isAtoB,…}).output
> 3. PriceBase = output / sellTokenMinAmount
> 4. tradePrice = calcTradeParams.minReceive / userInputSell
> 5. priceImpact = 1 - tradePrice/PriceBase - 0.005
> 6. If priceImpact < 0 priceImpact = 0
> Else priceImpact
## calculateSwap function
```ts
const calculateSwap = (
sellSymbol = "LRC",
buySymbol = "ETH",
isInputSellToBuy: boolean,
inputValue: number, // user Input value no decimal,
_slippage = 0.1,
// MOCK value
amountMap: { [key: string]: any } = userAmount,
market: string = deepMock.symbol,
// close = ticker.tickers[7],
depth: any = deepMock,
ammPoolSnapshot: sdk.AmmPoolSnapshot = ammPoolSnapshotMock,
tokenMap: sdk.LoopringMap = TokenMapMockSwap,
ammMap: { [key: string]: any } = AMM_MAP
) => {
let calcFor100USDAmount, calcForMinCost, calcForPriceImpact;
if (depth && market && tokenMap) {
const sellToken = tokenMap[sellSymbol];
const buyToken = tokenMap[buySymbol];
const isInputSellOutputBuy = isInputSellToBuy;
let input: any = inputValue;
console.log(
"sellToken: Symbol ",
sellSymbol,
"buyToken: Symbol",
buySymbol,
"is Input Sell Output Buy:",
isInputSellOutputBuy,
"input value",
input
);
input = input === undefined || isNaN(Number(input)) ? 0 : Number(input);
let slippage = sdk.toBig(_slippage).times(100).toString();
let totalFee = undefined;
let feeTakerRate = undefined;
let feeBips = undefined;
let takerRate = undefined;
let buyMinAmtInfo = undefined;
let sellMinAmtInfo = undefined;
let tradeCost = undefined;
let basePrice = undefined;
let maxFeeBips = MAPFEEBIPS;
let minAmt = undefined;
if (amountMap && amountMap[market] && ammMap) {
console.log(`amountMap[${market}]:`, amountMap[market]);
const ammMarket = `AMM-${market}`;
const amountMarket = amountMap[market]; // userAmount from LRC-ETH(Market)
buyMinAmtInfo = amountMarket[buySymbol];
sellMinAmtInfo = amountMarket[sellSymbol];
console.log(
`buyMinAmtInfo: ${market}, ${buySymbol}`,
buyMinAmtInfo,
`sellMinAmtInfo: ${market}, ${sellSymbol}`,
sellMinAmtInfo
);
feeBips = ammMap[ammMarket] ? ammMap[ammMarket].feeBips : 1;
feeTakerRate =
amountMarket[buySymbol] &&
amountMarket[buySymbol].userOrderInfo.takerRate;
tradeCost = amountMarket[buySymbol].tradeCost;
/** @description for charge fee calc, calcFor100USDAmount
* Loopring market consider buyToken value small then max(buyMinAmtInfo.userOrderInfo.minAmount,buyToken.orderAmounts.dust) is a small order,
* the fee will take the Max(tradeCost,userTakeRate)
* use the buyMinAmount Input calc the selltoken value,
* please read Line:321
* **/
const minAmountInput = BigNumber.max(
buyMinAmtInfo.userOrderInfo.minAmount,
buyToken.orderAmounts.dust
)
.div(sdk.toBig(1).minus(sdk.toBig(slippage).div(10000)))
.div("1e" + buyToken.decimals)
.toString();
calcFor100USDAmount = sdk.getOutputAmount({
input: minAmountInput,
sell: sellSymbol,
buy: buySymbol,
isAtoB: false,
marketArr: marketArray as string[],
tokenMap: tokenMap as any,
marketMap: marketMap as any,
depth,
ammPoolSnapshot: ammPoolSnapshot,
feeBips: feeBips ? feeBips.toString() : 1,
takerRate: "0",
slipBips: slippage,
});
console.log(
"buyMinAmtInfo.userOrderInfo.minAmount:",
buyMinAmtInfo.userOrderInfo.minAmount,
`buyMinAmtInfo.userOrderInfo.minAmount, with slippage:${slippage}`,
sdk
.toBig(buyMinAmtInfo.userOrderInfo.minAmount)
.div(sdk.toBig(1).minus(sdk.toBig(slippage).div(10000)))
.toString()
);
/*** calc for Price Impact ****/
const sellMinAmtInput = sdk
.toBig(sellMinAmtInfo.baseOrderInfo.minAmount)
.div("1e" + sellToken.decimals)
.toString();
calcForPriceImpact = sdk.getOutputAmount({
input: sellMinAmtInput,
sell: sellSymbol,
buy: buySymbol,
isAtoB: true,
marketArr: marketArray as string[],
tokenMap: tokenMap as any,
marketMap: marketMap as any,
depth,
ammPoolSnapshot: ammPoolSnapshot,
feeBips: feeBips ? feeBips.toString() : 1,
takerRate: "0",
slipBips: "10",
});
basePrice = sdk.toBig(calcForPriceImpact?.output).div(sellMinAmtInput);
console.log(
"calcForPriceImpact input: ",
sellMinAmtInput,
", output: ",
sdk.toBig(calcForPriceImpact?.output).div(sellMinAmtInput).toNumber(),
", calcForPriceImpact:",
calcForPriceImpact?.amountBOutSlip?.minReceivedVal,
", calcForPriceImpact basePrice: ",
basePrice.toNumber()
);
/**** calc for mini Cost ****/
//minCostBuyToken = max(dustBuyToken, tradeCostETH/maxAllowBips)
const dustToken = buyToken;
let minCostBuyTokenInput = BigNumber.max(
sdk.toBig(tradeCost).times(2), //maxAllowBips = 50% tradeCostETH/50%
dustToken.orderAmounts.dust
);
const tradeCostInput = sdk
.toBig(minCostBuyTokenInput)
.div(sdk.toBig(1).minus(sdk.toBig(slippage).div(10000)))
.div("1e" + dustToken.decimals)
.toString();
console.log(
`tradeCost: ${tradeCost}*2:`,
sdk.toBig(tradeCost).times(2).toString(),
"buyToken.orderAmounts.dust",
buyToken.orderAmounts.dust,
"minCostBuyToken:",
minCostBuyTokenInput.toString(),
`calcForMinCostInput, with slippage:${slippage}`,
sdk
.toBig(minCostBuyTokenInput ?? 0)
.div(sdk.toBig(1).minus(sdk.toBig(slippage).div(10000)))
.toString(),
"calcForMinCost, Input",
tradeCostInput
);
calcForMinCost = sdk.getOutputAmount({
input: tradeCostInput,
sell: sellSymbol,
buy: buySymbol,
isAtoB: false,
marketArr: marketArray as string[],
tokenMap: tokenMap as any,
marketMap: marketMap as any,
depth,
ammPoolSnapshot: ammPoolSnapshot,
feeBips: feeBips ? feeBips.toString() : 1,
takerRate: "0",
slipBips: slippage,
});
//add additionally 10% tolerance for minimum quantity user has to set on sell Token
/**
* @output: minAmt for UI
* this value mini-order Sell token amount (show on the UI for available order check)
* setSellMinAmt(minAmt.toString());
*/
minAmt = BigNumber.max(
sellToken.orderAmounts.dust,
calcForMinCost?.amountS ?? 0
).times(1.1);
console.log(
"UI show mini-order Sell token amount:",
minAmt.toString(),
sdk
.toBig(minAmt)
.div("1e" + sellToken.decimals)
.toString()
);
console.log(
`calcFor100USDAmount.amountS`,
sdk
.toBig(calcFor100USDAmount?.amountS ?? 0)
.div("1e" + sellToken.decimals)
.toString(),
"calcForMinCost.amountS",
sdk
.toBig(calcForMinCost?.amountS ?? 0)
.div("1e" + sellToken.decimals)
.toString()
);
}
const calcTradeParams = sdk.getOutputAmount({
input: input.toString(),
sell: sellSymbol,
buy: buySymbol,
isAtoB: isInputSellOutputBuy,
marketArr: marketArray as string[],
tokenMap: tokenMap as any,
marketMap: marketMap as any,
depth,
ammPoolSnapshot: ammPoolSnapshot,
feeBips: feeBips ? feeBips.toString() : 1,
takerRate: "0", // for new calc miniReceive will minus fee, so takeRate can fix as 0
slipBips: slippage,
});
const minSymbol = buySymbol;
const tradePrice = sdk
.toBig(calcTradeParams?.amountBOutSlip?.minReceivedVal ?? 0)
.div(isInputSellOutputBuy ? input.toString() : calcTradeParams?.output);
const priceImpact = sdk
.toBig(1)
.minus(sdk.toBig(tradePrice).div(basePrice ?? 1))
.minus(0.001);
if (calcTradeParams && priceImpact.gte(0)) {
calcTradeParams.priceImpact = priceImpact.toFixed(4, 1);
} else {
calcTradeParams && (calcTradeParams.priceImpact = "0");
}
console.log(
"calcTradeParams input:",
input.toString(),
", calcTradeParams Price: ",
sdk
.toBig(calcTradeParams?.amountBOutSlip?.minReceivedVal ?? 0)
.div(input.toString())
.toNumber(),
`isAtoB mean isInputSellOutputBuy:${isInputSellOutputBuy}, ${
isInputSellOutputBuy ? input.toString() : calcTradeParams?.output
} tradePrice: `,
tradePrice.toString(),
"basePrice: ",
basePrice?.toString(),
"toBig(tradePrice).div(basePrice)",
sdk
.toBig(tradePrice)
.div(basePrice ?? 1)
.toNumber(),
"priceImpact (1-tradePrice/basePrice) - 0.001",
priceImpact.toNumber(),
"priceImpact view",
calcTradeParams?.priceImpact
);
if (
tradeCost &&
calcTradeParams &&
calcTradeParams.amountBOutSlip?.minReceived &&
feeTakerRate
) {
let value = sdk
.toBig(calcTradeParams.amountBOutSlip?.minReceived)
.times(feeTakerRate)
.div(10000);
console.log(
"input Accounts",
calcTradeParams?.amountS,
"100 U Amount Sell:",
calcFor100USDAmount?.amountS
);
let validAmt = !!(
calcTradeParams?.amountS &&
calcFor100USDAmount?.amountS &&
sdk.toBig(calcTradeParams?.amountS).gte(calcFor100USDAmount.amountS)
);
let totalFeeRaw;
console.log(
`${minSymbol} tradeCost:`,
tradeCost,
"useTakeRate Fee:",
value.toString(),
"calcFor100USDAmount?.amountS:",
calcFor100USDAmount?.amountS,
`is setup minTrade amount, ${calcFor100USDAmount?.amountS}:`,
validAmt
);
if (!validAmt) {
if (sdk.toBig(tradeCost).gte(value)) {
totalFeeRaw = sdk.toBig(tradeCost);
} else {
totalFeeRaw = value;
}
console.log(
"maxFeeBips update for tradeCost before value:",
maxFeeBips,
"totalFeeRaw",
totalFeeRaw.toString()
);
maxFeeBips = Math.ceil(
totalFeeRaw
.times(10000)
.div(calcTradeParams.amountBOutSlip?.minReceived)
.toNumber()
);
console.log("maxFeeBips update for tradeCost after value:", maxFeeBips);
} else {
totalFeeRaw = sdk.toBig(value);
}
/**
* totalFee
*/
totalFee = totalFeeRaw
.div("1e" + tokenMap[minSymbol].decimals)
.toString();
/** @output: UI
* getValuePrecisionThousand(
* totalFeeRaw.div("1e" + tokenMap[minSymbol].decimals).toString(),
* tokenMap[minSymbol].precision,
* tokenMap[minSymbol].precision,
* tokenMap[minSymbol].precision,
* false,
* { floor: true }
* );
*/
tradeCost = sdk
.toBig(tradeCost)
// @ts-ignore
.div("1e" + tokenMap[minSymbol].decimals)
.toString();
/** @output: UI code with precision
* getValuePrecisionThousand(
* sdk
* .toBig(tradeCost)
* .div("1e" + tokenMap[minSymbol].decimals)
* .toString(),
* tokenMap[minSymbol].precision,
* tokenMap[minSymbol].precision,
* tokenMap[minSymbol].precision,
* false,
* { floor: true }
* );
*/
console.log("totalFee view value:", totalFee + " " + minSymbol);
console.log("tradeCost view value:", tradeCost + " " + minSymbol);
}
const minimumReceived = sdk
.toBig(calcTradeParams?.amountBOutSlip?.minReceivedVal ?? 0)
.minus(totalFee ?? 0)
.toString();
console.log("minimumReceived:", minimumReceived);
/** @output: UI code with precision
* getValuePrecisionThousand(
* toBig(calcTradeParams?.amountBOutSlip?.minReceivedVal ?? 0)
* .minus(totalFee)
* .toString(),
* tokenMap[minSymbol].precision,
* tokenMap[minSymbol].precision,
* tokenMap[minSymbol].precision,
* false,
* { floor: true }
* );
*/
let priceImpactView: any = calcTradeParams?.priceImpact
? parseFloat(calcTradeParams?.priceImpact) * 100
: undefined;
console.log("priceImpact view:", priceImpactView + "%");
// @output: UI code with color alert
// const priceImpactObj = getPriceImpactInfo(calcTradeParams);
// const _tradeCalcData: Partial> = {
// priceImpact: priceImpactObj.value.toString(),
// priceImpactColor: priceImpactObj.priceImpactColor,
// minimumReceived: !minimumReceived?.toString().startsWith("-")
// ? minimumReceived
// : undefined,
// fee: totalFee,
// feeTakerRate,
// tradeCost,
// };
console.log(
`isInputSellOutputBuy:${isInputSellOutputBuy}`,
`output ${isInputSellOutputBuy ? "Buy" : "Sell"}`,
calcTradeParams?.output
);
return {
market,
feeBips,
takerRate,
sellMinAmtInfo: sellMinAmtInfo as any,
buyMinAmtInfo: buyMinAmtInfo as any,
totalFee,
maxFeeBips,
feeTakerRate,
tradeCost,
minimumReceived,
calcTradeParams,
minAmt,
};
}
};
```
### Step 1. get apikey & eddsaKey
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
const eddsaKey = await signatureKeyPairMock(accInfo);
apiKey = (
await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
)
).apiKey;
```
### Step Step 2 : storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
sellTokenId: TOKEN_INFO.tokenMap[sell].tokenId,
},
apiKey
);
```
### Step 3. get user AmountMap, which decided user minimum order
```ts
const amountMap = {
[AMM_MARKET]: (
await LoopringAPI.userAPI.getMinimumTokenAmt(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
market: AMM_MAP[AMM_MARKET].market,
},
apiKey
)
).amountMap,
[MARKET]: (
await LoopringAPI.userAPI.getMinimumTokenAmt(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
market: MARKET,
},
apiKey
)
).amountMap,
};
```
### Step 4. depth, ammPoolSnapshot ,tickMap
```ts
const [{depth}, {ammPoolSnapshot}] = await Promise.all([
LoopringAPI.exchangeAPI.getMixDepth({
market: AMM_MAP["AMM-LRC-ETH"].market,
}),
LoopringAPI.ammpoolAPI.getAmmPoolSnapshot({
poolAddress: AMM_MAP["AMM-LRC-ETH"].address,
}),
]);
```
### Step 5. check MinAmt
```ts
let buyMinAmtInfo = (amountMap[AMM_MARKET] ?? amountMap[MARKET])[buy];
let takerRate = buyMinAmtInfo
? buyMinAmtInfo.userOrderInfo.takerRate
: 0;
const minAmountInput = buyMinAmtInfo.userOrderInfo.minAmount;
```
### Step 6. calcTradeParams
```ts
const calcTradeParams = sdk.getOutputAmount({
input: LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
sell,
buy,
isAtoB,
marketArr: ["LRC-ETH", "ETH-USDT", "DAI-USDT", "USDC-ETH"],
tokenMap: TOKEN_INFO.tokenMap,
marketMap: TOKEN_INFO.marketMap,
depth,
ammPoolSnapshot: ammPoolSnapshot,
feeBips: AMM_MAP["AMM-LRC-ETH"].feeBips.toString(),
takerRate: takerRate ? takerRate.toString() : "0",
slipBips: slippage,
});
console.log(
"Buy",
",LRC:",
LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
",minAmountInput LRC:",
minAmountInput,
",ETH:",
calcTradeParams?.amountBOutSlip?.minReceivedVal
);
const response: { hash: string } | any =
await LoopringAPI.userAPI.submitOrder(
{
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
storageId: storageId.orderId,
sellToken: {
tokenId: TOKEN_INFO.tokenMap[sell].tokenId,
volume: calcTradeParams?.amountS as string,
},
buyToken: {
tokenId: TOKEN_INFO.tokenMap[buy].tokenId,
volume: calcTradeParams?.amountBOutSlip.minReceived as string,
},
allOrNone: false,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
maxFeeBips: 63,
fillAmountBOrS: false, // amm only false
tradeChannel: calcTradeParams?.exceedDepth
? sdk.TradeChannel.BLANK
: sdk.TradeChannel.MIXED,
orderType: calcTradeParams?.exceedDepth
? sdk.OrderType.ClassAmm
: sdk.OrderType.TakerOnly,
eddsaSignature: "",
},
eddsaKey.sk,
apiKey
);
console.log("submitOrder", response);
```
***
## ETH-LRC : for Quote to Base
### MOCK Data
> user should had apikey, please check get apikey
```ts
const buy = "ETH",
sell = "LRC",
MARKET = "LRC-ETH",
AMM_MARKET = "AMM-LRC-ETH",
slippage = "50";
const isAtoB = false;
```
### Step 1. get apikey & eddsaKey
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
const eddsaKey = await signatureKeyPairMock(accInfo);
apiKey = (
await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
)
).apiKey;
```
### Step 2. storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
sellTokenId: TOKEN_INFO.tokenMap[sell].tokenId,
},
apiKey
);
```
### Step 3. get user AmountMap, which decided user minimum order
```ts
const amountMap = {
[AMM_MARKET]: (
await LoopringAPI.userAPI.getMinimumTokenAmt(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
market: AMM_MAP[AMM_MARKET].market,
},
apiKey
)
).amountMap,
[MARKET]: (
await LoopringAPI.userAPI.getMinimumTokenAmt(
{
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
market: MARKET,
},
apiKey
)
).amountMap,
};
```
### Step 4. depth, ammPoolSnapshot ,tickMap
```ts
const [{depth}, {ammPoolSnapshot}] = await Promise.all([
LoopringAPI.exchangeAPI.getMixDepth({
market: AMM_MAP["AMM-LRC-ETH"].market,
}),
LoopringAPI.ammpoolAPI.getAmmPoolSnapshot({
poolAddress: AMM_MAP["AMM-LRC-ETH"].address,
}),
]);
```
### Step 5. check MinAmt see log and calc mini receive and ouput value & maxfeeBips & priceImpact
```ts
const { calcTradeParams, maxFeeBips, minimumReceived } = calculateSwap(
sell,
buy,
isAtoB,
10, // user Input value no decimal 10 lrc,
0.1,
//TODO MOCK value
amountMap,
"LRC-ETH",
// close = ticker.tickers[7],
depth,
ammPoolSnapshot,
TOKEN_INFO.tokenMap,
AMM_MAP
);
console.log(
"Buy",
",ETH:",
LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
",minAmountInput ETH:",
minAmountInput,
",LRC:",
calcTradeParams?.amountBOutSlip?.minReceivedVal
);
const response: { hash: string } | any =
await LoopringAPI.userAPI.submitOrder(
{
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
storageId: storageId.orderId,
sellToken: {
tokenId: TOKEN_INFO.tokenMap[sell].tokenId,
volume: calcTradeParams?.amountS as string,
},
buyToken: {
tokenId: TOKEN_INFO.tokenMap[buy].tokenId,
volume: calcTradeParams?.amountBOutSlip.minReceived as string,
},
allOrNone: false,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
maxFeeBips: 63,
fillAmountBOrS: false, // amm only false
tradeChannel: calcTradeParams?.exceedDepth
? sdk.TradeChannel.BLANK
: sdk.TradeChannel.MIXED,
orderType: calcTradeParams?.exceedDepth
? sdk.OrderType.ClassAmm
: sdk.OrderType.TakerOnly,
eddsaSignature: "",
},
eddsaKey.sk,
apiKey
);
console.log("submitOrder", response);
```
### MockSwapData
```ts
//Default config value from getTokens & getMixMarkets
import * as sdk from "../index";
export const marketArray = ["LRC-ETH"];
export const marketMap = {
"LRC-ETH": {
baseTokenId: 1,
enabled: true,
market: "LRC-ETH",
orderbookAggLevels: 5,
precisionForPrice: 6,
quoteTokenId: 0,
status: 3,
isSwapEnabled: true,
createdAt: 1617967800000,
},
};
//v3/mix/depth?level=0&limit=50&market=LRC-ETH
export const deepMock = {
symbol: "LRC-ETH",
version: 23249677,
timestamp: 1655719492365,
mid_price: 0.00033248,
bids: [
{
price: 0.00030689,
amt: "12041160324514792497908",
vol: "3695372332571085210",
amtTotal: "618450503644320209925641",
volTotal: "198539605794234049017",
},
{
price: 0.00030752,
amt: "12016302126785109160251",
vol: "3695372332571085210",
amtTotal: "606409343319805417427733",
volTotal: "194844233461662963807",
},
{
price: 0.00030816,
amt: "11991520826895479525387",
vol: "3695372332571085210",
amtTotal: "594393041193020308267482",
volTotal: "191148861129091878597",
},
{
price: 0.00030881,
amt: "12329062048917727073625",
vol: "3807353312345966580",
amtTotal: "582401520366124828742095",
volTotal: "187453488796520793387",
},
{
price: 0.00030945,
amt: "11941442525358097768419",
vol: "3695372332571085210",
amtTotal: "570072458317207101668470",
volTotal: "183646135484174826807",
},
{
price: 0.0003102,
amt: "3223726000000000000000",
vol: "999999805200000000",
amtTotal: "558131015791849003900051",
volTotal: "179950763151603741597",
},
{
price: 0.0003104,
amt: "11904963012947201084062",
vol: "3695372332571085210",
amtTotal: "554907289791849003900051",
volTotal: "178950763346403741597",
},
{
price: 0.00031072,
amt: "11892800074855146120151",
vol: "3695372332571085210",
amtTotal: "543002326778901802815989",
volTotal: "175255391013832656387",
},
{
price: 0.00031137,
amt: "12227667622887455762012",
vol: "3807353312345966580",
amtTotal: "531109526704046656695838",
volTotal: "171560018681261571177",
},
{
price: 0.00031202,
amt: "11843337524732768607817",
vol: "3695372332571085210",
amtTotal: "518881859081159200933826",
volTotal: "167752665368915604597",
},
{
price: 0.00031266,
amt: "11819088718260537718160",
vol: "3695372332571085210",
amtTotal: "507038521556426432326009",
volTotal: "164057293036344519387",
},
{
price: 0.0003133,
amt: "11794914308461194855590",
vol: "3695372332571085210",
amtTotal: "495219432838165894607849",
volTotal: "160361920703773434177",
},
{
price: 0.00031395,
amt: "12127129883064173669497",
vol: "3807353312345966580",
amtTotal: "483424518529704699752259",
volTotal: "156666548371202348967",
},
{
price: 0.0003146,
amt: "11746060536480763829870",
vol: "3695372332571085210",
amtTotal: "471297388646640526082762",
volTotal: "152859195058856382387",
},
{
price: 0.00031524,
amt: "11722109721002274877424",
vol: "3695372332571085210",
amtTotal: "459551328110159762252892",
volTotal: "149163822726285297177",
},
{
price: 0.0003159,
amt: "12052351993023897680738",
vol: "3807353312345966580",
amtTotal: "447829218389157487375468",
volTotal: "145468450393714211967",
},
{
price: 0.00031655,
amt: "11673707113082743010268",
vol: "3695372332571085210",
amtTotal: "435776866396133589694730",
volTotal: "141661097081368245387",
},
{
price: 0.00031699,
amt: "615000000000000000000",
vol: "194954999999999949",
amtTotal: "424103159283050846684462",
volTotal: "137965724748797160177",
},
{
price: 0.00031719,
amt: "11649977142974837964110",
vol: "3695372332571085210",
amtTotal: "423488159283050846684462",
volTotal: "137770769748797160228",
},
{
price: 0.00031784,
amt: "11626319455782556517212",
vol: "3695372332571085210",
amtTotal: "411838182140076008720352",
volTotal: "134075397416226075018",
},
{
price: 0.0003185,
amt: "11953964321007990024755",
vol: "3807353312345966580",
amtTotal: "400211862684293452203140",
volTotal: "130380025083654989808",
},
{
price: 0.00031934,
amt: "11571837200722111826863",
vol: "3695372332571085210",
amtTotal: "388257898363285462178385",
volTotal: "126572671771309023228",
},
{
price: 0.00031979,
amt: "11555429099390519988949",
vol: "3695372332571085210",
amtTotal: "376686061162563350351522",
volTotal: "122877299438737938018",
},
{
price: 0.00032,
amt: "300000000000000000000",
vol: "96000000000000000",
amtTotal: "365130632063172830362573",
volTotal: "119181927106166852808",
},
{
price: 0.00032044,
amt: "11532058329906875386268",
vol: "3695372332571085210",
amtTotal: "364830632063172830362573",
volTotal: "119085927106166852808",
},
{
price: 0.0003211,
amt: "11857145662584872212009",
vol: "3807353312345966580",
amtTotal: "353298573733265954976305",
volTotal: "115390554773595767598",
},
{
price: 0.00032176,
amt: "11484826169666631095087",
vol: "3695372332571085210",
amtTotal: "341441428070681082764296",
volTotal: "111583201461249801018",
},
{
price: 0.00032241,
amt: "11461669156035188891431",
vol: "3695372332571085210",
amtTotal: "329956601901014451669209",
volTotal: "107887829128678715808",
},
{
price: 0.00032294,
amt: "22266286128259997368320",
vol: "7190674442260284416",
amtTotal: "318494932744979262777778",
volTotal: "104192456796107630598",
},
{
price: 0.00032307,
amt: "11784846142950476791130",
vol: "3807353312345966580",
amtTotal: "296228646616719265409458",
volTotal: "97001782353847346182",
},
{
price: 0.00032333,
amt: "31042151210389998665728",
vol: "10037169172367501312",
amtTotal: "284443800473768788618328",
volTotal: "93194429041501379602",
},
{
price: 0.00032354,
amt: "19082720347089998446592",
vol: "6174214168300968960",
amtTotal: "253401649263378789952600",
volTotal: "83157259869133878290",
},
{
price: 0.00032373,
amt: "11414868337793698506576",
vol: "3695372332571085210",
amtTotal: "234318928916288791506008",
volTotal: "76983045700832909330",
},
{
price: 0.00032438,
amt: "11391922481177051106050",
vol: "3695372332571085210",
amtTotal: "222904060578495092999432",
volTotal: "73287673368261824120",
},
{
price: 0.00032503,
amt: "11369045742792164433715",
vol: "3695372332571085210",
amtTotal: "211512138097318041893382",
volTotal: "69592301035690738910",
},
{
price: 0.00032512,
amt: "7101984539779999465472",
vol: "2308997213573273600",
amtTotal: "200143092354525877459667",
volTotal: "65896928703119653700",
},
{
price: 0.0003257,
amt: "11689707913498419991686",
vol: "3807353312345966580",
amtTotal: "193041107814745877994195",
volTotal: "63587931489546380100",
},
{
price: 0.00032636,
amt: "11322810509578528739940",
vol: "3695372332571085210",
amtTotal: "181351399901247458002509",
volTotal: "59780578177200413520",
},
{
price: 0.00032702,
amt: "11300141532643741902081",
vol: "3695372332571085210",
amtTotal: "170028589391668929262569",
volTotal: "56085205844629328310",
},
{
price: 0.00032709,
amt: "1511898000000000000000",
vol: "494541835799999898",
amtTotal: "158728447859025187360488",
volTotal: "52389833512058243100",
},
{
price: 0.00032767,
amt: "11277540564700637960582",
vol: "3695372332571085210",
amtTotal: "157216549859025187360488",
volTotal: "51895291676258243202",
},
{
price: 0.00032838,
amt: "11594014901713773553177",
vol: "3807353312345966580",
amtTotal: "145939009294324549399906",
volTotal: "48199919343687157992",
},
{
price: 0.00032899,
amt: "11232203228917728004878",
vol: "3695372332571085210",
amtTotal: "134344994392610775846729",
volTotal: "44392566031341191412",
},
{
price: 0.00032959,
amt: "31100749000000000753664",
vol: "10250806870400000000",
amtTotal: "123112791163693047841851",
volTotal: "40697193698770106202",
},
{
price: 0.00032965,
amt: "11209805337410429375102",
vol: "3695372332571085210",
amtTotal: "92012042163693047088187",
volTotal: "30446386828370106202",
},
{
price: 0.00033031,
amt: "11187474373893810850495",
vol: "3695372332571085210",
amtTotal: "80802236826282617713085",
volTotal: "26751014495799020992",
},
{
price: 0.00033098,
amt: "11503202913004006553102",
vol: "3807353312345966580",
amtTotal: "69614762452388806862590",
volTotal: "23055642163227935782",
},
{
price: 0.00033099,
amt: "26515451000000002129920",
vol: "8776614281000000512",
amtTotal: "58111559539384800309488",
volTotal: "19248288850881969202",
},
{
price: 0.0003313,
amt: "20499506300000000802816",
vol: "6791486437190000640",
amtTotal: "31596108539384798179568",
volTotal: "10471674569881968690",
},
{
price: 0.00033165,
amt: "11096602239384797376752",
vol: "3680188132691968050",
amtTotal: "11096602239384797376752",
volTotal: "3680188132691968050",
},
],
bids_prices: [
0.00030689, 0.00030752, 0.00030816, 0.00030881, 0.00030945, 0.0003102,
0.0003104, 0.00031072, 0.00031137, 0.00031202, 0.00031266, 0.0003133,
0.00031395, 0.0003146, 0.00031524, 0.0003159, 0.00031655, 0.00031699,
0.00031719, 0.00031784, 0.0003185, 0.00031934, 0.00031979, 0.00032,
0.00032044, 0.0003211, 0.00032176, 0.00032241, 0.00032294, 0.00032307,
0.00032333, 0.00032354, 0.00032373, 0.00032438, 0.00032503, 0.00032512,
0.0003257, 0.00032636, 0.00032702, 0.00032709, 0.00032767, 0.00032838,
0.00032899, 0.00032959, 0.00032965, 0.00033031, 0.00033098, 0.00033099,
0.0003313, 0.00033165,
],
bids_amtTotals: [
"618450503644320209925641",
"606409343319805417427733",
"594393041193020308267482",
"582401520366124828742095",
"570072458317207101668470",
"558131015791849003900051",
"554907289791849003900051",
"543002326778901802815989",
"531109526704046656695838",
"518881859081159200933826",
"507038521556426432326009",
"495219432838165894607849",
"483424518529704699752259",
"471297388646640526082762",
"459551328110159762252892",
"447829218389157487375468",
"435776866396133589694730",
"424103159283050846684462",
"423488159283050846684462",
"411838182140076008720352",
"400211862684293452203140",
"388257898363285462178385",
"376686061162563350351522",
"365130632063172830362573",
"364830632063172830362573",
"353298573733265954976305",
"341441428070681082764296",
"329956601901014451669209",
"318494932744979262777778",
"296228646616719265409458",
"284443800473768788618328",
"253401649263378789952600",
"234318928916288791506008",
"222904060578495092999432",
"211512138097318041893382",
"200143092354525877459667",
"193041107814745877994195",
"181351399901247458002509",
"170028589391668929262569",
"158728447859025187360488",
"157216549859025187360488",
"145939009294324549399906",
"134344994392610775846729",
"123112791163693047841851",
"92012042163693047088187",
"80802236826282617713085",
"69614762452388806862590",
"58111559539384800309488",
"31596108539384798179568",
"11096602239384797376752",
],
bids_volTotals: [
"198539605794234049017",
"194844233461662963807",
"191148861129091878597",
"187453488796520793387",
"183646135484174826807",
"179950763151603741597",
"178950763346403741597",
"175255391013832656387",
"171560018681261571177",
"167752665368915604597",
"164057293036344519387",
"160361920703773434177",
"156666548371202348967",
"152859195058856382387",
"149163822726285297177",
"145468450393714211967",
"141661097081368245387",
"137965724748797160177",
"137770769748797160228",
"134075397416226075018",
"130380025083654989808",
"126572671771309023228",
"122877299438737938018",
"119181927106166852808",
"119085927106166852808",
"115390554773595767598",
"111583201461249801018",
"107887829128678715808",
"104192456796107630598",
"97001782353847346182",
"93194429041501379602",
"83157259869133878290",
"76983045700832909330",
"73287673368261824120",
"69592301035690738910",
"65896928703119653700",
"63587931489546380100",
"59780578177200413520",
"56085205844629328310",
"52389833512058243100",
"51895291676258243202",
"48199919343687157992",
"44392566031341191412",
"40697193698770106202",
"30446386828370106202",
"26751014495799020992",
"23055642163227935782",
"19248288850881969202",
"10471674569881968690",
"3680188132691968050",
],
bids_amtTotal: "618450503644320209925641",
bids_volTotal: "198539605794234049017",
asks: [
{
price: 0.00033331,
amt: "26466599999999999737856",
vol: "8821317780000000000",
amtTotal: "26466599999999999737856",
volTotal: "8821317780000000000",
},
{
price: 0.0003336,
amt: "33880800000000001048576",
vol: "11302634880000000000",
amtTotal: "60347400000000000786432",
volTotal: "20123952660000000000",
},
{
price: 0.00033371,
amt: "11142477800817399987988",
vol: "3718356266910774549",
amtTotal: "71489877800817400774420",
volTotal: "23842308926910774549",
},
{
price: 0.00033439,
amt: "11410124103475892197402",
vol: "3815340399218399705",
amtTotal: "82900001904293292971822",
volTotal: "27657649326129174254",
},
{
price: 0.00033501,
amt: "39334199999999992922112",
vol: "13176957000000000000",
amtTotal: "122234201904293285893934",
volTotal: "40834606326129174254",
},
{
price: 0.00033506,
amt: "11052295534472154230337",
vol: "3703108939445215718",
amtTotal: "133286497438765440124271",
volTotal: "44537715265574389972",
},
{
price: 0.00033572,
amt: "11030456322913363709718",
vol: "3703093651997124565",
amtTotal: "144316953761678803833989",
volTotal: "48240808917571514537",
},
{
price: 0.00033638,
amt: "11008681778518069039476",
vol: "3703078409816047858",
amtTotal: "155325635540196872873465",
volTotal: "51943887327387562395",
},
{
price: 0.000337,
amt: "7188173683760000139264",
vol: "2422342649690282496",
amtTotal: "162513809223956873012729",
volTotal: "54366229977077844891",
},
{
price: 0.00033706,
amt: "11319571950108460741855",
vol: "3815277012711042502",
amtTotal: "173833381174065333754584",
volTotal: "58181506989788887393",
},
{
price: 0.00033773,
amt: "10964670732513311387215",
vol: "3703047602083844528",
amtTotal: "184798051906578645141799",
volTotal: "61884554591872731921",
},
{
price: 0.00033848,
amt: "10943090596879450870975",
vol: "3703921209677097807",
amtTotal: "195741142503458096012774",
volTotal: "65588475801549829728",
},
{
price: 0.00033893,
amt: "31086310012520000126976",
vol: "10535772189443278848",
amtTotal: "226827452515978096139750",
volTotal: "76124247990993108576",
},
{
price: 0.00033907,
amt: "10921574108084085072499",
vol: "3703126650187824910",
amtTotal: "237749026624062181212249",
volTotal: "79827374641180933486",
},
{
price: 0.00033934,
amt: "22061773569720001232896",
vol: "7486221625413088256",
amtTotal: "259810800193782182445145",
volTotal: "87313596266594021742",
},
{
price: 0.00033975,
amt: "11230093484570461106334",
vol: "3815326678720011608",
amtTotal: "271040893678352643551479",
volTotal: "91128922945314033350",
},
{
price: 0.00034042,
amt: "10878083875111520592396",
vol: "3703095772122414389",
amtTotal: "281918977553464164143875",
volTotal: "94832018717436447739",
},
{
price: 0.00034061,
amt: "19029641944770000453632",
vol: "6481686342808109056",
amtTotal: "300948619498234164597507",
volTotal: "101313705060244556795",
},
{
price: 0.00034109,
amt: "10856758733704286197912",
vol: "3703080631272015254",
amtTotal: "311805378231938450795419",
volTotal: "105016785691516572049",
},
{
price: 0.00034176,
amt: "10835496238854895014260",
vol: "3703065534900672185",
amtTotal: "322640874470793345809679",
volTotal: "108719851226417244234",
},
{
price: 0.00034244,
amt: "11141671799245742102774",
vol: "3815263899323431057",
amtTotal: "333782546270039087912453",
volTotal: "112535115125740675291",
},
{
price: 0.00034312,
amt: "10792518633700140402041",
vol: "3703035020801012310",
amtTotal: "344575064903739228314494",
volTotal: "116238150146541687601",
},
{
price: 0.00034379,
amt: "10771444484888773263977",
vol: "3703020058155356239",
amtTotal: "355346509388628001578471",
volTotal: "119941170204697043840",
},
{
price: 0.00034447,
amt: "11075875299655656611658",
vol: "3815217183808722096",
amtTotal: "366422384688283658190129",
volTotal: "123756387388505765936",
},
{
price: 0.00034515,
amt: "10728847020295584791583",
vol: "3702989813955495075",
amtTotal: "377151231708579242981712",
volTotal: "127459377202461261011",
},
{
price: 0.00034582,
amt: "10707958999548106661900",
vol: "3702974983460764366",
amtTotal: "387859190708127349643612",
volTotal: "131162352185922025377",
},
{
price: 0.00034649,
amt: "10687131919892014508549",
vol: "3702960196234208540",
amtTotal: "398546322628019364152161",
volTotal: "134865312382156233917",
},
{
price: 0.00034718,
amt: "10989265205593964126102",
vol: "3815155690641938295",
amtTotal: "409535587833613328278263",
volTotal: "138680468072798172212",
},
{
price: 0.00034809,
amt: "10645033127428079147754",
vol: "3705341637309061860",
amtTotal: "420180620961041407426017",
volTotal: "142385809710107234072",
},
{
price: 0.00034854,
amt: "10624389276271547157517",
vol: "3703021892850000724",
amtTotal: "430805010237312954583534",
volTotal: "146088831602957234796",
},
{
price: 0.00034922,
amt: "10603805418694082174472",
vol: "3703007072472544949",
amtTotal: "441408815656007036758006",
volTotal: "149791838675429779745",
},
{
price: 0.00034991,
amt: "10903667052066234563130",
vol: "3815203952623454269",
amtTotal: "452312482708073271321136",
volTotal: "153607042628053234014",
},
{
price: 0.00035059,
amt: "10562197544947631702552",
vol: "3702977114803447505",
amtTotal: "462874680253020903023688",
volTotal: "157310019742856681519",
},
{
price: 0.00035127,
amt: "10541794072549149477757",
vol: "3702962424303320597",
amtTotal: "473416474325570052501445",
volTotal: "161012982167160002116",
},
{
price: 0.00035196,
amt: "10839964505283434387937",
vol: "3815158086789770653",
amtTotal: "484256438830853486889382",
volTotal: "164828140253949772769",
},
{
price: 0.00035265,
amt: "10500550295026604907238",
vol: "3702932728783504366",
amtTotal: "494756989125880091796620",
volTotal: "168531072982733277135",
},
{
price: 0.00035333,
amt: "10480325106225117837726",
vol: "3702918166647567295",
amtTotal: "505237314232105209634346",
volTotal: "172233991149380844430",
},
{
price: 0.00035401,
amt: "10460158295034970667917",
vol: "3702903646543510388",
amtTotal: "515697472527140180302263",
volTotal: "175936894795924354818",
},
{
price: 0.0003547,
amt: "10756101480844670483856",
vol: "3815097705412174743",
amtTotal: "526453574007984850786119",
volTotal: "179751992501336529561",
},
{
price: 0.000355,
amt: "2879934000000000000000",
vol: "1022376570000000000",
amtTotal: "529333508007984850786119",
volTotal: "180774369071336529561",
},
{
price: 0.00035539,
amt: "10419392212908119384882",
vol: "3702874294964379056",
amtTotal: "539752900220892970171001",
volTotal: "184477243366300908617",
},
{
price: 0.00035607,
amt: "10399400937404414745812",
vol: "3702859901246016389",
amtTotal: "550152301158297384916813",
volTotal: "188180103267546925006",
},
{
price: 0.00035675,
amt: "10379467141462596339294",
vol: "3702845548912938279",
amtTotal: "560531768299759981256107",
volTotal: "191882948816459863285",
},
{
price: 0.00035781,
amt: "10673207910450037674513",
vol: "3818930797471485007",
amtTotal: "571204976210210018930620",
volTotal: "195701879613931348292",
},
{
price: 0.00035815,
amt: "10339171406073422424770",
vol: "3702919927697518809",
amtTotal: "581544147616283441355390",
volTotal: "199404799541628867101",
},
{
price: 0.00035883,
amt: "10319410450646547471681",
vol: "3702905502200057190",
amtTotal: "591863558066929988827071",
volTotal: "203107705043828924291",
},
{
price: 0.00035887,
amt: "1253970999999999795200",
vol: "449999996860000051",
amtTotal: "593117529066929988622271",
volTotal: "203557705040688924342",
},
{
price: 0.00035952,
amt: "10299706093934767574874",
vol: "3702891118019657590",
amtTotal: "603417235160864756197145",
volTotal: "207260596158708581932",
},
{
price: 0.00036,
amt: "491031000000000000000",
vol: "176771040000000000",
amtTotal: "603908266160864756197145",
volTotal: "207437367198708581932",
},
{
price: 0.00036022,
amt: "10591268908994289896668",
vol: "3815084938649532411",
amtTotal: "614499535069859046093813",
volTotal: "211252452137358114343",
},
],
asks_prices: [
0.00033331, 0.0003336, 0.00033371, 0.00033439, 0.00033501, 0.00033506,
0.00033572, 0.00033638, 0.000337, 0.00033706, 0.00033773, 0.00033848,
0.00033893, 0.00033907, 0.00033934, 0.00033975, 0.00034042, 0.00034061,
0.00034109, 0.00034176, 0.00034244, 0.00034312, 0.00034379, 0.00034447,
0.00034515, 0.00034582, 0.00034649, 0.00034718, 0.00034809, 0.00034854,
0.00034922, 0.00034991, 0.00035059, 0.00035127, 0.00035196, 0.00035265,
0.00035333, 0.00035401, 0.0003547, 0.000355, 0.00035539, 0.00035607,
0.00035675, 0.00035781, 0.00035815, 0.00035883, 0.00035887, 0.00035952,
0.00036, 0.00036022,
],
asks_amtTotals: [
"26466599999999999737856",
"60347400000000000786432",
"71489877800817400774420",
"82900001904293292971822",
"122234201904293285893934",
"133286497438765440124271",
"144316953761678803833989",
"155325635540196872873465",
"162513809223956873012729",
"173833381174065333754584",
"184798051906578645141799",
"195741142503458096012774",
"226827452515978096139750",
"237749026624062181212249",
"259810800193782182445145",
"271040893678352643551479",
"281918977553464164143875",
"300948619498234164597507",
"311805378231938450795419",
"322640874470793345809679",
"333782546270039087912453",
"344575064903739228314494",
"355346509388628001578471",
"366422384688283658190129",
"377151231708579242981712",
"387859190708127349643612",
"398546322628019364152161",
"409535587833613328278263",
"420180620961041407426017",
"430805010237312954583534",
"441408815656007036758006",
"452312482708073271321136",
"462874680253020903023688",
"473416474325570052501445",
"484256438830853486889382",
"494756989125880091796620",
"505237314232105209634346",
"515697472527140180302263",
"526453574007984850786119",
"529333508007984850786119",
"539752900220892970171001",
"550152301158297384916813",
"560531768299759981256107",
"571204976210210018930620",
"581544147616283441355390",
"591863558066929988827071",
"593117529066929988622271",
"603417235160864756197145",
"603908266160864756197145",
"614499535069859046093813",
],
asks_volTotals: [
"8821317780000000000",
"20123952660000000000",
"23842308926910774549",
"27657649326129174254",
"40834606326129174254",
"44537715265574389972",
"48240808917571514537",
"51943887327387562395",
"54366229977077844891",
"58181506989788887393",
"61884554591872731921",
"65588475801549829728",
"76124247990993108576",
"79827374641180933486",
"87313596266594021742",
"91128922945314033350",
"94832018717436447739",
"101313705060244556795",
"105016785691516572049",
"108719851226417244234",
"112535115125740675291",
"116238150146541687601",
"119941170204697043840",
"123756387388505765936",
"127459377202461261011",
"131162352185922025377",
"134865312382156233917",
"138680468072798172212",
"142385809710107234072",
"146088831602957234796",
"149791838675429779745",
"153607042628053234014",
"157310019742856681519",
"161012982167160002116",
"164828140253949772769",
"168531072982733277135",
"172233991149380844430",
"175936894795924354818",
"179751992501336529561",
"180774369071336529561",
"184477243366300908617",
"188180103267546925006",
"191882948816459863285",
"195701879613931348292",
"199404799541628867101",
"203107705043828924291",
"203557705040688924342",
"207260596158708581932",
"207437367198708581932",
"211252452137358114343",
],
asks_amtTotal: "614499535069859046093813",
asks_volTotal: "211252452137358114343",
};
//v3/amm/balance?poolAddress=0xfEB069407df0e1e4B365C10992F1bc16c078E34b
export const ammPoolSnapshot: sdk.AmmPoolSnapshot = {
poolName: "AMM-LRC-ETH",
poolAddress: "0x18920d6e6fb7ebe057a4dd9260d6d95845c95036",
pooled: [
{
tokenId: 1,
volume: "11198097977488137000000000",
},
{
tokenId: 0,
volume: "3725368050950874300000",
},
],
lp: {
tokenId: 83,
volume: "34138981282200",
},
risky: false,
};
//v3/amm/balance?poolAddress=0xfEB069407df0e1e4B365C10992F1bc16c078E34b
export const ammPool = {
poolName: "AMM-LRC-ETH",
poolAddress: "0x18920d6e6fb7ebe057a4dd9260d6d95845c95036",
pooled: [
{ tokenId: 1, volume: "11215027899488137000000000" },
{ tokenId: 0, volume: "3720052711450874300000" },
],
lp: { tokenId: 83, volume: "34141365482200" },
risky: false,
};
//v3/mix/ticker?market=LRC-ETH
export const ticker = {
tickers: [
[
"COMBINE-LRC-ETH",
"1655625077879",
"2429371927000000000000000",
"814655525450000000000",
"0.00035052",
"0.00035222",
"0.00032201",
"0.00033367",
"772",
"",
"",
"",
"",
],
],
};
//v3/user/orderUserRateAmount?accountId=10427&market=LRC-ETH
export const userAmount = {
"LRC-ETH": {
LRC: {
tokenSymbol: "LRC",
baseOrderInfo: {
minAmount: "266240681576144834931",
makerRate: 0,
takerRate: 10,
},
userOrderInfo: {
minAmount: "266240681576144834931",
makerRate: 0,
takerRate: 10,
},
tradeCost: "231046501539337141",
},
ETH: {
tokenSymbol: "ETH",
baseOrderInfo: {
minAmount: "86598080986525339",
makerRate: 0,
takerRate: 10,
},
userOrderInfo: {
minAmount: "86598080986525339",
makerRate: 0,
takerRate: 10,
},
tradeCost: "75150737796750",
},
},
"AMM-LRC-ETH": {
LRC: {
tokenSymbol: "LRC",
baseOrderInfo: {
minAmount: "266240681576144834931",
makerRate: 0,
takerRate: 10,
},
userOrderInfo: {
minAmount: "266240681576144834931",
makerRate: 0,
takerRate: 10,
},
tradeCost: "231046501539337141",
},
ETH: {
tokenSymbol: "ETH",
baseOrderInfo: {
minAmount: "86598080986525339",
makerRate: 0,
takerRate: 10,
},
userOrderInfo: {
minAmount: "86598080986525339",
makerRate: 0,
takerRate: 10,
},
tradeCost: "75150737796750",
},
},
};
export const TokenMapMockSwap = {
ETH: {
type: "ETH",
tokenId: 0,
symbol: "ETH",
name: "Ethereum",
address: "0x0000000000000000000000000000000000000000",
decimals: 18,
precision: 7,
precisionForOrder: 3,
orderAmounts: {
minimum: "1700000000000000",
maximum: "1000000000000000000000",
dust: "200000000000000",
},
luckyTokenAmounts: {
minimum: "50000000000000",
maximum: "1000000000000000000000",
dust: "50000000000000",
},
fastWithdrawLimit: "100000000000000000000",
gasAmounts: {
distribution: "85000",
deposit: "110000",
},
enabled: true,
isLpToken: false,
tradePairs: ["LRC"],
},
LRC: {
type: "ERC20",
tokenId: 1,
symbol: "LRC",
name: "Loopring",
address: "0xbbbbca6a901c926f240b89eacb641d8aec7aeafd",
decimals: 18,
precision: 3,
precisionForOrder: 3,
orderAmounts: {
minimum: "5000000000000000000",
maximum: "5000000000000000000000000",
dust: "5000000000000000000",
},
luckyTokenAmounts: {
minimum: "50000000000000000",
maximum: "5000000000000000000000000",
dust: "50000000000000000",
},
fastWithdrawLimit: "750000000000000000000000",
gasAmounts: {
distribution: "101827",
deposit: "150000",
},
enabled: true,
isLpToken: false,
tradePairs: ["ETH"],
},
};
export const MAPFEEBIPS = 63;
```
================================================
FILE: docs/js_sdk/exchange/ammpool_api.md
================================================
# AmmPool API
## getAmmPoolConf
```typescript
const { ammpools, pairs } = await api.getAmmPoolConf();
```
## getAmmPoolUserRewards
```typescript
const response: any = await api.getAmmPoolUserRewards({
owner: acc.accountId.toString(),
});
```
## getAmmPoolActivityRules
```typescript
const response: any = await api.getAmmPoolActivityRules();
```
## getAmmPoolStats
```typescript
const response: any = await api.getAmmPoolStats();
```
## getAmmPoolSnapshot
```typescript
const request: GetAmmPoolSnapshotRequest = {
poolAddress,
};
const response = await api.getAmmPoolSnapshot(request, acc.apiKey);
```
## getAmmPoolBalances
```typescript
const response = await api.getAmmPoolBalances();
```
## getAmmPoolTrades
```typescript
const request: GetAmmPoolTradesRequest = {
ammPoolAddress: poolAddress,
};
const response = await api.getAmmPoolTrades(request);
```
## getUserAmmPoolTxs
```typescript
const request: GetUserAmmPoolTxsRequest = {
accountId: acc.accountId,
};
const response = await api.getUserAmmPoolTxs(request, acc.apiKey);
```
## joinAmmPool
```typescript
const request2: JoinAmmPoolRequest = {
owner: acc.address,
poolAddress,
joinTokens: {
pooled: [
{ tokenId: "1", volume: "1000000000000000000000" },
{ tokenId: "0", volume: "1000000000000000000" },
],
minimumLp: { tokenId: "4", volume: "100000" },
},
storageIds: [storageId_1.offchainId, storageId.offchainId],
fee: "1000000000000000000",
};
const patch: AmmPoolRequestPatch = {
chainId: ChainId.GORLI,
ammName: "LRCETH-Pool",
poolAddress,
eddsaKey: acc.eddsaKey,
};
const response = await api.joinAmmPool(request2, patch, acc.apiKey);
```
## exitAmmPool
```typescript
const request2: ExitAmmPoolRequest = {
owner: acc.address,
poolAddress,
exitTokens: {
unPooled: [
{ tokenId: "1", volume: "1000000000000000000000" },
{ tokenId: "0", volume: "1000000000000000000" },
],
burned: { tokenId: "4", volume: "100000" },
},
storageId: storageId_1.offchainId,
maxFee: "1000000000000000000",
};
const patch: AmmPoolRequestPatch = {
chainId: ChainId.GORLI,
ammName: "LRCETH-Pool",
poolAddress,
eddsaKey: acc.eddsaKey,
};
const response = await api.exitAmmPool(request2, patch, acc.apiKey);
```
================================================
FILE: docs/js_sdk/exchange/exchange.md
================================================
# Loopring Exchange
Definition: Loopring Dex Main API for get Exchange Information, L2 Block, ERC20 Token Information, AMM Information,
Market Config and so on static and dynamic information
***
## getExchangeInfo
```ts
const response = await LoopringAPI.exchangeAPI.getExchangeInfo();
console.log(response);
```
***
## getTokens
```ts
const {tokensMap, coinMap, totalCoinMap, idIndex, addressIndex} =
await LoopringAPI.exchangeAPI.getTokens();
console.log(
"tokenMap:",
tokensMap,
coinMap,
totalCoinMap,
idIndex,
addressIndex
);
```
***
## getMixMarkets
```ts
const {markets, pairs, tokenArr, tokenArrStr, marketArr, marketArrStr} =
await LoopringAPI.exchangeAPI.getMixMarkets();
console.log("markets:", markets);
console.log("pairs:", pairs);
console.log("tokenArr:", tokenArr);
console.log("tokenArrStr:", tokenArrStr);
console.log("marketArr", marketArr);
console.log("marketArrStr", marketArrStr);
```
***
## getAmmPoolConf
```ts
const response = await LoopringAPI.ammpoolAPI.getAmmPoolConf();
console.log(response.ammpools);
console.log(response.pairs);
```
***
## getAvailableBroker
```ts
const result = await LoopringAPI.exchangeAPI.getAvailableBroker();
console.log(result);
```
***
## getTokenPrices
```ts
const response = await LoopringAPI.walletAPI.getTokenPrices({
token: TOKEN_INFO.tokenMap.LRC.address,
});
console.log(response);
```
***
## getLatestTokenPrices
```ts
const response = await LoopringAPI.walletAPI.getLatestTokenPrices();
console.log(response);
```
***
## getLatestTokenPrices_cny
```ts
const response = await LoopringAPI.walletAPI.getLatestTokenPrices({
currency: sdk.Currency.cny,
});
console.log(response);
```
***
## getWithdrawalAgents
```ts
const response = await LoopringAPI.exchangeAPI.getWithdrawalAgents({
tokenId: 1,
amount: "10000000000",
});
console.log(response);
```
***
## getCandlestick
```ts
const response = await LoopringAPI.exchangeAPI.getCandlestick({
market: "LRC-ETH",
interval: sdk.TradingInterval.min15,
limit: 96,
});
console.log(response);
```
***
## getAccountServices
```ts
const response = await LoopringAPI.exchangeAPI.getAccountServices({});
console.log(response);
```
***
## getExchangeFeeInfo
```ts
const response = await LoopringAPI.exchangeAPI.getExchangeFeeInfo();
console.log(response);
console.log(
response.raw_data[sdk.VipCatergory.ORDERBOOK_TRADING_FEES_STABLECOIN]
);
```
***
## getProtocolPortrait
```ts
const response = await LoopringAPI.exchangeAPI.getProtocolPortrait();
console.log(response);
```
***
## getRecommendedMarkets
```ts
const response = await LoopringAPI.exchangeAPI.getRecommendedMarkets();
console.log(response);
```
***
## getGasPrice
```ts
const response = await LoopringAPI.exchangeAPI.getGasPrice();
console.log(response);
```
***
## getGasPriceRange
```ts
const response = await LoopringAPI.exchangeAPI.getGasPriceRange();
console.log(response);
```
***
## getMarketTrades
```ts
const response = await LoopringAPI.exchangeAPI.getMarketTrades({
market: "ETH-USDT",
});
console.log(response.raw_data.trades);
```
***
## getRelayerCurrentTime
```ts
const response = await LoopringAPI.exchangeAPI.getRelayerCurrentTime();
console.log(response);
```
***
## getFiatPriceUSD
```ts
const response = await LoopringAPI.exchangeAPI.getFiatPrice({
legal: "USD",
});
console.log(response);
```
***
## getFiatPriceCNY
```ts
const response = await LoopringAPI.exchangeAPI.getFiatPrice({
legal: "CNY",
});
console.log(response);
```
***
## getMarkets
```ts
const response = await LoopringAPI.exchangeAPI.getMarkets();
console.log(response);
console.log(response.pairs.LRC.tokenList);
console.log(
"hasMarket LRC-ETH:",
sdk.hasMarket(response.marketArr, "LRC-ETH")
);
console.log(
"market 1:",
sdk.getExistedMarket(response.marketArr, "LRC", "ETH")
);
console.log(
"market 2:",
sdk.getExistedMarket(response.marketArr, "ETH", "LRC")
);
```
***
## getDepth
```ts
const response = await LoopringAPI.exchangeAPI.getDepth({
market: "LRC-ETH",
});
console.log(response);
```
***
## getTicker
```ts
const response = await LoopringAPI.exchangeAPI.getTicker({
market: "LRC-ETH",
});
console.log(response);
```
***
## getAllTickers
```ts
const response = await LoopringAPI.exchangeAPI.getAllTickers();
console.log(response);
```
***
## getMixDepth
```ts
const response = await LoopringAPI.exchangeAPI.getMixDepth({
market: "LRC-ETH",
});
console.log(response);
console.log(response.depth.bids);
```
***
## getMixTicker
```ts
const response = await LoopringAPI.exchangeAPI.getMixTicker({
market: ["LRC-ETH", "ETH-USDC", "DAI-USDT"].join(","),
});
console.log(response.tickMap["DAI-USDT"]);
```
***
## getAllMixTickers
```ts
const response: any = await LoopringAPI.exchangeAPI.getAllMixTickers();
console.log(response?.tickMap);
```
***
## getMixCandlestickAMM
```ts
const response = await LoopringAPI.exchangeAPI.getMixCandlestick({
market: "AMM-LRC-ETH",
interval: sdk.TradingInterval.min15,
limit: 96,
});
console.log(response);
```
});
================================================
FILE: docs/js_sdk/exchange/webSocket.md
================================================
#WebSocket Command
Definition: Loopring L2 websocket
### Loopring L2 websocket topic type:
WsTopicType
- `account`
- `orderbook`
- `mixorder`
- `mixtrade`
- `ticker`
- `candlestick`
- `ammpool`
## getWsKey (required by account related socket)
```ts
const response = await LoopringAPI.wsAPI.getWsKey();
console.log(response);
```
## getOrderBookArg
```ts
const arg1 = sdk.getMixOrderArg({ market: "LRC-ETH", level: 50 });
console.log(arg1);
const arg2 = sdk.getOrderBookArg({
market: "LRC-ETH",
level: 50,
count: 40,
snapshot: false,
});
console.log(arg2);
```
================================================
FILE: docs/js_sdk/transfer/transferERC20.md
================================================
# Transfer ERC20
Definition: Send ERC20 tokens to other account on Loopring L2,
> trade value should with decimals `sdk.toBig(value).times("1e" + TOKEN_INFO.tokenMap.LRC.decimals)`
***
## Step 1. get account Info
const { exchangeInfo } = await LoopringAPI.exchangeAPI.getExchangeInfo();
const LOOPRING_EXPORTED_ACCOUNT.exchangeAddress = exchangeInfo;
```ts
const { accInfo } = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
```
***
## Step 2. get eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. get apikey
```ts
const { apiKey } = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
const { userBalances } = await LoopringAPI.userAPI.getUserBalances(
{ accountId: LOOPRING_EXPORTED_ACCOUNT.accountId, tokens: "" },
apiKey
);
```
***
## Step 4. get storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
},
apiKey
);
console.log("storageId:", storageId);
```
***
## Step 5. get fee
```ts
const fee = await LoopringAPI.userAPI.getOffchainFeeAmt({
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.TRANSFER,
}, apiKey);
console.log("fee:", fee);
```
***
## Step 6. transfer
```ts
const transferResult = await LoopringAPI.userAPI.submitInternalTransfer({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
payerAddr: accInfo.owner,
payerId: accInfo.accountId,
payeeAddr: LOOPRING_EXPORTED_ACCOUNT.address2,
payeeId: LOOPRING_EXPORTED_ACCOUNT.accountId2,
storageId: storageId.offchainId,
token: {
tokenId: TOKEN_INFO.tokenMap.LRC.tokenId,
volume: LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
},
maxFee: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
volume: fee.fees["LRC"].fee ?? "9400000000000000000",
},
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Trezor,
eddsaKey: eddsaKey.sk,
apiKey: apiKey,
});
console.log("transferResult:", transferResult);
```
================================================
FILE: docs/js_sdk/transfer/transferNFT.md
================================================
# Transfer NFT
Definition: Send NFT to other account on Loopring L2
***
## Step 1. get account Info
```ts
const { exchangeInfo } = await LoopringAPI.exchangeAPI.getExchangeInfo();
const LOOPRING_EXPORTED_ACCOUNT.exchangeAddress = exchangeInfo;
const { accInfo } = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
```
***
## Step 2. get eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. get apiKey
```ts
const { apiKey } = await LoopringAPI.userAPI.getUserApiKey({
accountId: accInfo.accountId}, eddsaKey.sk
);
console.log("apiKey:", apiKey);
const { userNFTBalances } = await LoopringAPI.userAPI.getUserNFTBalances(
{ accountId: accInfo.accountId, limit: 20 },
apiKey
);
```
***
##Step 4. get storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId({
accountId: accInfo.accountId,
sellTokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
}, apiKey);
```
***
## Step 5. get fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt({
accountId: accInfo.accountId,
requestType: sdk.OffchainNFTFeeReqType.NFT_TRANSFER,
amount: "0",
}, apiKey);
console.log("fee:", fee);
```
***
## Step 6. Transfer NFT
```ts
const transferResult = await LoopringAPI.userAPI.submitNFTInTransfer({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
fromAccountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
fromAddress: LOOPRING_EXPORTED_ACCOUNT.address,
toAccountId: 0, // toAccountId is not required, input 0 as default
toAddress: LOOPRING_EXPORTED_ACCOUNT.address2,
token: {
tokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
amount: "1",
},
maxFee: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
amount: fee.fees["LRC"].fee ?? "9400000000000000000",
},
storageId: storageId.offchainId,
validUntil: LOOPRING_EXPORTED_ACCOUNT.validUntil,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.Unknown,
eddsaKey: eddsaKey.sk,
apiKey,
});
console.log("transfer Result:", transferResult);
```
================================================
FILE: docs/js_sdk/withdraw/withdrawERC20.md
================================================
# Withdraw ERC20
Definition: Loopring L2 withdraw ERC20 to Ethereum L1,
> trade value should with decimals `sdk.toBig(value).times("1e" + TOKEN_INFO.tokenMap.LRC.decimals)`
***
## Step 1. getAccount
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
```
##Step 2. eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. apiKey
```ts
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey({
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
```
***
## Step 4. storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId({
accountId: accInfo.accountId,
sellTokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
},
apiKey
);
console.log("storageId:", storageId);
```
***
## Step 5. fee
```ts
const fee = await LoopringAPI.userAPI.getOffchainFeeAmt({
accountId: accInfo.accountId,
requestType: sdk.OffchainFeeReqType.OFFCHAIN_WITHDRAWAL,
tokenSymbol: TOKEN_INFO.tokenMap["LRC"].symbol,
},
apiKey
);
console.log("fee:", fee);
```
***
## Step 6. withdraw
```ts
const response = await LoopringAPI.userAPI.submitOffchainWithdraw({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
counterFactualInfo: undefined,
fastWithdrawalMode: false,
hashApproved: "",
maxFee: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
volume: fee.fees["LRC"].fee ?? "9400000000000000000",
},
minGas: 0,
owner: LOOPRING_EXPORTED_ACCOUNT.address,
to: LOOPRING_EXPORTED_ACCOUNT.address,
storageId: 0,
token: {
tokenId: TOKEN_INFO.tokenMap.LRC.tokenId,
volume: LOOPRING_EXPORTED_ACCOUNT.tradeLRCValue.toString(),
},
validUntil: 0,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.MetaMask,
eddsaKey: eddsaKey.sk,
apiKey,
});
console.log("response:", response);
```
================================================
FILE: docs/js_sdk/withdraw/withdrawNFT.md
================================================
# Withdraw NFT
Definition: Loopring L2 withdraw NFT to Ethereum L1
***
## Step 1. getAccount
```ts
const {accInfo} = await LoopringAPI.exchangeAPI.getAccount({
owner: LOOPRING_EXPORTED_ACCOUNT.address,
});
console.log("accInfo:", accInfo);
```
***
## Step 2. eddsaKey
```ts
const eddsaKey = await signatureKeyPairMock(accInfo);
console.log("eddsaKey:", eddsaKey.sk);
```
***
## Step 3. apiKey
```ts
const {apiKey} = await LoopringAPI.userAPI.getUserApiKey(
{
accountId: accInfo.accountId,
},
eddsaKey.sk
);
console.log("apiKey:", apiKey);
```
***
## Step 4. storageId
```ts
const storageId = await LoopringAPI.userAPI.getNextStorageId(
{
accountId: accInfo.accountId,
sellTokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
},
apiKey
);
console.log("storageId:", storageId);
//Step 5. getUserNFTBalances
const {userNFTBalances} = await LoopringAPI.userAPI.getUserNFTBalances(
{accountId: LOOPRING_EXPORTED_ACCOUNT.accountId},
apiKey
);
const tokenInfo = userNFTBalances.find(
(item) =>
item.tokenAddress?.toLowerCase() ===
LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress.toLowerCase() &&
item.nftId &&
web3.utils.hexToNumberString(item.nftId) ===
LOOPRING_EXPORTED_ACCOUNT.nftTokenId.toString()
);
```
***
## Step 5. fee
```ts
const fee = await LoopringAPI.userAPI.getNFTOffchainFeeAmt(
{
accountId: accInfo.accountId,
requestType: sdk.OffchainNFTFeeReqType.NFT_WITHDRAWAL,
tokenAddress: LOOPRING_EXPORTED_ACCOUNT.nftTokenAddress,
deployInWithdraw:
tokenInfo?.deploymentStatus === DEPLOYMENT_STATUS.NOT_DEPLOYED, // when token is not deploy the fee is diff
},
apiKey
);
console.log("fee:", fee);
```
***
## Step 6. withdraw
```ts
const response = await LoopringAPI.userAPI.submitNFTWithdraw({
request: {
exchange: LOOPRING_EXPORTED_ACCOUNT.exchangeAddress,
accountId: LOOPRING_EXPORTED_ACCOUNT.accountId,
counterFactualInfo: undefined,
hashApproved: "",
maxFee: {
tokenId: TOKEN_INFO.tokenMap["LRC"].tokenId,
amount: fee.fees["LRC"].fee ?? "9400000000000000000",
},
minGas: 0,
owner: LOOPRING_EXPORTED_ACCOUNT.address,
to: LOOPRING_EXPORTED_ACCOUNT.address,
storageId: 0,
token: {
tokenId: LOOPRING_EXPORTED_ACCOUNT.nftTokenId,
nftData: LOOPRING_EXPORTED_ACCOUNT.nftData,
amount: "1",
},
validUntil: 0,
},
web3,
chainId: sdk.ChainId.GOERLI,
walletType: sdk.ConnectorNames.MetaMask,
eddsaKey: eddsaKey.sk,
apiKey,
});
console.log("response:", response);
```
================================================
FILE: jest.config.cjs
================================================
module.exports = {
preset: 'ts-jest',
verbose: true,
transform: {
'^.+\\.(js|jsx|ts|tsx)$': './node_modules/babel-jest',
},
"moduleNameMapper": {
"axios": "axios/dist/node/axios.cjs"
},
"transformIgnorePatterns": ["node_modules\/(?!axios)"]
}
================================================
FILE: package.json
================================================
{
"name": "@loopring-web/loopring-sdk",
"version": "3.9.23",
"author": "Loopring Dev Team",
"description": "Loopring SDK",
"license": "SEE LICENSE IN LICENSE",
"main": "dist/index.cjs",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"source": "src/index.ts",
"exports": {
"require": "./dist/index.cjs",
"import": "./dist/index.esm.js"
},
"files": [
"dist",
"!tests",
"!__snapshots__",
"!*.test.js",
"!*.test.js.map",
"!*.test.ts",
"!*.test.d.ts"
],
"type": "module",
"prettier": {
"semi": false,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all",
"jsxSingleQuote": true,
"bracketSpacing": true
},
"dependencies": {
"@ethereumjs/common": "^2.4.0",
"@ethereumjs/tx": "^3.3.0",
"@types/jsbn": "^1.2.30",
"axios": "^1.4.0",
"bignumber.js": "9.1.1",
"blake-hash": "^2.0.0",
"blake2b": "^2.1.3",
"bn.js": "^5.2.1",
"buffer": "^6.0.3",
"core-js": "3",
"crypto-js": "4.1.1",
"eth-sig-util": "2.3.0",
"ethereumjs-abi": "0.6.8",
"ethereumjs-util": "5.2.0",
"ethers": "^5.7.2",
"js-sha3": "^0.8.0",
"js-sha512": "^0.8.0",
"jsbn": "^1.1.0",
"multiformats": "^12.0.1",
"web-encoding": "^1.1.5",
"web3": "1.10.0"
},
"build": {
"files": [
"src/**/*",
"node_modules/**/*"
],
"publish": {
"provider": "custom",
"repo": "https://github.com/Loopring/loopring_sdk",
"owner": "Loopring Dev Team"
}
},
"scripts": {
"build": "rimraf dist && NODE_ENV=production cross-env BABEL_ENV=prod rollup -c --bundleConfigAsCjs",
"start": "NODE_ENV=development rollup -c --bundleConfigAsCjs -w",
"test": "tsdx test",
"test_calc": "tsdx test",
"lint": "eslint . --fix --quiet --ext .ts",
"prepublishDev": "NODE_ENV=dev build build",
"prepublishOnly": "yarn build",
"doc": "typedoc --out docs/detail src\\/",
"proxy": "export http_proxy=http://127.0.0.1:1087;export https_proxy=http://127.0.0.1:1087;",
"build-book": "npx honkit build",
"cp-file-doc": "cp README.md ./docs/README.md; cp Changelog.md ./docs/Changelog.md; ",
"build-b": "npm run doc; npm run build-book",
"serve-book": "npx honkit serve",
"deploy-book": "gh-pages -d _book"
},
"browserslist": {
"production": [
"last 2 chrome version",
"last 2 firefox version",
"last 2 safari version"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
},
"devDependencies": {
"@babel/plugin-transform-optional-chaining": "^7.23.4",
"@babel/plugin-transform-typescript": "^7.14.6",
"@babel/preset-env": "^7.14.5",
"@babel/preset-typescript": "^7.14.5",
"@microsoft/tsdoc": "^0.13.2",
"@rollup/plugin-babel": "^6.0.3",
"@rollup/plugin-commonjs": "^25.0.2",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.1.0",
"@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.4",
"@rollup/plugin-typescript": "^11.0.0",
"@types/bignumber.js": "^5.0.0",
"@types/blake2b": "^2.1.0",
"@types/classnames": "^2.2.11",
"@types/collections": "^5.1.2",
"@types/crypto-js": "^4.0.1",
"@types/eth-sig-util": "^2.1.0",
"@types/ethereumjs-abi": "^0.6.3",
"@types/ethereumjs-tx": "^2.0.0",
"@types/ethereumjs-util": "5.2.0",
"@types/jest": "^26.0.23",
"@types/lodash": "^4.14.168",
"@types/mocha": "^8.2.2",
"@types/ms": "^0.7.31",
"@types/node": "^12.0.0",
"@types/node-fetch": "^3.0.3",
"@types/request": "^2.48.6",
"@types/request-promise": "^4.1.48",
"@types/truffle-privatekey-provider": "^1.1.0",
"@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.6.0",
"async": "^3.2.0",
"babel-jest": "^26.6.3",
"babel-plugin-import": "^1.13.3",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"chai": "^4.3.4",
"cross-env": "^7.0.3",
"gh-pages": "^3.2.3",
"honkit": "^3.6.20",
"jest": "^26.6.3",
"mocha": "^8.3.2",
"prettier": "2.8.8",
"request-promise": "^4.2.6",
"rimraf": "^5.0.1",
"rollup": "^3.25.3",
"rollup-plugin-typescript2": "^0.36.0",
"truffle-privatekey-provider": "1.5.0",
"ts-node": "^10.0.0",
"tslib": "^2.3.0",
"typedoc": "^0.22.9",
"typedoc-plugin-markdown": "^3.11.7",
"typescript": "^5.1.3"
}
}
================================================
FILE: rollup.config.mjs
================================================
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel'
import terser from '@rollup/plugin-terser'
import json from '@rollup/plugin-json';
import { nodeResolve } from '@rollup/plugin-node-resolve';
import typescript from 'rollup-plugin-typescript2';
import { builtinModules } from 'module';
import pkg from './package.json' assert { type: "json" };
// console.log('pkg',pkg)
module.exports = {
input: pkg.source,
output: [
{ file:pkg.main , format: 'cjs', sourcemap: true, plugins: [terser()], },
{ file: pkg.module , format: 'esm', sourcemap: true, plugins: [terser()], }
],
external: [
...builtinModules,
...(pkg.dependencies ? Object.keys(pkg.dependencies) : []),
...(pkg.devDependencies ? Object.keys(pkg.devDependencies) : []),
...(pkg.peerDependencies ? Object.keys(pkg.peerDependencies) : [])
],
watch: {
include: 'src/**',
},
plugins: [
json(),
typescript({
abortOnError: process.env.NODE_ENV === 'production',
tsconfig:'./tsconfig.json',
tsconfigDefaults: {
exclude: [
// all TS test files, regardless whether co-located or in test/ etc
'**/*.spec.ts',
'**/*.test.ts',
'**/*.spec.ts',
'**/*.test.ts',
// TS defaults below
'node_modules',
'bower_components',
'jspm_packages',
],
compilerOptions: {
sourceMap: true,
declaration: true,
jsx: 'react',
},
},
tsconfigOverride: {
compilerOptions: Object.assign({
// TS -> esnext, then leave the rest to babel-preset-env
target: 'esnext' }, { declaration: true, declarationMap: true }),
},
}
),
commonjs() ,
nodeResolve({
exportConditions: ['import', 'default', 'require'],
mainFields: ['module', 'main', 'browser'],
modulesOnly: true,
preferBuiltins: false,
}),
babel({
babelHelpers: 'bundled',
include: ['src/**/*.ts'],
exclude: './node_modules/**',
}),
]
};
================================================
FILE: src/api/ammpool_api.ts
================================================
/* eslint-disable camelcase */
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
import { LOOPRING_URLs } from '../defs'
import * as sign_tools from './sign/sign_tools'
import { makeAmmPool } from '../utils'
export class AmmpoolAPI extends BaseAPI {
/*
* Returns the fee rate of users placing orders in specific markets
*/
public async getAmmPoolConf(): Promise<{
raw_data: R
ammpools: loopring_defs.LoopringMap
pairs: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: LOOPRING_URLs.GET_AMM_POOLS_CONF,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const { ammpools, pairs } = makeAmmPool(raw_data)
// if (raw_data.code) {
// return {
// ...raw_data,
// };
// }
return {
ammpools,
pairs,
raw_data,
}
}
/*
*/
public async getAmmPoolUserRewards(request: loopring_defs.GetAmmUserRewardsRequest): Promise<{
raw_data: R
ammUserRewardMap: loopring_defs.AmmUserRewardMap
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_AMMPOOL_REWARDS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const ammUserRewardMap: loopring_defs.AmmUserRewardMap = {}
if (raw_data?.current) {
raw_data?.current.forEach((item: loopring_defs.AmmUserReward) => {
ammUserRewardMap[item.market] = {
current: item,
lastDay: undefined,
}
})
}
if (raw_data?.lastDay) {
raw_data?.lastDay.forEach((item: loopring_defs.AmmUserReward) => {
ammUserRewardMap[item.market] = {
...ammUserRewardMap[item.market],
lastDay: item,
}
})
}
return {
ammUserRewardMap,
raw_data,
}
}
/*
*/
public async getAmmPoolGameRank(request: loopring_defs.GetAmmPoolGameRankRequest): Promise<{
raw_data: R
totalRewards: loopring_defs.TokenVolumeV3[]
userRankList: loopring_defs.GameRankInfo[]
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_AMMPOOL_GAME_RANK,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const totalRewards: loopring_defs.TokenVolumeV3[] = raw_data?.totalRewards
? raw_data.totalRewards
: []
const userRankList: loopring_defs.GameRankInfo[] = raw_data?.userRankList
? raw_data.userRankList
: []
return {
totalRewards,
userRankList,
raw_data,
}
}
/*
*/
public async getAmmPoolGameUserRank(
request: loopring_defs.GetAmmPoolGameUserRankRequest,
apiKey: string,
): Promise<{
raw_data: R
userRank: loopring_defs.GameRankInfo
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
apiKey,
url: LOOPRING_URLs.GET_AMMPOOL_GAME_USER_RANK,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
const userRank: loopring_defs.GameRankInfo = raw_data.data
return {
userRank,
raw_data: raw_data.data,
}
}
private getOrderList(lst: loopring_defs.AmmPoolActivityRule[], order: loopring_defs.SortOrder) {
return lst.sort(
(a: loopring_defs.AmmPoolActivityRule, b: loopring_defs.AmmPoolActivityRule) => {
if (order === loopring_defs.SortOrder.ASC) {
return a.rangeFrom < b.rangeFrom ? 1 : 0
}
return a.rangeFrom > b.rangeFrom ? 1 : 0
},
)
}
/*
*/
public async getAmmPoolActivityRules(): Promise<{
raw_data: R
activityInProgressRules: loopring_defs.LoopringMap
activityDateMap: loopring_defs.LoopringMap<{
AMM_MINING?: loopring_defs.LoopringMap
ORDERBOOK_MINING?: loopring_defs.LoopringMap
SWAP_VOLUME_RANKING?: loopring_defs.LoopringMap
}>
groupByRuleType: loopring_defs.LoopringMap
groupByActivityStatus: loopring_defs.LoopringMap
groupByRuleTypeAndStatus: loopring_defs.LoopringMap<
loopring_defs.LoopringMap
>
}> {
const reqParams: loopring_defs.ReqParams = {
url: LOOPRING_URLs.GET_AMM_ACTIVITY_RULES,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
let activityInProgressRules: loopring_defs.LoopringMap =
{}
const activityDateMap: loopring_defs.LoopringMap<{
AMM_MINING?: loopring_defs.LoopringMap
ORDERBOOK_MINING?: loopring_defs.LoopringMap
SWAP_VOLUME_RANKING?: loopring_defs.LoopringMap
}> = {}
//{AMM_MINING:{},ORDERBOOK_MINING:{},SWAP_VOLUME_RANKING:{}}
const groupByRuleType: loopring_defs.LoopringMap = {}
let groupByRuleTypeAndStatus: loopring_defs.LoopringMap<
loopring_defs.LoopringMap
> = {}
const groupByActivityStatus: loopring_defs.LoopringMap = {}
const currentTs = new Date().getTime()
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.AmmPoolActivityRule) => {
const status =
currentTs < item.rangeFrom
? loopring_defs.AmmPoolActivityStatus.NotStarted
: currentTs >= item.rangeFrom && currentTs <= item.rangeTo
? loopring_defs.AmmPoolActivityStatus.InProgress
: loopring_defs.AmmPoolActivityStatus.EndOfGame
item.status = status
if (status === loopring_defs.AmmPoolActivityStatus.InProgress) {
const ruleType = activityInProgressRules[item.market]
? [...activityInProgressRules[item.market].ruleType, item.ruleType]
: [item.ruleType]
activityInProgressRules = {
...activityInProgressRules,
[item.market]: { ...item, ruleType },
}
}
groupByRuleType[item.ruleType] = [
...(groupByRuleType[item.ruleType] ? groupByRuleType[item.ruleType] : []),
item,
]
groupByActivityStatus[status] = [
...(groupByActivityStatus[status] ? groupByActivityStatus[status] : []),
item,
]
activityDateMap[item.rangeFrom] = {
...(activityDateMap[item.rangeFrom] ? activityDateMap[item.rangeFrom] : {}),
[item.ruleType]: {
...(activityDateMap[item.rangeFrom]
? activityDateMap[item.rangeFrom][item.ruleType]
? activityDateMap[item.rangeFrom][item.ruleType]
: {}
: {}),
[item.market]: item,
},
}
groupByRuleTypeAndStatus = {
...groupByRuleTypeAndStatus,
[item.ruleType]: {
...(groupByRuleTypeAndStatus[item.ruleType]
? groupByRuleTypeAndStatus[item.ruleType]
: {}),
[status]: [
...(groupByRuleTypeAndStatus[item.ruleType]
? groupByRuleTypeAndStatus[item.ruleType][status]
? groupByRuleTypeAndStatus[item.ruleType][status]
: []
: []),
item,
],
},
}
})
}
return {
activityInProgressRules,
activityDateMap,
groupByRuleType,
groupByActivityStatus,
groupByRuleTypeAndStatus,
raw_data,
}
}
/*
*/
public async getAmmAssetHistory(request: loopring_defs.GetAmmAssetRequest): Promise<{
raw_data: R
poolAddress: string
market: string
dataSeries: any
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_AMM_ASSET_HISTORY,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const poolAddress = raw_data.poolAddress
const market = raw_data.market
const dataSeries = raw_data.data
return {
poolAddress,
market,
dataSeries,
raw_data,
}
}
/*
*/
public async getAmmPoolStats(): Promise<{
raw_data: R
ammPoolStats: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: LOOPRING_URLs.GET_AMM_POOL_STATS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
const ammPoolStats: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.AmmPoolStat) => {
ammPoolStats[item.market] = item
})
}
return {
ammPoolStats,
raw_data,
}
}
/*
*/
public async getAmmPoolSnapshot(request: loopring_defs.GetAmmPoolSnapshotRequest): Promise<{
raw_data: R
ammPoolSnapshot: loopring_defs.AmmPoolSnapshot
}> {
const reqParams: loopring_defs.ReqParams = {
url: LOOPRING_URLs.GET_AMM_POOLS_SNAPSHOT,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const ammPoolSnapshot: loopring_defs.AmmPoolSnapshot = raw_data
return {
ammPoolSnapshot,
raw_data,
}
}
/*
*/
public async getAmmPoolBalances(): Promise<{
raw_data: R
ammpoolsbalances: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: LOOPRING_URLs.GET_AMM_POOLS_BALANCES,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const ammpoolsbalances: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: any) => {
const tempPooled: any = {}
if (item?.pooled instanceof Array) {
item.pooled.forEach((item2: any) => {
tempPooled[item2.tokenId] = item2
})
}
item.pooledMap = tempPooled
let poolName = item.poolName
if (poolName.indexOf('LRCETH') >= 0) {
poolName = 'AMM-LRC-ETH'
}
ammpoolsbalances[poolName] = item
})
}
return {
ammpoolsbalances,
raw_data,
}
}
/*
*/
public async getLiquidityMining(
request: loopring_defs.GetLiquidityMiningRequest,
apiKey: string,
): Promise<{
raw_data: R
rewards: loopring_defs.RewardItem[]
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
apiKey,
url: LOOPRING_URLs.GET_LIQUIDITY_MINING,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
return {
rewards: raw_data?.data ? (raw_data.data as loopring_defs.RewardItem[]) : [],
raw_data,
}
}
/*
*/
public async getLiquidityMiningUserHistory(
request: loopring_defs.GetLiquidityMiningUserHistoryRequest,
): Promise<{
raw_data: R
userMiningInfos: loopring_defs.UserMiningInfo[]
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_LIQUIDITY_MINING_USER_HISTORY,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
return {
userMiningInfos: raw_data.data as loopring_defs.UserMiningInfo[],
raw_data,
}
}
/*
*/
public async getUserAmmPoolTxs(
request: loopring_defs.GetUserAmmPoolTxsRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userAmmPoolTxs: loopring_defs.UserAmmPoolTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
apiKey,
url: LOOPRING_URLs.GET_USER_AMM_POOL_TXS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data.totalNum,
userAmmPoolTxs: raw_data.transactions as loopring_defs.UserAmmPoolTx[],
raw_data,
}
}
/*
*/
public async getAmmPoolTxs(request: loopring_defs.GetAmmPoolTxsRequest): Promise<{
raw_data: R
totalNum: number
transactions: loopring_defs.AmmPoolTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_AMM_POOL_TXS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
let transactions = undefined
if (raw_data?.transactions) {
transactions = raw_data?.transactions
}
return {
totalNum: raw_data.totalNum,
transactions: transactions as loopring_defs.AmmPoolTx[],
raw_data,
}
}
/*
*/
public async getAmmPoolTrades(request: loopring_defs.GetAmmPoolTradesRequest): Promise<{
raw_data: R
totalNum: number
ammPoolTrades: loopring_defs.AmmPoolTrade[]
}> {
const reqParams: loopring_defs.ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_AMM_POOL_TRADE_TXS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data.totalNum,
ammPoolTrades: raw_data.transactions as loopring_defs.AmmPoolTrade[],
raw_data,
}
}
/*
*/
public async joinAmmPool(
request: loopring_defs.JoinAmmPoolRequest,
patch: loopring_defs.AmmPoolRequestPatch,
apiKey: string,
): Promise<{
raw_data: R
joinAmmPoolResult: loopring_defs.JoinAmmPoolResult
}> {
if (!request?.validUntil) request.validUntil = Date.now()
const reqParams: loopring_defs.ReqParams = {
bodyParams: request,
apiKey,
url: LOOPRING_URLs.POST_JOIN_AMM_POOL,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const { eddsaSig } = sign_tools.get_EddsaSig_JoinAmmPool(request, patch)
request.eddsaSignature = eddsaSig
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
joinAmmPoolResult: raw_data as loopring_defs.JoinAmmPoolResult,
raw_data,
}
}
/*
*/
public async exitAmmPool(
request: loopring_defs.ExitAmmPoolRequest,
patch: loopring_defs.AmmPoolRequestPatch,
apiKey: string,
): Promise<{
raw_data: R
exitAmmPoolResult: loopring_defs.ExitAmmPoolResult
}> {
if (!request?.validUntil) request.validUntil = Date.now()
const reqParams: loopring_defs.ReqParams = {
bodyParams: request,
apiKey,
url: LOOPRING_URLs.POST_EXIT_AMM_POOL,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const { eddsaSig } = sign_tools.get_EddsaSig_ExitAmmPool(request, patch)
request.eddsaSignature = eddsaSig
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
exitAmmPoolResult: raw_data as loopring_defs.ExitAmmPoolResult,
raw_data,
}
}
}
================================================
FILE: src/api/base_api.ts
================================================
import * as loopring_defs from '../defs'
import { Request } from './request'
import { addHexPrefix, toBuffer, toHex } from '../utils'
import { myLog } from '../utils/log_tools'
import { contracts as abi } from './ethereum/contracts'
import { AxiosResponse } from 'axios'
import * as ethUtil from 'ethereumjs-util'
import { isContract } from './contract_api'
import { getWindowSafely } from 'utils/window_utils'
export const KEY_MESSAGE =
'Sign this message to access Loopring Exchange: ' +
'${exchangeAddress}' +
' with key nonce: ' +
'${nonce}'
export class BaseAPI {
static KEY_MESSAGE: string = KEY_MESSAGE
protected baseUrl = ''
protected chainId: loopring_defs.ChainId = loopring_defs.ChainId.MAINNET
public genErr(err: Error | (AxiosResponse & Error)): loopring_defs.RESULT_INFO {
if (err.hasOwnProperty('request')) {
// const axiosError = errorInfo as AxiosResponse;
return {
// @ts-ignore;
message: loopring_defs.ConnectorError.HTTP_ERROR,
...err,
msg: loopring_defs.ConnectorError.HTTP_ERROR,
code: loopring_defs.LoopringErrorCode.HTTP_ERROR,
} as loopring_defs.RESULT_INFO
err?.message
} else if (!err || !err?.message) {
return {
message: 'unKnown',
code: loopring_defs.LoopringErrorCode.SKD_UNKNOW,
}
} else {
const key = Reflect.ownKeys(loopring_defs.ConnectorError).find(
(key) =>
err?.message.search(
loopring_defs.ConnectorError[key as keyof typeof loopring_defs.ConnectorError],
) !== -1,
)
if (key) {
return {
...err,
message: key as keyof typeof loopring_defs.ConnectorError,
code: loopring_defs.LoopringErrorCode[key as keyof typeof loopring_defs.ConnectorError],
} as loopring_defs.RESULT_INFO
}
return {
...(err instanceof Error
? Reflect.ownKeys(err).reduce((prev, item) => {
// @ts-ignore
return { ...prev, [item]: err[item.toString()] }
}, {})
: err),
code: loopring_defs.LoopringErrorCode.SKD_UNKNOW,
}
}
}
protected returnTxHash(
raw_data: T,
): (Omit & { raw_data: Omit }) | loopring_defs.RESULT_INFO {
if (raw_data?.resultInfo) {
return {
...raw_data.resultInfo,
message: raw_data.resultInfo?.msg ? raw_data.resultInfo?.msg : raw_data?.resultInfo.message,
}
}
return {
...raw_data,
raw_data,
}
}
private timeout: number
private baseUrlMap: { [key: number]: string } | undefined
public constructor(
param: InitParam,
timeout: number = 6000,
baseUrlMap = {
[loopring_defs.ChainId.MAINNET]: 'https://api3.loopring.io',
[loopring_defs.ChainId.GOERLI]: 'https://uat2.loopring.io',
},
) {
if (param.baseUrl) {
this.baseUrl = param.baseUrl
} else if (param.chainId !== undefined) {
this.setChainId(param.chainId)
} else {
this.setChainId(loopring_defs.ChainId.GOERLI)
}
this.baseUrlMap = baseUrlMap
this.timeout = timeout
}
public async getAvailableBroker(
request: loopring_defs.GetAvailableBrokerRequest,
): Promise<{ broker: string }> {
const reqParams: loopring_defs.ReqParams = {
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
queryParams: request,
url: loopring_defs.LOOPRING_URLs.GET_AVAILABLE_BROKER,
method: loopring_defs.ReqMethod.GET,
}
const result = (await this.makeReq().request(reqParams)).data
return result
}
public async getCounterFactualInfo(
request: loopring_defs.GetCounterFactualInfoRequest,
): Promise<{
raw_data: T
counterFactualInfo: loopring_defs.CounterFactualInfo | undefined
error?: loopring_defs.RESULT_INFO
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.COUNTER_FACTUAL_INFO,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
let counterFactualInfo: loopring_defs.CounterFactualInfo | undefined
let error: loopring_defs.RESULT_INFO | undefined = undefined
if (raw_data && raw_data?.resultInfo) {
error = raw_data?.resultInfo
} else {
counterFactualInfo = {
...raw_data,
} as loopring_defs.CounterFactualInfo
}
return {
counterFactualInfo,
error,
raw_data,
}
}
public setChainId(chainId: loopring_defs.ChainId) {
this.baseUrl =
this.baseUrlMap && this.baseUrlMap[0]
? getBaseUrlByChainId(chainId, this.baseUrlMap as any)
: getBaseUrlByChainId(chainId)
this.chainId = chainId
}
public setBaseUrl(baseUrl: string) {
this.baseUrl = baseUrl
}
protected makeReq(): Request {
return new Request(this.baseUrl, this.timeout)
}
}
export function ecRecover(
account: string,
msg: string,
sig: any,
// time = 3000
) {
try {
// let timer;
const hash = ethUtil.hashPersonalMessage(toBuffer(msg))
const signature = ethUtil.fromRpcSig(sig)
const result = ethUtil.ecrecover(hash, signature.v, signature.r, signature.s)
const result2 = ethUtil.pubToAddress(result)
const recAddress = toHex(result2)
myLog('ecRecover recAddress', result, result2, recAddress)
return {
result: recAddress.toLowerCase() === account.toLowerCase(),
}
} catch (error) {
return { error }
}
}
export async function contractWalletValidate32(web3: any, account: string, msg: string, sig: any) {
return new Promise((resolve) => {
const hash = ethUtil.hashPersonalMessage(toBuffer(msg))
const data = abi.Contracts.ContractWallet.encodeInputs('isValidSignature(bytes32,bytes)', {
_data: hash,
_signature: toBuffer(sig),
})
web3.eth.call(
{
to: account, // contract addr
data: data,
},
function (err: any, result: any) {
if (!err) {
const valid = abi.Contracts.ContractWallet.decodeOutputs(
'isValidSignature(bytes32,bytes)',
result,
)
resolve({
result: toHex(toBuffer(valid[0])) === data.slice(0, 10),
})
} else resolve({ error: err })
},
)
})
}
export async function mykeyWalletValid(web3: any, account: string, msg: string, sig: any) {
const myKeyContract = '0xADc92d1fD878580579716d944eF3460E241604b7'
return new Promise((resolve) => {
web3.eth.call(
{
to: myKeyContract,
data: abi.Contracts.ContractWallet.encodeInputs('getKeyData', {
_account: account,
_index: 3,
}),
},
function (err: any, res: any) {
if (!err) {
const signature = ethUtil.fromRpcSig(sig)
const hash = ethUtil.hashPersonalMessage(ethUtil.keccak256(toBuffer(msg)))
const address = addHexPrefix(
abi.Contracts.ContractWallet.decodeOutputs('getKeyData', res)[0],
)
const recAddress = toHex(
ethUtil.pubToAddress(ethUtil.ecrecover(hash, signature.v, signature.r, signature.s)),
)
resolve({
result: recAddress.toLowerCase() === address.toLowerCase(),
})
} else {
resolve({ error: err })
}
},
)
})
}
export async function ecRecover2(account: string, message: string, signature: any) {
const messageBuffer = Buffer.from(message, 'utf8')
signature = signature.split('x')[1]
const parts = [
Buffer.from(`\x19Ethereum Signed Message:\n${messageBuffer.length}`, 'utf8'),
messageBuffer,
]
const totalHash = ethUtil.keccak(Buffer.concat(parts))
const r = Buffer.from(signature.substring(0, 64), 'hex')
const s = Buffer.from(signature.substring(64, 128), 'hex')
const old_v = Number(addHexPrefix(signature.substring(128, 130)))
let v = old_v
if (v <= 1) v += 27
const pub = ethUtil.ecrecover(totalHash, v, r, s)
const recoveredAddress = '0x' + ethUtil.pubToAddress(pub).toString('hex')
// if (account.toLowerCase() !== recoveredAddress.toLowerCase()) {
// myLog('v:', v, 'old_v:', old_v, ' recoveredAddress:', recoveredAddress)
// }
return new Promise((resolve) =>
resolve({
result: account.toLowerCase() === recoveredAddress.toLowerCase(),
}),
)
}
const getBaseUrlByChainId = (
id: loopring_defs.ChainId,
baseUrlMap = {
[loopring_defs.ChainId.MAINNET]: 'https://api3.loopring.io',
[loopring_defs.ChainId.GOERLI]: 'https://uat2.loopring.io',
},
) => {
let baseUrl = ''
switch (id) {
case loopring_defs.ChainId.MAINNET:
baseUrl = baseUrlMap[loopring_defs.ChainId.MAINNET]
break
default:
baseUrl = baseUrlMap[loopring_defs.ChainId.GOERLI]
break
}
return baseUrl
}
/**
* @default chainId 1
* @default keySeed `Sign this message to access Loopring exchange: ${exchangeAddress} with key nonce: ${nonce}`
*/
export interface InitParam {
chainId?: loopring_defs.ChainId
baseUrl?: string
}
export function formatSig(rpcSig: string) {
const sig = ethUtil.fromRpcSig(rpcSig)
return ethUtil.toRpcSig(sig.v, sig.r, sig.s)
}
export function recoverSignType(web3: any, account: string, msg: string, sig: string) {
const ethRecover: any = ecRecover(account, msg, sig)
if (ethRecover.result) {
return '03'
} else {
return ''
}
}
export async function personalSign(
web3: any,
account: string | undefined,
pwd: string,
msg: string,
walletType: loopring_defs.ConnectorNames,
chainId: loopring_defs.ChainId,
accountId?: number,
counterFactualInfo?: loopring_defs.CounterFactualInfo,
isMobile?: boolean,
) {
if (!account) {
return { error: 'personalSign got no account' }
}
return new Promise((resolve) => {
try {
web3.eth.personal.sign(msg, account, pwd, async function (err: any, result: any) {
if (!err) {
// LOG: for signature
myLog('ecRecover before', 'msg', msg, 'result', result, counterFactualInfo)
// Valid:1. counter Factual signature Valid
if (counterFactualInfo && accountId) {
myLog('fcWalletValid counterFactualInfo accountId:')
const fcValid = await fcWalletValid(
web3,
account,
msg,
result,
accountId,
chainId,
counterFactualInfo,
)
if (fcValid.result) {
resolve({
sig: result,
counterFactualInfo: fcValid.counterFactualInfo,
})
return
}
}
// Valid: 2. webview directory signature Valid
myLog('ecRecover before', result)
const valid: any = ecRecover(account, msg, result)
myLog('ecRecover after', valid.result)
if (valid.result) {
return resolve({ sig: result })
} else {
myLog('ecRecover error', valid)
}
// Valid: 3. contractWallet no recover
// signature Valid `isValidSignature(bytes32,bytes)`
// LOG: for signature
myLog('Valid: 3. contractWallet before')
const isContractCheck = await isContract(web3, account)
if (isContractCheck) {
// LOG: for signature
myLog('Valid: 5 failed isContract. no ecrecover')
return resolve({ sig: result })
}
// Valid: 5. contractWallet signature Valid `isValidSignature(bytes32,bytes)`
// const walletValid2: any = await contractWalletValidate32(
// web3,
// account,
// msg,
// result
// );
// if (walletValid2.result) {
// return resolve({ sig: result });
// }
// Valid: 6. counter Factual signature Valid when no counterFactualInfo
if (accountId) {
const fcValid = await fcWalletValid(web3, account, msg, result, accountId, chainId)
if (fcValid.result) {
return resolve({
sig: result,
counterFactualInfo: fcValid.counterFactualInfo,
})
}
}
// Valid: 7. myKeyValid Valid again
const myKeyValid: any = await mykeyWalletValid(web3, account, msg, result)
if (myKeyValid.result) {
return resolve({ sig: result })
}
// Valid: Error cannot pass personalSign Valid
// eslint-disable-next-line no-console
console.log('web3.eth.personal.sign Valid, valid 5 ways, all failed!')
return resolve({
error: 'web3.eth.personal.sign Valid, valid 5 ways, all failed!',
})
} else {
return resolve({
error: 'personalSign err before Validate:' + err,
})
}
})
} catch (err) {
// LOG: for signature
myLog('personalSign callback err', (err as unknown as any)?.message)
resolve({ error: err as any })
}
})
}
export async function fcWalletValid(
web3: any,
account: string,
msg: string,
result: any,
accountId: number,
chainId: loopring_defs.ChainId,
counterFactualInfo?: loopring_defs.CounterFactualInfo,
): Promise<{
counterFactualInfo?: loopring_defs.CounterFactualInfo
error?: any
result?: boolean
}> {
const api = new BaseAPI({ chainId })
if (counterFactualInfo === undefined || !counterFactualInfo.walletOwner) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
counterFactualInfo = (await api.getCounterFactualInfo({ accountId })).counterFactualInfo
}
if (counterFactualInfo && counterFactualInfo.walletOwner) {
let _result: string
if (result.startsWith('0x')) {
_result = result.slice(0, 132)
} else {
_result = result
}
const valid: any = ecRecover(counterFactualInfo.walletOwner, msg, _result)
if (valid.result) {
myLog('fcWalletValid e:', result, counterFactualInfo)
return { result, counterFactualInfo }
} else {
return { error: 'valid walletOwner failed' }
}
} else {
return { error: 'valid walletOwner failed' }
}
}
================================================
FILE: src/api/config/abis/contractWallet.ts
================================================
export const contractWallet = [
{
inputs: [
{
internalType: 'bytes',
name: '_data',
type: 'bytes',
},
{
internalType: 'bytes',
name: '_signature',
type: 'bytes',
},
],
name: 'isValidSignature',
outputs: [
{
internalType: 'bytes4',
name: 'magicValue',
type: 'bytes4',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'bytes',
name: '_data',
type: 'bytes32',
},
{
internalType: 'bytes',
name: '_signature',
type: 'bytes',
},
],
name: 'isValidSignature',
outputs: [
{
internalType: 'bytes4',
name: 'magicValue',
type: 'bytes4',
},
],
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_account',
type: 'address',
},
{
name: '_index',
type: 'uint256',
},
],
name: 'getKeyData',
outputs: [
{
name: '',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
]
================================================
FILE: src/api/config/abis/erc1155.ts
================================================
export const erc1155 = [
{
inputs: [
{
internalType: 'string',
name: 'uri_',
type: 'string',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'account',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: false,
internalType: 'bool',
name: 'approved',
type: 'bool',
},
],
name: 'ApprovalForAll',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256[]',
name: 'ids',
type: 'uint256[]',
},
{
indexed: false,
internalType: 'uint256[]',
name: 'values',
type: 'uint256[]',
},
],
name: 'TransferBatch',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
{
indexed: false,
internalType: 'uint256',
name: 'value',
type: 'uint256',
},
],
name: 'TransferSingle',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'string',
name: 'value',
type: 'string',
},
{
indexed: true,
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
],
name: 'URI',
type: 'event',
},
{
inputs: [
{
internalType: 'bytes4',
name: 'interfaceId',
type: 'bytes4',
},
],
name: 'supportsInterface',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'uri',
outputs: [
{
internalType: 'string',
name: '',
type: 'string',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
{
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
],
name: 'balanceOf',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address[]',
name: 'accounts',
type: 'address[]',
},
{
internalType: 'uint256[]',
name: 'ids',
type: 'uint256[]',
},
],
name: 'balanceOfBatch',
outputs: [
{
internalType: 'uint256[]',
name: '',
type: 'uint256[]',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'operator',
type: 'address',
},
{
internalType: 'bool',
name: 'approved',
type: 'bool',
},
],
name: 'setApprovalForAll',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
{
internalType: 'address',
name: 'operator',
type: 'address',
},
],
name: 'isApprovedForAll',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'data',
type: 'bytes',
},
],
name: 'safeTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256[]',
name: 'ids',
type: 'uint256[]',
},
{
internalType: 'uint256[]',
name: 'amounts',
type: 'uint256[]',
},
{
internalType: 'bytes',
name: 'data',
type: 'bytes',
},
],
name: 'safeBatchTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
================================================
FILE: src/api/config/abis/erc20.ts
================================================
export const erc20 = [
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_spender',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'approve',
outputs: [
{
name: '',
type: 'bool',
},
],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_from',
type: 'address',
},
{
name: '_to',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'transferFrom',
outputs: [
{
name: '',
type: 'bool',
},
],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'decimals',
outputs: [
{
name: '',
type: 'uint8',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
name: 'balance',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_to',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'transfer',
outputs: [
{
name: '',
type: 'bool',
},
],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
{
name: '_spender',
type: 'address',
},
],
name: 'allowance',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
payable: true,
stateMutability: 'payable',
type: 'fallback',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'owner',
type: 'address',
},
{
indexed: true,
name: 'spender',
type: 'address',
},
{
indexed: false,
name: 'value',
type: 'uint256',
},
],
name: 'Approval',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: 'from',
type: 'address',
},
{
indexed: true,
name: 'to',
type: 'address',
},
{
indexed: false,
name: 'value',
type: 'uint256',
},
],
name: 'Transfer',
type: 'event',
},
]
================================================
FILE: src/api/config/abis/erc721.ts
================================================
export const erc721 = [
{
inputs: [
{
internalType: 'string',
name: 'name_',
type: 'string',
},
{
internalType: 'string',
name: 'symbol_',
type: 'string',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'owner',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'approved',
type: 'address',
},
{
indexed: true,
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'Approval',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'owner',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: false,
internalType: 'bool',
name: 'approved',
type: 'bool',
},
],
name: 'ApprovalForAll',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: true,
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'Transfer',
type: 'event',
},
{
inputs: [
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'approve',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'owner',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'getApproved',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'owner',
type: 'address',
},
{
internalType: 'address',
name: 'operator',
type: 'address',
},
],
name: 'isApprovedForAll',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'name',
outputs: [
{
internalType: 'string',
name: '',
type: 'string',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'ownerOf',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'safeTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
{
internalType: 'bytes',
name: '_data',
type: 'bytes',
},
],
name: 'safeTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'operator',
type: 'address',
},
{
internalType: 'bool',
name: 'approved',
type: 'bool',
},
],
name: 'setApprovalForAll',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'bytes4',
name: 'interfaceId',
type: 'bytes4',
},
],
name: 'supportsInterface',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'symbol',
outputs: [
{
internalType: 'string',
name: '',
type: 'string',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'tokenURI',
outputs: [
{
internalType: 'string',
name: '',
type: 'string',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'transferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
================================================
FILE: src/api/config/abis/exchange_3_6.ts
================================================
export const exchange = [
{
inputs: [
{
internalType: "address",
name: "owner",
type: "address",
},
{
internalType: "address",
name: "tokenAddress",
type: "address",
},
{
internalType: "uint32",
name: "accountID",
type: "uint32",
},
],
name: "forceWithdraw",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "from",
type: "address",
},
{
internalType: "address",
name: "to",
type: "address",
},
{
internalType: "address",
name: "tokenAddress",
type: "address",
},
{
internalType: "uint96",
name: "amount",
type: "uint96",
},
{
internalType: "bytes",
name: "extraData",
type: "bytes",
},
],
name: "deposit",
outputs: [],
stateMutability: "payable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "from",
type: "address",
},
{
internalType: "address",
name: "to",
type: "address",
},
{
internalType: "enum ExchangeData.NftType",
name: "nftType",
type: "uint8",
},
{
internalType: "address",
name: "tokenAddress",
type: "address",
},
{
internalType: "uint256",
name: "nftId",
type: "uint256",
},
{
internalType: "uint96",
name: "amount",
type: "uint96",
},
{
internalType: "bytes",
name: "extraData",
type: "bytes",
},
],
name: "depositNFT",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "owner",
type: "address",
},
{
internalType: "address",
name: "token",
type: "address",
},
],
name: "withdrawFromDepositRequest",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address[]",
name: "owners",
type: "address[]",
},
{
internalType: "address[]",
name: "tokens",
type: "address[]",
},
],
name: "withdrawFromApprovedWithdrawals",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "owner",
type: "address",
},
{
internalType: "address",
name: "token",
type: "address",
},
],
name: "getAmountWithdrawable",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [
{
internalType: "address",
name: "owner",
type: "address",
},
{
internalType: "bytes32",
name: "txHash",
type: "bytes32",
},
],
name: "approveTransaction",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
];
================================================
FILE: src/api/config/abis/hebao.ts
================================================
export const hebao = [
{
inputs: [
{
internalType: 'address',
name: 'wallet',
type: 'address',
},
],
name: 'lock',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'wallet',
type: 'address',
},
{
internalType: 'address',
name: 'guardian',
type: 'address',
},
],
name: 'unlock',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
================================================
FILE: src/api/config/abis/index.ts
================================================
export * as contractWalletAbi from './contractWallet'
export * as erc20Abi from './erc20'
export * as erc721Abi from './erc721'
export * as erc1155Abi from './erc1155'
export * as exchange36Abi from './exchange_3_6'
export * as hebao from './hebao'
export * as smartWallet from './smartWallet'
================================================
FILE: src/api/config/abis/smartWallet.ts
================================================
export default [
{
inputs: [
{ internalType: 'contract PriceOracle', name: '_priceOracle', type: 'address' },
{ internalType: 'address', name: '_blankOwner', type: 'address' },
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
inputs: [],
name: 'DOMAIN_SEPARATOR',
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'guardian', type: 'address' }],
name: 'addGuardian',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'addr', type: 'address' }],
name: 'addToWhitelist',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{ internalType: 'uint256', name: 'value', type: 'uint256' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
{ internalType: 'bool', name: 'forceUseQuota', type: 'bool' },
],
name: 'approveThenCallContract',
outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{ internalType: 'bool', name: 'forceUseQuota', type: 'bool' },
],
name: 'approveToken',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'address', name: 'addr', type: 'address' },
],
name: 'addToWhitelistWA',
outputs: [{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{ internalType: 'uint256', name: 'value', type: 'uint256' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
],
name: 'approveThenCallContractWA',
outputs: [
{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' },
{ internalType: 'bytes', name: 'returnData', type: 'bytes' },
],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address[]', name: 'to', type: 'address[]' },
{ internalType: 'bytes[]', name: 'data', type: 'bytes[]' },
],
name: 'batchCall',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'blankOwner',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'value', type: 'uint256' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
{ internalType: 'bool', name: 'forceUseQuota', type: 'bool' },
],
name: 'callContract',
outputs: [{ internalType: 'bytes', name: '', type: 'bytes' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'value', type: 'uint256' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
],
name: 'callContractWA',
outputs: [
{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' },
{ internalType: 'bytes', name: 'returnData', type: 'bytes' },
],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'uint256', name: 'newQuota', type: 'uint256' }],
name: 'changeDailyQuota',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'uint256', name: 'newQuota', type: 'uint256' },
],
name: 'changeDailyQuotaWA',
outputs: [{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'address', name: 'newMasterCopy', type: 'address' },
],
name: 'changeMasterCopy',
outputs: [{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'nonce', type: 'uint256' },
{ internalType: 'address', name: 'gasToken', type: 'address' },
{ internalType: 'uint256', name: 'gasPrice', type: 'uint256' },
{ internalType: 'uint256', name: 'gasLimit', type: 'uint256' },
{ internalType: 'uint256', name: 'gasOverhead', type: 'uint256' },
{ internalType: 'address', name: 'feeRecipient', type: 'address' },
{ internalType: 'bool', name: 'requiresSuccess', type: 'bool' },
{ internalType: 'bytes', name: 'data', type: 'bytes' },
{ internalType: 'bytes', name: 'signature', type: 'bytes' },
],
name: 'executeMetaTx',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'getCreationTimestamp',
outputs: [{ internalType: 'uint64', name: '', type: 'uint64' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'bool', name: 'includePendingAddition', type: 'bool' }],
name: 'getGuardians',
outputs: [
{
components: [
{ internalType: 'address', name: 'addr', type: 'address' },
{ internalType: 'uint8', name: 'status', type: 'uint8' },
{ internalType: 'uint64', name: 'timestamp', type: 'uint64' },
],
internalType: 'struct Guardian[]',
name: '',
type: 'tuple[]',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getMasterCopy',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [],
name: 'getOwner',
outputs: [{ internalType: 'address', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'addr', type: 'address' }],
name: 'getWhitelistEffectiveTime',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'newOwner', type: 'address' }],
name: 'inherit',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'owner', type: 'address' },
{ internalType: 'address[]', name: 'guardians', type: 'address[]' },
{ internalType: 'uint256', name: 'quota', type: 'uint256' },
{ internalType: 'address', name: 'inheritor', type: 'address' },
{ internalType: 'address', name: 'feeRecipient', type: 'address' },
{ internalType: 'address', name: 'feeToken', type: 'address' },
{ internalType: 'uint256', name: 'feeAmount', type: 'uint256' },
],
name: 'initialize',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'addr', type: 'address' },
{ internalType: 'bool', name: 'includePendingAddition', type: 'bool' },
],
name: 'isGuardian',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{ internalType: 'bytes32', name: 'signHash', type: 'bytes32' },
{ internalType: 'bytes', name: 'signature', type: 'bytes' },
],
name: 'isValidSignature',
outputs: [{ internalType: 'bytes4', name: 'magicValue', type: 'bytes4' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'addr', type: 'address' }],
name: 'isWhitelisted',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'view',
type: 'function',
},
{ inputs: [], name: 'lock', outputs: [], stateMutability: 'nonpayable', type: 'function' },
{
inputs: [],
name: 'priceOracle',
outputs: [{ internalType: 'contract PriceOracle', name: '', type: 'address' }],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'address', name: 'newOwner', type: 'address' },
{ internalType: 'address[]', name: 'newGuardians', type: 'address[]' },
],
name: 'recover',
outputs: [{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'addr', type: 'address' }],
name: 'removeFromWhitelist',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: 'guardian', type: 'address' }],
name: 'removeGuardian',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'address[]', name: 'newGuardians', type: 'address[]' }],
name: 'resetGuardians',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
components: [
{ internalType: 'address[]', name: 'signers', type: 'address[]' },
{ internalType: 'bytes[]', name: 'signatures', type: 'bytes[]' },
{ internalType: 'uint256', name: 'validUntil', type: 'uint256' },
{ internalType: 'address', name: 'wallet', type: 'address' },
],
internalType: 'struct Approval',
name: 'approval',
type: 'tuple',
},
{ internalType: 'address[]', name: 'newGuardians', type: 'address[]' },
],
name: 'resetGuardiansWA',
outputs: [{ internalType: 'bytes32', name: 'approvedHash', type: 'bytes32' }],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'inheritor', type: 'address' },
{ internalType: 'uint32', name: 'waitingPeriod', type: 'uint32' },
],
name: 'setInheritor',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ internalType: 'address', name: '_owner', type: 'address' }],
name: 'transferOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{ internalType: 'address', name: 'token', type: 'address' },
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{ internalType: 'bytes', name: 'logdata', type: 'bytes' },
{ internalType: 'bool', name: 'forceUseQuota', type: 'bool' },
],
name: 'transferToken',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [],
name: 'wallet',
outputs: [
{ internalType: 'address', name: 'owner', type: 'address' },
{ internalType: 'uint64', name: 'creationTimestamp', type: 'uint64' },
{ internalType: 'uint256', name: 'nonce', type: 'uint256' },
{ internalType: 'bool', name: 'locked', type: 'bool' },
{ internalType: 'address', name: 'inheritor', type: 'address' },
{ internalType: 'uint32', name: 'inheritWaitingPeriod', type: 'uint32' },
{ internalType: 'uint64', name: 'lastActive', type: 'uint64' },
{
components: [
{ internalType: 'uint128', name: 'currentQuota', type: 'uint128' },
{ internalType: 'uint128', name: 'pendingQuota', type: 'uint128' },
{ internalType: 'uint128', name: 'spentAmount', type: 'uint128' },
{ internalType: 'uint64', name: 'spentTimestamp', type: 'uint64' },
{ internalType: 'uint64', name: 'pendingUntil', type: 'uint64' },
],
internalType: 'struct Quota',
name: 'quota',
type: 'tuple',
},
],
stateMutability: 'view',
type: 'function',
},
{ stateMutability: 'payable', type: 'receive' },
]
// recovery
// transfer
// add_guardian
// remove_guardian
// unlock_wallet
// upgrade_contract
// deposit_wallet
================================================
FILE: src/api/config/guardianTypeData.ts
================================================
import { ChainId, ConnectorNames, HEBAO_META_TYPE, SigSuffix } from '../../defs'
import { myLog } from '../../utils/log_tools'
import { getEcDSASig, GetEcDSASigType } from '../sign/sign_tools'
import { personalSign } from '../base_api'
import { utils } from 'ethers'
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
]
const domain = (chainId: ChainId, guardiaContractAddress: any, name: string, version: string) => {
return {
name,
version: version,
chainId: chainId,
verifyingContract: guardiaContractAddress,
}
}
function getApproveRecoverTypedData({
chainId,
guardiaContractAddress,
wallet,
validUntil,
message,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
// newOwner: any,
newGuardians?: Buffer | any
message?: {
[key: string]: any
}
walletVersion: 2 | 1
}) {
const typedData = {
types: {
EIP712Domain,
recover: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
{ name: 'newOwner', type: 'address' },
...(walletVersion == 1 ? [] : [{ name: 'newGuardians', type: 'address[]' }]),
],
},
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'GuardianModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'recover',
message: {
wallet: wallet,
validUntil: validUntil,
...message,
// newOwner: newOwner,
},
}
const hash = utils._TypedDataEncoder.hash(typedData.domain, {recover: typedData.types.recover}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
function getApproveTransferTypedData({
chainId,
guardiaContractAddress,
wallet,
validUntil,
newGuardians,
message,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
newGuardians?: Buffer | any
message?: { [key: string]: any }
walletVersion: 2 | 1
}) {
const typedData = {
types: {
EIP712Domain,
transferToken: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
{ name: 'token', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'logdata', type: 'bytes' },
],
},
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'TransferModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'transferToken',
message: {
wallet: wallet,
validUntil: validUntil,
...message,
},
}
const hash= utils._TypedDataEncoder.hash(typedData.domain, {transferToken: typedData.types.transferToken}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
// function getAddGuardianTypedData({
// chainId,
// guardiaContractAddress,
// wallet,
// validUntil,
// message,
// }: {
// chainId: ChainId
// guardiaContractAddress: any
// wallet: any
// validUntil: any
// message?: { [key: string]: any }
// }) {
// const typedData = {
// types: {
// EIP712Domain,
// addGuardian: [
// { name: 'wallet', type: 'address' },
// { name: 'validUntil', type: 'uint256' },
// { name: 'guardian', type: 'address' },
// ],
// },
// domain: domain(chainId, guardiaContractAddress),
// primaryType: 'addGuardian',
// message: {
// wallet: wallet,
// validUntil: validUntil,
// ...message,
// },
// }
// return typedData
// }
function getRemoveGuardianTypedData({
chainId,
guardiaContractAddress,
wallet,
validUntil,
message,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
message?: { [key: string]: any }
walletVersion: 2 | 1
}) {
const typedData = {
types: {
EIP712Domain,
removeGuardian: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
{ name: 'guardian', type: 'address' },
],
},
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'GuardianModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'removeGuardian',
message: {
wallet: wallet,
validUntil: validUntil,
guardian: message!['guardian'],
},
}
const hash= utils._TypedDataEncoder.hash(typedData.domain, {removeGuardian: typedData.types.removeGuardian}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
function getUnlockWalletTypedData({
chainId,
guardiaContractAddress,
wallet,
validUntil,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
walletVersion: 2 | 1
// message?: { [key: string]: any }
}) {
const typedData = {
types: {
EIP712Domain,
unlock: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
],
},
// EIP712.hash(
// EIP712.Domain("LoopringWallet", "2.0.0", address(this))
// )
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'GuardianModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'unlock',
message: {
wallet: wallet,
validUntil: validUntil,
},
}
const hash= utils._TypedDataEncoder.hash(typedData.domain, {unlock: typedData.types.unlock}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
function getApproveChangeMasterCopy({
chainId,
guardiaContractAddress,
wallet,
validUntil,
newGuardians,
message,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
newGuardians?: Buffer | any
message?: { [key: string]: any }
walletVersion: 2 | 1
}) {
const typedData = {
types: {
EIP712Domain,
changeMasterCopy: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
{ name: 'masterCopy', type: 'address' },
],
},
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'GuardianModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'changeMasterCopy',
message: {
wallet: wallet,
validUntil: validUntil,
...message,
},
}
const hash= utils._TypedDataEncoder.hash(typedData.domain, {changeMasterCopy: typedData.types.changeMasterCopy}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
function getDepositWalletTypedData({
chainId,
guardiaContractAddress,
wallet,
validUntil,
newGuardians,
message,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
newGuardians?: Buffer | any
message?: { [key: string]: any }
walletVersion: 2 | 1
}) {
const typedData = {
types: {
EIP712Domain,
callContract: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
{ name: 'to', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'data', type: 'bytes' },
],
},
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'TransferModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'callContract',
message: {
wallet: wallet,
validUntil: validUntil,
...message,
},
}
const hash= utils._TypedDataEncoder.hash(typedData.domain, {callContract: typedData.types.callContract}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
function getApproveTokenCopy({
chainId,
guardiaContractAddress,
wallet,
validUntil,
message,
walletVersion,
}: {
chainId: ChainId
guardiaContractAddress: any
wallet: any
validUntil: any
newGuardians?: Buffer | any
message?: { [key: string]: any }
walletVersion: 2 | 1
}) {
const typedData = {
types: {
EIP712Domain,
approveToken: [
{ name: 'wallet', type: 'address' },
{ name: 'validUntil', type: 'uint256' },
{ name: 'token', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
},
domain: domain(
chainId,
guardiaContractAddress,
walletVersion == 1 ? 'TransferModule' : 'LoopringWallet',
walletVersion == 1 ? '1.2.0' : '2.0.0',
),
primaryType: 'approveToken',
message: {
wallet: wallet,
validUntil: validUntil,
...message,
},
}
const hash= utils._TypedDataEncoder.hash(typedData.domain, {approveToken: typedData.types.approveToken}, typedData.message)
console.log('EIP712 hash', hash)
return {
typedData,
hash
}
}
export async function signHebaoApproveWrap(
props: {
web3: any
chainId: ChainId
owner: string
isHWAddr: boolean
type: HEBAO_META_TYPE
} & {
newGuardians?: any
masterCopy?: string
wallet: string
validUntil: number
forwarderModuleAddress?: string
messageData?: {
[key: string]: any
}
guardian: any
walletVersion: 1 | 2
},
) {
try {
const {
chainId,
web3,
type,
owner,
isHWAddr,
wallet,
validUntil,
forwarderModuleAddress,
masterCopy,
messageData,
guardian,
walletVersion,
} = props as any
let messageHash
let data: {typedData: any, hash: string} | undefined
myLog('backend hash', guardian?.messageHash)
switch (type) {
case HEBAO_META_TYPE.recovery:
let newOwner = messageData?.newOwner
if (!newOwner) {
throw 'no newOwner'
}
data = getApproveRecoverTypedData({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
message: {
newOwner,
...(walletVersion == 1
? {}
: {
newGuardians: messageData?.newGuardians,
// : ethers.utils.solidityKeccak256(
// Array(messageData?.newGuardians?.length).fill('address'),
// messageData?.newGuardians,
// ),
// ethUtil.keccak256(
// messageData?.newGuardians
// .map((address: string) => address.toLowerCase().replace('0x', ''))
// .join(''),
// ),
// ethers.utils.solidityKeccak256(
// Array(messageData?.newGuardians?.length).fill('address'),
// messageData?.newGuardians,
// ),
}),
},
walletVersion,
})
break
case HEBAO_META_TYPE.remove_guardian:
data = getRemoveGuardianTypedData({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
message: {
guardian: messageData?.guardianAddress,
},
walletVersion,
})
break
case HEBAO_META_TYPE.unlock_wallet:
data = getUnlockWalletTypedData({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
// message: {
// guardian: messageData.guardian,
// },
walletVersion,
})
break
case HEBAO_META_TYPE.transfer:
data = getApproveTransferTypedData({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
message: {
token: messageData.token,
to: messageData.to,
amount: messageData.amount,
logdata: messageData.logdata,
},
walletVersion,
})
break
case HEBAO_META_TYPE.deposit_wallet:
data = getDepositWalletTypedData({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
message: {
to: messageData.to,
value: messageData.value,
data: messageData.data,
},
walletVersion,
})
break
case HEBAO_META_TYPE.approve_token:
data = getApproveTokenCopy({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
message: {
token: messageData.token,
to: messageData.to,
amount: messageData.amount,
// logdata: messageData.logdata,
// masterCopy: messageData.newMasterCopy,
},
walletVersion,
})
break
case HEBAO_META_TYPE.upgrade_contract:
data = getApproveChangeMasterCopy({
chainId,
guardiaContractAddress: forwarderModuleAddress ? forwarderModuleAddress : masterCopy,
wallet, // guardian.signedRequest.wallet,
validUntil, // guardian.signedRequest.validUntil,
message: {
masterCopy: messageData.newMasterCopy,
},
walletVersion,
})
break
default:
messageHash = guardian?.messageHash
}
if (data && data?.typedData) {
const result = await getEcDSASig(
web3,
data.typedData,
owner,
isHWAddr ? GetEcDSASigType.WithoutDataStruct : GetEcDSASigType.HasDataStruct,
chainId,
undefined,
'',
ConnectorNames.Unknown,
// counterFactualInfo
)
// ecdsaSignature
return {
signature: result.ecdsaSig += SigSuffix.Suffix02,
hash: data.hash
}
} else {
// const messageHash =
const signature: any = await personalSign(
web3,
owner,
'',
messageHash,
ConnectorNames.Unknown,
chainId,
)
if (signature?.sig) {
return {
signature: signature.sig += SigSuffix.Suffix03,
hash: data?.hash
}
} else {
throw 'empty'
}
}
// }
} catch (error) {
// console.log('EcDSASig error try sign WithoutDataStruct')
throw error
}
}
================================================
FILE: src/api/config/index.ts
================================================
/* tslint:disable */
// @ts-nocheck
import { toBig, toFixed } from '../../utils/formatter'
import BigNumber from 'bignumber.js'
function getTokenBySymbol(symbol, tokens) {
if (typeof symbol === 'undefined') {
return {}
}
return tokens.find((token) => token.symbol.toLowerCase() === symbol.toLowerCase()) || {}
}
function fromWEI(symbol, valueInWEI, tokens, { precision, ceil }: any = {}) {
try {
const token = getTokenBySymbol(symbol, tokens)
const precisionToFixed = precision ? precision : token.precision
const value = toBig(valueInWEI).div('1e' + token.decimals)
return toFixed(value, precisionToFixed, ceil)
} catch (err) {
return undefined
}
}
function toWEI(symbol, value, tokens, rm = BigNumber.ROUND_FLOOR) {
const token = getTokenBySymbol(symbol, tokens)
if (typeof token === 'undefined') {
return 0
}
return toBig(value)
.times('1e' + token.decimals)
.toFixed(0, rm)
}
export { fromWEI, toWEI }
export * from './guardianTypeData'
================================================
FILE: src/api/contacts_api.ts
================================================
/* eslint-disable camelcase */
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
export class ContactAPI extends BaseAPI {
public async getContacts(
request: loopring_defs.GetContactsRequest,
apiKey: string,
// url: string = loopring_defs.LOOPRING_URLs.GET_CONTACTS
) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_CONTACTS,
queryParams: request, //request
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
...raw_data,
raw_data,
} as {
raw_data: R
} & R
}
public async createContact(request: loopring_defs.CreateContactRequest, apiKey: string) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.CREATE_CONTACT,
bodyParams: request, //request
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
public async updateContact(request: loopring_defs.UpdateContactRequest, apiKey: string) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.UPDATE_CONTACT,
bodyParams: request, //request
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
public async deleteContact(
request: loopring_defs.DeleteContactRequest,
apiKey: string,
// url: string = loopring_defs.LOOPRING_URLs.GET_CONTACTS
) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.DELETE_CONTACT,
bodyParams: request, //request
method: loopring_defs.ReqMethod.DELETE,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
}
// ContactAPI.get
================================================
FILE: src/api/contract_api.ts
================================================
import Web3 from 'web3'
import { Transaction } from '@ethereumjs/tx'
import * as loopring_defs from '../defs'
import * as fm from '../utils/formatter'
import { addHexPrefix, toHex, toNumber } from '../utils/formatter'
import { Contracts } from './ethereum/contracts'
export enum ERC20Method {
Approve = 'approve',
Deposit = 'deposit',
ForceWithdraw = 'forceWithdraw',
}
export const ApproveVal = {
Zero: '0x0',
Max: '0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
}
function checkWeb3(web3: any) {
if (!web3) throw new Error('got undefined web3')
}
/**
* @description sign hash
* @param web3
* @param account
* @param hash
* @returns {Promise.<*>}
*/
export async function sign(web3: any, account: string, pwd: string, hash: string) {
checkWeb3(web3)
return new Promise((resolve) => {
web3.eth.sign(hash, account, pwd, function (err: any, result: any) {
if (!err) {
const r = result.slice(0, 66)
const s = addHexPrefix(result.slice(66, 130))
let v = toNumber(addHexPrefix(result.slice(130, 132)))
if (v === 0 || v === 1) v = v + 27 // 修复ledger的签名
resolve({ result: { r, s, v } })
} else {
const errorMsg = err.message.substring(0, err.message.indexOf(' at '))
resolve({ error: { message: errorMsg } })
}
})
})
}
/**
* @description Signs ethereum tx
* @param web3
* @param account
* @param rawTx
* @returns {Promise.<*>}
*/
export async function signEthereumTx(
web3: any,
account: string,
rawTx: any,
chainId: loopring_defs.ChainId,
): Promise<{ result: string; rawTx: object } | { error: any }> {
const ethTx = Transaction.fromSerializedTx(rawTx)
const hash = toHex(ethTx.hash())
try {
const response: any = await sign(web3, account, '', hash)
if (!response.error) {
const signature = response['result']
signature.v += chainId * 2 + 8
const jsonTx = Object.assign(ethTx.toJSON(), signature)
jsonTx.from = rawTx.from
return { result: fm.toHex(JSON.stringify(jsonTx)), rawTx: jsonTx }
} else {
return { error: response.error }
// throw new Error(response["error"]["message"]);
}
} catch (err) {
return { error: err }
}
}
export async function getNonce(web3: Web3, addr: string) {
if (web3) return await web3.eth.getTransactionCount(addr)
return -1
}
export async function sendRawTx(
web3: any,
from: string,
to: string,
value: any,
data: any,
chainId: loopring_defs.ChainId,
nonce: string | number | undefined | null,
gasPrice: any,
gasLimit: string | number | undefined,
sendByMetaMask = true,
) {
checkWeb3(web3)
gasPrice = fm.fromGWEI(gasPrice).toNumber()
const rawTx = {
from,
to,
value,
data,
chainId,
nonce,
gasPrice,
gasLimit,
}
if (sendByMetaMask) {
return await sendTransaction(web3, rawTx)
}
const res: any = await signEthereumTx(web3, from, rawTx, chainId)
if (res?.rawTx) {
return await sendTransaction(web3, res.rawTx)
} else {
}
return res
}
function _genContractData(Contract: any, method: string, data: any) {
return Contract.encodeInputs(method, data)
}
function genERC20Data(method: string, data: any) {
return _genContractData(Contracts.ERC20Token, method, data)
}
export function genExchangeData(method: string, data: any) {
return _genContractData(Contracts.ExchangeContract, method, data)
}
export async function approve(
web3: Web3,
from: string,
to: string,
depositAddress: string,
_value: string,
chainId: loopring_defs.ChainId,
nonce: string | number,
gasPrice: string | number,
gasLimit: string | number,
sendByMetaMask: boolean,
) {
const data = genERC20Data(ERC20Method.Approve, {
_spender: depositAddress,
_value,
})
return await sendRawTx(
web3,
from,
to,
'0',
data,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
}
// 3.6
/**
* Approve Zero
* @param tokenAddress: approve token symbol to zero
* @param nonce: Ethereum nonce of this address
* @param gasPrice: gas price in gwei
* @param sendByMetaMask
*/
export async function approveZero(
web3: any,
owner: string,
tokenAddress: string,
depositAddress: string,
gasPrice: number,
gasLimit: number,
chainId: loopring_defs.ChainId = loopring_defs.ChainId.GOERLI,
nonce: number,
sendByMetaMask = false,
) {
return await approve(
web3,
owner,
tokenAddress,
depositAddress,
ApproveVal.Zero,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
}
// 3.6
/**
* Approve Max
* @param tokenAddress: approve token symbol to max
* @param nonce: Ethereum nonce of this address
* @param gasPrice: gas price in gwei
* @param sendByMetaMask
*/
export async function approveMax(
web3: any,
owner: string,
tokenAddress: string,
depositAddress: string,
gasPrice: string,
gasLimit: string,
chainId: loopring_defs.ChainId = loopring_defs.ChainId.GOERLI,
nonce: string,
sendByMetaMask = false,
) {
return await approve(
web3,
owner,
tokenAddress,
depositAddress,
ApproveVal.Max,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
}
// 3.6
/**
* deposit
*/
export async function deposit(
web3: any,
from: string,
exchangeAddress: string,
token: loopring_defs.TokenInfo,
value: number,
fee: number,
gasPrice: string,
gasLimit: string,
chainId: loopring_defs.ChainId = loopring_defs.ChainId.GOERLI,
nonce: string,
sendByMetaMask = true,
to?: string,
) {
let valueC = fm.toBig(value).times('1e' + token.decimals)
const amount = fm.toHex(valueC)
const data = genExchangeData(ERC20Method.Deposit, {
tokenAddress: token.address,
amount,
from,
to: to ? to : from,
extraData: '',
})
if (token.type === 'ETH') {
valueC = valueC.plus(fee)
} else {
valueC = fm.toBig(fee)
}
return await sendRawTx(
web3,
from,
exchangeAddress,
valueC.toFixed(),
data,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
}
/**
* forceWithdrawal
*/
export async function forceWithdrawal(
web3: any,
from: string,
accountID: number,
exchangeAddress: string,
token: loopring_defs.TokenInfo,
fee: number,
gasPrice: number,
gasLimit: number,
chainId: loopring_defs.ChainId = loopring_defs.ChainId.GOERLI,
nonce: number,
sendByMetaMask = false,
) {
const valueC = fm.toBig(fee)
const data = genExchangeData(ERC20Method.ForceWithdraw, {
owner: from,
tokenAddress: token.address,
accountID,
})
return await sendRawTx(
web3,
from,
exchangeAddress,
valueC.toFixed(),
data,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
}
/**
* @description Sends ethereum tx through MetaMask
* @param web3
* @param tx
* @returns {*}
*/
export async function sendTransaction(web3: any, tx: any) {
delete tx.gasPrice
// delete tx.gas;
const response: any = await new Promise((resolve) => {
web3.eth.sendTransaction(tx, function (err: any, transactionHash: string) {
if (!err) {
resolve({ result: transactionHash })
} else {
resolve({ error: { message: err.message } })
}
})
})
if (response['result']) {
return response
} else {
throw new Error(response['error']['message'])
}
}
export async function isContract(web3: any, address: string) {
const code = await web3.eth.getCode(address)
return code && code.length > 2
}
================================================
FILE: src/api/defi_api.ts
================================================
/* eslint-disable camelcase */
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
import { makeInvestMarkets, makeMarkets, sortObjDictionary } from '../utils'
import * as sign_tools from './sign/sign_tools'
import { AxiosResponse } from 'axios'
import { getMidPrice } from './exchange_api'
import { getEdDSASigWithPoseidon } from './sign/sign_tools'
import * as string_decoder from 'string_decoder'
export class DefiAPI extends BaseAPI {
/*
* Returns the fee rate of users placing orders in specific markets
*/
public async getDefiToken(): Promise<{
raw_data: R
tokensMap: loopring_defs.LoopringMap
idIndex: loopring_defs.LoopringMap
addressIndex: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DEFI_TOKENS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const tokensMap: loopring_defs.LoopringMap = {}
const addressIndex: loopring_defs.LoopringMap = {}
const idIndex: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.TokenInfo) => {
if (item.symbol.startsWith('LP-')) {
item.isLpToken = true
} else {
item.isLpToken = false
}
tokensMap[item.symbol] = item
const coinInfo = {
icon: loopring_defs.SoursURL + `ethereum/assets/${item.address}/logo.png`,
name: item.symbol,
simpleName: item.symbol,
description: item.type,
company: '',
}
// totalCoinMap[item.symbol] = coinInfo;
addressIndex[item.address.toLowerCase()] = item.symbol
idIndex[item.tokenId] = item.symbol
})
}
return {
tokensMap,
idIndex,
addressIndex,
raw_data,
}
}
public async getDefiMarkets(
request: loopring_defs.GetDefiMarketRequest,
url: string = loopring_defs.LOOPRING_URLs.GET_DEFI_MARKETS,
): Promise<{
markets: loopring_defs.LoopringMap
pairs: loopring_defs.LoopringMap
tokenArr: string[]
tokenArrStr: string
marketArr: string[]
marketArrStr: string
raw_data: R
}> {
const reqParams: loopring_defs.ReqParams = {
url,
queryParams: {},
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const types = request?.defiType?.toString()?.split(',')
return {
...makeInvestMarkets(raw_data, types),
raw_data,
}
}
public async orderDefi(
request: loopring_defs.DefiOrderRequest,
privateKey: string,
apiKey: string,
): Promise<
(Omit & { raw_data: Omit }) | loopring_defs.RESULT_INFO
> {
const dataToSig = [
request.exchange,
request.storageId,
request.accountId,
request.sellToken.tokenId,
request.buyToken.tokenId,
request.sellToken.volume,
request.buyToken.volume,
request.validUntil,
request.maxFeeBips,
request.fillAmountBOrS ? 1 : 0,
0,
]
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_DEFI_ORDER,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG_POSEIDON,
sigObj: {
dataToSig,
sigPatch: loopring_defs.SigPatchField.EddsaSignature,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
public async getDefiReward(
request: loopring_defs.GetUserDefiRewardRequest,
apiKey: string,
): Promise<
| {
raw_data: R
totalNum: number
totalRewards: string
lastDayRewards: string
rewards: []
}
| loopring_defs.RESULT_INFO
> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DEFI_REWARDS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
public async getDefiTransaction(
request: loopring_defs.GetUserDefiTxRequest,
apiKey: string,
): Promise<
| {
raw_data: R
totalNum: number
userDefiTxs: loopring_defs.UserDefiTxsHistory[]
}
| loopring_defs.RESULT_INFO
> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DEFI_TRANSACTIONS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userDefiTxs: raw_data.transactions as loopring_defs.UserDefiTxsHistory[],
raw_data,
}
}
public async getDualInfos(request: loopring_defs.GetDualInfosRequest): Promise<
| loopring_defs.RESULT_INFO
| {
totalNum: number
dualInfo: {
infos: loopring_defs.DualProductAndPrice[]
index: loopring_defs.DualIndex
balance: loopring_defs.DualBalance[]
rules: loopring_defs.DualRulesCoinsInfo[]
}
raw_data: R
}
> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DUAL_INFOS,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
dualInfo: {
infos: raw_data.infos as loopring_defs.DualProductAndPrice[],
index: raw_data.index as loopring_defs.DualIndex,
balance: raw_data.balance as loopring_defs.DualBalance[],
rules: raw_data.rules as loopring_defs.DualRulesCoinsInfo[],
},
raw_data,
}
}
public async getDualBalance(request = undefined) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DUAL_BALANCE,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
dualBalanceMap: [...raw_data].reduce((prev, item) => {
return { ...prev, [item.coin]: item }
}, {} as loopring_defs.LoopringMap),
}
}
public async getDualPrices(request: loopring_defs.GetDualPricesRequest) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DUAL_PRICES,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data?.totalNum,
infos: raw_data.infos as loopring_defs.DualPrice[],
}
}
public async getDualIndex(request: { baseSymbol: string; quoteSymbol: string }) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DUAL_INDEX,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
dualPrice: raw_data as loopring_defs.DualPrice[],
}
}
public async getDualTransactions(request: loopring_defs.GetUserDualTxRequest, apiKey: string) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DUAL_TRANSACTIONS,
queryParams: {
...request,
...(request.retryStatuses ? { retryStatuses: request.retryStatuses.join(',') } : {}),
},
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
indexes: raw_data?.indexes,
userDualTxs: raw_data.transactions as loopring_defs.UserDualTxsHistory[],
raw_data,
}
return
}
public async orderDual(
request: loopring_defs.DualOrderRequest,
privateKey: string,
apiKey: string,
) {
const dataToSig = [
request.exchange,
request.storageId,
request.accountId,
request.sellToken.tokenId,
request.buyToken.tokenId,
request.sellToken.volume,
request.buyToken.volume,
request.validUntil,
request.maxFeeBips,
request.fillAmountBOrS ? 1 : 0,
0,
]
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_DUAL_ORDER,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG_POSEIDON,
sigObj: {
dataToSig,
sigPatch: loopring_defs.SigPatchField.EddsaSignature,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
public async editDual(
request: loopring_defs.DualEditRequest,
privateKey: string,
apiKey: string,
) {
const { newOrder } = request
let bodyParams = { ...request }
if (newOrder) {
const dataToSig = [
newOrder.exchange,
newOrder.storageId,
newOrder.accountId,
newOrder.sellToken.tokenId,
newOrder.buyToken.tokenId,
newOrder.sellToken.volume,
newOrder.buyToken.volume,
newOrder.validUntil,
newOrder.maxFeeBips,
newOrder.fillAmountBOrS ? 1 : 0,
0,
]
const eddsaSignature = getEdDSASigWithPoseidon(dataToSig, privateKey).result
bodyParams = { ...bodyParams, newOrder: { ...newOrder, eddsaSignature } }
}
const _dataToSig: Map = sortObjDictionary(bodyParams)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_DUAL_EDIT,
bodyParams,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig: _dataToSig,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
public async getDualUserLocked(
{
lockTag = [loopring_defs.DUAL_TYPE.DUAL_BASE, loopring_defs.DUAL_TYPE.DUAL_CURRENCY],
...request
}: loopring_defs.DualUserLockedRequest,
apiKey: string,
) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DUAL_USER_LOCKED,
queryParams: { ...request, lockTag: lockTag.join(',') },
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
lockRecord: raw_data.lockRecord,
raw_data,
}
return
}
public async sendStakeClaim(
req: loopring_defs.OriginStakeClaimRequestV3WithPatch,
options?: { accountId?: number; counterFactualInfo?: any },
) {
const { request, web3, chainId, eddsaKey, apiKey, isHWAddr: isHWAddrOld } = req
const { accountId, counterFactualInfo }: any = options ? options : { accountId: 0 }
const { transfer } = request
const isHWAddr = !!isHWAddrOld
let ecdsaSignature = undefined
transfer.payeeId = 0
transfer.memo = `STAKE-CLAIM->${request.accountId}`
try {
ecdsaSignature = await sign_tools.transferWrap({
transfer: transfer as loopring_defs.OriginTransferRequestV3,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
})
// ecdsaSignature += isHWAddr ? SigSuffix.Suffix03 : SigSuffix.Suffix02
} catch (error) {
throw error
}
if (counterFactualInfo) {
transfer.counterFactualInfo = counterFactualInfo
}
transfer.eddsaSignature = sign_tools.get_EddsaSig_Transfer(
transfer as loopring_defs.OriginTransferRequestV3,
eddsaKey,
).result
transfer.ecdsaSignature = ecdsaSignature
const dataToSig: Map = sortObjDictionary(request)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_STAKE_CLAIM,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey,
},
}
let raw_data
try {
raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, ...raw_data }
} catch (error) {
throw error as AxiosResponse
}
// return this.returnTxHash(raw_data);
// const raw_data = (await this.makeReq().request(reqParams)).data;
}
public async sendStakeRedeem(
request: {
accountId: number
hash: string
token: loopring_defs.TokenVolumeV3
},
privateKey: string,
apiKey: string,
) {
const dataToSig: Map = sortObjDictionary(request)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_STAKE_REDEEM,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, ...raw_data }
}
public async sendStake(
request: {
accountId: number
token: loopring_defs.TokenVolumeV3
timestamp: number
},
privateKey: string,
apiKey: string,
) {
const dataToSig: Map = sortObjDictionary(request)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_STAKE,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, ...raw_data }
}
public async getStakeProducts(): Promise<{
products: loopring_defs.STACKING_PRODUCT[]
raw_data: R
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_STAKE_PRODUCTS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { products: raw_data, raw_data }
}
public async getStakeSummary(
request: {
accountId: number
tokenId: number
start?: number
end?: number
limit?: number
offset?: number
hashes?: string
statuses?: string
},
apiKey: string,
): Promise<
| {
raw_data: R
totalNum: number
totalStaked: string
totalStakedRewards: string
totalLastDayPendingRewards: string
totalClaimableRewards: string
list: loopring_defs.StakeInfoOrigin[]
}
| loopring_defs.RESULT_INFO
> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_STAKE_SUMMARY,
queryParams: { ...request },
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { ...raw_data, list: raw_data.staking, raw_data }
}
public async getStakeTransactions(
request: {
accountId: number
tokenId: number
start?: number
end?: number
limit?: number
offset?: number
hashes?: string
types?: string
},
apiKey: string,
): Promise<{
list: loopring_defs.STACKING_TRANSACTIONS[]
totalNum: number
raw_data: R
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_STAKE_TRANSACTIONS,
queryParams: { ...request },
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
list: raw_data.transactions,
totalNum: raw_data.totalNum,
raw_data,
}
}
public async getBtradeMarkets(): Promise<{
markets: loopring_defs.LoopringMap
pairs: loopring_defs.LoopringMap
tokenArr: string[]
tokenArrStr: string
marketArr: string[]
marketArrStr: string
raw_data: R
}> {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_BTRATE_MARKETS,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
let result: any = {}
const pairs: loopring_defs.LoopringMap = {}
// const isMix = url === loopring_defs.LOOPRING_URLs.GET_MIX_MARKETS;
if (raw_data instanceof Array) {
const reformat = raw_data.reduce((prev, ele: loopring_defs.BTRADE_MARKET) => {
if (/-/gi.test(ele.market)) {
return [
...prev,
{
...ele,
btradeMarket: ele.market,
market: ele.market.replace(loopring_defs.BTRADENAME, ''),
// enabled: true,
} as loopring_defs.BTRADE_MARKET,
]
} else {
return prev
}
}, [] as loopring_defs.BTRADE_MARKET[])
result = makeMarkets({ markets: reformat })
}
return {
markets: result.markets,
pairs: result.pairs,
tokenArr: result.tokenArr,
tokenArrStr: result.tokenArrStr,
marketArr: result.marketArr,
marketArrStr: result.marketArrStr,
raw_data,
}
}
public async getBtradeDepth({
request,
tokenMap,
}: {
request: {
market: string
level: number
limit?: number
}
tokenMap?: any
}): Promise<{
depth: loopring_defs.DepthData
raw_data: R
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_BTRATE_DEPTH,
queryParams: { ...request },
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const symbol = raw_data.market
const timestamp = raw_data.timestamp
const { asks, bids, mid_price } = getMidPrice({
_asks: raw_data.asks,
_bids: raw_data.bids,
})
const depth: loopring_defs.DepthData = {
symbol,
// @ts-ignore
market: raw_data.market,
version: raw_data.version,
timestamp,
mid_price,
bids: bids.ab_arr,
bids_prices: bids.ab_prices,
bids_amtTotals: bids.ab_amtTotals,
bids_volTotals: bids.ab_volTotals,
bids_amtTotal: bids.amtTotal.toString(),
bids_volTotal: bids.volTotal.toString(),
asks: asks.ab_arr,
asks_prices: asks.ab_prices,
asks_amtTotals: asks.ab_amtTotals,
asks_volTotals: asks.ab_volTotals,
asks_amtTotal: asks.amtTotal.toString(),
asks_volTotal: asks.volTotal.toString(),
}
return {
depth,
raw_data: raw_data as unknown as R,
}
}
public async getBtradeOrders({
request,
apiKey,
}: {
request: loopring_defs.GetOrdersRequest
apiKey: string
}) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_BTRATE_ORDERS,
queryParams: { ...request },
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
// baseSettled quoteSettled
list: raw_data.transactions,
totalNum: raw_data.totalNum,
raw_data,
}
}
public async sendBtradeOrder({
request,
privateKey,
apiKey,
}: {
request: loopring_defs.OriginBTRADEV3OrderRequest
privateKey: string
apiKey: string
}) {
// const dataToSig: Map = sortObjDictionary(request);
const dataToSig = [
request.exchange,
request.storageId,
request.accountId,
request.sellToken.tokenId,
request.buyToken.tokenId,
request.sellToken.volume,
request.buyToken.volume,
request.validUntil,
request.maxFeeBips,
request.fillAmountBOrS ? 1 : 0,
0,
]
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_BTRATE_ORDER,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG_POSEIDON,
sigObj: {
dataToSig,
sigPatch: loopring_defs.SigPatchField.EddsaSignature,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, ...raw_data }
}
public async getDefiApys<
R = {
product: string
defiType: string
apys: {
apy: string
createdAt: string
}[]
},
>({
request,
}: {
request: {
start?: number
end?: number
defiType: 'LIDO' | 'ROCKETPOOL' | 'L2STAKING' | 'CIAN' | string
product: string
}
}): Promise<{
raw_data: R
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DEFI_APYS,
queryParams: { ...request },
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
public async getDefiDepositList(
{
types,
markets,
number = 10,
...request
}: {
accountId: number
number: number //
markets: Array | string
types: Array<'LIDO' | 'ROCKETPOOL' | 'L2STAKING' | 'CIAN' | string> | string
product: string
},
apiKey: string,
): Promise<{
raw_data: R
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_DEFI_STAKE_TRANSACTIONS,
apiKey,
queryParams: {
...request,
number,
markets: typeof markets === 'string' ? markets : markets?.join(','),
types: typeof types === 'string' ? types : types?.join(','),
},
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
public async getTaikoFarmingPositionInfo({
accountId,
}: {
accountId: number
}): Promise<{
data: {
tokenId: number
symbol: string
address: string
decimals: number
status: number
apr: string
precision: number
stakedTotal: string
claimedTotal: string
holdClaimedTotal: string
claimableTotal: string
totalPoints: string
minAmount: string
maxAmount: string
}[]
account: {
status: number // 0 => init, 1 => opened, 2 => minting, 3 => redeeming
}
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_POSITION_INFO,
queryParams: {
accountId,
},
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data.resultInfo.code !== 0) {
throw raw_data.resultInfo
} else {
return raw_data
}
}
public async getTaikoFarmingTransactions(
queryParams: {
accountId: number
tokenId: number
start?: number
end?: number
limit?: number
offset?: number
hashes?: string
types?: string
},
apiKey: string,
): Promise<{
totalNum: number
transactions: {
accountId: number
tokenId: number
amount: string
productId: string
hash: string
stakingType: 'subscribe' | 'unsubscribe'
createdAt: number
updatedAt: number
}[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_TRANSACTIONS,
apiKey,
queryParams: queryParams,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data
}
public async getTaikoFarmingUserSummary(queryParams: {
accountId: number
tokenId: number
start?: number
end?: number
limit?: number
offset?: number
hashes?: string
statuses?: string
}): Promise<{
totalNum: number
totalStaked: string
totalStakedRewards: string
totalLastDayPendingRewards: string
totalClaimableRewards: string
staking: {
accountId: number
tokenId: number
stakeAt: number
initialAmount: string
remainAmount: string
totalRewards: string
productId: string
hash: string
status: string
createdAt: number
updatedAt: number
claimableTime: number
lastDayPendingRewards: string
apr: string
}[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_USER_SUMMARY,
queryParams: queryParams,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data
}
public async getTaikoFarmingAvailableNft(
queryParams: {
accountId: number
},
apiKey: string,
): Promise {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_AVAILABLE_NFT,
apiKey,
queryParams: queryParams,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data
}
public async getTaikoFarmingTransactionByHash(
queryParams: {
accountId: number
hash: string
},
apiKey: string,
) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_TRANSACTION_BY_HASH,
apiKey,
queryParams: queryParams,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data as {
operation: {
accountId: number
tokenId: number
amount: string
productId: string
hash: string
stakingType: string
status: number
createdAt: number
updatedAt: number
}
}
}
public async getTaikoFarmingDepositDurationList(queryParams: {
accountId: number
tokenId: number
start?: number
end?: number
limit?: number
offset?: number
hashes?: string
statuses?: string
}) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_DEPOSIT_DURATION_LIST,
queryParams: queryParams,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data as {
totalNum: number
data: {
accountId: number
tokenId: number
stakeAt: number
txHash: string
eventIndex: number
lockDuration: number
hash: string
status: string
createdAt: number
updatedAt: number
}[]
}
}
public async getTaikoFarmingGetRedeem(
queryParams: {
accountId: number
tokenId: number
},
apiKey: string,
) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_TAIKO_FARMING_GET_REDEEM,
queryParams: queryParams,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data as {
redeemAmount: string
profit: string
profitOfU: string
resultInfo?: loopring_defs.RESULT_INFO
}
}
public async submitTaikoFarmingClaim({
request,
eddsaKey,
apiKey,
}: loopring_defs.TaikoFarmingSubmitOrderNFTRequestV3WithPatch) {
const takerOrderEddsaSignature = sign_tools.get_EddsaSig_NFT_Order(request, eddsaKey).result
const _request = {
...request,
eddsaSignature: takerOrderEddsaSignature,
}
const dataToSig: Map = sortObjDictionary(_request)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_TAIKO_FARMING_SUMBIT_CLAIM,
bodyParams: _request,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey,
},
apiKey,
}
try {
const raw_data = (await this.makeReq().request(reqParams)).data
return raw_data as {
hash: string
status: string
isIdempotent: boolean
accountId: number
tokens: number[]
storageId: number
}
} catch (error) {
throw error as AxiosResponse
}
}
}
================================================
FILE: src/api/delegate_api.ts
================================================
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
export class DelegateAPI extends BaseAPI {
public async getCode(address: string): Promise {
const reqParams: loopring_defs.ReqParams = {
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
url: loopring_defs.LOOPRING_URLs.GET_DELEGATE_GET_CODE,
method: loopring_defs.ReqMethod.POST,
bodyParams: { address },
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return raw_data
}
public async getIPFS(path: string): Promise {
const reqParams: loopring_defs.ReqParams = {
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
url: loopring_defs.LOOPRING_URLs.GET_DELEGATE_GET_IPFS,
method: loopring_defs.ReqMethod.GET,
queryParams: { path },
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
return raw_data
}
public getCollectionDomain() {
return this.chainId === loopring_defs.ChainId.GOERLI
? 'https://uatnftinfos.loopring.io'
: 'https://nftinfos.loopring.io'
}
}
================================================
FILE: src/api/ethereum/contracts/AbiFunction.ts
================================================
/* tslint:disable */
// @ts-nocheck
import { addHexPrefix, clearHexPrefix, toBuffer, toHex } from '../../../utils/formatter'
import { methodID, rawDecode, rawEncode } from 'ethereumjs-abi'
import BN from 'bn.js'
export class AbiFunction {
name
inputTypes
inputs
outputTypes
outputs
constant
methodAbiHash
constructor({ inputs, name, outputs, constant }: any) {
this.name = name
this.inputTypes = inputs.map(({ type }) => type)
this.inputs = inputs
this.outputTypes = outputs.map(({ type }) => type)
this.outputs = outputs
this.constant = constant
this.methodAbiHash = toHex(methodID(name, this.inputTypes))
}
/**
* @description Returns encoded methodId and inputs
* @param inputs Object, examples {owner:'0x000...}
* @returns {string}
*/
encodeInputs(inputs) {
const abiInputs = this.parseInputs(inputs)
return this.methodAbiHash + clearHexPrefix(toHex(rawEncode(this.inputTypes, abiInputs)))
}
/**
* @description decode ethereum jsonrpc response result
* @param outputs
* @returns {*}
*/
decodeOutputs(outputs) {
return this.parseOutputs(rawDecode(this.outputTypes, toBuffer(outputs)))
}
/**
* @description decode encoded inputs
* @param encoded
* @returns {*}
*/
decodeEncodedInputs(encoded) {
return this.parseOutputs(rawDecode(this.inputTypes, toBuffer(addHexPrefix(encoded))))
}
parseInputs(inputs = {}) {
return this.inputs.map(({ name, type }) => {
if (inputs[name] === undefined) {
throw new Error(`Parameter ${name} of type ${type} is required!`)
}
return inputs[name]
})
}
parseOutputs(outputs) {
return outputs.map((output) => {
if (output instanceof BN) {
return toHex(output)
}
return output
})
}
}
================================================
FILE: src/api/ethereum/contracts/Contract.ts
================================================
/* tslint:disable */
// @ts-nocheck
import { methodID } from 'ethereumjs-abi'
import { toHex } from '../../../utils/formatter'
import { AbiFunction } from './AbiFunction'
export class Contract {
abiFunctions
constructor(abi) {
const funAbi = abi.filter(({ type }) => type === 'function')
this.abiFunctions = funAbi.reduce((acc, item) => {
const inputTypes = item.inputs.map(({ type }) => type)
const key = `${item.name}(${inputTypes.toString()})`
const methodHash = methodID(item.name, inputTypes)
return {
...acc,
[item.name]: new AbiFunction(item),
[key]: new AbiFunction(item),
//@ts-ignore
[methodHash]: new AbiFunction(item),
}
}, {})
}
/**
* @description Encodes inputs data according to ethereum abi
* @param method string can be full method or just method name, examples: 'balanceOf' or balanceOf(address)
* @param inputs array
* @returns {*|string}
*/
encodeInputs(method, inputs) {
const abiFunction = this.abiFunctions[method]
if (abiFunction) {
return abiFunction.encodeInputs(inputs)
} else {
throw new Error(`No ${method} method according to abi `)
}
}
/**
* @description Decodes outputs
* @param method string can be full method or just method name, examples: 'balanceOf' or balanceOf(address)
* @param outputs string
* @returns {*}
*/
decodeOutputs(method, outputs) {
const abiFunction = this.abiFunctions[method]
if (abiFunction) {
return abiFunction.decodeOutputs(outputs)
} else {
throw new Error(`No ${method} method according to abi `)
}
}
/**
* @description Decode encoded method and inputs
* @param encode string | Buffer
* @returns {*}
*/
decodeEncodeInputs(encode) {
encode = toHex(encode)
const methodId = encode.slice(0, 10)
const abiFunction = this.abiFunctions[methodId]
if (abiFunction) {
return abiFunction.decodeEncodedInputs(encode.slice(10))
} else {
throw new Error(`No corresponding method according to abi `)
}
}
}
================================================
FILE: src/api/ethereum/contracts/Contracts.ts
================================================
/* eslint-disable */
import {
contractWalletAbi,
erc20Abi,
erc721Abi,
erc1155Abi,
exchange36Abi,
hebao,
} from '../../config/abis'
import { Contract } from './Contract'
const ERC20Token = new Contract(erc20Abi.erc20)
const ExchangeContract = new Contract(exchange36Abi.exchange)
const ContractWallet = new Contract(contractWalletAbi.contractWallet)
const ERC1155 = new Contract(erc1155Abi.erc1155)
const ERC721 = new Contract(erc721Abi.erc721)
const HeBao = new Contract(hebao.hebao)
export {
ERC20Token,
ERC1155,
ERC721,
ExchangeContract,
ContractWallet,
erc721Abi,
erc1155Abi,
HeBao,
exchange36Abi
}
================================================
FILE: src/api/ethereum/contracts/index.ts
================================================
import { AbiFunction } from './AbiFunction'
import { Contract } from './Contract'
import * as Contracts from './Contracts'
const contracts = { Contracts }
export { AbiFunction, Contract, contracts, Contracts }
================================================
FILE: src/api/exchange_api.ts
================================================
/* eslint-disable camelcase */
import { BaseAPI } from './base_api'
import {
LOOPRING_URLs,
SIG_FLAG,
ReqMethod,
Side,
VipCatergory,
TradingInterval,
ReqParams,
MarketTradeInfo,
FiatPriceInfo,
LoopringMap,
GetAccountRequest,
GetCandlestickRequest,
GetDepthRequest,
GetTickerRequest,
GetMarketTradesRequest,
GetFiatPriceRequest,
GetTokenBalancesRequest,
GetAllowancesRequest,
MarketInfo,
ExchangeInfo,
TickerData,
DepthData,
Candlestick,
TokenRelatedInfo,
ABInfo,
GetEthBalancesRequest,
GetEthNonceRequest,
GetWithdrawalAgentsRequest,
GetAccountServicesRequest,
VipFeeRateInfoMap,
AccountInfo,
TokenAddress,
SEP,
GetALLTokenBalancesRequest,
TOKENMAPLIST,
DatacenterTokenInfo,
GetDatacenterTokenInfoRequest,
GetDatacenterTokenQuoteTrend,
GetDatacenterTokenQuoteTrendRequest,
DatacenterTokenInfoSimple,
GetDatacenterTokenOhlcvQuoteTrendRequest,
GetCmcTokenRelationsRequest,
getLatestTokenPricesRequest,
GetUserTradeAmount,
} from '../defs'
import BigNumber from 'bignumber.js'
import { getBaseQuote, makeMarket, makeMarkets } from '../utils'
const checkAmt = (rawStr: string) => {
if (rawStr.trim() === '') {
return '0'
}
return rawStr
}
function getFeeMap(feeArr: any[], type = 0) {
const feesMap: any = {}
if (feeArr instanceof Array) {
feeArr.forEach((item: any, index: number, array: any) => {
let key = ''
switch (type) {
case 1:
key = item.type
break
default:
key = item.token
}
// feesMap[key] = new BigNumber(item.fee)
feesMap[key] = item.fee
})
}
return feesMap
}
function genAB(data: any[], isReverse = false) {
const ab_arr: ABInfo[] = []
let amtTotal: BigNumber = new BigNumber(0)
let volTotal: BigNumber = new BigNumber(0)
const ab_prices: number[] = []
const ab_amtTotals: string[] = []
const ab_volTotals: string[] = []
const best = 0
if (data instanceof Array) {
data.forEach((item: any) => {
const price = parseFloat(item[0])
const amt = new BigNumber(item[1]) // base amt
const vol = new BigNumber(item[2]) // quote vol
amtTotal = amtTotal.plus(amt)
volTotal = volTotal.plus(vol)
ab_arr.push({
price: price,
amt: amt.toString(),
vol: vol.toString(),
amtTotal: amtTotal.toString(),
volTotal: volTotal.toString(),
})
ab_prices.push(price)
ab_amtTotals.push(amtTotal.toString())
ab_volTotals.push(volTotal.toString())
})
}
if (isReverse) {
ab_arr.reverse()
ab_prices.reverse()
ab_amtTotals.reverse()
ab_volTotals.reverse()
}
return {
ab_arr,
ab_prices,
amtTotal,
volTotal,
ab_amtTotals,
ab_volTotals,
best,
}
}
export function getMidPrice({
_asks,
askReverse,
_bids,
bidReverse,
}: {
_asks: any
askReverse?: boolean
_bids: any
bidReverse?: boolean
}) {
if (askReverse === undefined) {
askReverse = false
}
if (bidReverse === undefined) {
bidReverse = true
}
const bids = genAB(_bids, bidReverse)
const asks = genAB(_asks, askReverse)
const mid_price = (bids.ab_prices[bids.ab_prices.length - 1] + asks.ab_prices[0]) / 2
return {
bids,
asks,
mid_price,
}
}
export function getBtradeMidPrice({ _asks, _bids }: { _asks: any[]; _bids: any[] }) {
const bids = genAB(_bids)
const asks = genAB(_asks)
const mid_price = (bids.ab_prices[bids.ab_prices.length - 1] + asks.ab_prices[0]) / 2
return {
bids,
asks,
mid_price,
}
}
export class ExchangeAPI extends BaseAPI {
/*
* Returns the relayer's current time in millisecond
*/
public async getRelayerCurrentTime(): Promise<
{
raw_data: R
} & R
> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_RELAYER_CURRENT_TIME,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
/*
* Returns Protocol Portrait
*/
public async getProtocolPortrait(): Promise<
{
raw_data: R
} & R
> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_PROTOCOL_PORTRAIT,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
...raw_data,
raw_data,
}
}
/*
* Returns exchange fee info
*/
public async getExchangeFeeInfo(): Promise<{
raw_data: R
orderbookTradingFeesStablecoin: VipFeeRateInfoMap
orderbookTradingFees: VipFeeRateInfoMap
ammTradingFees: VipFeeRateInfoMap
otherFees: { [key: string]: string }
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_EXCHANGE_FEEINFO,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
orderbookTradingFeesStablecoin: raw_data[
VipCatergory.ORDERBOOK_TRADING_FEES_STABLECOIN
] as VipFeeRateInfoMap,
orderbookTradingFees: raw_data[VipCatergory.ORDERBOOK_TRADING_FEES] as VipFeeRateInfoMap,
ammTradingFees: raw_data[VipCatergory.AMM_TRADING_FEES] as VipFeeRateInfoMap,
otherFees: raw_data[VipCatergory.OTHER_FEES] as { [key: string]: string },
raw_data,
}
}
public async getWithdrawalAgents(request: GetWithdrawalAgentsRequest): Promise<{
raw_data: R
supportTokenMap: { [key: string]: any }
}> {
const reqParams: ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_WITHDRAWAL_AGENTS,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const supportTokenMap: { [key: string]: any } = {}
if (raw_data && raw_data.length > 0) {
raw_data.forEach((item: any) => {
if (item.symbol) {
supportTokenMap[item.symbol] = item
}
})
}
return {
supportTokenMap,
raw_data,
}
}
public async getRecommendedMarkets(): Promise<{
raw_data: R
recommended: string[]
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_RECOMENDED_MARKETS,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
let recommended = []
if (raw_data?.recommended) {
if (typeof raw_data.recommended === 'string') {
recommended = raw_data.recommended.split(',')
} else {
recommended = raw_data.recommended
}
}
return {
recommended,
raw_data,
}
}
/*
* Returns the configurations of all supported markets (trading pairs)
*/
public async getMarkets(url: string = LOOPRING_URLs.GET_MARKETS): Promise<{
markets: LoopringMap
pairs: LoopringMap
tokenArr: string[]
tokenArrStr: string
marketArr: string[]
marketArrStr: string
raw_data: R
}> {
const reqParams: ReqParams = {
url,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const { markets, pairs, tokenArr, tokenArrStr, marketArr, marketArrStr } = makeMarkets(
raw_data,
url,
)
return {
markets,
pairs,
tokenArr,
tokenArrStr,
// tokenArrStr: tokenArr.join(SEP),
marketArr,
marketArrStr,
// marketArrStr: marketArr.join(SEP),
raw_data,
}
}
/*
* Returns the configurations of all supported markets (trading pairs)
*/
public async getMixMarkets(): Promise<{
markets: LoopringMap
pairs: LoopringMap
tokenArr: string[]
tokenArrStr: string
marketArr: string[]
marketArrStr: string
raw_data: R
}> {
return await this.getMarkets(LOOPRING_URLs.GET_MIX_MARKETS)
}
/*
* Returns the configurations of all supported tokens, including Ether.
*/
public async getTokens(): Promise<
TOKENMAPLIST & {
raw_data: R
}
> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_TOKENS,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
// raw_data
// const coinMap: LoopringMap<{
// icon?: string;
// name: string; makeMarket
// simpleName: string;
// description?: string;
// company: string;
// }> = {};
// const totalCoinMap: LoopringMap<{
// icon?: string;
// name: string;
// simpleName: string;
// description?: string;
// company: string;
// }> = {};
// const addressIndex: LoopringMap = {};
// const idIndex: LoopringMap = {};
// const tokensMap: LoopringMap = {};
// if (raw_data instanceof Array) {
// raw_data.forEach((item: TokenInfo) => {
// if (item.symbol.startsWith("LP-")) {
// item.isLpToken = true;
// } else {
// item.isLpToken = false;
// }
// tokensMap[item.symbol] = item;
//
// const coinInfo = {
// icon: SoursURL + `ethereum/assets/${item.address}/logo.png`,
// name: item.symbol,
// simpleName: item.symbol,
// description: item.type,
// company: "",
// };
// if (!item.symbol.startsWith("LP-")) {
// coinMap[item.symbol] = coinInfo;
// }
// totalCoinMap[item.symbol] = coinInfo;
// addressIndex[item.address.toLowerCase()] = item.symbol;
// idIndex[item.tokenId] = item.symbol;
// });
// }
// raw_data: R;
return {
...makeMarket(raw_data),
// tokensMap,
// coinMap,
// totalCoinMap,
// idIndex,
// addressIndex,
raw_data,
}
}
// private splitTokens(token: string, tokens: LoopringMap) {
// let tokenArray: any = [];
// const tokenAddrArr: string[] = [];
//
// if (tokens) {
// if (token) {
// tokenArray = token.split(SEP);
// }
//
// if (
// tokenArray.length <= 0 ||
// (tokenArray.length === 1 && tokenArray[0] === "")
// ) {
// tokenArray = Reflect.ownKeys(tokens);
// }
//
// tokenArray.forEach((item: string) => {
// tokenAddrArr.push(tokens[item].address);
// });
//
// token = tokenAddrArr.join(SEP);
// }
//
// return {
// tokenArray,
// token,
// };
// }
/*
* Returns the balances of all supported tokens, including Ether.
*/
public async getEthBalances(request: GetEthBalancesRequest): Promise<{
raw_data: R
ethBalance: string
}> {
const reqParams: ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_ETH_BALANCES,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const ethBalance = raw_data.amount
return {
ethBalance,
raw_data,
}
}
/*
* Returns the balances of all supported tokens, including Ether.
*/
public async getTokenBalances(
request: GetTokenBalancesRequest,
): Promise<{
tokenBalances: Map
raw_data: R
}> {
const reqParams: ReqParams = {
queryParams: { ...request, token: request.token.join(SEP) },
url: LOOPRING_URLs.GET_TOKEN_BALANCES,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const tokenBalances: Map = new Map()
if (raw_data?.amount instanceof Array) {
raw_data.amount.forEach((value: any, index: number) => {
// tokenBalances[tokenArray[index]] = raw_data.amount[index];
tokenBalances.set(request.token[index] as unknown as T, value)
})
}
return {
tokenBalances,
raw_data,
}
}
public async getAllTokenBalances(
request: GetALLTokenBalancesRequest,
): Promise<{
tokenBalances: LoopringMap
raw_data: R
}> {
const reqParams: ReqParams = {
queryParams: { ...request },
url: LOOPRING_URLs.GET_AKK_TOKEN_BALANCES,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
tokenBalances: raw_data,
raw_data,
}
}
/*
* Returns the allowances of all supported tokens
*/
public async getAllowances(
request: GetAllowancesRequest,
// tokens: any
): Promise<{
raw_data: R
tokenAllowances: Map
}> {
const reqParams: ReqParams = {
queryParams: {
...request,
token: request.token.join(SEP),
},
url: LOOPRING_URLs.GET_ALLOWANCES,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const tokenAllowances: Map = new Map()
if (raw_data?.allowances instanceof Array) {
raw_data.allowances.forEach((value: any, index: number) => {
tokenAllowances.set(request.token[index] as unknown as T, value)
})
}
return {
tokenAllowances,
raw_data,
}
}
/*
* Return various configurations of Loopring.io
*/
public async getExchangeInfo(): Promise<{
exchangeInfo: ExchangeInfo
raw_data: R
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_EXCHANGE_INFO,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const exchangeInfo: ExchangeInfo = {
ammExitFees: getFeeMap(raw_data.ammExitFees),
chainId: raw_data.chainId,
depositAddress: raw_data.depositAddress,
exchangeAddress: raw_data.exchangeAddress,
fastWithdrawalFees: getFeeMap(raw_data.fastWithdrawalFees),
onchainFees: getFeeMap(raw_data.onchainFees, 1),
openAccountFees: getFeeMap(raw_data.openAccountFees),
transferFees: getFeeMap(raw_data.transferFees),
updateFees: getFeeMap(raw_data.updateFees),
withdrawalFees: getFeeMap(raw_data.withdrawalFees),
}
return {
exchangeInfo,
raw_data,
}
}
public async getMixDepth(request: GetDepthRequest) {
return await this.getDepth(request, LOOPRING_URLs.GET_MIX_DEPTH)
}
/*
* Returns the order book of a given trading pair.
*/
public async getDepth(
request: GetDepthRequest,
url: string = LOOPRING_URLs.GET_DEPTH,
): Promise<{
depth: DepthData
raw_data: R
}> {
if (request?.level === undefined) {
request.level = 0
}
if (request?.limit === undefined) {
request.limit = 50
}
const reqParams: ReqParams = {
queryParams: request,
url,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const timestamp = raw_data['timestamp']
const { asks, bids, mid_price } = getMidPrice({
_asks: raw_data['asks'],
_bids: raw_data['bids'],
})
const depth: DepthData = {
symbol: raw_data.market,
version: parseInt(raw_data.version),
timestamp,
mid_price,
bids: bids.ab_arr,
bids_prices: bids.ab_prices,
bids_amtTotals: bids.ab_amtTotals,
bids_volTotals: bids.ab_volTotals,
bids_amtTotal: bids.amtTotal.toString(),
bids_volTotal: bids.volTotal.toString(),
asks: asks.ab_arr,
asks_prices: asks.ab_prices,
asks_amtTotals: asks.ab_amtTotals,
asks_volTotals: asks.ab_volTotals,
asks_amtTotal: asks.amtTotal.toString(),
asks_volTotal: asks.volTotal.toString(),
}
return {
depth,
raw_data,
}
}
public async getMixTicker(request: GetTickerRequest): Promise<{
tickMap: LoopringMap
tickList: TickerData[]
raw_data: R
}> {
return await this.getTicker(request, LOOPRING_URLs.GET_MIX_TICKER)
}
/*
* Gets a markets ticker.
* Generally speaking, a ticker in Loopring consists in data from the market taken last 24Hours.
*/
public async getTicker(
request: GetTickerRequest,
url: string = LOOPRING_URLs.GET_TICKER,
): Promise<{
tickMap: LoopringMap
tickList: TickerData[]
raw_data: R
}> {
const reqParams: ReqParams = {
url,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const tickers = raw_data.tickers
const tickMap: LoopringMap = {}
const tickList: TickerData[] = []
if (tickers && tickers.length) {
tickers.forEach((item: any, ind: number, arr: any) => {
const open = parseFloat(item[4])
const close = parseFloat(item[7])
const symbol = item[0].replace('COMBINE-', '')
const { base, quote } = getBaseQuote(symbol)
let change = 0
if (!isNaN(open) && !isNaN(close)) {
change = (close - open) / open
}
const timestamp = parseInt(item[1])
const tick: TickerData = {
symbol,
base,
quote,
timestamp,
base_token_volume: item[2],
quote_token_volume: item[3],
open,
high: parseFloat(item[5]),
low: parseFloat(item[6]),
close,
count: parseInt(item[8]),
bid: parseFloat(item[9]),
ask: parseFloat(item[10]),
base_fee_amt: checkAmt(item[11]),
quote_fee_amt: checkAmt(item[12]),
change,
}
tickMap[symbol] = tick
tickList.push(tick)
})
}
return {
tickMap,
tickList,
raw_data,
}
}
public async getAllMixTickers(markets: string | undefined = undefined) {
let request: GetTickerRequest
if (!markets) {
const result = await this.getMixMarkets()
if (result.marketArrStr) {
request = {
market: result.marketArrStr,
}
} else {
return result
}
} else {
request = {
market: markets,
}
}
return await this.getMixTicker(request)
}
public async getAllTickers(markets: string | undefined = undefined) {
let request: GetTickerRequest
if (!markets) {
const result = await this.getMarkets()
if (result.marketArrStr) {
request = {
market: result.marketArrStr,
}
} else {
return result
}
} else {
request = {
market: markets,
}
}
return await this.getTicker(request)
}
public async getMixCandlestick(request: GetCandlestickRequest) {
return await this.getCandlestick(request, LOOPRING_URLs.GET_MIX_CANDLESTICK)
}
/*
* Gets candlesticks.
*/
public async getCandlestick(
request: GetCandlestickRequest,
url: string = LOOPRING_URLs.GET_CANDLESTICK,
): Promise<{
candlesticks: Candlestick[]
raw_data: R
}> {
const reqParams: ReqParams = {
url,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
let tsStep = 60000
switch (request.interval) {
case TradingInterval.min1:
break
case TradingInterval.min5:
tsStep = 300000
break
case TradingInterval.min15:
tsStep = 900000
break
case TradingInterval.min30:
tsStep = 1800000
break
case TradingInterval.hr1:
tsStep = 3600000
break
case TradingInterval.hr2:
tsStep = 7200000
break
case TradingInterval.hr4:
tsStep = 14400000
break
case TradingInterval.hr12:
tsStep = 43200000
break
case TradingInterval.d1:
tsStep = 86400000
break
case TradingInterval.w1:
tsStep = 604800000
break
default:
break
}
let candlesticks: Candlestick[] = []
if (raw_data?.candlesticks instanceof Array) {
const rawCandlesticks = raw_data.candlesticks.reverse()
let lastCandlestick: Candlestick | undefined = undefined
let lastTs = -1
rawCandlesticks.forEach((item: any) => {
const curTs = parseInt(item[0])
if (lastCandlestick === undefined) {
lastTs = curTs
const candlestick: Candlestick = {
timestamp: curTs,
txs: parseInt(item[1]),
open: parseFloat(item[2]),
close: parseFloat(item[3]),
high: parseFloat(item[4]),
low: parseFloat(item[5]),
baseVol: item[6],
quoteVol: item[7],
}
lastCandlestick = candlestick
candlesticks.push(candlestick)
} else {
const counter = (curTs - lastTs) / tsStep
// myLog('counter:', curTs, lastTs, counter)
for (let i = 1; i <= counter; i++) {
let candlestick: Candlestick
if (i === counter) {
candlestick = {
timestamp: lastTs + i * tsStep,
txs: parseInt(item[1]),
open: parseFloat(item[2]),
close: parseFloat(item[3]),
high: parseFloat(item[4]),
low: parseFloat(item[5]),
baseVol: item[6],
quoteVol: item[7],
}
lastTs = curTs
lastCandlestick = candlestick
} else {
candlestick = {
timestamp: lastTs + i * tsStep,
txs: 0,
open: lastCandlestick.close,
close: lastCandlestick.close,
high: lastCandlestick.close,
low: lastCandlestick.close,
baseVol: '0',
quoteVol: '0',
}
}
candlesticks.push(candlestick)
}
}
})
}
candlesticks = candlesticks.reverse()
return {
candlesticks,
raw_data,
}
}
/*
* Fetches, for all the tokens supported by Loopring, their fiat price.
*/
public async getFiatPrice(request: GetFiatPriceRequest) {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_FIAT_PRICE,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const fiatPrices: LoopringMap = {}
if (raw_data?.prices instanceof Array) {
raw_data.prices.forEach((item: FiatPriceInfo) => {
fiatPrices[item.symbol] = item
})
}
return {
fiatPrices,
raw_data,
}
}
/*
* Fetches, for all the tokens supported by Loopring, their fiat price.
*/
public async disableWithdrawTokenList(): Promise<{
raw_data: R
disableWithdrawTokenList: any[]
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_IGNORE_WITHDRAW,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const disableWithdrawTokenList = [...raw_data]
return {
disableWithdrawTokenList,
raw_data,
}
}
/*
* Query trades with specified market
*/
public async getMarketTrades(request: GetMarketTradesRequest): Promise<{
totalNum: number
marketTrades: MarketTradeInfo[]
raw_data: R
}> {
if (request.limit === undefined) {
request.limit = 20
}
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_TRADES,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const marketTrades: MarketTradeInfo[] = []
if (raw_data?.trades instanceof Array) {
raw_data.trades.forEach((item: any) => {
marketTrades.push({
tradeTime: parseInt(item[0]),
tradeId: item[1],
side: item[2] as Side,
volume: item[3],
price: item[4],
market: item[5],
fee: item[6],
type: item[13],
})
})
}
return {
totalNum: raw_data.totalNum,
marketTrades,
raw_data,
}
}
/*
* Returns data associated with the user's exchange account.
*/
public async getAccount(request: GetAccountRequest): Promise<{
accInfo: AccountInfo
raw_data: R
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.ACCOUNT_ACTION,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const accInfo: AccountInfo = raw_data as AccountInfo
return {
accInfo,
raw_data,
}
}
/*
*/
public async getEthNonce(
request: GetEthNonceRequest,
): Promise<{ nonce: number; raw_data: R }> {
const reqParams: ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_ETH_NONCE,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
nonce: raw_data?.nonce,
raw_data,
}
}
/*
*/
public async getGasPrice(): Promise<{
gasPrice: number
raw_data: R
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_GAS_PRICE,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const gasPrice = raw_data?.price
return {
gasPrice,
raw_data,
}
}
/*
*/
public async getGasPriceRange(): Promise<{
gasPriceRanges: any
raw_data: R
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_GAS_PRICE_RANGE,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const gasPriceRanges = raw_data?.ranges
return {
gasPriceRanges,
raw_data,
}
}
public async getAccountServices(request: GetAccountServicesRequest): Promise<{
register: any
order: any
joinAmm: any
dAppTrade: any
legal: any
raw_data: R
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_ACCOUNT_SERVICES,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
...raw_data,
register: raw_data?.register,
order: raw_data?.order,
joinAmm: raw_data?.joinAmm,
dAppTrade: raw_data?.dAppTrade,
legal: raw_data?.dAppTrade,
raw_data,
}
}
public async getTokenInfo(
request: { token: string; currency: 'USD' },
url: string = LOOPRING_URLs.GET_QUOTE_TOKEN_INFO,
) {
const reqParams: ReqParams = {
url,
queryParams: { ...request },
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
list: raw_data.data,
}
}
public async getSupportTokens(
request: GetDatacenterTokenInfoRequest,
url: string = LOOPRING_URLs.GET_SUPPORT_TOKENS,
): Promise<{ raw_data: R; list: R }> {
const reqParams: ReqParams = {
url,
queryParams: {
...request,
cmcTokenIds:
typeof request.cmcTokenIds === 'string'
? request.cmcTokenIds
: request.cmcTokenIds?.join(','),
},
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
list: raw_data.data,
}
}
public async getQuoteTokenInfo(
request: GetDatacenterTokenQuoteTrendRequest,
url: string = LOOPRING_URLs.GET_QUOTE_TREND,
): Promise<{ list: R; raw_data: R }> {
const reqParams: ReqParams = {
url,
queryParams: { ...request },
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
list: raw_data?.data,
}
}
public async getQuoteTokenOhlcv(
request: GetDatacenterTokenQuoteTrendRequest,
url: string = LOOPRING_URLs.GET_QUOTE_TOKEN_OHLCV_TREND,
): Promise<{ list: R; raw_data: R }> {
const reqParams: ReqParams = {
url,
queryParams: {
...request,
},
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
list: raw_data?.data,
}
}
public async getCmcTokenRelations<
R = {
cmcTokenId: number
tokenAddress: string
symbol: string
}[],
>(
request: GetCmcTokenRelationsRequest,
url: string = LOOPRING_URLs.GET_QUOTE_TOKEN_GETCMCTOKENRELATIONS,
): Promise<{ list: R; raw_data: R }> {
const reqParams: ReqParams = {
url,
queryParams: {
...request,
cmcTokenIds:
typeof request.cmcTokenIds === 'string'
? request.cmcTokenIds
: request.cmcTokenIds?.join(','),
tokenAddresses:
typeof request.tokenAddresses === 'string'
? request.tokenAddresses
: request.tokenAddresses?.join(',') ?? '',
},
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
list: raw_data?.data,
}
}
public async getLatestTokenPrices(request?: getLatestTokenPricesRequest) {
const reqParams: ReqParams = {
queryParams: request,
url: LOOPRING_URLs.GET_LATEST_TOKEN_PRICES,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
const tokenPrices: LoopringMap = {}
if (raw_data?.data instanceof Array) {
raw_data.data.forEach((item: any) => {
tokenPrices[item.token.toLowerCase()] = parseFloat(item.price)
})
}
return {
tokenPrices,
raw_data,
}
}
/*
* Get user trade amount
*/
public async getUserTradeAmount(request: GetUserTradeAmount) {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_USER_TRADE_AMOUNT,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
}
}
}
================================================
FILE: src/api/global_api.ts
================================================
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
import { sortObjDictionary } from '../utils'
const GLOBAL_KEY = {
GOERLI: {
key: '685xvATlBCsvzyiTxaS02vu0b1xN0DAFpNpslKUNCuSxDhx8gyyz8VmvUqqe5HSQ',
id: 10013,
},
MAIN: {
key: 're356TcrQ6KhlpkvWxP4UN0C4EqxQVV7ZjvLjunwTjaQPZ20ue2ZgClFeT7okpDQ',
id: 22638,
},
}
export class GlobalAPI extends BaseAPI {
public async getActiveFeeInfo(request: { accountId?: number }) {
const _request: loopring_defs.GetOffchainFeeAmtRequest = {
accountId: request.accountId
? request.accountId
: this.chainId === loopring_defs.ChainId.MAINNET
? GLOBAL_KEY.MAIN.id
: GLOBAL_KEY.GOERLI.id,
requestType: loopring_defs.OffchainFeeReqType.UPDATE_ACCOUNT,
}
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_OFFCHAIN_FEE_AMT,
queryParams: _request,
apiKey:
this.chainId === loopring_defs.ChainId.MAINNET
? GLOBAL_KEY.MAIN.key
: GLOBAL_KEY.GOERLI.key,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const gasPrice = parseInt(raw_data.gasPrice)
const fees: loopring_defs.LoopringMap = {}
if (raw_data?.fees instanceof Array) {
raw_data.fees.forEach((item: loopring_defs.OffchainFeeInfo) => {
fees[item.token] = item
})
}
return {
fees,
gasPrice,
raw_data,
}
}
public async getUserBalanceForFee(request: { accountId: number; tokens: string }) {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_EXCHANGE_BALANCES,
queryParams: request,
apiKey:
this.chainId === loopring_defs.ChainId.MAINNET
? GLOBAL_KEY.MAIN.key
: GLOBAL_KEY.GOERLI.key,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const userBalances: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.UserBalanceInfo) => {
userBalances[item.tokenId] = item
})
}
return {
userBalances,
raw_data,
}
}
public async getAmmPoolGameUserRank(
request: loopring_defs.GetAmmPoolGameUserRankRequest,
): Promise<{
raw_data: R
userRank: loopring_defs.GameRankInfo
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_AMMPOOL_GAME_USER_RANK,
queryParams: request,
apiKey:
this.chainId === loopring_defs.ChainId.MAINNET
? GLOBAL_KEY.MAIN.key
: GLOBAL_KEY.GOERLI.key,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
const userRank: loopring_defs.GameRankInfo = raw_data.data
return {
userRank,
raw_data: raw_data.data,
}
}
public async getBanxaAPI(
{
method,
query,
payload,
url,
accountId,
}: {
method: loopring_defs.ReqMethod
query: string
payload: string
url: string
accountId: number
},
eddsaKey: string,
apiKey: string,
): Promise<{
result: R
raw_data: R
}> {
const queryParams = {
accountId,
url,
method: method.toString(),
query: query,
payload: payload ? payload : '',
}
const dataToSig = sortObjDictionary({
...queryParams,
url: encodeURIComponent(queryParams.url),
query: encodeURIComponent(queryParams.query),
payload: encodeURIComponent(queryParams.payload),
})
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_BANXA_API_KEY,
method: loopring_defs.ReqMethod.GET,
queryParams,
apiKey,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
PrivateKey: eddsaKey,
dataToSig: dataToSig,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
result: raw_data.result,
raw_data: raw_data,
}
}
// public async getBanxaAPIRequest({}) {}
// http://dev.loopring.io?method=GET&query=/api/coins&payload
}
================================================
FILE: src/api/index.ts
================================================
export * from './ws_api'
export * from './exchange_api'
export * from './ammpool_api'
export * from './user_api'
export * from './wallet_api'
export * from './whitelisted_user_api'
export * from './contract_api'
export * from './sign/sign_tools'
export * from './nft_api'
export * from './global_api'
export * from './delegate_api'
export * from './base_api'
export * from './defi_api'
export * from './luckToken_api'
export * from './contacts_api'
export * from './vault_api'
export * from './ethereum/contracts'
export { signHebaoApproveWrap } from './config'
export { RabbitWithdrawAPI } from './rabbitWithdraw_api'
================================================
FILE: src/api/luckToken_api.ts
================================================
/* eslint-disable camelcase */
import { BaseAPI } from './base_api'
import {
LOOPRING_URLs,
NFTTokenInfo,
ReqMethod,
ReqParams,
RESULT_INFO,
SIG_FLAG,
UserNFTBalanceInfo,
} from '../defs'
import * as loopring_defs from '../defs'
import { sortObjDictionary } from '../utils'
import * as sign_tools from './sign/sign_tools'
import { AxiosResponse } from 'axios'
export class LuckTokenAPI extends BaseAPI {
public async getLuckTokenAgents(): Promise<{
raw_data: R
luckTokenAgents: { [key: string]: loopring_defs.LuckyTokenInfo }
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_AGENTS,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const luckTokenAgents = raw_data.reduce(
(
prev: { [key: string]: loopring_defs.LuckyTokenInfo },
item: { owner: string; infos: any[] },
) => {
prev[item.owner] = {
signer: item.infos[0],
signerUrl: item.infos[1],
logoUrl: item.infos[2],
memo: item.infos[3],
}
return prev
},
{} as { [key: string]: loopring_defs.LuckyTokenInfo },
)
return {
raw_data,
luckTokenAgents,
}
}
public async getLuckTokenAuthorizedSigners(): Promise<{
raw_data: R
luckTokenAgents: { [key: string]: loopring_defs.LuckyTokenInfo }
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_AUTHORIZEDSIGNERS,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const luckTokenAgents = raw_data.reduce(
(
prev: { [key: string]: loopring_defs.LuckyTokenInfo },
item: { owner: string; infos: any[] },
) => {
prev[item.owner] = {
signer: item.infos[0],
signerUrl: item.infos[1],
logoUrl: item.infos[2],
memo: item.infos[3],
}
return prev
},
{} as { [key: string]: loopring_defs.LuckyTokenInfo },
)
return {
raw_data,
luckTokenAgents,
}
}
public async getLuckTokenClaimHistory(
request: { fromId: number; limit?: number; isNft?: boolean },
apiKey: string,
): Promise<{
totalNum: number
list: loopring_defs.LuckTokenHistory[]
raw_data: R
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_CLAIMHISTORY,
queryParams: request,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const totalNum: number = raw_data.totalNum
const list: loopring_defs.LuckTokenHistory[] = raw_data.list
return {
totalNum,
list,
raw_data,
}
}
public async getLuckTokenLuckyTokens(
request: {
senderId: number
hash: string
partitions: string
modes: string
scopes: string
statuses: string
startTime: number
endTime: number
fromId: number
limit?: number
official: boolean
isNft?: boolean
},
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
list: loopring_defs.LuckyTokenItemForReceive[]
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_LUCKYTOKENS,
queryParams: request,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const totalNum: number = raw_data.totalNum
const list: loopring_defs.LuckyTokenItemForReceive[] = raw_data.list
return {
totalNum,
list,
raw_data,
}
}
public async getLuckTokenDetail(
request: {
limit?: number
hash: string
fromId?: number
accountId?: number,
serialNo?: number
},
apiKey: string,
): Promise<{
raw_data: R
detail: loopring_defs.LuckTokenClaimDetail
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_LUCKYTOKENDETAIL,
queryParams: request,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, detail: raw_data }
}
public async getBlindBoxDetail(
request: {
limit?: number
hash: string
fromId?: number
showHelper: boolean
accountId?: number,
serialNo?: number
},
apiKey: string,
): Promise<{
raw_data: R
// detail: loopring_defs.LuckTokenClaimDetail;
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_BLINDBOXDETAIL,
queryParams: request,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data }
}
public async getLuckTokenWithdrawals(
request: {
statuses: loopring_defs.LuckyTokenWithdrawStatus[]
tokenId?: number
startTime?: number
endTime?: number
fromId?: number
offset?: number
limit?: number
isNft?: boolean
},
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
luckTokenWithdraw: loopring_defs.LuckTokenWithdraw[]
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_WITHDRAWALS,
queryParams: { ...request, statuses: request.statuses.join(',') },
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data?.totalNum,
luckTokenWithdraw: raw_data.list,
}
}
public async getLuckTokenBalances(
request: {
accountId: number
tokens?: number[]
isNft?: boolean
offset?: number
limit?: number
},
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
tokenBalance: Array<
loopring_defs.UserBalanceInfo & {
isNft?: boolean
nftTokenInfo?: loopring_defs.NFTTokenInfo
}
>
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_BALANCES,
queryParams: {
...request,
// statuses: request.tokens?.join(",")
},
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data?.length,
tokenBalance: raw_data,
}
}
public async getLuckTokenClaimedLuckyTokens(
request: {
fromId: number
limit?: number
hashes?: string[]
isNft?: boolean
},
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
claimedHistory: Array
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_CLAIMEDLUCKYTOKENS,
queryParams: { ...request, hashes: request?.hashes?.join(',') },
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data?.totalNum,
claimedHistory: raw_data.list,
}
}
public async getLuckTokenSummary(apiKey: string): Promise<{
raw_data: R
tokenSummaryList: {
tokenId: number
amount: string
isNft?: Boolean
nftTokenInfo?: NFTTokenInfo & Partial
}[]
totalNum: number
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_SUMMARY,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data.count,
tokenSummaryList: raw_data.tokenSummaryList,
}
}
public async getLuckTokenNFTBalances(
request: {
accountId: number
},
apiKey: string,
): Promise<{
raw_data: R
amount: number
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_NFTBALANCES,
queryParams: request,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
amount: raw_data.amount as number,
// totalNum: raw_data.count,
// tokenSummaryList: raw_data.tokenSummaryList,
}
}
public async sendLuckTokenClaimLuckyToken({
request,
apiKey,
eddsaKey,
}: {
request: {
hash: string
claimer: string
referrer: string,
serialNo?: number
}
eddsaKey: string
apiKey: string
}): Promise<{
raw_data: R
}> {
const dataToSig: Map = sortObjDictionary(request)
const reqParams: ReqParams = {
url: LOOPRING_URLs.POST_LUCK_TOKEN_CLAIMLUCKYTOKEN,
bodyParams: request,
method: ReqMethod.POST,
sigFlag: SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey,
},
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, ...raw_data }
}
public async sendLuckTokenClaimBlindBox({
request,
apiKey,
eddsaKey,
}: {
request: {
hash: string
claimer: string
referrer: string
serialNo?: number
}
eddsaKey: string
apiKey: string
}): Promise<{
raw_data: R
}> {
const dataToSig: Map = sortObjDictionary(request)
const reqParams: ReqParams = {
url: LOOPRING_URLs.POST_LUCK_TOKEN_CLAIMBLINDBOX,
bodyParams: request,
method: ReqMethod.POST,
sigFlag: SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey,
},
apiKey,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return { raw_data, ...raw_data }
}
public async sendLuckTokenWithdraws(
req: loopring_defs.OriginLuckTokenWithdrawsRequestV3WithPatch,
options?: { accountId?: number; counterFactualInfo?: any },
): Promise<(Omit & { raw_data: Omit }) | RESULT_INFO> {
let { request, web3, chainId, walletType, eddsaKey, apiKey, isHWAddr: isHWAddrOld } = req
const { accountId, counterFactualInfo }: any = options
const isHWAddr = !!isHWAddrOld
let ecdsaSignature = undefined
let { transfer } = request
transfer.payeeId = 0
transfer.memo = `LuckTokenWithdrawalBy${request.claimer}`
try {
ecdsaSignature = await sign_tools.transferWrap({
transfer: transfer as loopring_defs.OriginTransferRequestV3,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
})
// ecdsaSignature += isHWAddr ? SigSuffix.Suffix03 : SigSuffix.Suffix02
} catch (error) {
throw error
}
if (counterFactualInfo) {
transfer.counterFactualInfo = counterFactualInfo
}
let { maxFee, token, ..._transfer } = transfer
// @ts-ignore
_transfer = {
..._transfer,
maxFeeAmount: maxFee?.volume ? maxFee?.volume : '0',
feeToken: maxFee?.tokenId ? maxFee?.tokenId : 0,
amount: token.volume,
token: token.tokenId,
ecdsaAuth: ecdsaSignature,
eddsaSig: sign_tools.get_EddsaSig_Transfer(
transfer as loopring_defs.OriginTransferRequestV3,
eddsaKey,
).result,
} as any
request = {
...request,
transfer: JSON.stringify(_transfer) as any,
}
const reqParams: ReqParams = {
url: LOOPRING_URLs.POST_LUCK_TOKEN_WITHDRAWALS,
apiKey,
method: ReqMethod.POST,
bodyParams: { ...request },
sigFlag: SIG_FLAG.NO_SIG,
}
let raw_data
try {
raw_data = (await this.makeReq().request(reqParams)).data
} catch (error) {
throw error as AxiosResponse
}
return this.returnTxHash(raw_data)
}
public async sendLuckTokenSend<
R = {
hash: string
status: string
isIdempotent: boolean
accountId: number
tokenId: number
storageId: number
},
>(
req: loopring_defs.OriginLuckTokenSendRequestV3WithPatch,
options?: { accountId?: number; counterFactualInfo?: any },
): Promise<(Omit & { raw_data: Omit }) | RESULT_INFO> {
let { request, web3, chainId, walletType, eddsaKey, apiKey, isHWAddr: isHWAddrOld } = req
const { accountId, counterFactualInfo }: any = options
const isHWAddr = !!isHWAddrOld
let ecdsaSignature = undefined
const {
luckyToken: { maxFeeAmount, token, amount, feeToken, ...rest },
// @ts-ignore
nftData,
} = request
try {
let transfer: any, eddsaSig
if (nftData) {
transfer = {
...rest,
fromAccountId: rest.payerId,
fromAddress: rest.payerAddr,
toAccountId: 0,
toAddress: rest.payeeAddr,
maxFee: {
tokenId: feeToken,
amount: maxFeeAmount,
},
payeeId: 0,
memo: `LuckTokenSendBy${accountId}`,
token: {
nftData,
tokenId: token,
amount: amount,
},
} as loopring_defs.OriginNFTTransferRequestV3
try {
ecdsaSignature = await sign_tools.transferNFTWrap({
transfer,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
})
// ecdsaSignature += isHWAddr ? SigSuffix.Suffix03 : SigSuffix.Suffix02
} catch (error) {
throw error
}
eddsaSig = sign_tools.get_EddsaSig_NFT_Transfer(transfer, eddsaKey).result
} else {
transfer = {
...rest,
maxFee: {
tokenId: feeToken,
volume: maxFeeAmount,
},
payeeId: 0,
memo: `LuckTokenSendBy${accountId}`,
token: {
tokenId: token,
volume: amount,
},
} as loopring_defs.OriginTransferRequestV3
try {
ecdsaSignature = await sign_tools.transferWrap({
transfer: transfer as loopring_defs.OriginTransferRequestV3,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
})
// ecdsaSignature += isHWAddr ? SigSuffix.Suffix03 : SigSuffix.Suffix02
} catch (error) {
throw error
}
if (counterFactualInfo) {
transfer.counterFactualInfo = counterFactualInfo
}
transfer.eddsaSignature = sign_tools.get_EddsaSig_Transfer(
transfer as loopring_defs.OriginTransferRequestV3,
eddsaKey,
).result
eddsaSig = sign_tools.get_EddsaSig_Transfer(transfer, eddsaKey).result
}
request = {
...request,
nftData,
luckyToken: {
...request.luckyToken,
payeeId: 0,
memo: `LuckTokenSendBy${accountId}`,
eddsaSig,
counterFactualInfo,
},
}
const reqParams: loopring_defs.ReqParams = {
url: LOOPRING_URLs.POST_LUCK_TOKEN_SENDLUCKYTOKEN,
bodyParams: { ...request },
apiKey,
method: ReqMethod.POST,
sigFlag: SIG_FLAG.NO_SIG,
ecdsaSignature,
}
let raw_data
try {
raw_data = (await this.makeReq().request(reqParams)).data
} catch (error) {
throw error as AxiosResponse
}
return this.returnTxHash(raw_data)
} catch (error) {
throw error
}
}
public async getLuckTokenClaimedBlindBox(
request: {
fromId: number
limit?: number
isNft?: boolean
offset?: number
statuses?: number[]
},
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
list: Array
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_CLAIMEDBLINDBOX,
queryParams: { ...request, statuses: request?.statuses?.join(',') },
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data?.totalNum,
list: raw_data.list,
}
}
public async getLuckTokenUnclaimNFTBlindboxCnt(
request: {
accountId: number
},
apiKey: string,
): Promise<{
raw_data: R
count: number
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.POST_LUCK_TOKEN_UNCLAIMNFTANDBLINDCNT,
queryParams: request,
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
count: raw_data?.count,
}
}
public async getLuckTokenUserLuckyTokenTargets(
request: {
statuses?: number[]
fromId?: number
limit?: number
offset?: number
isAll?: number // 0-all, 1-unexpired
},
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
list: loopring_defs.LuckyTokenItemForReceive[]
}> {
const reqParams: ReqParams = {
url: LOOPRING_URLs.GET_LUCK_TOKEN_LUCKYTOKENTARGETS,
queryParams: {
...request,
statuses: request.statuses ? request.statuses.join(',') : undefined,
},
apiKey,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
totalNum: raw_data?.totalNum as number,
list: raw_data?.list as loopring_defs.LuckyTokenItemForReceive[],
}
}
public async sendLuckTokenSubmitAddTarget(
request: {
claimer: string[]
hash: string
notifyType: number // 0-red dot, 1-pop
},
eddsaSignKey: string,
apiKey: string,
): Promise<{
raw_data: R
}> {
const dataToSig: Map = sortObjDictionary(request)
const reqParams: ReqParams = {
url: LOOPRING_URLs.POST_LUCK_TOKEN_SUBMITADDTARGET,
bodyParams: request,
apiKey,
method: ReqMethod.POST,
sigFlag: SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaSignKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
}
}
}
================================================
FILE: src/api/nft_api.ts
================================================
/* eslint-disable @typescript-eslint/no-var-requires */
import { BaseAPI } from './base_api'
import CID from 'cids'
import * as loopring_defs from '../defs'
import {
ChainId,
ConnectorError,
LoopringErrorCode,
NftData,
NFTFactory,
NFTTokenInfo,
ReqMethod,
ReqParams,
SIG_FLAG,
} from '../defs'
import { myLog } from '../utils/log_tools'
import * as ethUtil from 'ethereumjs-util'
import { genExchangeData, sendRawTx } from './contract_api'
import { contracts } from './ethereum/contracts'
import BN from 'bn.js'
const CREATION_CODE = {
[ChainId.GOERLI]:
'3d602d80600a3d3981f3363d3d373d3d3d363d735854e62554ce1c146a375c370bc0d323368b372d5af43d82803e903d91602b57fd5bf3',
[ChainId.MAINNET]:
'3d602d80600a3d3981f3363d3d373d3d3d363d73b25f6d711aebf954fb0265a3b29f7b9beba7e55d5af43d82803e903d91602b57fd5bf3',
[ChainId.SEPOLIA]:
'3d602d80600a3d3981f3363d3d373d3d3d363d73f5c315fe77e81f49e3390b167b78a72bfffcf01c5af43d82803e903d91602b57fd5bf3',
[ChainId.TAIKOHEKLA]:
'',
[ChainId.TAIKO]:
'',
[ChainId.BASE]:
'',
[ChainId.BASESEPOLIA]:
'',
}
const {
Contracts: {
erc721Abi: { erc721 },
erc1155Abi: { erc1155 },
},
} = contracts
export enum NFTType {
ERC1155 = 0,
ERC721,
}
export enum NFT_TYPE_STRING {
ERC1155 = 'ERC1155',
ERC721 = 'ERC721',
}
export enum NFTMethod {
setApprovalForAll = 'setApprovalForAll',
isApprovedForAll = 'isApprovedForAll',
uri = 'uri',
tokenURI = 'tokenURI',
depositNFT = 'depositNFT',
balanceOf = 'balanceOf',
ownerOf = 'ownerOf',
// Deposit = 'deposit',
// ForceWithdraw = 'forceWithdraw'
}
export class NFTAPI extends BaseAPI {
private async callContractMethod(
web3: any,
method: string,
data: any[],
contractAddress: string,
type: NFTType = NFTType.ERC1155,
) {
// return _genContractData(Contracts.ERC20Token, method, data)
const contract = this._genContract(web3, contractAddress, type)
return contract.methods[method](...data).call()
}
private _genContractData(Contract: any, method: string, data: any) {
return Contract.encodeInputs(method, data)
}
private _genERC1155Data(method: string, data: any) {
return this._genContractData(contracts.Contracts.ERC1155, method, data)
}
private _genERC721Data(method: string, data: any) {
return this._genContractData(contracts.Contracts.ERC721, method, data)
}
private _genContract(web3: any, contractAddress: string, type: NFTType = NFTType.ERC1155) {
return new web3.eth.Contract(type === NFTType.ERC1155 ? erc1155 : erc721, contractAddress)
}
/**
* getNFTBalance
* @param web3
* @param tokenAddress
* @param account
* @param nftId
* @param nftType
*/
public async getNFTBalance({
web3,
tokenAddress,
account,
nftId,
nftType = NFTType.ERC1155,
}: loopring_defs.UserNFTBalanceParam): Promise<{
count?: string
}> {
try {
if (nftType === NFTType.ERC721) {
const result: string = await this.callContractMethod(
web3,
NFTMethod.ownerOf,
[nftId],
tokenAddress,
nftType,
)
if (result.toLowerCase() === account.toLowerCase()) {
return {
count: '1',
}
} else {
return {
count: '0',
}
}
} else {
const result: string = await this.callContractMethod(
web3,
NFTMethod.balanceOf,
[account, web3.utils.hexToNumberString(nftId)],
tokenAddress,
nftType,
)
return {
count: result.toString(),
}
}
} catch (err) {
return {
...(err as any),
code: LoopringErrorCode.CONTRACTNFT_BALANCE,
message: ConnectorError.CONTRACTNFT_BALANCE,
}
}
}
/**
* getInfoForNFTTokens
* @param nftDatas NftData[]
*/
public async getInfoForNFTTokens({
nftDatas,
}: {
nftDatas: NftData[]
}): Promise<{ [key: string]: NFTTokenInfo } | undefined> {
try {
const reqParams: ReqParams = {
sigFlag: SIG_FLAG.NO_SIG,
url: loopring_defs.LOOPRING_URLs.GET_NFTs_INFO,
method: ReqMethod.GET,
queryParams: { nftDatas: nftDatas.join(',') },
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const result = raw_data.reduce(
(prev: { [key: string]: NFTTokenInfo }, item: NFTTokenInfo) => {
if (item.nftId && item.nftId.startsWith('0x')) {
const hashBN = new BN(item.nftId.replace('0x', ''), 16)
item.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
prev[item.nftData] = item
return prev
},
{},
)
return {
...result,
raw_data,
}
} catch (err) {
return undefined
}
}
public async callRefreshNFT(
request: loopring_defs.CallRefreshNFT,
): Promise<{ status: string; createdAt: number; updatedAt: number } | undefined> {
try {
const reqParams: ReqParams = {
sigFlag: SIG_FLAG.NO_SIG,
bodyParams: request,
url: loopring_defs.LOOPRING_URLs.POST_NFT_VALIDATE_REFRESH_NFT,
method: ReqMethod.POST,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const result = raw_data.reduce(
(prev: { [key: string]: NFTTokenInfo }, item: NFTTokenInfo) => {
if (item.nftId && item.nftId.startsWith('0x')) {
const hashBN = new BN(item.nftId.replace('0x', ''), 16)
item.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
prev[item.nftData] = item
return prev
},
{},
)
return {
...result,
raw_data,
}
} catch (err) {
return undefined
}
}
/**
* getContractNFTMeta
* @param web3
* @param tokenAddress
* @param _id
* @param nftType
*/
public async getContractNFTMeta(
{ web3, tokenAddress, nftId, nftType = NFTType.ERC1155 }: loopring_defs.ContractNFTMetaParam,
_IPFS_META_URL: string = loopring_defs.LOOPRING_URLs.IPFS_META_URL,
) {
try {
myLog(tokenAddress, 'nftid', nftId, web3.utils.hexToNumberString(nftId))
let result: string
result = await this.callContractMethod(
web3,
nftType === NFTType.ERC1155 ? NFTMethod.uri : NFTMethod.tokenURI,
[web3.utils.hexToNumberString(nftId)],
tokenAddress,
nftType,
)
result = result.replace(/^ipfs:\/\/(ipfs\/)?/, loopring_defs.LOOPRING_URLs.IPFS_META_URL)
result = result.replace('{id}', web3.utils.hexToNumberString(nftId))
return await fetch(result).then((response) => response.json())
} catch (err) {
return {
code: LoopringErrorCode.CONTRACTNFT_URI,
message: ConnectorError.CONTRACTNFT_URI,
...(err as any),
}
}
}
/**
* approveNFT
* @param web3
* @param from The address that deposits the funds to the exchange
* @param to The address deposits to
* @param nftId the nftId
* @param chainId
* @param nftType The type of NFTAction contract address (ERC721/ERC1155/...)
* @param nonce
* @param gasPrice
* @param gasLimit
* @param sendByMetaMask
*/
public async approveNFT({
web3,
from,
depositAddress,
tokenAddress,
nftId,
nftType = NFTType.ERC1155,
gasPrice,
gasLimit,
chainId,
nonce,
approved = true,
sendByMetaMask = true,
}: loopring_defs.ApproveParam) {
let data: any
if (nftType === NFTType.ERC1155) {
data = this._genERC1155Data(NFTMethod.setApprovalForAll, {
operator: depositAddress,
approved,
})
} else if (nftType === NFTType.ERC721) {
data = this._genERC721Data(NFTMethod.setApprovalForAll, {
operator: depositAddress,
approved,
})
}
try {
return await sendRawTx(
web3,
from,
tokenAddress,
'0',
data,
chainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
} catch (err) {
return {
...(err as any),
code: LoopringErrorCode.CONTRACTNFT_SET_APPROVE,
message: ConnectorError.CONTRACTNFT_SET_APPROVE,
}
}
}
public ipfsCid0ToNftID(cidV0Str: string): string {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const cid = new CID(cidV0Str)
const hashHex = Buffer.from(cid.multihash.slice(2)).toString('hex')
const hashBN = new BN(hashHex, 16)
return '0x' + hashBN.toString('hex').padStart(64, '0')
}
/**
*
* @param nftId 16
*/
public ipfsNftIDToCid(nftId: string) {
const hashBN = new BN(nftId.replace('0x', ''), 16)
const hex = hashBN.toString(16, 64)
const buf = Buffer.from('1220' + hex, 'hex')
const cid = new CID(buf)
return cid.toString()
}
/**
* isApprovedForAll
* @param web3
* @param from The address that deposits the funds to the exchange
* @param exchangeAddress loopring exchange address
* @param nftType NFTType
* @param tokenAddress The address of NFT token
*/
public async isApprovedForAll({
web3,
from,
exchangeAddress,
nftType = NFTType.ERC1155,
tokenAddress,
}: loopring_defs.IsApproveParam) {
try {
const result = await this.callContractMethod(
web3,
NFTMethod.isApprovedForAll,
[from, exchangeAddress],
tokenAddress,
nftType,
)
return result
} catch (err) {
return {
...(err as any),
code: LoopringErrorCode.CONTRACTNFT_IS_APPROVE,
message: ConnectorError.CONTRACTNFT_IS_APPROVE,
}
}
}
/**
* @DepositParam an NFTAction to the specified account.
* @param web3
* @param from The address that deposits the funds to the exchange
* @param to The account owner's address receiving the funds
* @param nftType The type of NFTAction contract address (ERC721/ERC1155/...)
* @param tokenAddress The address of NFTAction token
* @param nftId The token type 'id`.
* @param amount The amount of tokens to deposit.
* @param nonce: number,
* @param gasPrice: number,
* @param gasLimit: number,
* @param extraData Optional extra data used by the deposit contract.
* @param chainId 0|5
* @param sendByMetaMask boolean
*/
public async depositNFT({
web3,
from,
exchangeAddress,
nftType = NFTType.ERC1155,
tokenAddress,
nftId,
amount,
gasPrice,
gasLimit,
chainId = ChainId.MAINNET,
nonce,
extraData,
sendByMetaMask = true,
}: loopring_defs.DepositNFTParam) {
const data = genExchangeData(NFTMethod.depositNFT, {
from,
to: from,
nftType,
tokenAddress,
nftId,
amount,
extraData: extraData ? extraData : '',
})
// myLog('depositNFT data',data)
return await sendRawTx(
web3,
from,
exchangeAddress,
'0',
data,
chainId as ChainId,
nonce,
gasPrice,
gasLimit,
sendByMetaMask,
)
}
/**
*
* @function computeNFTAddress
* @param owner {string} nftOwner address
* @param nftFactory {string} Hash address
* @return tokenAddress
* @throws Error
*/
public computeNFTAddress({
nftOwner,
nftFactory = '0xDB42E6F6cB2A2eFcF4c638cb7A61AdE5beD82609',
nftBaseUri = '',
}: {
nftOwner: string
nftFactory?: string
nftBaseUri?: string
}): { tokenAddress: string } {
try {
if (!nftFactory) {
nftFactory = NFTFactory[this.chainId]
}
if (nftOwner.startsWith('0x')) {
nftOwner = nftOwner.slice(2)
}
const saltBuf = Buffer.concat([
Buffer.from('NFT_CONTRACT_CREATION', 'utf8'),
Buffer.from(nftOwner, 'hex'),
Buffer.from(nftBaseUri, 'utf8'),
])
const codeHash = ethUtil.keccak(Buffer.from(CREATION_CODE[this.chainId], 'hex'))
const saltHash = ethUtil.keccak(saltBuf)
const rawBuf = Buffer.concat([
Buffer.from('ff', 'hex'),
Buffer.from(nftFactory.slice(2), 'hex'),
saltHash,
codeHash,
])
const addr = ethUtil.keccak(rawBuf).slice(12).toString('hex')
return {
tokenAddress: ethUtil.toChecksumAddress('0x' + addr),
}
} catch (err) {
return err as any
}
}
public async getPublicCollectionById(request: {
id: string
}): Promise<({ raw_data: R } & loopring_defs.CollectionMeta) | loopring_defs.RESULT_INFO> {
try {
const reqParams: ReqParams = {
sigFlag: SIG_FLAG.NO_SIG,
queryParams: request,
url: loopring_defs.LOOPRING_URLs.GET_NFT_COLLECTION_PUBLISH,
method: ReqMethod.GET,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const result = raw_data as loopring_defs.CollectionMeta
return {
...result,
raw_data,
}
} catch (err) {
return {
...(err as any),
code: exports.LoopringErrorCode.SKD_UNKNOW,
}
}
}
async getCollectionWholeNFTs(request: loopring_defs.GetCollectionWholeNFTsRequest) {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_COLLECTION_WHOLE_NFTS,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
if (raw_data.nftTokenInfos.length) {
raw_data.nftTokenInfos = raw_data.nftTokenInfos.reduce(
(prev: loopring_defs.UserNFTBalanceInfo[], item: loopring_defs.UserNFTBalanceInfo) => {
if (item.nftId && item.nftId.startsWith('0x')) {
const hashBN = new BN(item.nftId.replace('0x', ''), 16)
item.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
if (
request.metadata === true &&
item.metadata &&
item.metadata.nftId &&
item.metadata.nftId.startsWith('0x')
) {
// const hashBN = new BN(item.metadata.nftId.replace("0x", ""), 16);
item.metadata.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
}
return [...prev, item]
},
[],
)
// const hashBN = new BN(raw_data.transactions.metadata.nftId.replace("0x", ""), 16);
// raw_data.transactions.metadata.nftId= "0x" + hashBN.toString("hex").padStart(64, "0");
}
return {
totalNum: raw_data?.totalNum,
userNFTBalances: raw_data.nftTokenInfos as loopring_defs.UserNFTBalanceInfo[],
raw_data,
}
}
async getHadUnknownCollection(request: { accountId: number }): Promise {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_HAD_UNKNOWN_COLLECTION,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return raw_data
}
async getUserNFTBurnAddress(request: {
accountId: number
tokenId: number
}): Promise {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_BURN_ADDRESS,
queryParams: request,
method: ReqMethod.GET,
sigFlag: SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
return raw_data
}
}
================================================
FILE: src/api/rabbitWithdraw_api.ts
================================================
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
import {RabbitWithdrawRequest} from '../defs'
import { ethers, utils } from 'ethers';
import { get_EddsaSig_Transfer } from './sign/sign_tools';
import { AxiosResponse } from 'axios';
const {_TypedDataEncoder} = utils
interface CounterFactualInfo {
walletFactory: string;
walletOwner: string;
walletSalt: string;
}
export class RabbitWithdrawAPI extends BaseAPI {
public async getConfig(): Promise<{ config: string }> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_RABBIT_WITHDRAW_CONFIG, // You'll need to add this URL in url_defs.ts
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const data = (await this.makeReq().request(reqParams)).data
return data as {
config: string
}
}
public async getNetworkWithdrawalAgents(req: {
tokenId: number
amount: string
network: string
}): Promise<
{
address: string
tokenId: number
symbol: string
totalAmount: string
freezeAmount: string
timestamp: number
}[]
> {
const reqParams: loopring_defs.ReqParams = {
queryParams: req,
url: loopring_defs.LOOPRING_URLs.GET_NETWORK_WITHDRAWAL_AGENTS, // You'll need to add this URL in url_defs.ts
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const data = (await this.makeReq().request(reqParams)).data
return data
}
public async submitRabitWithdraw(
req: RabbitWithdrawRequest,
extra: {
signer: ethers.providers.JsonRpcSigner
chainId: number
exchangeAddr: string
eddsaSignKey: string
},
): Promise<{
hash: string
status: string
isIdempotent: boolean
accountId: number
tokenId: number
storageId: number
}> {
const { signer, chainId, exchangeAddr } = extra
const mapRequestToTypedData = (request: Record) => {
return {
fromNetwork: request['fromNetwork'],
toNetwork: request['toNetwork'],
withdrawToAddr: request['toAddress'],
payerId: request['transfer']['payerId'],
payerAddr: request['transfer']['payerAddr'],
payeeId: request['transfer']['payeeId'],
payeeAddr: request['transfer']['payeeAddr'],
tokenID: request['transfer']['token']['tokenId'],
amount: request['transfer']['token']['volume'],
feeTokenID: request['transfer']['maxFee']['tokenId'],
maxFee: request['transfer']['maxFee']['volume'],
validUntil: request['transfer']['validUntil'],
storageID: request['transfer']['storageId'],
} as Record
}
const signFastWithdrawTypedData = (args: {
signer: ethers.providers.JsonRpcSigner
chainId: number
exchangeAddress: string
data: Record
}) => {
const { signer, chainId, exchangeAddress } = args
const domain = {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: exchangeAddress,
}
const types = {
RabbitWithdraw: [
{ type: 'uint32', name: 'payerId' },
{ type: 'address', name: 'payerAddr' },
{ type: 'uint32', name: 'payeeId' },
{ type: 'address', name: 'payeeAddr' },
{ type: 'uint16', name: 'tokenID' },
{ type: 'uint96', name: 'amount' },
{ type: 'uint16', name: 'feeTokenID' },
{ type: 'uint96', name: 'maxFee' },
{ type: 'uint32', name: 'validUntil' },
{ type: 'uint32', name: 'storageID' },
{ type: 'string', name: 'fromNetwork' },
{ type: 'string', name: 'toNetwork' },
{ type: 'address', name: 'withdrawToAddr' },
],
}
console.log('EIP712 hash', _TypedDataEncoder.hash(domain, types, args.data))
return signer._signTypedData(domain, types, args.data)
}
const ecdsaSignature = await signFastWithdrawTypedData({
signer: signer,
chainId: chainId,
exchangeAddress: exchangeAddr,
data: mapRequestToTypedData(req),
})
const eddsaSignature = get_EddsaSig_Transfer(req.transfer, extra.eddsaSignKey).result
const reqParams: loopring_defs.ReqParams = {
bodyParams: {
...req,
transfer: { ...req.transfer, eddsaSignature },
},
url: loopring_defs.LOOPRING_URLs.POST_RABBIT_WITHDRAW, // You'll need to add this URL in url_defs.ts
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
ecdsaSignature: ecdsaSignature,
}
const data = (await this.makeReq().request(reqParams)).data
return data
}
public async getUserCrossChainFee(
request: loopring_defs.GetUserCrossChainFeeRequest,
apiKey: string,
): Promise<{
gasPrice: string
fees: {
token: string
tokenId: number
fee: string
discount: number
}[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_CROSSCHAIN_OFFCHAIN_FEE_AMT,
queryParams: { ...request },
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
let raw_data
try {
raw_data = (await this.makeReq().request(reqParams)).data
} catch (error) {
throw error as AxiosResponse
}
return raw_data
}
}
================================================
FILE: src/api/request.ts
================================================
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { SIG_FLAG, ReqParams, ReqOptions } from '../defs/loopring_enums'
import { getEdDSASig, getEdDSASigWithPoseidon } from './sign/sign_tools'
import { sortObject } from '../utils/obj_tools'
/**
*
* @export
*/
export const setSearchParams = function (url: URL, ...objects: any[]) {
const searchParams = new URLSearchParams(url.search)
for (const object of objects) {
if (object) {
const objectTmp = sortObject(object)
for (const key in objectTmp) {
if (objectTmp[key] != undefined) searchParams.set(key, objectTmp[key])
}
}
}
url.search = searchParams.toString()
}
/**
*
* @export
*/
export const serializeDataIfNeeded_For_SetReffer = function (value: any) {
return JSON.stringify(value, Object.keys(value).sort())
}
export const serializeDataIfNeeded = function (value: any) {
const nonString = typeof value !== 'string'
return nonString ? JSON.stringify(value !== undefined ? sortObject(value) : {}) : value || ''
}
/**
*
* @export
*/
export const toPathString = function (url: URL) {
return url.pathname + url.search + url.hash
}
export class Request {
private _axios: AxiosInstance
private baseOptions: any = {}
public getIns() {
return this._axios
}
constructor(baseUrl: string, timeout: number) {
this.baseOptions = {
baseURL: baseUrl,
timeout: timeout,
headers: {
// 'Accept': '*/*',
// 'Accept-Encoding': 'gzip, deflate, br',
feeVersion: 'v2',
'Content-Type': 'application/json',
pf: 'web', // tag for recognizing source
},
validateStatus: function (status: any) {
if ((status >= 200 && status < 300) || status === 400) {
return true
}
return false
// return true // always true, handle exception in each bussiness logic
},
insecure: true,
}
this._axios = axios.create(this.baseOptions)
}
public async request(params: ReqParams, options: any = {}) {
const localUrl = new URL(params.url, this.baseOptions.baseURL)
const localVarRequestOptions = { method: params.method, ...options }
setSearchParams(localUrl, params?.queryParams)
const urlPathStr = toPathString(localUrl)
let headers: any = {}
if (params?.apiKey) {
headers['X-API-KEY'] = params?.apiKey
}
let sig: any = params.sigObj?.sig
if (params.sigFlag !== SIG_FLAG.NO_SIG && !params?.sigObj?.dataToSig) {
throw Error('no dataToSig field!')
}
switch (params.sigFlag) {
case SIG_FLAG.NO_SIG:
break
case SIG_FLAG.EDDSA_SIG_POSEIDON:
sig = getEdDSASigWithPoseidon(params.sigObj?.dataToSig, params.sigObj?.PrivateKey).result
break
case SIG_FLAG.EDDSA_SIG:
sig = getEdDSASig(
params.method,
this.baseOptions.baseURL,
params.url,
params.sigObj?.dataToSig,
params.sigObj?.PrivateKey,
)
break
default:
break
}
if (sig) {
headers['X-API-SIG'] = sig
} else if (params?.ecdsaSignature) {
headers['X-API-SIG'] = params?.ecdsaSignature
} else if (params?.eddsaSignature) {
headers['X-API-SIG'] = params?.eddsaSignature
}
if (params?.eddsaSignatureREFER && params.sigObj?.PrivateKey) {
const sig = getEdDSASig(
params.method,
this.baseOptions.baseURL,
params.url,
params.sigObj?.dataToSig,
params.sigObj?.PrivateKey,
)
headers['X-API-SIG-REFER'] = sig
}
// myLog('headers["X-API-SIG"]', headers["X-API-SIG"]);
if (params?.bodyParams) {
const bodyParams = params?.bodyParams
if (sig && params.sigObj?.sigPatch) {
bodyParams[params.sigObj?.sigPatch] = sig
}
if (params?.ecdsaSignature) {
bodyParams.ecdsaSignature = params?.ecdsaSignature
}
if (params?.eddsaSignature) {
bodyParams.eddsaSignature = params?.eddsaSignature
}
localVarRequestOptions.data = serializeDataIfNeeded(bodyParams)
}
headers = { ...this.baseOptions.headers, ...headers }
const optInOne = {
...this.baseOptions,
headers: {
...headers,
...params.extraHeaders,
},
...localVarRequestOptions,
url: this.baseOptions.baseURL + urlPathStr,
}
// myLog(optInOne);
// myLog("headers config", optInOne);
return await this._axios.request(optInOne)
}
public updateOpt(reqOpt: ReqOptions) {
// @ts-ignore
this._axios.interceptors.request.use((req: AxiosRequestConfig) => {
if (reqOpt?.baseUrl) {
req.baseURL = reqOpt?.baseUrl
}
if (reqOpt?.url) {
req.url = reqOpt?.url
}
req.headers = req.headers ?? {}
if (reqOpt?.apiKey) {
req.headers['X-API-KEY'] = reqOpt?.apiKey
}
if (reqOpt?.signature) {
req.headers['X-API-SIG'] = reqOpt?.signature
}
return req
})
return this
}
public addApiKey(apiKey: string) {
return this.updateOpt({ apiKey })
}
public addSig(signature: string) {
return this.updateOpt({ signature })
}
public updateBaseUrl(baseUrl: string) {
return this.updateOpt({ baseUrl })
}
public handle400(callback: any = undefined) {
this._axios.interceptors.response.use((res: AxiosResponse) => {
if (res.status == 400) {
// eslint-disable-next-line no-console
console.log(res.request + ' got ' + res.statusText)
}
if (callback) {
callback()
}
return res
})
}
}
================================================
FILE: src/api/sign/poseidon/EDDSAUtil.ts
================================================
import { BigNumber } from "ethers";
import { SignatureScheme } from "./eddsa";
import { FQ } from "./field";
import { jubjub } from "./jubjub";
import { babyJub } from "./babyJub";
export class EDDSAUtil {
static sign(PrivateKey: string | undefined, hash: any) {
const strKey = BigNumber.from(PrivateKey)
const msg = BigNumber.from(hash)
// console.log("strKey", strKey.toString())
// console.log("msg", msg.toString())
const copyKey = new FQ(strKey)
const B = SignatureScheme.B()
const signed = SignatureScheme.sign(msg, copyKey, B)
// console.log("signed", signed.toStr())
const x = EDDSAUtil.formatted(signed.sig.R.x.n.toHexString().slice(2))
const y = EDDSAUtil.formatted(signed.sig.R.y.n.toHexString().slice(2))
const s = EDDSAUtil.formatted(signed.sig.s.n.toHexString().slice(2))
const result = `0x${x}${y}${s}`
// console.log("result", result)
return {
"Rx": signed.sig.R.x.n.toString(),
"Ry": signed.sig.R.y.n.toString(),
"s": signed.sig.s.n.toString()
}
}
static formatted(hexString: string) {
const outputLength = 32 * 2
const more = outputLength - hexString.length
if (more > 0) {
for (let i = 0; i < more; i++) {
hexString = "0" + (hexString)
}
} else {
hexString = hexString.slice(0, outputLength)
}
return hexString
}
static generateKeyPair(seed: any) {
let bigInt = BigNumber.from(0)
for (let i = 0; i < seed.length; i++) {
const item = seed[i]
const itemBigInt = BigNumber.from(item)
const tmp = BigNumber.from("256").pow(BigNumber.from(i))
bigInt = bigInt.add(itemBigInt.mul(tmp))
}
// console.log("sum", bigInt.toString())
const secretKey = bigInt.mod(jubjub.JUBJUB_L)
// console.log("secretKey", secretKey.toString())
const copySecretKey = BigNumber.from(secretKey.toString())
// console.log("copySecretKey", copySecretKey.toString())
const B = SignatureScheme.B()
// console.log("B", B.toString())
const publicKey = B.mul(copySecretKey)
// console.log("publicKey", publicKey.x.n.toString(), publicKey.y.n.toString())
const keyPair = {
"publicKeyX": publicKey.x.n.toString(),
"publicKeyY": publicKey.y.n.toString(),
"secretKey": secretKey.toString()
}
return keyPair
}
static pack(publicKeyX: string, publicKeyY: string) {
const P0 = BigNumber.from(publicKeyX)
const P1 = BigNumber.from(publicKeyY)
const newPack = babyJub.packPoint(P0, P1)
return newPack
}
}
================================================
FILE: src/api/sign/poseidon/TestsEDDSAUtil_test.ts
================================================
import { BigNumber } from "ethers";
import { EDDSAUtil } from "./EDDSAUtil";
import { bnToBuf, bnToBufWithFixedLength, SignatureScheme } from "./eddsa";
import { assert } from "console";
function testEddsaPack_1() {
console.log("testEddsaPack")
// Input
const publicKeyX = "0x10440b6de1fc92536e20501e7513b5ca78d4c8c876450d97cfc2a4e24a4c67c7"
const publicKeyY = "0x10dfbb7cd80bde2eeedbf9e0fad12819ca20be36b96a7f28c37c6a7550ed366c"
// Output
const eddsaPackOutput = "10dfbb7cd80bde2eeedbf9e0fad12819ca20be36b96a7f28c37c6a7550ed366c"
const packed = EDDSAUtil.pack(publicKeyX, publicKeyY)
assert(packed === eddsaPackOutput)
console.log("testEddsaPack passed")
}
function testEddsaPack_2() {
console.log("testEddsaPack")
// Input
const publicKeyX = "0x191f8455196d7c9116065b69a745edbe3f5b6193125f48d818e16101b0ebd13f"
const publicKeyY = "0x0729569e74800ba4344767d608ec54f12b14cc4404a8726b5f73ebf8b58f9a09"
// Output
const eddsaPackOutput = "8729569e74800ba4344767d608ec54f12b14cc4404a8726b5f73ebf8b58f9a09"
const packed = EDDSAUtil.pack(publicKeyX, publicKeyY)
assert(packed === eddsaPackOutput)
console.log("testEddsaPack passed")
}
function testEddsaPack_3() {
console.log("testEddsaPack")
// Input
const publicKeyX = "0x1ae187c43a6b534be1ac60b335d5cc8c12a9d479339e5ce4134315e20c2940c4"
const publicKeyY = "0x16fb785193a8657feb41b7944afde61fe05ea519621fa0f19d872a8fbba83463"
// Output
const eddsaPackOutput = "96fb785193a8657feb41b7944afde61fe05ea519621fa0f19d872a8fbba83463"
const packed = EDDSAUtil.pack(publicKeyX, publicKeyY)
assert(packed === eddsaPackOutput)
console.log("testEddsaPack passed")
}
function testEddsaSign() {
console.log("testEddsaSign")
const strKey = "1965533437444427599736796973543479035828634172708055838572430750620147597402"
const msg = "20823375595941673465102915960468301465677704522962441935281926279865178787657"
const result = EDDSAUtil.sign(strKey, msg)
// assert(result == "0x04ad08469a4a8622a661a793615ebddfa6ea2b72c8f1b02e33288ea2bd71cede0ed7f67939097ab95d3c9d8647e198f4ef523bf6cfb8da0ead89a57e27eff0d12ea6ac32cc118b1bc0aeb8f1be37f317f399197f9e41c8a90ecfe9e296be42e5")
console.log(result.Rx)
console.log(result.Ry)
console.log(result.s)
assert(result.Rx === "2114973053955517366033592592501464590076342821657201629830614924692550700766")
assert(result.Ry === "6713953096854639492359183468711112854151280690992619923536842965423886430417")
assert(result.s === "21100876117443371431735908718802018647851328087147897184613053393129281831653")
console.log("testEddsaSign passed")
}
function test_generateKeyPair() {
console.log("generateKeyPair")
const seedHex = "0x" + "7f16a4c491d3494c3bc8ef097e7c123a9da6fa8167631efd5f82b89e803b0682"
const seed = BigNumber.from(seedHex)
console.log(`seed ${seed.toString()}`)
const bitIntDataItems = bnToBufWithFixedLength(seed.toString(), 32);
console.log(`bigIntData ${bitIntDataItems}`)
const keyPair = EDDSAUtil.generateKeyPair(bitIntDataItems)
assert(keyPair.publicKeyX === "18584920749226215196041463810216086509508234553287520526360090588893385486657")
assert(keyPair.publicKeyY === "4521439321743413283487130903749053907482128013050103604539374548984130428531")
console.log("generateKeyPair passed")
}
function main() {
console.log("\n\TestsEDDSAUtil_test\n")
// testEddsaPack_1()
// testEddsaPack_2()
// testEddsaPack_3()
test_generateKeyPair()
// testEddsaSign()
}
main();
================================================
FILE: src/api/sign/poseidon/babyJub.ts
================================================
import { BigNumber } from "ethers";
import { SignatureScheme, bytesToHexString } from "./eddsa";
import { field } from "./field";
export class babyJub {
static packPoint(P0: BigNumber, P1: BigNumber) {
const packed = SignatureScheme.to_bytes(P1).reverse()
// console.log("packed", packed)
if (babyJub.lt(P0, BigNumber.from("0"))) {
// console.log("Update .... lt ")
packed[0] = packed[0] | 0x80
}
const hexStr = bytesToHexString(packed)
// console.log("hexStr", hexStr)
return hexStr
}
static lt(a: BigNumber, b: BigNumber) {
const half = field.SNARK_SCALAR_FIELD.div(BigNumber.from("2"))
const p = field.SNARK_SCALAR_FIELD
let aa: BigNumber
let bb: BigNumber
if (a.gt(half)) {
aa = a.sub(p)
} else {
aa = a
}
if (b.gt(half)) {
bb = b.sub(p)
} else {
bb = b
}
// console.log("lt", a.toString(), b.toString(), aa.toString(), bb.toString());
return aa.lt(bb)
}
static gt(a: BigNumber, b: BigNumber) {
const half = field.SNARK_SCALAR_FIELD.div(BigNumber.from("2"))
const p = field.SNARK_SCALAR_FIELD
let aa: BigNumber
let bb: BigNumber
if (a.gt(half)) {
aa = a.sub(p)
} else {
aa = a
}
if (b.gt(half)) {
bb = b.sub(p)
} else {
bb = b
}
// console.log("gt", a.toString(), b.toString(), aa.toString(), bb.toString());
return aa.gt(bb)
}
}
================================================
FILE: src/api/sign/poseidon/eddsa.ts
================================================
/*
Implements Pure-EdDSA and Hash-EdDSA
The signer has two secret values:
* k = Secret key
* r = Per-(message,key) nonce
The signer provides a signature consiting of two values:
* R = Point, image of `r*B`
* s = Image of `r + (k*t)`
The signer provides the verifier with their public key:
* A = k*B
Both the verifier and the signer calculate the common reference string:
* t = H(R, A, M)
The nonce `r` is secret, and protects the value `s` from revealing the
signers secret key.
For Hash-EdDSA, the message `M` is compressed before H(R,A,M)
For further information see: https://ed2519.cr.yp.to/eddsa-20150704.pdf
*/
import { BigNumber } from "ethers";
import { field, FQ } from "./field";
import { jubjub, Point } from "./jubjub";
import { sha512 } from "js-sha512";
import { permunation, PoseidonParams } from "./permutation";
export class Signature {
public R: Point;
public s: FQ;
constructor(R: Point, s: FQ) {
this.R = R;
this.s = s;
}
toStr() {
return `${this.R.x.n} ${this.R.y.n} ${this.s.n}`;
}
}
export class SignedMessage {
public A: Point;
public sig: Signature;
public msg: BigNumber;
constructor(A: Point, sig: Signature, msg: BigNumber) {
this.A = A;
this.sig = sig;
this.msg = msg;
}
toStr() {
return `${this.A.x.n} ${
this.A.y.n
} ${this.sig.toStr()} ${this.msg.toString()}`;
}
}
export class SignatureScheme {
static to_bytes(arg: BigNumber) {
const outputLength = 32;
// console.log(`input ${arg.toString()}`)
let bitIntDataItems = bnToBuf(arg.toString());
// console.log(`bigIntData ${bitIntDataItems}`)
const more = outputLength - bitIntDataItems.length;
// console.log('more', more)
if (more > 0) {
for (let i = 0; i < more; i++) {
bitIntDataItems = [0].concat(bitIntDataItems);
}
} else {
bitIntDataItems = bitIntDataItems.slice(0, outputLength);
}
bitIntDataItems = bitIntDataItems.reverse();
// console.log(`bigIntData return ${bitIntDataItems}`)
return bitIntDataItems;
}
/*
Identity function for message
Can be used to truncate the message before hashing it
as part of the public parameters.
*/
static prehash_message(M: BigNumber) {
return M;
}
/*
Hash the key and message to create `r`, the blinding factor for this signature.
If the same `r` value is used more than once, the key for the signature is revealed.
From: https://eprint.iacr.org/2015/677.pdf (EdDSA for more curves)
Page 3:
(Implementation detail: To save time in the computation of `rB`, the signer
can replace `r` with `r mod L` before computing `rB`.)
*/
static hash_secret_python(k: FQ, arg: BigNumber) {
const byteArray0 = this.to_bytes(k.n);
const byteArray1 = this.to_bytes(arg);
const sum = byteArray0.concat(byteArray1);
// console.log("sum", sum)
// let byteArrayHexStr = bytesToHexString(sum)
// console.log("byteArrayHexStr", byteArrayHexStr)
const digest1 = sha512.array(new Uint8Array(sum).buffer);
// let digest1 = createHash('sha512').update .digest("SHA-512", new Uint8Array(sum).buffer)
let sha512StrItems: any;
// console.log('digest1', digest1);
for (let i = 0; i < digest1.length; i++) {
const itemInt = digest1[i];
let st = itemInt.toString(16);
if (st.length == 1) {
st = "0" + st;
}
sha512StrItems = [st].concat(sha512StrItems);
}
const sha512MessageHexStr = sha512StrItems.join("");
// console.log(`sha512MessageHexStr ${sha512MessageHexStr}`)
const sha512MessageHexStrBigInt = BigNumber.from(
"0x" + sha512MessageHexStr
);
// console.log(`sha512MessageHexStrBigInt ${sha512MessageHexStrBigInt}`)
const hashed = sha512MessageHexStrBigInt.mod(jubjub.JUBJUB_L);
// console.log(`hashed ${hashed.toString()}`)
return hashed;
}
static B() {
return Point.generate();
}
static sign(msg: BigNumber, key: FQ, B: Point) {
// console.log("B ", B.x.n.toString(), B.y.n.toString())
const copyKey = new FQ(key.n, key.m);
const A = B.mul(copyKey.n);
// console.log("A.x ", A.x.n.toString(), A.x.m.toString())
// console.log("A.y ", B.y.n.toString(), A.y.m.toString())
const M = this.prehash_message(msg);
// console.log("M ", M.toString())
const r = this.hash_secret_python(key, M);
// console.log("r ", r.toString())
const copy_r = BigNumber.from(r.toString());
const R = B.mul(copy_r);
// console.log("R.x ", R.x.n.toString(), R.x.m.toString())
// console.log("R.y ", R.y.n.toString(), R.y.m.toString())
const t = this.hash_public(R, A, M);
// console.log("hello world")
// console.log("t ", t.toString())
const t_c = t;
const key_n_t = key.n.mul(t_c);
const left = r.add(key_n_t);
const S = left.mod(jubjub.JUBJUB_E);
// console.log("S ", S.toString())
const signatureResult = new Signature(R, new FQ(S));
// console.log("signatureResult", signatureResult.toStr())
const signedMessage = new SignedMessage(A, signatureResult, msg);
// console.log("signedMessage", signedMessage.toStr())
return signedMessage;
}
static as_scalar(point: Point) {
// console.log(`as_scalar ${point.x.n.toString()}`)
return [point.x.n, point.y.n];
}
static hash_public(R: Point, A: Point, M: BigNumber) {
let inputMsg: any;
inputMsg = this.as_scalar(R).concat(this.as_scalar(A)).concat([M]);
// console.log(`inputMsg ${inputMsg}`)
const params = new PoseidonParams(
field.SNARK_SCALAR_FIELD,
6,
6,
52,
"poseidon",
BigNumber.from(5),
null,
null,
128
);
const result = permunation.poseidon(inputMsg, params);
return result;
}
}
export function bnToBuf(bn: string) {
let hex = BigInt(bn).toString(16);
if (hex.length % 2) {
hex = "0" + hex;
}
const len = hex.length / 2;
// console.log("length", len);
const u8 = new Uint8Array(len);
let i = 0;
let j = 0;
while (i < len) {
u8[i] = parseInt(hex.slice(j, j + 2), 16);
i += 1;
j += 2;
}
return Array.from(u8);
}
export function bnToBufWithFixedLength(bn: string, outputLength: number) {
let hex = BigInt(bn).toString(16);
if (hex.length % 2) {
hex = "0" + hex;
}
const len = hex.length / 2;
// console.log("len", len);
const u8 = new Uint8Array(len);
let i = 0;
let j = 0;
while (i < len) {
u8[i] = parseInt(hex.slice(j, j + 2), 16);
i += 1;
j += 2;
}
let bitIntDataItems = Array.from(u8);
const more = outputLength - bitIntDataItems.length;
// console.log('diff len', more)
if (more > 0) {
for (let i = 0; i < more; i++) {
bitIntDataItems = [0].concat(bitIntDataItems);
}
} else {
bitIntDataItems = bitIntDataItems.slice(0, outputLength);
}
return bitIntDataItems;
}
export function bufToBn(buf: any) {
let hex: any;
hex = [];
const u8 = Uint8Array.from(buf);
u8.forEach(function (i) {
let h = i.toString(16);
if (h.length % 2) {
h = "0" + h;
}
hex.push(h);
});
return BigInt("0x" + hex.join(""));
}
export function bytesToHexString(bytes: any) {
let strItems: any;
strItems = [];
for (let i = 0; i < bytes.length; i++) {
const item = bytes[i];
let st = item.toString(16);
if (st.length == 1) {
st = "0" + st;
}
// st = st.toUpperCase()
strItems.push(st);
}
const strItemsJoined = strItems.join("");
return strItemsJoined;
}
================================================
FILE: src/api/sign/poseidon/eddsa_test.ts
================================================
import { assert } from "console"
import { BigNumber } from "ethers"
import { SignatureScheme } from "./eddsa"
import { FQ } from "./field"
import { Point } from "./jubjub"
function test_sign_1() {
// console.log("test_sign_1")
const startTime: any = new Date();
const msgHash = BigNumber.from("20693456676802104653139582814194312788878632719314804297029697306071204881418")
const private_key = new FQ(BigNumber.from("1"))
const signed = SignatureScheme.sign(msgHash, private_key, SignatureScheme.B())
assert(signed.toStr() === "16540640123574156134436876038791482806971768689494387082833631921987005038935 20819045374670962167435360035096875258406992893633759881276124905556507972311 4991609103248925747358645194965349262579784734809679007552644294476920671344 423391641476660815714427268720766993055332927752794962916609674122318189741 4678160339597842896640121413028167917237396460457527040724180632868306529961 20693456676802104653139582814194312788878632719314804297029697306071204881418")
const endTime: any = new Date();
let timeDiff = endTime - startTime; //in ms
timeDiff /= 1000;
// console.log(timeDiff + " seconds");
// console.log("test_sign_1 passed")
}
function test_prehash_message_1() {
// console.log("test_prehash_message_1")
const msg = BigNumber.from("20693456676802104653139582814194312788878632719314804297029697306071204881418")
const result = SignatureScheme.prehash_message(msg)
assert(result.eq(BigNumber.from("20693456676802104653139582814194312788878632719314804297029697306071204881418")))
// console.log("test_prehash_message_1 passed")
}
function test_to_bytes_1() {
// console.log("test_to_bytes_1")
const arg = BigNumber.from("20693456676802104653139582814194312788878632719314804297029697306071204881418")
const resutls = SignatureScheme.to_bytes(arg)
assert(JSON.stringify(resutls) === JSON.stringify([10, 228, 215, 147, 146, 102, 9, 42, 66, 160, 26, 94, 171, 73, 235, 194, 245, 106, 249, 114, 50, 52, 155, 182, 188, 18, 133, 216, 215, 20, 192, 45]))
// console.log("test_to_bytes_1 passed")
}
function test_hash_secret_1() {
// console.log("test_hash_secret_1")
const startTime: any = new Date();
const arg = BigNumber.from("20693456676802104653139582814194312788878632719314804297029697306071204881418")
const result = SignatureScheme.hash_secret_python(new FQ(BigNumber.from("1")), arg)
assert(result.eq(BigNumber.from("456425617452149303537516185998917840598824274191970480768523181450944242406")))
const endTime: any = new Date();
let timeDiff = endTime - startTime; //in ms
timeDiff /= 1000;
// console.log(timeDiff + " seconds");
// strip the ms
// console.log("test_hash_secret_1 passed")
}
function test_hash_public_1() {
// console.log("test_hash_public_1")
const R = new Point(new FQ(BigNumber.from("4991609103248925747358645194965349262579784734809679007552644294476920671344")), new FQ(BigNumber.from("423391641476660815714427268720766993055332927752794962916609674122318189741")))
const A = new Point(new FQ(BigNumber.from("16540640123574156134436876038791482806971768689494387082833631921987005038935")), new FQ(BigNumber.from("20819045374670962167435360035096875258406992893633759881276124905556507972311")))
const M = BigNumber.from("20693456676802104653139582814194312788878632719314804297029697306071204881418")
const result = SignatureScheme.hash_public(R, A, M)
assert(result.eq(BigNumber.from("4221734722145693593102605227029250076638572186265556559955657451417362287555")))
// console.log("test_hash_public_1 passed")
}
function main() {
// console.log("\n\neddsa_test\n")
test_sign_1()
test_prehash_message_1()
test_to_bytes_1()
test_hash_secret_1()
test_hash_public_1()
}
main();
================================================
FILE: src/api/sign/poseidon/field.ts
================================================
import { BigNumber } from "ethers";
import { BigInteger } from "jsbn";
export class field {
// Fq is the base field of Jubjub
static SNARK_SCALAR_FIELD = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
// Fr is the scalar field of Jubjub
static FR_ORDER = BigNumber.from("21888242871839275222246405745257275088614511777268538073601725287587578984328")
}
// A class for field elements in FQ. Wrap a number in this class,
// and it becomes a field element.
export class FQ {
public m: BigNumber;
public n: BigNumber;
constructor(n: BigNumber, field_modulus = field.SNARK_SCALAR_FIELD) {
this.m = field_modulus;
this.n = n.mod(this.m);
}
//
// Use this.n as other
//
add(other: BigNumber) {
const on = other
const n = (this.n.add(on)).mod(this.m)
return new FQ(n, this.m)
}
mul(other: BigNumber) {
const on = other
const n = this.n.mul(on).mod(this.m)
return new FQ(n, this.m)
}
sub(other: BigNumber) {
const on = other
let new_n: BigNumber;
if (this.n.gte(on)) {
new_n = (this.n.sub(on)).mod(this.m)
} else {
new_n = (this.n.sub(on).add(this.m)).mod(this.m)
}
return new FQ(new_n, this.m)
}
div(other: BigNumber) {
const on_c = other
const m_c = this.m
const two_c = BigNumber.from("2")
const on_power_c = modulo(on_c, m_c.sub(two_c), m_c)
const n_on_power_remainder = this.n.mul(on_power_c).mod(this.m)
return new FQ(n_on_power_remainder, this.m)
}
static one(modulus: BigNumber = field.SNARK_SCALAR_FIELD) {
return new FQ(BigNumber.from("1"), modulus)
}
static zero(modulus: BigNumber = field.SNARK_SCALAR_FIELD) {
return new FQ(BigNumber.from("0"), modulus)
}
}
export function modulo(n: BigNumber, p: BigNumber, m: BigNumber) {
const n_ = new BigInteger(n.toString())
const p_ = new BigInteger(p.toString())
const m_ = new BigInteger(m.toString())
// console.log("modulo", n_.toString(), p_.toString(), m_.toString());
const result = n_.modPow(p_, m_)
// console.log(n_.toString(), p_.toString(), m_.toString(), result.toString())
return BigNumber.from(result.toString())
}
================================================
FILE: src/api/sign/poseidon/field_test.ts
================================================
import { assert } from "console";
import { BigNumber } from "ethers";
import { field, FQ, modulo } from "./field";
function test_constants() {
// console.log("test_constants")
assert(field.SNARK_SCALAR_FIELD.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
assert(field.FR_ORDER.eq(BigNumber.from("21888242871839275222246405745257275088614511777268538073601725287587578984328")))
// console.log("test_constants passed")
}
function test_half() {
// console.log("test_half")
const half = BigNumber.from("10944121435919637611123202872628637544274182200208017171849102093287904247808")
assert(field.SNARK_SCALAR_FIELD.div(2).eq(half))
// console.log("test_half passed")
}
function test_p() {
// console.log("test_p")
const p = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
assert(field.SNARK_SCALAR_FIELD.eq(p))
// console.log("test_p passed")
}
function test_FQ_1() {
// console.log("test_FQ_1")
const n = BigNumber.from("1")
const privateKey = new FQ(n)
assert(privateKey.n.eq(BigNumber.from("1")))
assert(privateKey.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
// console.log("test_FQ_1 passed")
}
function test_FQ_add_1() {
// console.log("test_FQ_add_1")
const field1 = new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const field2 = new FQ(BigNumber.from("64019726205844806607227168444173457603185468776494125031546307012808629654"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const result1 = field1.add(field2.n)
const result2 = field2.add(field1.n)
assert(result1.n.eq(result2.n))
assert(result1.m.eq(result2.m))
// console.log("test_FQ_add_1 passed")
}
function test_FQ_mul_1() {
// console.log("test_FQ_mul_1")
const other = BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822")
const field = new FQ(BigNumber.from("8023312754331632317345164874475855606161388395970421403351236980717209379200"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const result = field.mul(other)
assert(result.n.eq("7078307911818432186422689430568175567157289995259698798344014234848622444761"))
assert(result.m.eq("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
// console.log("test_FQ_mul_1 passed")
}
function test_FQ_mul_2() {
// console.log("test_FQ_mul_2")
const other = BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822")
const field = new FQ(BigNumber.from("64019726205844806607227168444173457603185468776494125031546307012808629654"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const result = field.mul(other)
assert(result.n.eq("8023312754331632317345164874475855606161388395970421403351236980717209379200"))
assert(result.m.eq("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
// console.log("test_FQ_mul_2 passed")
}
function test_FQ_mul_3() {
// console.log("test_FQ_mul_3")
const field1 = new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const field2 = new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const result1 = field1.add(field2.n)
const result2 = field2.add(field1.n)
assert(result1.n.eq(result2.n))
assert(result1.m.eq(result2.m))
// console.log("test_FQ_mul_3 passed")
}
function test_FQ_mul_4() {
// console.log("test_FQ_mul_4")
const field1 = new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const field2 = new FQ(BigNumber.from("64019726205844806607227168444173457603185468776494125031546307012808629654"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const field3 = new FQ(BigNumber.from("8023312754331632317345164874475855606161388395970421403351236980717209379200"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const result1 = field1.mul(field2.n).mul(field3.n)
const result2 = field2.mul(field1.n).mul(field3.n)
const result3 = field3.mul(field1.n).mul(field2.n)
const result4 = field3.mul(field2.n).mul(field1.n)
assert(result1.n.eq(result2.n))
assert(result1.m.eq(result2.m))
assert(result1.n.eq(result3.n))
assert(result1.m.eq(result3.m))
assert(result1.n.eq(result4.n))
assert(result1.m.eq(result4.m))
// console.log("test_FQ_mul_4 passed")
}
function test_FQ_module() {
// console.log("test_FQ_module")
const n = BigNumber.from("-14716512740585223458018047979811180521493135094147670344522878941283115968141")
const m = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const n_m = n.add(m).mod(m)
assert(n_m.eq(BigNumber.from("7171730131254051764228357765446094567055229306268363999175325245292692527476")))
// console.log("test_FQ_module passed")
}
function test_FQ_sub() {
// console.log("test_FQ_sub")
const field = new FQ(BigNumber.from("397593468802287547516812218524989251246873285051017590397180750166966730405"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const other = BigNumber.from("15114106209387511005534860198336169772740008379198687934920059691450082698546")
// console.log("test_FQ_sub passed")
}
function test_modulo() {
console.log("test_modulo")
const n = BigNumber.from("15511699678189798553051672416861159023986881664249705525317240441617049428951")
const p = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495615")
const m = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const on_power_c = modulo(n, p, m)
console.log("on_power_c", on_power_c.toString())
assert(on_power_c.toString() === "5307867014497036273760343161114758635681413365004069877684017182635568142756")
}
function main() {
// console.log("\n\nfield_test\n")
// test_constants()
// test_half()
// test_p()
// test_FQ_1()
// test_FQ_add_1()
// test_FQ_mul_1()
// test_FQ_mul_2()
// test_FQ_mul_3()
// test_FQ_mul_4()
// test_FQ_module()
// test_FQ_sub()
test_modulo()
}
main();
================================================
FILE: src/api/sign/poseidon/jubjub.ts
================================================
/*
This module implements the extended twisted edwards and extended affine coordinates
described in the paper "Twisted Edwards Curves Revisited":
- https://iacr.org/archive/asiacrypt2008/53500329/53500329.pdf
Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson
Information Security Institute,
Queensland University of Technology, QLD, 4000, Australia
{h.hisil, kk.wong, g.carter, e.dawson}@qut.edu.au
By using the extended coordinate system we can avoid expensive modular exponentiation
calls, for example - a scalar multiplication call (or multiple...) may perform only
one 3d->2d projection at the point where affine coordinates are necessary, and every
intermediate uses a much faster form.
# XXX: none of these functions are constant time, they should not be used interactively!
*/
import { BigNumber } from "ethers";
import { field, FQ } from "./field";
export class jubjub {
static JUBJUB_Q = field.SNARK_SCALAR_FIELD
static JUBJUB_E = BigNumber.from("21888242871839275222246405745257275088614511777268538073601725287587578984328")
static JUBJUB_C = BigNumber.from("8") // Cofactor
static JUBJUB_L = jubjub.JUBJUB_E.div(jubjub.JUBJUB_C) // L*B = 0, and (2^C)*L == #E
static JUBJUB_A = BigNumber.from("168700") // Coefficient A
static JUBJUB_D = BigNumber.from("168696") // Coefficient D
}
export class Point {
public x: FQ
public y: FQ
constructor(x: FQ, y: FQ) {
this.x = x
this.y = y
}
static generate() {
const xBigInt = BigNumber.from("16540640123574156134436876038791482806971768689494387082833631921987005038935")
const yBigInt = BigNumber.from("20819045374670962167435360035096875258406992893633759881276124905556507972311")
const point = new Point(new FQ(xBigInt), new FQ(yBigInt))
return point
}
mul(scaler: BigNumber) {
let p = new Point(this.x, this.y)
let a = Point.infinity()
let i = 0
while (!scaler.eq(BigNumber.from("0"))) {
const bitwiseAnd = scaler.and(BigNumber.from("1"))
if (!bitwiseAnd.eq(BigNumber.from("0"))) {
a = a.add(p)
}
let copyP1 = new Point(p.x, p.y)
let copyP2 = new Point(p.x, p.y)
p = copyP1.add(copyP2)
scaler = scaler.div(BigNumber.from("2"))
// console.log(i + " scaler", scaler.toString())
i = i + 1
}
return a
}
add(other: Point) {
if (this.x.n.eq(BigNumber.from("0")) && this.y.n.eq(BigNumber.from("0"))) {
return other
}
const u1 = this.x
const v1 = this.y
const u2 = other.x
const v2 = other.y
const u3_tmp0 = (u1.mul(v2.n)).add(v1.mul(u2.n).n)
const u3_tmp1 = u1.mul(u2.n).mul(v1.n).mul(v2.n).mul(jubjub.JUBJUB_D)
const u3_tmp2 = FQ.one().add(u3_tmp1.n)
const u3 = u3_tmp0.div(u3_tmp2.n)
const v3_tmp0 = v1.mul(v2.n)
const v3_tmp1 = u1.mul(u2.n).mul(jubjub.JUBJUB_A)
const v3_tmp3 = v3_tmp0.sub(v3_tmp1.n)
const v3_tmp5 = FQ.one().sub(u3_tmp1.n)
const v3 = v3_tmp3.div(v3_tmp5.n)
return new Point(u3, v3)
}
static infinity() {
return new Point(new FQ(BigNumber.from("0")), new FQ(BigNumber.from("1")))
}
}
================================================
FILE: src/api/sign/poseidon/jubjub_test.ts
================================================
import { assert } from "console";
import { BigNumber } from "ethers";
import { FQ } from "./field";
import { jubjub, Point } from "./jubjub";
function test_constants() {
// console.log("test_constants")
assert(jubjub.JUBJUB_Q.eq("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
assert(jubjub.JUBJUB_E.eq("21888242871839275222246405745257275088614511777268538073601725287587578984328"))
assert(jubjub.JUBJUB_C.eq("8"))
assert(jubjub.JUBJUB_L.eq("2736030358979909402780800718157159386076813972158567259200215660948447373041"))
assert(jubjub.JUBJUB_A.eq("168700"))
assert(jubjub.JUBJUB_D.eq("168696"))
// console.log("test_constants passed")
}
function test_add_1() {
// console.log("test_add_1")
const point = new Point(new FQ(BigNumber.from("5925710879559963920674585068280151559572021649049974518737186312396312983287")), new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822")))
const other = new Point(new FQ(BigNumber.from("5925710879559963920674585068280151559572021649049974518737186312396312983287")), new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822")))
const sum = point.add(other)
assert(sum.x.n.eq(BigNumber.from("3921821752680400551661691533275335336907961697969280331905459386565873550491")))
assert(sum.x.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
assert(sum.y.n.eq(BigNumber.from("8522068897570808837785568881356377871354274006792075192589502922612862896342")))
assert(sum.y.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
// console.log("test_add_1 passed")
}
function test_add_2() {
// console.log("test_add_2")
const point = new Point(new FQ(BigNumber.from("10975113445185536695224737904225227344281568447400334915125839333792816477396")), new FQ(BigNumber.from("18445435810976842694581549336952093637971779711294581156054437925992025486446")))
const other = new Point(new FQ(BigNumber.from("5925710879559963920674585068280151559572021649049974518737186312396312983287")), new FQ(BigNumber.from("16975020951829843291561856284829257584634286376639034318405002894754175986822")))
const sum = point.add(other)
assert(sum.x.n.eq(BigNumber.from("4991609103248925747358645194965349262579784734809679007552644294476920671344")))
assert(sum.x.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
assert(sum.y.n.eq(BigNumber.from("423391641476660815714427268720766993055332927752794962916609674122318189741")))
assert(sum.y.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
// console.log("test_add_2 passed")
}
function test_divide_1() {
// console.log("test_divide_1")
const scaler = BigNumber.from("1")
const result = scaler.div(2)
assert(result.eq(BigNumber.from("0")))
// console.log("test_divide_1 passed")
}
function test_divide_2() {
// console.log("test_divide_2")
const scaler = BigNumber.from("2")
const result = scaler.div(2)
assert(result.eq(BigNumber.from("1")))
// console.log("test_divide_2 passed")
}
function test_bitwiseand_1() {
// console.log("test_bitwiseand_1")
const scaler_c = BigNumber.from("1")
const result_c = scaler_c.and(BigNumber.from(1))
assert(result_c.eq(BigNumber.from("1")))
// console.log("test_bitwiseand_1 passed")
}
function test_bitwiseand_2() {
// console.log("test_bitwiseand_2")
const scaler_c = BigNumber.from("2")
const result_c = scaler_c.and(BigNumber.from(1))
assert(result_c.eq(BigNumber.from("0")))
// console.log("test_bitwiseand_2 passed")
}
function test_mul_1() {
// console.log("test_mul_1")
const B = new Point(new FQ(BigNumber.from("16540640123574156134436876038791482806971768689494387082833631921987005038935")), new FQ(BigNumber.from("20819045374670962167435360035096875258406992893633759881276124905556507972311")))
const k = BigNumber.from("1")
const A = B.mul(k)
assert(A.x.n.eq(BigNumber.from("16540640123574156134436876038791482806971768689494387082833631921987005038935")))
assert(A.x.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
assert(A.y.n.eq(BigNumber.from("20819045374670962167435360035096875258406992893633759881276124905556507972311")))
assert(A.y.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
// console.log("test_mul_1 passed")
}
function test_mul_2() {
// console.log("test_mul_2")
const B = new Point(new FQ(BigNumber.from("16540640123574156134436876038791482806971768689494387082833631921987005038935")), new FQ(BigNumber.from("20819045374670962167435360035096875258406992893633759881276124905556507972311")))
const k = BigNumber.from("2")
const A = B.mul(k)
assert(A.x.n.eq(BigNumber.from("17324563846726889236817837922625232543153115346355010501047597319863650987830")))
assert(A.x.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
assert(A.y.n.eq(BigNumber.from("20022170825455209233733649024450576091402881793145646502279487074566492066831")))
assert(A.y.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")))
// console.log("test_mul_2 passed")
}
function test_mul_3() {
// console.log("test_mul_3")
const x = new FQ(BigNumber.from("16540640123574156134436876038791482806971768689494387082833631921987005038935"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const y = new FQ(BigNumber.from("20819045374670962167435360035096875258406992893633759881276124905556507972311"), BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617"))
const B = new Point(x, y)
const k = BigNumber.from("456425617452149303537516185998917840598824274191970480768523181450944242406")
const A = B.mul(k)
assert(A.x.n.eq(BigNumber.from("4991609103248925747358645194965349262579784734809679007552644294476920671344")), "A.x.n" + A.x.n.toString())
assert(A.x.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")), "A.x.m" + A.x.m.toString())
assert(A.y.n.eq(BigNumber.from("423391641476660815714427268720766993055332927752794962916609674122318189741")), "A.y.n" + A.y.n.toString())
assert(A.y.m.eq(BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")), "A.y.m" + A.y.m.toString())
// console.log("test_mul_3 passed")
}
function main() {
// console.log("hello")
test_constants()
test_add_1()
test_add_2()
test_divide_1()
test_divide_2()
test_bitwiseand_1()
test_bitwiseand_2()
test_mul_1()
test_mul_2()
test_mul_3()
}
main();
================================================
FILE: src/api/sign/poseidon/permutation.ts
================================================
/*
Implements the Poseidon permutation:
Starkad and Poseidon: New Hash Functions for Zero Knowledge Proof Systems
- Lorenzo Grassi, Daniel Kales, Dmitry Khovratovich, Arnab Roy, Christian Rechberger, and Markus Schofnegger
- https://eprint.iacr.org/2019/458.pdf
Other implementations:
- https://github.com/shamatar/PoseidonTree/
- https://github.com/iden3/circomlib/blob/master/src/poseidon.js
- https://github.com/dusk-network/poseidon252
*/
import { BigNumber } from 'ethers'
import { SignatureScheme } from './eddsa'
import { modulo } from './field'
import { TextEncoder } from 'web-encoding'
import blake2b from 'blake2b'
export class PoseidonParams {
public p: BigNumber
public t: number
public nRoundsF: number
public nRoundsP: number
public seed: string
public e: BigNumber
public constants_C: [BigNumber]
public constants_M: [[BigNumber]]
public security_target: number
constructor(
p: BigNumber,
t: number,
nRoundsF: number,
nRoundsP: number,
seed: string,
e: BigNumber,
constants_C: [BigNumber] | null,
constants_M: [[BigNumber]] | null,
security_target: number,
) {
this.p = p
this.t = t
this.nRoundsF = nRoundsF
this.nRoundsP = nRoundsP
this.seed = seed
this.e = e
if (constants_C == null) {
this.constants_C = permunation.poseidon_constants(p, `${seed}_constants`, nRoundsF + nRoundsP)
} else {
this.constants_C = constants_C
}
if (constants_M == null) {
this.constants_M = permunation.poseidon_matrix(p, `${seed}_matrix_0000`, t)
} else {
this.constants_M = constants_M
}
this.security_target = security_target
}
}
export class permunation {
static H(arg: string) {
const outputLength = 32
const enc = new TextEncoder()
const message = enc.encode(arg)
// console.log(`message ${message}`)
const buf = Buffer.alloc(outputLength)
// console.log(`hashOfSize32Bytes ${buf.toString()}`)
// console.log(`message ${message}`)
// @ts-ignore
blake2b(buf.length, undefined).update(message).final(buf)
const items = buf.toJSON().data
// console.log(`H items ${items}`)
let sum = BigNumber.from('0')
var i = 0
for (var i = 0; i < items.length; i++) {
const itemBigInt = BigNumber.from(items[i])
const tmp = itemBigInt.mul(BigNumber.from('256').pow(BigNumber.from(i)))
sum = sum.add(tmp)
}
// console.log(`sum ${sum}`)
return sum
}
static H_Bigint(arg: BigNumber) {
const outputLength = 32
const message = new Uint8Array(SignatureScheme.to_bytes(arg))
// console.log(`message ${message}`)
const buf = Buffer.alloc(outputLength)
// console.log(`hashOfSize32Bytes ${buf.toString()}`)
// @ts-ignore
blake2b(buf.length, undefined).update(message).final(buf)
const items = buf.toJSON().data
// console.log(`H_Bigint items ${items}`)
let sum = BigNumber.from('0')
var i = 0
for (var i = 0; i < items.length; i++) {
const itemBigInt = BigNumber.from(items[i])
const tmp = itemBigInt.mul(BigNumber.from('256').pow(BigNumber.from(i)))
sum = sum.add(tmp)
}
// console.log(`sum ${sum}`)
return sum
}
static poseidon_constants(p: BigNumber, seed: string, n: number) {
let c: any
c = []
let seedBigInt = this.H(seed)
const result = seedBigInt.mod(p)
c.push(result)
for (let i = 0; i < n - 1; i++) {
seedBigInt = this.H_Bigint(seedBigInt)
const result = seedBigInt.mod(p)
c.push(result)
}
return c
}
static poseidon_matrix(p: BigNumber, seed: string, t: number) {
const c = this.poseidon_constants(p, seed, t * 2)
let matrix: any
matrix = []
for (let i = 0; i < t; i++) {
let row: any
row = []
for (let j = 0; j < t; j++) {
const c_i = c[i]
const c_t_j = c[t + j]
const p_c = p
const c_t_j_p = c_t_j.mod(p_c)
const left = c_i.sub(c_t_j_p)
const p_2 = p_c.sub(2)
const item_c = modulo(left, p_2, p_c)
row.push(item_c)
}
matrix.push(row)
}
return matrix
}
static poseidon_sbox(state: [BigNumber], i: number, params: PoseidonParams) {
/*
iacr.org/2019/458 § 2.2 The Hades Strategy (pg 6)
In more details, assume R_F = 2 · R_f is an even number. Then
- the first R_f rounds have a full S-Box layer,
- the middle R_P rounds have a partial S-Box layer (i.e., 1 S-Box layer),
- the last R_f rounds have a full S-Box layer
*/
const half_F = params.nRoundsF / 2
if (i < half_F || i >= half_F + params.nRoundsP) {
for (let j = 0; j < state.length; j++) {
const element_c = state[j]
const e_c = params.e
const p_c = params.p
const item = modulo(element_c, e_c, p_c)
state[j] = item
}
} else {
const element_c = state[0]
const e_c = params.e
const p_c = params.p
const item = modulo(element_c, e_c, p_c)
state[0] = item
}
return state
}
static poseidon_mix(state: [BigNumber], M: [[BigNumber]], p: BigNumber) {
/*
The mixing layer is a matrix vector product of the state with the mixing matrix
- https://mathinsight.org/matrix_vector_multiplication
*/
let newState: any
newState = []
for (let i = 0; i < M.length; i++) {
let sum = BigNumber.from(0)
for (let j = 0; j < state.length; j++) {
const element = state[j]
sum = sum.add(M[i][j].mul(element))
}
newState.push(sum.mod(p))
}
return newState
}
// poseidon
/*
Main instansiation of the Poseidon permutation
The state is `t` elements wide, there are `F` full-rounds
followed by `P` partial rounds, then `F` full rounds again.
[ ARK ] --,
| | | | | | |
[ SBOX ] - Full Round
| | | | | | |
[ MIX ] --`
[ ARK ] --,
| | | | | | |
[ SBOX ] - Partial Round
| | Only 1 element is substituted in partial round
[ MIX ] --`
There are F+P rounds for the full permutation.
You can provide `r = N - 2s` bits of input per round, where `s` is the desired
security level, in most cases this means you can provide `t-1` inputs with
appropriately chosen parameters. The permutation can be 'chained' together
to form a sponge construct.
*/
static poseidon(inputs: [BigNumber], params: PoseidonParams) {
let state: any
state = []
state = state.concat(inputs)
// console.log(`state ${state}`)
for (var i = 0; i < params.t - inputs.length; i++) {
state.push(BigNumber.from(0))
}
// console.log(`state ${state}`)
// console.log(`params.constants_C.length ${params.constants_C.length}`)
for (var i = 0; i < params.constants_C.length; i++) {
const C_i = params.constants_C[i]
for (let index = 0; index < state.length; index++) {
const element = state[index]
state[index] = element.add(C_i)
}
state = this.poseidon_sbox(state, i, params)
// console.log(`after poseidon_sbox ${state}`)
state = this.poseidon_mix(state, params.constants_M, params.p)
// console.log(`after poseidon_mix ${state}`)
}
// console.log(`hash is ${state[0]}`)
return state[0]
}
}
================================================
FILE: src/api/sign/poseidon/permutation_test.ts
================================================
import { assert } from "console";
import { BigNumber } from "ethers";
import { field } from "./field";
import { permunation, PoseidonParams } from "./permutation";
function test_PoseidonParams() {
// console.log("test_PoseidonParams")
const p = field.SNARK_SCALAR_FIELD
const t = 6
const nRoundsF = 8
const nRoundsP = 57
const seed = "poseidon"
const e = BigNumber.from(5)
const security_target = 126
const poseidonParams = new PoseidonParams(p, t, nRoundsF, nRoundsP, seed, e, null, null, security_target)
// console.log(`poseidonParams ${poseidonParams.constants_C}`)
// @ts-ignore
assert(poseidonParams.constants_C[0].eq(BigNumber.from("14397397413755236225575615486459253198602422701513067526754101844196324375522")), "poseidonParams.constants_C[0]")
// @ts-ignore
assert(poseidonParams.constants_C[1].eq(BigNumber.from("10405129301473404666785234951972711717481302463898292859783056520670200613128")), "poseidonParams.constants_C[2]")
// @ts-ignore
assert(poseidonParams.constants_C[63].eq(BigNumber.from("14423660424692802524250720264041003098290275890428483723270346403986712981505")), "poseidonParams.constants_C[3]")
// @ts-ignore
assert(poseidonParams.constants_C[64].eq(BigNumber.from("10635360132728137321700090133109897687122647659471659996419791842933639708516")), "poseidonParams.constants_C[64]")
// @ts-ignore
assert(poseidonParams.constants_M[0][0].eq(BigNumber.from("19167410339349846567561662441069598364702008768579734801591448511131028229281")), "poseidonParams.constants_M[0][0]")
// @ts-ignore
assert(poseidonParams.constants_M[0][1].eq(BigNumber.from("14183033936038168803360723133013092560869148726790180682363054735190196956789")), "poseidonParams.constants_M[0][1]")
// @ts-ignore
assert(poseidonParams.constants_M[0][2].eq(BigNumber.from("9067734253445064890734144122526450279189023719890032859456830213166173619761")), "poseidonParams.constants_M[0][2]")
// @ts-ignore
assert(poseidonParams.constants_M[5][3].eq(BigNumber.from("8035240799672199706102747147502951589635001418759394863664434079699838251138")), "poseidonParams.constants_M[0][0]")
// @ts-ignore
assert(poseidonParams.constants_M[5][4].eq(BigNumber.from("21642389080762222565487157652540372010968704000567605990102641816691459811717")), "poseidonParams.constants_M[0][1]")
// @ts-ignore
assert(poseidonParams.constants_M[5][5].eq(BigNumber.from("20261355950827657195644012399234591122288573679402601053407151083849785332516")), "poseidonParams.constants_M[0][2]")
// console.log("test_PoseidonParams passed")
}
function test_H_1() {
console.log("test_H_1")
const arg = "poseidon_constants"
const hash = permunation.H(arg)
assert(hash.eq(BigNumber.from("80062126029273061892314832722231078464247515902761170557848714403923749862373")))
console.log("test_H_1 passed")
}
function test_H_2() {
console.log("test_H_2")
const arg = "poseidon_matrix_0000"
const hash = permunation.H(arg)
assert(hash.eq(BigNumber.from("14132513739920849383792069751007754351800355055139761101807090020635929082500")))
console.log("test_H_2 passed")
}
function test_H_3() {
console.log("test_H_3")
const arg = BigNumber.from("14132513739920849383792069751007754351800355055139761101807090020635929082500")
const hash = permunation.H_Bigint(arg)
assert(hash.eq(BigNumber.from("2944673226682481007627110343206629017840128596422012786319796010373889882365")))
console.log("test_H_3 passed")
}
function test_poseidon_constants() {
// console.log("test_poseidon_constants")
const p = field.SNARK_SCALAR_FIELD
const seed = "poseidon_constants"
const n = 65
const constants_C = permunation.poseidon_constants(p, seed, n)
assert(constants_C[0].eq(BigNumber.from("14397397413755236225575615486459253198602422701513067526754101844196324375522")))
assert(constants_C[1].eq(BigNumber.from("10405129301473404666785234951972711717481302463898292859783056520670200613128")))
assert(constants_C[2].eq(BigNumber.from("5179144822360023508491245509308555580251733042407187134628755730783052214509")))
assert(constants_C[63].eq(BigNumber.from("14423660424692802524250720264041003098290275890428483723270346403986712981505")))
assert(constants_C[64].eq(BigNumber.from("10635360132728137321700090133109897687122647659471659996419791842933639708516")))
// console.log("test_poseidon_constants passed")
}
function test_poseidon_matrix() {
console.log("test_poseidon_matrix")
const p = field.SNARK_SCALAR_FIELD
const seed = "poseidon_matrix_0000"
const t = 9
const constants_M = permunation.poseidon_matrix(p, seed, t)
assert(constants_M[0][0].eq(BigNumber.from("16378664841697311562845443097199265623838619398287411428110917414833007677155")))
assert(constants_M[0][1].eq(BigNumber.from("12968540216479938138647596899147650021419273189336843725176422194136033835172")))
assert(constants_M[0][2].eq(BigNumber.from("3636162562566338420490575570584278737093584021456168183289112789616069756675")))
assert(constants_M[1][3].eq(BigNumber.from("8642889650254799419576843603477253661899356105675006557919250564400804756641")))
assert(constants_M[8][8].eq(BigNumber.from("11398590172899810645820530606484864595574598270604175688862890426075002823331")))
console.log("test_poseidon_matrix passed")
}
function test_poseidon_1() {
console.log("test_poseidon_1")
const p = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const MAX_INPUT = 8
const poseidonParams = new PoseidonParams(p, MAX_INPUT+1, 6, 53, "poseidon", BigNumber.from(5), null, null, 128)
let intputs: any
intputs = []
intputs.push(BigNumber.from(1))
const state0 = permunation.poseidon(intputs, poseidonParams)
assert(state0.eq(BigNumber.from("20640057815290657586474256351705900781103272844170318852926520138679251832017")))
console.log("test_poseidon_1 passed")
}
function test_poseidon_2() {
console.log("test_poseidon_2")
const p = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const MAX_INPUT = 8
const poseidonParams = new PoseidonParams(p, MAX_INPUT+1, 6, 53, "poseidon", BigNumber.from(5), null, null, 128)
let intputs: any
intputs = []
intputs.push(BigNumber.from(1))
intputs.push(BigNumber.from(2))
const state0 = permunation.poseidon(intputs, poseidonParams)
assert(state0.eq(BigNumber.from("9251914430137119038619835991672259215400712688203929648293348181354900386736")))
console.log("test_poseidon_2 passed")
}
function test_poseidon_3() {
console.log("test_poseidon_3")
const p = BigNumber.from("21888242871839275222246405745257275088548364400416034343698204186575808495617")
const MAX_INPUT = 8
const poseidonParams = new PoseidonParams(p, MAX_INPUT+1, 6, 53, "poseidon", BigNumber.from(5), null, null, 128)
let intputs: any
intputs = []
intputs.push(BigNumber.from(1))
intputs.push(BigNumber.from(2))
intputs.push(BigNumber.from(3))
intputs.push(BigNumber.from(4))
intputs.push(BigNumber.from(5))
intputs.push(BigNumber.from(6))
intputs.push(BigNumber.from(7))
intputs.push(BigNumber.from(8))
const state0 = permunation.poseidon(intputs, poseidonParams)
assert(state0.eq(BigNumber.from("1792233229836714442925799757877868602259716425270865187624398529027734741166")))
console.log("test_poseidon_3 passed")
}
function main() {
// console.log("\n\npermutation_test\n")
// test_PoseidonParams()
test_H_1()
test_H_2()
test_H_3()
// test_poseidon_constants()
test_poseidon_matrix()
test_poseidon_1()
test_poseidon_2()
test_poseidon_3()
}
main();
================================================
FILE: src/api/sign/sign_tools.ts
================================================
// import sha256 from 'crypto-js/sha256'
import * as crypto from 'crypto-js'
import * as abi from 'ethereumjs-abi'
import * as sigUtil from 'eth-sig-util'
import * as ethUtil from 'ethereumjs-util'
import BN from 'bn.js'
import BigInteger from 'bignumber.js'
import { bnToBufWithFixedLength } from './poseidon/eddsa'
import { EDDSAUtil } from './poseidon/EDDSAUtil'
import { field } from './poseidon/field'
import { permunation, PoseidonParams } from './poseidon/permutation'
import * as fm from '../../utils/formatter'
import { toBig, toHex } from '../../utils'
import {
AmmPoolRequestPatch,
ChainId,
ConnectorNames,
CounterFactualInfo,
DualOrderRequest,
ExitAmmPoolRequest,
IsMobile,
JoinAmmPoolRequest,
NFTMintRequestV3,
NFTOrderRequestV3,
NFTTokenAmountInfo,
NFTTradeRequestV3,
NFTWithdrawRequestV3,
OffChainWithdrawalRequestV3,
OriginNFTTransferRequestV3,
OriginTransferRequestV3,
PublicKey,
SubmitOrderRequestV3,
UpdateAccountRequestV3,
} from '../../defs'
import { Buffer } from 'buffer'
/**
* BigNumber -> BigInt
* BigNumber.from() -> BigInt() or \dn
* div -> /
* sub -> -
* gt -> >
* lt -> <
* gte -> >=
* eq -> ==
* and -> &&=
*/
import * as loopring_defs from '../../defs/loopring_defs'
import Web3 from 'web3'
import { myLog } from '../../utils/log_tools'
import { personalSign } from '../base_api'
import { BigNumber } from '@ethersproject/bignumber'
import { getWindowSafely } from 'utils/window_utils'
import { ethers } from 'ethers'
// import { hashMessage } from 'ethers/lib/utils'
export enum GetEcDSASigType {
HasDataStruct,
WithoutDataStruct,
Contract,
}
const MIN_NFT_TOKENID = 32768
const SNARK_SCALAR_FIELD = new BigInteger(
'21888242871839275222246405745257275088548364400416034343698204186575808495617',
10,
)
export interface KeyPairParams {
web3: any
address: string
walletType: ConnectorNames
keySeed: string
chainId: ChainId
accountId?: number
counterFactualInfo?: CounterFactualInfo
isMobile?: boolean
}
export function generatePrivateKey(result: { sig: string; counterFactualInfo: any; error: any }) {
if (!result.error && result.sig) {
// myLog("sig:", result.sig);
const seedBuff = ethUtil.sha256(fm.toBuffer(result.sig))
// myLog(`seedBuff.toString('hex') ${seedBuff.toString('hex')}`)
const seed = BigNumber.from('0x' + seedBuff.toString('hex'))
// myLog(`seed ${seed.toString()}`)
const bitIntDataItems = bnToBufWithFixedLength(seed.toString(), 32)
// myLog(`bigIntData ${bitIntDataItems}`)
const keyPair = EDDSAUtil.generateKeyPair(bitIntDataItems)
// myLog("keyPair", keyPair)
const formatedPx = fm.formatEddsaKey(fm.toHex(fm.toBig(keyPair.publicKeyX)))
const formatedPy = fm.formatEddsaKey(fm.toHex(fm.toBig(keyPair.publicKeyY)))
const sk = toHex(toBig(keyPair.secretKey))
return {
keyPair,
formatedPx,
formatedPy,
sk,
counterFactualInfo: result.counterFactualInfo,
}
} else {
console.log('generateKeyPair personalSign error', result.error)
throw Error(result.error)
}
}
export async function generateKeyPair(
{
web3,
address,
walletType,
keySeed,
chainId,
accountId,
counterFactualInfo,
isMobile,
}: KeyPairParams,
publicKey: { x: string; y: string } | undefined = undefined,
) {
// LOG: for signature
myLog(
'personalSign ->',
'counterFactualInfo',
counterFactualInfo,
'keySeed',
keySeed,
'walletType',
walletType,
'publicKey from sever side ',
publicKey,
)
const result: any = await personalSign(
web3,
address,
'',
keySeed,
walletType,
chainId,
accountId,
counterFactualInfo,
isMobile === undefined ? IsMobile.any() : isMobile,
)
if (process.env.REACT_APP_LOG_SIGNATURE === 'true') {
try {
const hash = ethers.utils.keccak256(ethers.utils.toUtf8Bytes(result.sig));
console.log('UnlockAccountEcdsaSigHashOutput', 'message hash', keySeed, ethers.utils.hashMessage(keySeed))
console.log('UnlockAccountEcdsaSigHashOutput', 'signature hash', hash)
console.log('UnlockAccountEcdsaSigHashOutput', 'isSigEmpty', result.sig === '')
} catch {}
}
if (!result.error && !result.sig) {
throw Error('sig is empty')
}
try {
let { keyPair, formatedPx, formatedPy, sk, counterFactualInfo } = generatePrivateKey(result)
if (
publicKey &&
result.sig.length > 3 &&
publicKey.x &&
publicKey.y &&
(!fm.toBig(formatedPx).eq(fm.toBig(publicKey.x)) ||
!fm.toBig(formatedPy).eq(fm.toBig(publicKey.y)))
) {
let value = result.sig.split('')
let end = value.splice(result.sig.length - 2, 2).join('')
end = end == '1c' ? '01' : '1c'
result.sig = value.concat(end.split('')).join('')
let newValue = generatePrivateKey(result)
// LOG: for signature
myLog(
'personalSign ->',
'publicKey calc by sign',
'x',
formatedPx,
'y',
formatedPy,
'publicKey from server',
publicKey,
'personalSign again->',
'publicKey calc by sign',
'x',
end,
newValue.formatedPx,
'y',
newValue.formatedPy,
)
return {
keyPair: newValue.keyPair,
formatedPx: newValue.formatedPx,
formatedPy: newValue.formatedPy,
sk: newValue.sk,
counterFactualInfo,
}
} else {
return { keyPair, formatedPx, formatedPy, sk, counterFactualInfo }
}
} catch (error) {
throw Error(error as any)
}
}
const makeRequestParamStr = (request: Map) => {
const arrObj = Array.from(request)
arrObj.sort(function (a, b) {
return a[0].localeCompare(b[0])
})
const orderedMap = new Map(arrObj.map((i) => [i[0], i[1]]))
const paramlist: Array = []
const keys = Object.keys(Object.fromEntries(orderedMap))
if (keys) {
keys.forEach((key: string) => {
const value = request.get(key)
if (value !== undefined && value !== '') paramlist.push(`${key}=${value}`)
})
}
// force to change encode ',' due to different encode rules between server and client
return encodeURIComponent(paramlist.join('&')).replace(/%2C/g, '%252C')
}
//submitOrderV3
const genSigWithPadding = (PrivateKey: string | undefined, hash: any) => {
const signature = EDDSAUtil.sign(PrivateKey, hash)
let signatureRx_Hex = fm.clearHexPrefix(fm.toHex(fm.toBN(signature.Rx)))
if (signatureRx_Hex.length < 64) {
const padding = new Array(64 - signatureRx_Hex.length).fill(0)
signatureRx_Hex = padding.join('').toString() + signatureRx_Hex
}
let signatureRy_Hex = fm.clearHexPrefix(fm.toHex(fm.toBN(signature.Ry)))
if (signatureRy_Hex.length < 64) {
const padding = new Array(64 - signatureRy_Hex.length).fill(0)
signatureRy_Hex = padding.join('').toString() + signatureRy_Hex
}
let signatureS_Hex = fm.clearHexPrefix(fm.toHex(fm.toBN(signature.s)))
if (signatureS_Hex.length < 64) {
const padding = new Array(64 - signatureS_Hex.length).fill(0)
signatureS_Hex = padding.join('').toString() + signatureS_Hex
}
const result = '0x' + signatureRx_Hex + signatureRy_Hex + signatureS_Hex
return result
}
const makeObjectStr = (request: Map) => {
const jsonTxt = JSON.stringify(Object.fromEntries(request))
return encodeURIComponent(jsonTxt).replace(/[!'()]/g, escape) //replace(/'/ig, "%27")
}
export function getEdDSASig(
method: string,
basePath: string,
api_url: string,
requestInfo: any,
PrivateKey: string | undefined,
) {
let params = undefined
method = method.toUpperCase().trim()
if (method === 'GET' || method === 'DELETE') {
params = makeRequestParamStr(requestInfo)
} else if (method === 'POST' || method === 'PUT') {
params = makeObjectStr(requestInfo)
} else {
throw new Error(`${method} is not supported yet!`)
}
const uri = encodeURIComponent(`${basePath}${api_url}`)
const message = `${method}&${uri}&${params}`
// LOG: for signature
myLog('getEdDSASig', message)
let _hash: any = new BigInteger(crypto.SHA256(message).toString(), 16)
let hash = _hash.mod(SNARK_SCALAR_FIELD).toFormat(0, 0, {})
// LOG: for signature
myLog('getEdDSASig hash', message, '_hash', _hash.toString(), 'hash', hash)
const sig = genSigWithPadding(PrivateKey, hash)
return sig
}
export function creatEdDSASigHasH({
method,
basePath,
api_url,
requestInfo,
}: {
method: string
basePath: string
api_url: string
requestInfo: any
}) {
let params = undefined
method = method.toUpperCase().trim()
if (method === 'GET' || method === 'DELETE') {
params = makeRequestParamStr(requestInfo)
} else if (method === 'POST' || method === 'PUT') {
params = makeObjectStr(requestInfo)
} else {
throw new Error(`${method} is not supported yet!`)
}
const uri = encodeURIComponent(`${basePath}${api_url}`)
const message = `${method}&${uri}&${params}`
// LOG: for signature
myLog('getEdDSASig', message)
let _hash: any = new BigInteger(crypto.SHA256(message).toString(), 16)
let hash = _hash.mod(SNARK_SCALAR_FIELD).toFormat(0, 0, {})
// LOG: for signature
myLog('getEdDSASig hash', message, '_hash', _hash.toString(), 'hash', hash)
return { hash, hashRaw: toHex(_hash) }
}
export function verifyEdDSASig(
hash: string,
input: { Rx: string; Ry: string; s: string },
): Boolean {
return true
}
export const getEdDSASigWithPoseidon = (inputs: any, PrivateKey: string | undefined) => {
const p = field.SNARK_SCALAR_FIELD
const poseidonParams = new PoseidonParams(
p,
inputs.length + 1,
6,
53,
'poseidon',
BigNumber.from(5),
null,
null,
128,
)
let bigIntInputs: any
bigIntInputs = []
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i]
bigIntInputs.push(BigNumber.from(input))
}
const hash = permunation.poseidon(bigIntInputs, poseidonParams)
return {
hash,
result: genSigWithPadding(PrivateKey, hash),
}
}
/**
* @description sign EIP712
* @param web3
* @param account
* @param method
* @param params
* @returns {Promise.<*>}
*/
export async function signEip712(web3: any, account: string, method: string, params: any) {
const provider = new ethers.providers.Web3Provider(web3.currentProvider)
const response: any = await provider.send(
method,
params
).catch(err => {
return { error: { message: err.message } }
})
if (response?.result) {
return response
} else {
throw new Error(response['error']['message'])
}
}
export async function signEip712WalletConnect(web3: any, account: string, typedData: any) {
try {
let response: any
if (getWindowSafely()?.ethereum?.isLoopring || !web3.currentProvider?.signer?.session) {
const result: any = await new Promise((resolve) => {
web3.currentProvider?.sendAsync(
{
method: 'eth_signTypedData',
params: [account, typedData],
account,
},
(err: any, result: any) => {
if (err) {
resolve({ error: { message: err.message } })
return
}
if (result.error) {
resolve({ error: { message: result.error.message } })
return
}
resolve({ result: result.result })
},
)
})
// LOG: for signature
myLog('eth_signTypedData', result)
response = result?.result
} else {
const provider = new ethers.providers.Web3Provider(web3.currentProvider)
response = await provider.send('eth_signTypedData', [account, typedData])
}
// LOG: for signature
myLog('eth_signTypedData success', response)
return response
} catch (err) {
// LOG: for signature
myLog('eth_signTypedData error', err)
return { error: err as any }
}
}
export async function getEcDSASig(
web3: any,
typedData: any,
address: string | undefined,
type: GetEcDSASigType,
chainId: ChainId,
accountId?: number,
pwd = '',
walletType?: ConnectorNames,
counterFactualInfo?: CounterFactualInfo,
) {
const msgParams = JSON.stringify(typedData)
const params = [address, msgParams]
let response: any, hash: Buffer | string, signEip712Result: any, signature: any
switch (type) {
case GetEcDSASigType.HasDataStruct:
try {
const provider = new ethers.providers.Web3Provider(web3.currentProvider)
response = await provider.send(
'eth_signTypedData_v4',
params,
)
} catch (error) {
console.log('eth_signTypedData_v4 error', error)
throw error
}
return {
ecdsaSig: response,
}
case GetEcDSASigType.WithoutDataStruct:
hash = sigUtil.TypedDataUtils.sign(typedData)
hash = fm.toHex(hash)
if (!walletType) {
throw Error('no walletType set!')
}
signature = await personalSign(
web3,
address,
pwd,
hash,
walletType,
chainId,
counterFactualInfo ? counterFactualInfo.accountId : undefined,
counterFactualInfo,
IsMobile.any(),
)
if (signature?.sig) {
return {
ecdsaSig: signature.sig,
counterFactualInfo: signature.counterFactualInfo,
}
}
console.log('WithoutDataStruct error', signature?.error)
if (typeof signature?.error == 'string') {
throw new Error(signature.error)
} else {
throw signature?.error
}
// case GetEcDSASigType.Contract:
// signEip712Result = await signEip712WalletConnect(
// web3,
// address as string,
// msgParams
// );
//
// if (signEip712Result.error) {
// throw Error("Contract sig error");
// }
//
// return {
// ecdsaSig: signEip712Result,
// };
default:
break
}
throw Error('getEcDSASig unsupported switch case:' + type)
}
export function convertPublicKey2(pk: PublicKey) {
// return new BN(EdDSA.pack(pk.x, pk.y), 16);
return new BN(EDDSAUtil.pack(pk.x, pk.y), 16)
}
export function convertPublicKey(pk: PublicKey) {
const publicKeyX = fm.formatEddsaKey(fm.toHex(fm.toBig(pk.x)))
const publicKeyY = fm.formatEddsaKey(fm.toHex(fm.toBig(pk.y)))
// return new BN(EdDSA.pack(publicKeyX, publicKeyY), 16);
return new BN(EDDSAUtil.pack(publicKeyX, publicKeyY), 16)
}
export function getUpdateAccountEcdsaTypedData(data: UpdateAccountRequestV3, chainId: ChainId) {
const message: any = {
owner: data.owner,
accountID: data.accountId,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.volume,
publicKey: fm.addHexPrefix(convertPublicKey2(data.publicKey).toString(16)),
validUntil: data.validUntil,
nonce: data.nonce,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
AccountUpdate: [
{ name: 'owner', type: 'address' },
{ name: 'accountID', type: 'uint32' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'publicKey', type: 'uint256' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'nonce', type: 'uint32' },
],
},
primaryType: 'AccountUpdate',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
// withdraw
export function get_EddsaSig_OffChainWithdraw(
request: OffChainWithdrawalRequestV3,
eddsaKey: string,
) {
const onchainDataHash = abi
.soliditySHA3(
['uint256', 'address', 'bytes'],
[
request.minGas,
new BN(fm.clearHexPrefix(request.to), 16),
new Buffer(request.extraData ?? ''),
],
)
.slice(0, 20)
const orderHashStr = fm.addHexPrefix(onchainDataHash.toString('hex'))
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.accountId,
request.token.tokenId,
request.token.volume,
request.maxFee.tokenId,
request.maxFee.volume,
orderHashStr,
request.validUntil,
request.storageId,
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export function getOrderHash(request: SubmitOrderRequestV3) {
const p = field.SNARK_SCALAR_FIELD
const poseidonParams = new PoseidonParams(
p,
12,
6,
53,
'poseidon',
BigNumber.from(5),
null,
null,
128,
)
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.storageId,
request.accountId,
request.sellToken.tokenId,
request.buyToken.tokenId,
request.sellToken.volume,
request.buyToken.volume,
request.validUntil,
request.maxFeeBips,
request.fillAmountBOrS ? 1 : 0,
new BN(ethUtil.toBuffer(request.taker)).toString(),
]
let bigIntInputs: any
bigIntInputs = []
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i]
bigIntInputs.push(BigNumber.from(input))
}
const hash = permunation.poseidon(bigIntInputs, poseidonParams)
let hashInHex = hash.toHexString()
return hashInHex
}
export function getWithdrawTypedData(
data: OffChainWithdrawalRequestV3,
chainId: ChainId,
): sigUtil.EIP712TypedData {
const message = {
owner: data.owner,
accountID: data.accountId,
tokenID: data.token.tokenId,
amount: data.token.volume,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.volume,
to: data.to,
extraData: data.extraData ? data.extraData : '0x',
minGas: data.minGas,
validUntil: data.validUntil,
storageID: data.storageId,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Withdrawal: [
{ name: 'owner', type: 'address' },
{ name: 'accountID', type: 'uint32' },
{ name: 'tokenID', type: 'uint16' },
{ name: 'amount', type: 'uint96' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'to', type: 'address' },
{ name: 'extraData', type: 'bytes' },
{ name: 'minGas', type: 'uint256' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'storageID', type: 'uint32' },
],
},
primaryType: 'Withdrawal',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
export async function offchainWithdrawWrap({
withdraw,
chainId,
web3,
accountId,
isHWAddr,
counterFactualInfo,
}: {
withdraw: loopring_defs.OffChainWithdrawalRequestV3
web3: any
chainId: ChainId
accountId: number
isHWAddr: boolean
counterFactualInfo?: CounterFactualInfo
}) {
const typedData = getWithdrawTypedData(withdraw, chainId)
try {
const result = await getEcDSASig(
web3,
typedData,
withdraw.owner,
isHWAddr ? GetEcDSASigType.WithoutDataStruct : GetEcDSASigType.HasDataStruct,
chainId,
accountId,
'',
ConnectorNames.Unknown,
counterFactualInfo,
)
return result.ecdsaSig
} catch (error) {
// console.log('EcDSASig error try sign WithoutDataStruct')
throw error
}
}
//NFT Withdraw
export function get_EddsaSig_NFT_Withdraw(request: NFTWithdrawRequestV3, eddsaKey: string) {
const onchainDataHash = abi
.soliditySHA3(
['uint256', 'address', 'bytes'],
[
request.minGas,
new BN(fm.clearHexPrefix(request.to), 16),
new Buffer(request.extraData ?? ''),
],
)
.slice(0, 20)
const orderHashStr = fm.addHexPrefix(onchainDataHash.toString('hex'))
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.accountId,
request.token.tokenId,
request.token.amount,
request.maxFee.tokenId,
request.maxFee.amount,
orderHashStr,
request.validUntil,
request.storageId,
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export function getNftData(request: NFTMintRequestV3) {
const p = field.SNARK_SCALAR_FIELD
const poseidonParams = new PoseidonParams(
p,
7,
6,
52,
'poseidon',
BigNumber.from(5),
null,
null,
128,
)
const idNo0x = fm.clearHexPrefix(request.nftId)
let nftIdLo, nftIdHi
if (idNo0x.length > 32) {
nftIdLo = new BN(idNo0x.substr(idNo0x.length - 32, 32), 16).toString(10)
nftIdHi = new BN(idNo0x.substr(0, idNo0x.length - 32), 16).toString(10)
} else {
nftIdLo = new BN(idNo0x.substr(0, idNo0x.length), 16).toString(10)
nftIdHi = 0
}
myLog('nftIdLo', nftIdLo, 'nftIdHi', nftIdHi)
const inputs = [
request.minterAddress,
request.nftType,
request.tokenAddress,
nftIdLo,
nftIdHi,
request.royaltyPercentage,
]
let bigIntInputs: any
bigIntInputs = []
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i]
bigIntInputs.push(BigNumber.from(input))
}
const hash = permunation.poseidon(bigIntInputs, poseidonParams)
return hash
}
export function getNFTMintTypedData(
data: NFTMintRequestV3,
chainId: ChainId,
web3: Web3,
): sigUtil.EIP712TypedData {
let nftId = data.nftId
if (data.nftId.startsWith('0x')) {
nftId = web3.utils.hexToNumberString(data.nftId)
}
const message = {
minterAddress: data.minterAddress,
toAccountId: data.toAccountId,
nftType: data.nftType.toString(),
amount: data.amount,
nftId: nftId,
nftAddress: data.tokenAddress,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.amount,
validUntil: data.validUntil,
storageID: data.storageId,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Mint: [
{ name: 'minterAddress', type: 'address' },
{ name: 'toAccountId', type: 'uint32' },
{ name: 'nftType', type: 'string' },
{ name: 'amount', type: 'uint96' },
{ name: 'nftId', type: 'uint256' },
{ name: 'nftAddress', type: 'address' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'storageID', type: 'uint32' },
],
},
primaryType: 'Mint',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
export function getNFTWithdrawTypedData(
data: NFTWithdrawRequestV3,
chainId: ChainId,
): sigUtil.EIP712TypedData {
const message = {
owner: data.owner,
accountID: data.accountId,
tokenID: data.token.tokenId,
amount: data.token.amount,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.amount,
to: data.to,
extraData: data.extraData ? data.extraData : '',
minGas: data.minGas,
validUntil: data.validUntil,
storageID: data.storageId,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Withdrawal: [
{ name: 'owner', type: 'address' },
{ name: 'accountID', type: 'uint32' },
{ name: 'tokenID', type: 'uint16' },
{ name: 'amount', type: 'uint96' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'to', type: 'address' },
{ name: 'extraData', type: 'bytes' },
{ name: 'minGas', type: 'uint256' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'storageID', type: 'uint32' },
],
},
primaryType: 'Withdrawal',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
export async function withdrawNFTWrap({
withdraw,
chainId,
web3,
accountId,
isHWAddr,
counterFactualInfo,
}: {
withdraw: loopring_defs.NFTWithdrawRequestV3
web3: any
chainId: ChainId
accountId: number
isHWAddr: boolean
counterFactualInfo?: CounterFactualInfo
}) {
const typedData = getNFTWithdrawTypedData(withdraw, chainId)
try {
const result = await getEcDSASig(
web3,
typedData,
withdraw.owner,
isHWAddr ? GetEcDSASigType.WithoutDataStruct : GetEcDSASigType.HasDataStruct,
chainId,
accountId,
'',
ConnectorNames.Unknown,
counterFactualInfo,
)
return result.ecdsaSig
} catch (error) {
// console.log('EcDSASig error try sign WithoutDataStruct')
throw error
}
}
//NFT Mint
export function get_EddsaSig_NFT_Mint(request: NFTMintRequestV3, eddsaKey: string) {
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.minterId,
request.toAccountId,
getNftData(request),
request.amount,
request.maxFee.tokenId,
request.maxFee.amount,
request.validUntil,
request.storageId,
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export function get_Is_Nft_Token(tokenId: number) {
return tokenId >= MIN_NFT_TOKENID
}
// NFT Order
export function get_EddsaSig_NFT_Order(request: NFTOrderRequestV3, eddsaKey: string) {
let fillAmountBOrS = 0
if (request.fillAmountBOrS) {
fillAmountBOrS = 1
}
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.storageId,
request.accountId,
request.sellToken?.tokenId !== undefined ? request.sellToken.tokenId : '',
(request.buyToken as any)?.nftData
? (request.buyToken as NFTTokenAmountInfo).nftData
: request.buyToken.tokenId,
request.sellToken?.amount ? request.sellToken.amount : 0,
request.buyToken?.amount ? request.buyToken.amount : 0,
request.validUntil,
request.maxFeeBips,
fillAmountBOrS,
new BN(ethUtil.toBuffer(request.taker)).toString(),
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export function get_EddsaSig_Dual_Order(request: DualOrderRequest, eddsaKey: string) {
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.storageId,
request.accountId,
request.sellToken.tokenId,
request.buyToken.tokenId,
request.sellToken.volume,
request.buyToken.volume,
request.validUntil,
request.maxFeeBips,
request.fillAmountBOrS ? 1 : 0,
0,
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export async function mintNFTWrap({
mint,
chainId,
web3,
accountId,
isHWAddr,
counterFactualInfo,
}: {
mint: loopring_defs.NFTMintRequestV3
chainId: ChainId
web3: any
accountId: number
isHWAddr: boolean
counterFactualInfo?: CounterFactualInfo
}) {
const typedData = getNFTMintTypedData(mint, chainId, web3)
try {
const result = await getEcDSASig(
web3,
typedData,
mint.minterAddress,
isHWAddr ? GetEcDSASigType.WithoutDataStruct : GetEcDSASigType.HasDataStruct,
chainId,
accountId,
'',
ConnectorNames.Unknown,
counterFactualInfo,
)
return result.ecdsaSig
} catch (error) {
// console.log('EcDSASig error try sign WithoutDataStruct')
throw error
}
}
// transfer
export function get_EddsaSig_Transfer(request: OriginTransferRequestV3, eddsaKey: string) {
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.payerId,
request.payeeId,
request.token.tokenId,
request.token.volume,
request.maxFee.tokenId,
request.maxFee.volume,
new BN(ethUtil.toBuffer(request.payeeAddr)).toString(),
0,
0,
request.validUntil,
request.storageId,
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export function getTransferOldTypedData(
data: OriginTransferRequestV3,
chainId: ChainId,
): sigUtil.EIP712TypedData {
const message = {
from: data.payerAddr,
to: data.payeeAddr,
tokenID: data.token.tokenId,
amount: data.token.volume,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.volume,
validUntil: data.validUntil,
storageID: data.storageId,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Transfer: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'tokenID', type: 'uint16' },
{ name: 'amount', type: 'uint96' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'storageID', type: 'uint32' },
],
},
primaryType: 'Transfer',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
export function getTransferTypedData(
data: OriginTransferRequestV3,
chainId: ChainId,
): sigUtil.EIP712TypedData {
const message = {
from: data.payerAddr,
to: data.payeeAddr,
tokenID: data.token.tokenId,
amount: data.token.volume,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.volume,
validUntil: data.validUntil,
storageID: data.storageId,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Transfer: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'tokenID', type: 'uint16' },
{ name: 'amount', type: 'uint96' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'storageID', type: 'uint32' },
],
},
primaryType: 'Transfer',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
export async function transferWrap({
transfer,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
}: {
transfer: loopring_defs.OriginTransferRequestV3
web3: any
chainId: ChainId
accountId: number
isHWAddr: boolean
counterFactualInfo?: CounterFactualInfo
}): Promise {
const typedData = getTransferTypedData(transfer, chainId)
try {
const result = await getEcDSASig(
web3,
typedData,
transfer.payerAddr,
isHWAddr ? GetEcDSASigType.WithoutDataStruct : GetEcDSASigType.HasDataStruct,
chainId,
accountId,
'',
ConnectorNames.Unknown,
counterFactualInfo,
)
return result.ecdsaSig
} catch (error) {
// console.log('EcDSASig error try sign WithoutDataStruct')
throw error
}
}
export async function transferNFTWrap({
transfer,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
}: {
transfer: loopring_defs.OriginNFTTransferRequestV3
chainId: ChainId
web3: any
accountId: number
isHWAddr: boolean
counterFactualInfo?: CounterFactualInfo
}): Promise {
const typedData = getNFTTransferTypedData(transfer, chainId)
try {
const result = await getEcDSASig(
web3,
typedData,
transfer.fromAddress,
isHWAddr ? GetEcDSASigType.WithoutDataStruct : GetEcDSASigType.HasDataStruct,
chainId,
accountId,
'',
ConnectorNames.Unknown,
counterFactualInfo,
)
return result.ecdsaSig
} catch (error) {
// console.log('EcDSASig error try sign WithoutDataStruct')
throw error
}
}
export function get_EddsaSig_NFT_Transfer(request: OriginNFTTransferRequestV3, eddsaKey: string) {
const inputs = [
new BN(ethUtil.toBuffer(request.exchange)).toString(),
request.fromAccountId,
request.toAccountId,
request.token.tokenId,
request.token.amount,
request.maxFee.tokenId,
request.maxFee.amount,
new BN(ethUtil.toBuffer(request.toAddress)).toString(),
0,
0,
request.validUntil,
request.storageId,
]
return getEdDSASigWithPoseidon(inputs, eddsaKey)
}
export function getNftTradeHash(request: NFTTradeRequestV3) {
const p = field.SNARK_SCALAR_FIELD
const poseidonParams = new PoseidonParams(
p,
7,
6,
52,
'poseidon',
BigNumber.from(5),
null,
null,
128,
)
const inputs = [
request.taker.accountId,
request.taker.sellToken.tokenId,
request.taker.storageId,
request.maker.accountId,
request.maker.sellToken.tokenId,
request.maker.storageId,
]
let bigIntInputs: any
bigIntInputs = []
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i]
bigIntInputs.push(BigNumber.from(input))
}
const hash = permunation.poseidon(bigIntInputs, poseidonParams)
let hashInHex = hash.toHexString()
return hashInHex
}
export function getNFTTransferTypedData(
data: OriginNFTTransferRequestV3,
chainId: ChainId,
): sigUtil.EIP712TypedData {
const message = {
from: data.fromAddress,
to: data.toAddress,
tokenID: data.token.tokenId,
amount: data.token.amount,
feeTokenID: data.maxFee.tokenId,
maxFee: data.maxFee.amount,
validUntil: data.validUntil,
storageID: data.storageId,
}
const typedData: sigUtil.EIP712TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
Transfer: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'tokenID', type: 'uint16' },
{ name: 'amount', type: 'uint96' },
{ name: 'feeTokenID', type: 'uint16' },
{ name: 'maxFee', type: 'uint96' },
{ name: 'validUntil', type: 'uint32' },
{ name: 'storageID', type: 'uint32' },
],
},
primaryType: 'Transfer',
domain: {
name: 'Loopring Protocol',
version: '3.6.0',
chainId: chainId,
verifyingContract: data.exchange,
},
message: message,
}
return typedData
}
export function eddsaSign(typedData: any, eddsaKey: string) {
const hash = fm.toHex(sigUtil.TypedDataUtils.sign(typedData))
// LOG: for signature
// myLog('eddsaSign', hash)
const sigHash = fm.toHex(new BigInteger(hash, 16).idiv(8))
const signature = EDDSAUtil.sign(eddsaKey, sigHash)
return {
eddsaSig:
fm.formatEddsaKey(fm.toHex(fm.toBig(signature.Rx))) +
fm.clearHexPrefix(fm.formatEddsaKey(fm.toHex(fm.toBig(signature.Ry)))) +
fm.clearHexPrefix(fm.formatEddsaKey(fm.toHex(fm.toBig(signature.s)))),
}
}
export function eddsaSignWithDomain(
domainHax: string,
primaryType: string,
message: sigUtil.EIP712Message,
types: sigUtil.EIP712Types,
eddsaKey: string,
) {
const parts = [Buffer.from('1901', 'hex')]
parts.push(Buffer.from(domainHax.slice(2), 'hex'))
//https://github.com/MetaMask/eth-sig-util/blob/main/CHANGELOG.md
parts.push(sigUtil.TypedDataUtils.hashStruct(primaryType, message, types))
const hash = fm.toHex(ethUtil.keccak(Buffer.concat(parts))) //5.2.0 - 2018-04-27 keccak sha3() -> keccak()
const sigHash = fm.toHex(new BigInteger(hash, 16).idiv(8))
const signature = EDDSAUtil.sign(eddsaKey, sigHash)
return {
eddsaSig:
fm.formatEddsaKey(fm.toHex(fm.toBig(signature.Rx))) +
fm.clearHexPrefix(fm.formatEddsaKey(fm.toHex(fm.toBig(signature.Ry)))) +
fm.clearHexPrefix(fm.formatEddsaKey(fm.toHex(fm.toBig(signature.s)))),
}
}
export function getAmmJoinEcdsaTypedData(data: JoinAmmPoolRequest, patch: AmmPoolRequestPatch) {
const message = {
owner: data.owner,
joinAmounts: [data.joinTokens.pooled[0].volume, data.joinTokens.pooled[1].volume],
joinStorageIDs: data.storageIds,
mintMinAmount: data.joinTokens.minimumLp.volume,
fee: data.fee,
validUntil: data.validUntil,
}
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
PoolJoin: [
{ name: 'owner', type: 'address' },
{ name: 'joinAmounts', type: 'uint96[]' },
{ name: 'joinStorageIDs', type: 'uint32[]' },
{ name: 'mintMinAmount', type: 'uint96' },
{ name: 'fee', type: 'uint96' },
{ name: 'validUntil', type: 'uint32' },
],
},
primaryType: 'PoolJoin',
domain: {
name: patch.ammName,
version: '1.0.0',
chainId: patch.chainId,
verifyingContract: patch.poolAddress,
},
message: message,
}
return typedData
}
// ammpool join
export function get_EddsaSig_JoinAmmPool(data: JoinAmmPoolRequest, patch: AmmPoolRequestPatch) {
if (data.domainSeparator) {
const typedData = getAmmJoinEcdsaTypedData(data, patch)
return eddsaSignWithDomain(
data.domainSeparator,
typedData.primaryType,
typedData.message,
typedData.types,
patch.eddsaKey,
)
} else {
const typedData = getAmmJoinEcdsaTypedData(data, patch)
return eddsaSign(typedData, patch.eddsaKey)
}
}
export function getAmmExitEcdsaTypedData(data: ExitAmmPoolRequest, patch: AmmPoolRequestPatch) {
const message: any = {
owner: data.owner,
burnAmount: data.exitTokens.burned.volume,
burnStorageID: data.storageId,
exitMinAmounts: [data.exitTokens.unPooled[0].volume, data.exitTokens.unPooled[1].volume],
fee: data.maxFee,
validUntil: data.validUntil,
}
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' },
],
PoolExit: [
{ name: 'owner', type: 'address' },
{ name: 'burnAmount', type: 'uint96' },
{ name: 'burnStorageID', type: 'uint32' },
{ name: 'exitMinAmounts', type: 'uint96[]' },
{ name: 'fee', type: 'uint96' },
{ name: 'validUntil', type: 'uint32' },
],
},
primaryType: 'PoolExit',
domain: {
name: patch.ammName,
version: '1.0.0',
chainId: patch.chainId,
verifyingContract: patch.poolAddress,
},
message: message,
}
return typedData
}
// ammpool exit
export function get_EddsaSig_ExitAmmPool(data: ExitAmmPoolRequest, patch: AmmPoolRequestPatch) {
if (data.domainSeparator) {
const typedData = getAmmExitEcdsaTypedData(data, patch)
return eddsaSignWithDomain(
data.domainSeparator,
typedData.primaryType,
typedData.message,
typedData.types,
patch.eddsaKey,
)
} else {
const typedData = getAmmExitEcdsaTypedData(data, patch)
return eddsaSign(typedData, patch.eddsaKey)
}
}
================================================
FILE: src/api/user_api.ts
================================================
/* eslint-disable camelcase */
import { BaseAPI } from './base_api'
import * as loopring_defs from '../defs'
import * as sign_tools from './sign/sign_tools'
import {
generateKeyPair,
getEcDSASig,
GetEcDSASigType,
getEdDSASigWithPoseidon,
getUpdateAccountEcdsaTypedData,
KeyPairParams,
} from './sign/sign_tools'
import BN from 'bn.js'
import { RequiredPart, sortObjDictionary } from '../utils'
import { AxiosResponse } from 'axios'
export class UserAPI extends BaseAPI {
/*
* Change the ApiKey associated with the user's account.
* The current ApiKey must be provided as the value of the X-API-KEY HTTP header.
*/
public async updateUserApiKey(
request: loopring_defs.UpdateUserApiKeyRequest,
apiKey: string,
eddsaKey: string,
): Promise<{ raw_data: R }> {
const dataToSig: Map = new Map()
dataToSig.set('accountId', request.accountId)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.API_KEY_ACTION,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
}
}
/*
* Fetches the next order id for a given sold token.
* If the need arises to repeatedly place orders in a short span of time,
* the order id can be initially fetched through the API and then managed locally.
* Each new order id can be derived from adding 2 to the last one
*/
public async getNextStorageId(
request: loopring_defs.GetNextStorageIdRequest,
apiKey: string,
): Promise<{ raw_data: R; orderId: number; offchainId: number }> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NEXT_STORAGE_ID,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const { orderId, offchainId } = raw_data
return {
orderId,
offchainId,
raw_data,
}
}
/*
* Get the details of an order based on order hash.
*/
public async getOrderDetails(
request: loopring_defs.GetOrderDetailsRequest,
apiKey: string,
): Promise<{ raw_data: R; orderDetail: loopring_defs.OrderDetail }> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.ORDER_ACTION,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
orderDetail: raw_data,
raw_data,
}
}
public async getOrders(
request: loopring_defs.GetOrdersRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
orders: loopring_defs.OrderDetail[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_MULTI_ORDERS,
queryParams: {
...request,
status: request.status ? request.status.join(',') : '',
},
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const totalNum: number = raw_data.totalNum
const orders: loopring_defs.OrderDetail[] = raw_data.orders
return {
totalNum,
orders,
raw_data,
}
}
/*
* Submit an order
*/
public async submitOrder(
{
extraOrderType = loopring_defs.EXTRAORDER_TYPE.TRADITIONAL_ORDER,
...orderRequest
}: loopring_defs.SubmitOrderRequestV3,
privateKey: string,
apiKey: string,
) {
if (!orderRequest.tradeChannel) {
orderRequest.tradeChannel = loopring_defs.TradeChannel.MIXED
}
const dataToSig = [
orderRequest.exchange,
orderRequest.storageId,
orderRequest.accountId,
orderRequest.sellToken.tokenId,
orderRequest.buyToken.tokenId,
orderRequest.sellToken.volume,
orderRequest.buyToken.volume,
orderRequest.validUntil,
orderRequest.maxFeeBips,
orderRequest.fillAmountBOrS ? 1 : 0,
0,
]
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.ORDER_ACTION,
bodyParams: orderRequest,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG_POSEIDON,
sigObj: {
dataToSig,
sigPatch: loopring_defs.SigPatchField.EddsaSignature,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
public async submitStopOrder(
{
extraOrderType = loopring_defs.EXTRAORDER_TYPE.STOP_LIMIT,
stopSide, // = STOP_SIDE.NO_CONDITION,
...orderRequest
}: RequiredPart<
loopring_defs.SubmitOrderRequestV3,
'extraOrderType' | 'stopSide' | 'stopPrice'
>,
privateKey: string,
apiKey: string,
) {
if (!orderRequest.tradeChannel) {
orderRequest.tradeChannel = loopring_defs.TradeChannel.MIXED
}
const dataToSig = [
orderRequest.exchange,
orderRequest.storageId,
orderRequest.accountId,
orderRequest.sellToken.tokenId,
orderRequest.buyToken.tokenId,
orderRequest.sellToken.volume,
orderRequest.buyToken.volume,
orderRequest.validUntil,
orderRequest.maxFeeBips,
orderRequest.fillAmountBOrS ? 1 : 0,
0,
]
const eddsaSignature = getEdDSASigWithPoseidon(dataToSig, privateKey).result
const bodyParams = {
...orderRequest,
extraOrderType,
stopSide,
eddsaSignature,
}
const _dataToSig: Map = sortObjDictionary(bodyParams)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.ORDER_ACTION,
bodyParams: bodyParams,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig: _dataToSig,
PrivateKey: privateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
/*
* Cancel order using order hash or client-side ID.
*/
public async cancelOrder(
request: loopring_defs.CancelOrderRequest,
PrivateKey: string,
apiKey: string,
): Promise<{ raw_data: R }> {
const dataToSig: Map = new Map()
dataToSig.set('accountId', request.accountId)
if (request.orderHash) dataToSig.set('orderHash', request.orderHash)
if (request.clientOrderId) dataToSig.set('clientOrderId', request.clientOrderId)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.ORDER_ACTION,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.DELETE,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
}
}
/*
* Cancel multiple orders using order hashes
*/
public async cancelMultiOrdersByHash(
request: loopring_defs.CancelMultiOrdersByHashRequest,
PrivateKey: string,
apiKey: string,
): Promise<{ raw_data: R }> {
const dataToSig: Map = new Map()
dataToSig.set('accountId', request.accountId)
dataToSig.set('orderHash', request.orderHash)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.ORDER_CANCEL_HASH_LIST,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.DELETE,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
}
}
/*
* Cancel multiple orders using clientOrderIds
*/
public async cancelMultiOrdersByCreditOrderId(
request: loopring_defs.CancelMultiOrdersByClientOrderIdRequest,
PrivateKey: string,
apiKey: string,
): Promise<{ raw_data: R }> {
const dataToSig: Map = new Map()
dataToSig.set('accountId', request.accountId)
dataToSig.set('clientOrderId', request.clientOrderId)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.ORDER_CANCEL_CLIENT_ORDER_ID_LIST,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.DELETE,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
raw_data,
}
}
/*
* Returns a list Ethereum transactions from users for exchange account registration.
*/
public async getUserRegTxs(
request: loopring_defs.GetUserRegTxsRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userRegTxs: loopring_defs.UserRegTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_REG_TXS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const userRegTxs: loopring_defs.UserRegTx[] = raw_data.transactions
return {
totalNum: raw_data.totalNum,
userRegTxs,
raw_data,
}
}
/*
* Returns a list Ethereum transactions from users for resetting exchange passwords.
*/
public async getUserPwdResetTxs(
request: loopring_defs.GetUserPwdResetTxsRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userPwdResetTxs: loopring_defs.UserPwdResetTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_PWD_RESET_TXS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const userPwdResetTxs: loopring_defs.UserPwdResetTx[] = raw_data.transactions
return {
totalNum: raw_data.totalNum,
userPwdResetTxs,
raw_data,
}
}
/*
* Returns user's Ether and token balances on exchange.
*/
public async getUserBalances(
request: loopring_defs.GetUserBalancesRequest,
apiKey: string,
): Promise<{
raw_data: R
userBalances: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_EXCHANGE_BALANCES,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const userBalances: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.UserBalanceInfo) => {
userBalances[item.tokenId] = item
})
}
return {
userBalances,
raw_data,
}
}
public async getAssetLookRecords(
request: loopring_defs.GetUserBalancesRequest,
apiKey: string,
): Promise<{
raw_data: R
userBalances: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_ASSET_LOCK_RECORDS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const userBalances: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.UserBalanceInfo) => {
userBalances[item.tokenId] = item
})
}
return {
userBalances,
raw_data,
}
}
/*
* Returns user's deposit records.
*/
public async getUserDepositHistory(
request: loopring_defs.GetUserDepositHistoryRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userDepositHistory: loopring_defs.UserDepositHistoryTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_DEPOSITS_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userDepositHistory: raw_data.transactions as loopring_defs.UserDepositHistoryTx[],
raw_data,
}
}
/*
* Get user onchain withdrawal history.
*/
public async getUserOnchainWithdrawalHistory(
request: loopring_defs.GetUserOnchainWithdrawalHistoryRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userOnchainWithdrawalHistory: loopring_defs.UserOnchainWithdrawalHistoryTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.WITHDRAWALS_ACTION,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userOnchainWithdrawalHistory:
raw_data.transactions as loopring_defs.UserOnchainWithdrawalHistoryTx[],
raw_data,
}
}
/*
* Get user transfer list.
*/
public async getUserTransferList(
request: loopring_defs.GetUserTransferListRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userTransfers: loopring_defs.UserTransferRecord[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_TRANSFERS_LIST,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userTransfers: raw_data.transactions as loopring_defs.UserTransferRecord[],
raw_data,
}
}
/*
* Get user txs
*/
public async getUserTxs(
request: loopring_defs.GetUserTxsRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userTxs: loopring_defs.UserTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_TXS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
const userTxs: loopring_defs.UserTx[] = []
if (raw_data?.transactions instanceof Array) {
raw_data.transactions.forEach((item: loopring_defs.UserTx) => {
userTxs.push(item)
})
}
return {
totalNum: raw_data?.totalNum,
userTxs,
raw_data,
}
}
/*
* Get user trade history
*/
public async getUserTrades(
request: loopring_defs.GetUserTradesRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userTrades: loopring_defs.UserTrade[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_TRADE_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo && raw_data?.resultInfo.code) {
return {
...raw_data?.resultInfo,
}
}
const userTrades: loopring_defs.UserTrade[] = []
if (raw_data?.trades instanceof Array) {
raw_data.trades.forEach((item: any[]) => {
userTrades.push({
tradeTime: item[0],
tradeId: item[1],
side: item[2],
volume: item[3],
price: item[4],
market: item[5],
fee: item[6],
type: item[13],
})
})
}
return {
totalNum: raw_data.totalNum,
userTrades,
raw_data,
}
}
/*
* deprecated
* Returns the fee rate of users placing orders in specific markets
*/
public async getUserFeeRate(
request: loopring_defs.GetUserFeeRateRequest,
apiKey: string,
): Promise<{
raw_data: R
userFreeRateMap: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_FEE_RATE,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const userFreeRateMap: loopring_defs.LoopringMap = {}
if (raw_data instanceof Array) {
raw_data.forEach((item: loopring_defs.UserFeeRateInfo) => {
userFreeRateMap[item.symbol] = item
})
}
return {
userFreeRateMap,
raw_data,
}
}
/*
* Returns the user order fee rate of users placing orders in specific markets
*/
public async getUserOrderFeeRate(
request: loopring_defs.GetUserOrderFeeRateRequest,
apiKey: string,
): Promise<{
raw_data: R
feeRate: loopring_defs.FeeRateInfo
gasPrice: number
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_ORDER_FEE_RATE,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const gasPrice = parseInt(raw_data.gasPrice)
return {
feeRate: raw_data.feeRate as loopring_defs.FeeRateInfo,
gasPrice,
raw_data,
}
}
/*
* Query current token minimum amount to place order based on users VIP level and max fee bips
*/
public async getMinimumTokenAmt(
request: loopring_defs.GetMinimumTokenAmtRequest,
apiKey: string,
): Promise<{
raw_data: R
amounts: [loopring_defs.TokenAmount, loopring_defs.TokenAmount]
amountMap: loopring_defs.LoopringMap
gasPrice: number
cacheOverdueAt: any
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_MINIMUM_TOKEN_AMT,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const gasPrice = parseInt(raw_data.gasPrice)
const amounts: [loopring_defs.TokenAmount, loopring_defs.TokenAmount] = raw_data?.amounts
const amountMap: loopring_defs.LoopringMap = {}
if (amounts instanceof Array) {
amounts.forEach((item: loopring_defs.TokenAmount) => {
amountMap[item.tokenSymbol] = item
})
}
return {
amounts,
amountMap,
gasPrice,
cacheOverdueAt: raw_data.cacheOverdueAt,
raw_data,
}
}
/*
* Query current fee amount
*/
public async getOffchainFeeAmt(
request: loopring_defs.GetOffchainFeeAmtRequest,
apiKey: string,
): Promise<{
raw_data: R
fees: loopring_defs.LoopringMap
gasPrice: number
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_OFFCHAIN_FEE_AMT,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const gasPrice = parseInt(raw_data.gasPrice)
const fees: loopring_defs.LoopringMap = {}
if (raw_data?.fees instanceof Array) {
raw_data.fees.forEach((item: loopring_defs.OffchainFeeInfo) => {
fees[item.token] = item
})
}
return {
fees,
gasPrice,
raw_data,
}
}
/*
* Query current NFTAction fee amount
*/
public async getNFTOffchainFeeAmt(
request: loopring_defs.GetNFTOffchainFeeAmtRequest,
apiKey: string,
): Promise<{
raw_data: R
fees: loopring_defs.LoopringMap
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NFT_OFFCHAIN_FEE_AMT,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const fees: loopring_defs.LoopringMap = {}
if (raw_data?.fees instanceof Array) {
raw_data.fees.forEach((item: loopring_defs.OffchainFeeInfo) => {
fees[item.token] = item
})
}
return {
fees,
raw_data,
}
}
/*
* Submit NFTAction Validate Order request
*/
public async submitNFTValidateOrder(
req: loopring_defs.OriginNFTValidateOrderRequestV3WithPatch,
): Promise<
(Omit & { raw_data: Omit }) | loopring_defs.RESULT_INFO
> {
const { request, eddsaKey, apiKey } = req
request.eddsaSignature = sign_tools.get_EddsaSig_NFT_Order(request, eddsaKey).result
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_NFT_VALIDATE_ORDER,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
// myLog("NFTAction Validate Order request", request);
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
/*
* Submit NFTAction Trade request
*/
public async submitNFTTrade(
req: loopring_defs.OriginNFTTradeRequestV3WithPatch,
): Promise<
(Omit & { raw_data: Omit }) | loopring_defs.RESULT_INFO
> {
const { request, apiKey, eddsaKey } = req
const dataToSig: Map = new Map()
dataToSig.set('maker', request.maker)
dataToSig.set('makerFeeBips', request.makerFeeBips)
dataToSig.set('taker', request.taker)
dataToSig.set('takerFeeBips', request.takerFeeBips)
// request.eddsaSignature = sign_tools.get_EddsaSig_Transfer(
// request,
// eddsaKey
// );
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.POST_NFT_TRADE,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
// sigPatch: loopring_defs.SigPatchField.EddsaSignature,
PrivateKey: eddsaKey,
},
}
// myLog("NFTAction Trade request", request);
const raw_data = (await this.makeReq().request(reqParams)).data
return this.returnTxHash(raw_data)
}
async getUserOwenCollection(
request: loopring_defs.GetUserOwnerCollectionRequest,
apiKey: string,
) {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NFT_COLLECTION,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data != null && raw_data.resultInfo && raw_data != null && raw_data.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
totalNum: raw_data == null ? void 0 : raw_data.totalNum,
collections: raw_data.collections.map(({ collection, ...rest }: any) => {
return {
...collection,
extends: {
...rest,
},
}
}) as loopring_defs.CollectionMeta & { extends: { [a: string]: any } }[],
raw_data,
}
}
async getUserLegacyCollection(
request: loopring_defs.GetUserLegacyCollectionRequest,
apiKey: string,
) {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NFT_LEGACY_COLLECTION,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data != null && raw_data.resultInfo && raw_data != null && raw_data.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
totalNum: raw_data == null ? void 0 : raw_data.totalNum,
collections: raw_data.collections.map(({ collection, ...rest }: any) => {
return {
...collection,
extends: {
...rest,
},
}
}) as loopring_defs.CollectionMeta & { extends: { [a: string]: any } }[],
raw_data,
}
}
async getUserNFTCollection(request: loopring_defs.GetUserNFTCollectionRequest, apiKey: string) {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NFT_COLLECTION_HASNFT,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data != null && raw_data.resultInfo && raw_data != null && raw_data.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
totalNum: raw_data == null ? void 0 : raw_data.totalNum,
collections: raw_data.collections.map(({ collection, ...rest }: any) => {
return {
...collection,
extends: {
...rest,
},
}
}) as loopring_defs.CollectionMeta & { extends: { [a: string]: any } }[],
raw_data,
}
}
async getUserNFTLegacyTokenAddress(request: { accountId: number }, apiKey: string) {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NFT_LEGACY_TOKENADDRESS,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data != null && raw_data.resultInfo && raw_data != null && raw_data.resultInfo.code) {
return {
...raw_data.resultInfo,
}
}
return {
result: raw_data.addresses,
raw_data,
}
}
/*
* Returns User NFTAction deposit records.
*/
public async getUserNFTDepositHistory(
request: loopring_defs.GetUserNFTDepositHistoryRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTDepositHistory: loopring_defs.UserNFTDepositHistoryTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_DEPOSIT_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userNFTDepositHistory: raw_data.deposits as loopring_defs.UserNFTDepositHistoryTx[],
raw_data,
}
}
/*
* Get User NFTAction Withdrawal History.
*/
public async getUserNFTWithdrawalHistory(
request: loopring_defs.GetUserNFTWithdrawalHistoryRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTWithdrawalHistory: loopring_defs.UserNFTWithdrawalHistoryTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_WITHDRAW_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userNFTWithdrawalHistory: raw_data.withdrawals as loopring_defs.UserNFTWithdrawalHistoryTx[],
raw_data,
}
}
/*
* Get user NFTAction transfer list.
*/
public async getUserNFTTransferHistory(
request: loopring_defs.GetUserNFTTransferHistoryRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTTransfers: loopring_defs.UserNFTTransferHistoryTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_TRANSFER_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userNFTTransfers: raw_data.transfers as loopring_defs.UserNFTTransferHistoryTx[],
raw_data,
}
}
/**
* Get user NFTAction Mint list.
* @param request
* @param apiKey
*/
public async getUserNFTMintHistory(
request: loopring_defs.GetUserNFTMintHistoryRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTMints: loopring_defs.UserNFTMintHistoryTx[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_MINT_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
totalNum: raw_data?.totalNum,
userNFTMints: raw_data.transfers as loopring_defs.UserNFTMintHistoryTx[],
raw_data,
}
}
/*
* Get user All NFTAction Transaction list.
*
*/
public async getUserNFTTransactionHistory(
request: loopring_defs.GetUserNFTTxsRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTTxs: loopring_defs.UserNFTTxsHistory[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_TRANSACTION_HISTORY,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
if (
request.metadata === true &&
raw_data.transactions.length
// raw_data.transactions.metadata &&
// raw_data.transactions.metadata.nftId &&
// raw_data.transactions.metadata.nftId.startsWith("0x")
) {
raw_data.transactions = raw_data.transactions.reduce(
(prev: loopring_defs.UserNFTTxsHistory[], item: loopring_defs.UserNFTTxsHistory) => {
if (item.metadata && item.metadata.nftId && item.metadata.nftId.startsWith('0x')) {
const hashBN = new BN(item.metadata.nftId.replace('0x', ''), 16)
item.metadata.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
return [...prev, item]
},
[],
)
// const hashBN = new BN(raw_data.transactions.metadata.nftId.replace("0x", ""), 16);
// raw_data.transactions.metadata.nftId= "0x" + hashBN.toString("hex").padStart(64, "0");
}
return {
totalNum: raw_data?.totalNum,
userNFTTxs: raw_data.transactions as loopring_defs.UserNFTTxsHistory[],
raw_data,
}
}
public async getUserNFTTradeHistory(
request: loopring_defs.GetUserNFTTradeRequest,
apiKey: string,
): Promise<
| {
raw_data: R
totalNum: number
trades: loopring_defs.UserNFTTradeHistory[]
}
| loopring_defs.RESULT_INFO
> {
const reqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_TRADE_HISTORY,
queryParams: { ...request },
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data != null && raw_data.resultInfo) {
return {
...raw_data.resultInfo,
}
}
let trades = raw_data.trades
return {
totalNum: raw_data?.totalNum,
trades,
raw_data,
}
}
public async SetReferrer(
request: loopring_defs.SetReferrerRequest,
eddsaKey: string,
): Promise<{ raw_data: R; result: any }> {
const dataToSig: Map = new Map()
dataToSig.set('address', request.address)
dataToSig.set('promotionCode', request.promotionCode)
dataToSig.set('publicKeyX', request.publicKeyX)
dataToSig.set('publicKeyY', request.publicKeyY)
dataToSig.set('referrer', request.referrer)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.SET_REFERRER,
bodyParams: request,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey,
},
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
result: raw_data?.result,
raw_data,
}
}
// Get users NFTAction balance, besides amount, it also includes tokenId and nftData
public async getUserNFTBalances(
request: loopring_defs.GetUserNFTBalancesRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTBalances: loopring_defs.UserNFTBalanceInfo[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_BALANCES,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
if (raw_data.data.length) {
raw_data.data = raw_data.data.reduce(
(prev: loopring_defs.UserNFTBalanceInfo[], item: loopring_defs.UserNFTBalanceInfo) => {
if (item.nftId && item.nftId.startsWith('0x')) {
const hashBN = new BN(item.nftId.replace('0x', ''), 16)
item.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
if (
request.metadata === true &&
item.metadata &&
item.metadata.nftId &&
item.metadata.nftId.startsWith('0x')
) {
// const hashBN = new BN(item.metadata.nftId.replace("0x", ""), 16);
item.metadata.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
}
return [...prev, item]
},
[],
)
// const hashBN = new BN(raw_data.transactions.metadata.nftId.replace("0x", ""), 16);
// raw_data.transactions.metadata.nftId= "0x" + hashBN.toString("hex").padStart(64, "0");
}
// if (raw_data.data.nftId && raw_data.data.nftId.startsWith("0x")) {
// const hashBN = new BN(raw_data.data.nftId.replace("0x", ""), 16);
// raw_data.data.nftId = "0x" + hashBN.toString("hex").padStart(64, "0");
// }
return {
totalNum: raw_data?.totalNum,
userNFTBalances: raw_data.data as loopring_defs.UserNFTBalanceInfo[],
raw_data,
}
}
public async getUserNFTBalancesByCollection(
request: loopring_defs.GetUserNFTBalancesByCollectionRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTBalances: loopring_defs.UserNFTBalanceInfo[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_NFT_BALANCES_BY_COLLECTION,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
if (raw_data.data.length) {
raw_data.data = raw_data.data.reduce(
(prev: loopring_defs.UserNFTBalanceInfo[], item: loopring_defs.UserNFTBalanceInfo) => {
if (item.nftId && item.nftId.startsWith('0x')) {
const hashBN = new BN(item.nftId.replace('0x', ''), 16)
item.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
if (
request.metadata === true &&
item.metadata &&
item.metadata.nftId &&
item.metadata.nftId.startsWith('0x')
) {
// const hashBN = new BN(item.metadata.nftId.replace("0x", ""), 16);
item.metadata.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
}
return [...prev, item]
},
[],
)
// const hashBN = new BN(raw_data.transactions.metadata.nftId.replace("0x", ""), 16);
// raw_data.transactions.metadata.nftId= "0x" + hashBN.toString("hex").padStart(64, "0");
}
// if (raw_data.data.nftId && raw_data.data.nftId.startsWith("0x")) {
// const hashBN = new BN(raw_data.data.nftId.replace("0x", ""), 16);
// raw_data.data.nftId = "0x" + hashBN.toString("hex").padStart(64, "0");
// }
return {
totalNum: raw_data?.totalNum,
userNFTBalances: raw_data.data as loopring_defs.UserNFTBalanceInfo[],
raw_data,
}
}
public async getUserNFTLegacyBalance(
request: loopring_defs.GetUserNFTLegacyBalanceRequest,
apiKey: string,
): Promise<{
raw_data: R
totalNum: number
userNFTBalances: loopring_defs.UserNFTBalanceInfo[]
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_NFT_LEGACY_BALANCE,
queryParams: request,
apiKey,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
if (raw_data.data.length) {
raw_data.data = raw_data.data.reduce(
(prev: loopring_defs.UserNFTBalanceInfo[], item: loopring_defs.UserNFTBalanceInfo) => {
if (item.nftId && item.nftId.startsWith('0x')) {
const hashBN = new BN(item.nftId.replace('0x', ''), 16)
item.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
if (
request.metadata === true &&
item.metadata &&
item.metadata.nftId &&
item.metadata.nftId.startsWith('0x')
) {
// const hashBN = new BN(item.metadata.nftId.replace("0x", ""), 16);
item.metadata.nftId = '0x' + hashBN.toString('hex').padStart(64, '0')
}
}
return [...prev, item]
},
[],
)
// const hashBN = new BN(raw_data.transactions.metadata.nftId.replace("0x", ""), 16);
// raw_data.transactions.metadata.nftId= "0x" + hashBN.toString("hex").padStart(64, "0");
}
// if (raw_data.data.nftId && raw_data.data.nftId.startsWith("0x")) {
// const hashBN = new BN(raw_data.data.nftId.replace("0x", ""), 16);
// raw_data.data.nftId = "0x" + hashBN.toString("hex").padStart(64, "0");
// }
return {
totalNum: raw_data?.totalNum,
userNFTBalances: raw_data.data as loopring_defs.UserNFTBalanceInfo[],
raw_data,
}
}
public async getUserVIPAssets(
request: loopring_defs.getUserVIPAssetsRequest,
): Promise<{ raw_data: { data: R }; vipAsset: R }> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_VIP_ASSETS,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
return {
vipAsset: raw_data.data ? raw_data.data : raw_data,
raw_data,
}
}
public async getUserVIPInfo(
request: loopring_defs.GetUserVIPInfoRequest,
apiKey: string,
): Promise<{
raw_data: R
vipInfo: {
createdAt: number
validTo: string
org: any
vipTag: any
}
}> {
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.GET_USER_VIP_INFO,
queryParams: request,
method: loopring_defs.ReqMethod.GET,
apiKey: apiKey,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
}
const raw_data = (await this.makeReq().request(reqParams)).data
if (raw_data?.resultInfo) {
return {
...raw_data?.resultInfo,
}
}
const vipInfo = {
createdAt: raw_data.created_at,
validTo: raw_data.valid_to,
org: raw_data.org,
vipTag: raw_data.vip_tag,
}
return {
vipInfo,
raw_data,
}
}
public async unLockAccount(
{
keyPair,
request,
}: {
keyPair: KeyPairParams
request: loopring_defs.GetUserApiKeyRequest
},
publicKey: { x: string; y: string } | undefined = undefined,
): Promise<
| AxiosResponse
| loopring_defs.RESULT_INFO
| {
raw_data: R
eddsaKey: {
keyPair: object
formatedPx: string
formatedPy: string
sk: string
counterFactualInfo: loopring_defs.CounterFactualInfo
}
apiKey: string
}
> {
let eddsaKey
try {
eddsaKey = await generateKeyPair(keyPair, publicKey)
} catch (error) {
throw error
}
if (eddsaKey) {
const dataToSig: Map = sortObjDictionary(request)
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.API_KEY_ACTION,
queryParams: request,
bodyParams: request,
method: loopring_defs.ReqMethod.GET,
sigFlag: loopring_defs.SIG_FLAG.EDDSA_SIG,
sigObj: {
dataToSig,
PrivateKey: eddsaKey.sk,
},
}
let raw_data
try {
raw_data = (await this.makeReq().request(reqParams)).data
} catch (error) {
throw error as AxiosResponse
}
if (raw_data?.resultInfo) {
throw {
...raw_data?.resultInfo,
}
} else {
return {
apiKey: raw_data.apiKey,
raw_data,
eddsaKey,
}
}
} else {
throw {
code: loopring_defs.LoopringErrorCode.NO_EDDSA_KEY,
message: loopring_defs.ConnectorError.NO_EDDSA_KEY,
}
}
}
/*
* Submit offchain withdraw request
*/
public async submitOffchainWithdraw(
req: loopring_defs.OffChainWithdrawalRequestV3WithPatch,
options?: { accountId?: number; counterFactualInfo?: any },
): Promise<
(Omit & { raw_data: Omit }) | loopring_defs.RESULT_INFO
> {
const { request, web3, chainId, walletType, eddsaKey, apiKey, isHWAddr: isHWAddrOld } = req
const { accountId, counterFactualInfo }: any = options ? options : { accountId: 0 }
const isHWAddr = !!isHWAddrOld
let ecdsaSignature = undefined
try {
ecdsaSignature = await sign_tools.offchainWithdrawWrap({
withdraw: request,
chainId,
web3,
isHWAddr,
accountId,
counterFactualInfo,
})
// ecdsaSignature += isHWAddr ? SigSuffix.Suffix03 : SigSuffix.Suffix02
} catch (error) {
throw error
}
request.eddsaSignature = sign_tools.get_EddsaSig_OffChainWithdraw(request, eddsaKey).result
if (counterFactualInfo) {
request.counterFactualInfo = counterFactualInfo
}
const reqParams: loopring_defs.ReqParams = {
url: loopring_defs.LOOPRING_URLs.WITHDRAWALS_ACTION,
bodyParams: request,
apiKey,
method: loopring_defs.ReqMethod.POST,
sigFlag: loopring_defs.SIG_FLAG.NO_SIG,
ecdsaSignature,
}
let raw_data
try {
raw_data = (await this.makeReq().request(reqParams)).data
} catch (error) {
throw error as AxiosResponse
}
return this.returnTxHash(raw_data)
}
/*
* Submit Internal Transfer request
*/
public async submitInternalTransfer(
req: loopring_defs.OriginTransferRequestV3WithPatch,
options?: { accountId?: number; counterFactualInfo?: any },
): Promise<
(Omit