├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── javascript-sdk.iml ├── modules.xml └── vcs.xml ├── .npmignore ├── .prettierrc ├── LICENSE ├── Makefile ├── README.md ├── docs └── Protobuf.md ├── example.env ├── example ├── cdp.ts ├── hard.ts ├── query_auctions.ts ├── query_cdps.ts ├── query_swaps.ts ├── static │ └── env.ts ├── swap.ts ├── swap_incoming.ts ├── swap_outgoing.ts └── transfer.ts ├── package-lock.json ├── package.json ├── src ├── client │ ├── hard │ │ └── index.ts │ ├── index.ts │ └── swap │ │ └── index.ts ├── crypto │ └── index.ts ├── index.test.ts ├── index.ts ├── msg │ ├── cosmos │ │ ├── index.test.ts │ │ └── index.ts │ ├── earn │ │ └── index.ts │ ├── evmutil │ │ └── index.ts │ ├── hard │ │ └── index.ts │ ├── index.ts │ ├── kava │ │ └── index.ts │ ├── liquid │ │ └── index.ts │ ├── router │ │ └── index.ts │ ├── savings │ │ └── index.ts │ └── swap │ │ └── index.ts ├── tx │ └── index.ts ├── types │ ├── Address.ts │ ├── Coin.ts │ ├── DenomToClaim.ts │ ├── Message.ts │ ├── Strategy.ts │ ├── VoteType.ts │ ├── Wallet.ts │ └── index.ts └── utils │ └── index.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "ecmaVersion": 2020, 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["@typescript-eslint"], 13 | "rules": {} 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # build dir 5 | lib 6 | src/proto 7 | .build 8 | 9 | .eslintcache 10 | 11 | # Environment configuration for running examples 12 | .env -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 17 | 18 | 26 | 27 | 30 | 31 | 38 | 39 | 46 | 47 | 54 | 55 | 60 | 61 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/javascript-sdk.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | 4 | # misc 5 | package-lock.json 6 | 7 | # examples 8 | example -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT_DIR := $(patsubst %/,%,$(dir $(abspath $(lastword $(MAKEFILE_LIST))))) 2 | BUILD_DIR := $(ROOT_DIR)/.build 3 | OUT_DIR := $(ROOT_DIR)/src/proto 4 | 5 | KAVA_TAG ?= release/v0.24.x 6 | KAVA_PROTO_DIR = $(BUILD_DIR)/kava/proto 7 | KAVA_THIRD_PARTY_PROTO_DIR = $(BUILD_DIR)/kava/third_party/proto 8 | 9 | NPM_BIN_DIR := $(ROOT_DIR)/node_modules/.bin 10 | TS_PROTO_PLUGIN_PATH := $(NPM_BIN_DIR)/protoc-gen-ts_proto 11 | 12 | .PHONY: all 13 | all: proto-deps proto-gen 14 | 15 | .PHONY: proto-deps 16 | proto-deps: clean 17 | mkdir -p $(BUILD_DIR) && \ 18 | cd $(BUILD_DIR) && \ 19 | git clone https://github.com/kava-labs/kava.git --no-checkout --depth 1 --filter=blob:none --sparse && \ 20 | cd $(BUILD_DIR)/kava && \ 21 | git sparse-checkout init &&\ 22 | git sparse-checkout add proto third_party && \ 23 | git fetch origin $(KAVA_TAG) --depth 1 && \ 24 | git checkout FETCH_HEAD 25 | 26 | .PHONY: proto-gen 27 | proto-gen: 28 | mkdir -p $(OUT_DIR) && \ 29 | protoc \ 30 | --plugin="protoc-gen-ts_proto=$(TS_PROTO_PLUGIN_PATH)" \ 31 | --ts_proto_out="$(OUT_DIR)" \ 32 | --ts_proto_opt="esModuleInterop=true,forceLong=long,useOptionals=messages,useExactTypes=false" \ 33 | --proto_path="$(KAVA_PROTO_DIR)" \ 34 | --proto_path="$(KAVA_THIRD_PARTY_PROTO_DIR)" \ 35 | $(shell find $(KAVA_PROTO_DIR) $(KAVA_THIRD_PARTY_PROTO_DIR) -path -prune -o -name '*.proto' -print0 | xargs -0) 36 | 37 | .PHONY: clean 38 | clean: 39 | rm -rf $(BUILD_DIR) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kava JavaScript SDK 2 | 3 | The Kava JavaScript SDK allows browsers and node.js clients to interact with Kava. Core functionality and query examples are in the `examples` folder. 4 | 5 | - 🟡 client - client that implements Kava transactions and messages. 6 | - 🟡 tx - Kava transaction types. 7 | - 🟢 msg - Kava message types. 8 | - 🟡 crypto - core cryptographic functions. 9 | - 🟢 utils - utility functions such as client-side secret generation. 10 | 11 | ## Proceed with Caution 12 | 13 | Due to limited resources on our team, some parts of the SDK are better maintained than others. 14 | 15 | - 🟡 Modules marked yellow are partially maintained and may be only partially functional. 16 | - 🟢 Modules marked green are best maintained and most reliable. Functionality should be up-to-date and functional in the latest stable or beta release. 17 | 18 | We welcome outside contributions to help keep the SDK as useful and up-to-date as possible. 19 | 20 | ## Installation 21 | 22 | Install the package via npm. 23 | 24 | ```bash 25 | npm install @kava-labs/javascript-sdk 26 | ``` 27 | 28 | ## Examples 29 | 30 | Examples are still being updated to TypeScript, but can be run with the proper env config and typechecking disabled. 31 | 32 | ## Network Information 33 | 34 | ### Mainnet 35 | 36 | - Chain ID: kava-9 37 | - REST API endpoint: https://api.kava.io 38 | - Binance Chain mainnet REST API endpoint: https://dex.binance.org/ 39 | 40 | ### Testnet 41 | 42 | - Chain ID: kava-testnet-14000 43 | - REST API endpoint: https://api.testnet.kava.io 44 | - Binance Chain testnet REST API endpoint: https://testnet-dex.binance.org/ 45 | 46 | ### Binance Chain 47 | 48 | - Chain ID: Binance-Chain-Tigris 49 | - Binance Chain mainnet REST API endpoint: https://dex.binance.org/ 50 | 51 | ### Deputy Addresses 52 | 53 | **BNB** 54 | 55 | - bnb1jh7uv2rm6339yue8k4mj9406k3509kr4wt5nxn 56 | - kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6 57 | 58 | **BTCB** 59 | 60 | - bnb1xz3xqf4p2ygrw9lhp5g5df4ep4nd20vsywnmpr 61 | - kava14qsmvzprqvhwmgql9fr0u3zv9n2qla8zhnm5pc 62 | 63 | **BUSD** 64 | 65 | - bnb10zq89008gmedc6rrwzdfukjk94swynd7dl97w8 66 | - kava1hh4x3a4suu5zyaeauvmv7ypf7w9llwlfufjmuu 67 | 68 | **XRPB** 69 | 70 | - bnb15jzuvvg2kf0fka3fl2c8rx0kc3g6wkmvsqhgnh 71 | - kava1c0ju5vnwgpgxnrktfnkccuth9xqc68dcdpzpas 72 | 73 | ## Client Setup 74 | 75 | The client requires an address mnemonic and the url of Kava's REST api endpoint. 76 | 77 | ```javascript 78 | const Kava = require('@kava-labs/javascript-sdk'); 79 | 80 | var main = async () => { 81 | const mnemonic = 'secret words that unlock a kava address'; 82 | const testnetUrl = 'https://api.data-testnet-12000.kava.io'; // testnet REST api endpoint 83 | 84 | // Declare a new Kava client, set wallet, and initialize 85 | let client = new Kava.KavaClient(testnetUrl); 86 | client.setWallet(mnemonic); 87 | client.setBroadcastMode('async'); 88 | await client.initChain(); 89 | 90 | // ...transfer coins, bid on an auction, create a CDP, etc. 91 | }; 92 | ``` 93 | 94 | ## Client Usage 95 | 96 | The following selected examples demonstrate basic client usage. Detailed examples can be found in the `examples` directory of the repository. It contains complete code examples for transferring funds from Binance Chain to Kava, opening a CDP, and transferring funds back to Binance Chain. 97 | 98 | ### Transfer coins 99 | 100 | ```javascript 101 | // Import Kava and initialize client... 102 | 103 | // Load coins and transfer to recipient's address 104 | const coins = Kava.utils.formatCoins(1, 'kava'); 105 | const recipient = 'kava1c84ezutjcgrsxarjq5mzsxxz2k9znn94zxmqjz'; 106 | const txHash = await client.transfer(recipient, coins); 107 | 108 | // Check the resulting tx hash 109 | const txRes = await client.checkTxHash(txHash, 15000); // 15 second timeout 110 | console.log('Tx result:', txRes.raw_log); 111 | ``` 112 | 113 | ### Create CDP 114 | 115 | Collateralized debt positions have a minimum value of 10 USD and must be overcollateralized above a certain percentage threshold. Supported collateral coin types, their supply limits, and their minimum overcollateralization ratios can be checked at https://api.data.kava.io/cdp/parameters. 116 | 117 | While USDX has 6 decimals, our example collateral coin BNB has 8. We'll need to apply each asset's conversion factor before sending the transaction. 118 | 119 | ```javascript 120 | const BNB_CONVERSION_FACTOR = 10 ** 8; 121 | const USDX_CONVERSION_FACTOR = 10 ** 6; 122 | 123 | // Apply conversion factor 124 | const principalAmount = 10 * USDX_CONVERSION_FACTOR; 125 | const collateralAmount = 2 * BNB_CONVERSION_FACTOR; 126 | 127 | // Load principal, collateral as formatted coins and set up collateral type 128 | const principal = Kava.utils.formatCoin(principalAmount, 'usdx'); 129 | const collateral = Kava.utils.formatCoin(collateralAmount, 'bnb'); 130 | const collateralType = 'bnb-a'; 131 | 132 | // Send create CDP tx using Kava client 133 | const txHashCDP = await client.createCDP(principal, collateral, collateralType); 134 | console.log('Create CDP tx hash (Kava): '.concat(txHashCDP)); 135 | 136 | // Check the tx hash 137 | const txRes = await client.checkTxHash(txHashCDP, 15000); 138 | console.log('\nTx result:', txRes); 139 | ``` 140 | 141 | ### Transferring funds to Kava 142 | 143 | Kava supports secure transfers of BNB from Binance Chain to Kava and back via atomic swaps. The [bep3-deputy](https://github.com/binance-chain/bep3-deputy) process sits between the two blockchains and services swaps by relaying information back and forth. 144 | 145 | Swaps use a simple secret sharing scheme. A secret random number is generated on the client and hashed with a timestamp in order to create a random number hash that's stored with the swap. The swap can be securely claimed on the opposite chain using the secret random number. Swaps expire after n blocks, a duration that can be modified via the height span parameter. Once expired, the swap can be refunded. 146 | 147 | BEP3 transfer user steps 148 | 149 | 1. Create an atomic swap on Binance Chain (note: atomic swaps are called HTLTs on Binance Chain) 150 | The deputy will automatically relay the swap from Kava to Binance Chain 151 | 2a. Claim the atomic swap on Kava within swap's height span. Users have about 30 minutes to claim a swap after it is created. 152 | 2b. Refund the atomic swap on Kava after the swap's height span - this happens if the swap is not claimed in time. 153 | 154 | ### Create swap 155 | 156 | In order for an address to submit a swap on Kava it must hold pegged bnb tokens. The Binance Chain [docs](https://docs.binance.org/atomic-swap.html) describe how to create a swap on Binance Chain with BNB. When creating the swap on Binance Chain make sure to use the deputy's Binance Chain address as the swap's `recipient` and the deputy's Kava address as the swap's `senderOtherChain` or the deputy will not relay the swap. 157 | 158 | Users create outgoing swaps on Kava by entering the deputy's Kava address in the recipient field. The following example is for the testnet. See full code examples for creating and claiming a swap between Kava and Binance Chain, see `incoming_swap.js` and `outgoing_swap.js` in the examples folder. 159 | 160 | ```javascript 161 | // Import utils 162 | const utils = kava.utils; 163 | 164 | // Declare addresses involved in the swap 165 | const recipient = 'kava1tfvn5t8qwngqd2q427za2mel48pcus3z9u73fl'; // deputy's address on kava testnet 166 | const recipientOtherChain = 'tbnb1hc0gvpxgw78ky9ay6xfql8jw9lry9ftc5g7ddj'; // user's address on bnbchain testnet 167 | const senderOtherChain = 'tbnb1mdvtph9y0agm4nx7dcl86t7nuvt5mtcul8zld6'; // deputy's address on bnbchain testnet 168 | 169 | // Set up swap parameters 170 | const amount = 1000000; 171 | const asset = 'bnb'; 172 | const coins = utils.formatCoins(amount, asset); 173 | const heightSpan = '250'; 174 | 175 | // Generate random number hash from timestamp and hex-encoded random number 176 | const randomNumber = utils.generateRandomNumber(); 177 | const timestamp = Math.floor(Date.now() / 1000); 178 | const randomNumberHash = utils.calculateRandomNumberHash( 179 | randomNumber, 180 | timestamp 181 | ); 182 | console.log('\nSecret random number:', randomNumber.toUpperCase()); 183 | 184 | // Calculate the expected swap ID on Kava 185 | const kavaSwapID = utils.calculateSwapID( 186 | randomNumberHash, 187 | client.wallet.address, 188 | senderOtherChain 189 | ); 190 | console.log('Expected Kava swap ID:', kavaSwapID); 191 | 192 | // Calculate the expected swap ID on Bnbchain 193 | const bnbchainSwapID = utils.calculateSwapID( 194 | randomNumberHash, 195 | senderOtherChain, 196 | client.wallet.address 197 | ); 198 | console.log('Expected Bnbchain swap ID:', bnbchainSwapID); 199 | 200 | // Create the swap 201 | console.log('Sending createSwap transaction...'); 202 | const txHash = await client.createSwap( 203 | recipient, 204 | recipientOtherChain, 205 | senderOtherChain, 206 | randomNumberHash, 207 | timestamp, 208 | coins, 209 | heightSpan 210 | ); 211 | 212 | // Check the claim tx hash 213 | const txRes = await client.checkTxHash(txHash, 15000); 214 | console.log('\nTx result:', txRes.raw_log); 215 | ``` 216 | 217 | Note: swap height span must be within range 220-270, you can check the current mainet BEP3 module parameters at https://api.data.kava.io/bep3/parameters. 218 | 219 | ### Claim swap 220 | 221 | Only active swaps can be claimed. Anyone can send the claim request, but funds will only be released to the intended recipient if the secret random number matches the random number hash. A successful claim sends funds exclusively to the intended recipient's address. 222 | 223 | ```javascript 224 | // Use the secret random number from swap creation 225 | const randomNumber = 226 | 'e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3'; 227 | const swapID = 228 | 'e897e4ee12b4d6ec4776a5d30300a7e3bb1f62b0c49c3e05ad2e6aae1279c940'; 229 | 230 | const txHash = await client.claimSwap(swapID, randomNumber); 231 | ``` 232 | 233 | ### Refund swap 234 | 235 | Only expired swaps can be refunded. Anyone can send the refund request, but funds are always returned to the swap's original creator. 236 | 237 | ```javascript 238 | const swapID = 239 | 'e897e4ee12b4d6ec4776a5d30300a7e3bb1f62b0c49c3e05ad2e6aae1279c940'; 240 | 241 | const txHash = await client.refundSwap(swapID); 242 | ``` 243 | 244 | ## Contributing 245 | 246 | Kava is an open source project and contributions to the Kava JavaScript SDK are welcome. If you'd like contribute, please open an issue or pull request. 247 | -------------------------------------------------------------------------------- /docs/Protobuf.md: -------------------------------------------------------------------------------- 1 | # Protobuf 2 | 3 | ## Build 4 | 5 | 1. Run `make` to download the protobuf definitions from Kava 6 | (You can also specify a version, e.g. `KAVA_TAG=v0.16.0 make all`) 7 | 8 | ### Troubleshooting 9 | 10 | 1. `brew install protobuf` 11 | 2. `brew install git` (Makes sure you have the latest version of Git that supports the `sparse` checkout (2.34.1 or later)) 12 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | KAVA_ADDRESS= 2 | KAVA_MNEMONIC= 3 | DEPUTY_ADDRESS= 4 | DEPUTY_MNEMONIC= 5 | BINANCE_ADDRESS= 6 | BINANCE_MNEMONIC= 7 | BINANCE_DEPUTY_ADDRESS= 8 | BINANCE_LOCAL_ENDPOINT= 9 | -------------------------------------------------------------------------------- /example/cdp.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const kavaUtils = require('../src/utils').utils; 3 | const KavaClient = require('../src/client').KavaClient; 4 | 5 | const BNB_CONVERSION_FACTOR = 10 ** 8; 6 | const USDX_CONVERSION_FACTOR = 10 ** 6; 7 | 8 | var main = async () => { 9 | // Start new Kava client 10 | const kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 11 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 12 | kavaClient.setBroadcastMode('async'); 13 | await kavaClient.initChain(); 14 | 15 | // Get minimum principal amount required for CDP creation 16 | const paramsCDP = await kavaClient.getParamsCDP(); 17 | const debtParam = paramsCDP.debt_param; 18 | const principalAmount = Number(debtParam.debt_floor); 19 | console.log('Minimum principal:', principalAmount + 'usdx'); 20 | 21 | // Calculate collateral required for this principal amount 22 | const bnbValuation = await kavaClient.getPrice('bnb:usd'); 23 | const equivalentCollateral = 24 | Number(principalAmount) / Number(bnbValuation.price); 25 | 26 | // Assuming the collateralization ratio is 200%, we'll do 210% 27 | const rawRequiredAmount = equivalentCollateral * 2.1; 28 | const adjustedAmount = 29 | (rawRequiredAmount / USDX_CONVERSION_FACTOR) * BNB_CONVERSION_FACTOR; 30 | const collateralAmount = adjustedAmount.toFixed(0); 31 | console.log('Required collateral:', collateralAmount + 'bnb'); 32 | 33 | // Confirm that our account has sufficient funds 34 | try { 35 | const account = await kavaClient.getAccount(kavaClient.wallet.address); 36 | const coins = account.value.coins; 37 | const bnbBalance = coins.find((coin) => coin.denom == 'bnb').amount; 38 | if (bnbBalance * BNB_CONVERSION_FACTOR < Number(collateralAmount)) { 39 | throw { message: 'Account only has ' + bnbBalance + 'bnb' }; 40 | } 41 | } catch (err) { 42 | console.log('Error:', err.message); 43 | return; 44 | } 45 | 46 | // Load principal, collateral as formatted coins 47 | const principal = kavaUtils.formatCoin(principalAmount, 'usdx'); 48 | const collateral = kavaUtils.formatCoin(collateralAmount, 'bnb'); 49 | const collateralType = 'bnb-a'; 50 | 51 | // Send create CDP tx using Kava client 52 | const txHashCDP = await kavaClient.createCDP( 53 | principal, 54 | collateral, 55 | collateralType 56 | ); 57 | console.log('Create CDP tx hash (Kava): '.concat(txHashCDP)); 58 | 59 | // Check the claim tx hash 60 | const txRes = await kavaClient.checkTxHash(txHashCDP, 15000); 61 | console.log('\nTx result:', txRes); 62 | }; 63 | 64 | main(); 65 | -------------------------------------------------------------------------------- /example/hard.ts: -------------------------------------------------------------------------------- 1 | import { env as Env } from './static/env'; 2 | import { KavaClient } from '../src/client'; 3 | import { utils as kavaUtils } from '../src/utils'; 4 | 5 | var main = async () => { 6 | // Start new Kava client 7 | const kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 8 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 9 | await kavaClient.initChain(); 10 | 11 | // Deposit coins into hard's BNB pool 12 | const despoitCoins = kavaUtils.formatCoins(100, 'bnb'); 13 | const depositRes = await kavaClient.hard.deposit(despoitCoins); 14 | console.log('Deposit tx:', depositRes); 15 | 16 | await sleep(5000); // Wait 5 seconds 17 | 18 | // Withdraw coins from hard's BNB pool 19 | const withdrawCoins = kavaUtils.formatCoins(20, 'bnb'); 20 | const withdrawRes = await kavaClient.hard.withdraw(withdrawCoins); 21 | console.log('Withdraw tx:', withdrawRes); 22 | 23 | await sleep(5000); // Wait 5 seconds 24 | 25 | // Claim hard rewards 26 | const args = { owner: Env.KavaAccount.Testnet.Address, type: 'hard' }; 27 | const claim = await kavaClient.getRewards(args); 28 | if (claim) { 29 | const claimRes = await kavaClient.claimHardLiquidityProviderReward('small'); 30 | console.log('Claim tx:', claimRes); 31 | } 32 | }; 33 | 34 | // Sleep is a wait function 35 | function sleep(ms) { 36 | return new Promise((resolve) => setTimeout(resolve, ms)); 37 | } 38 | 39 | main(); 40 | -------------------------------------------------------------------------------- /example/query_auctions.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const KavaClient = require('../src/client').KavaClient; 3 | 4 | var main = async () => { 5 | // Start new Kava client 6 | kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 7 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 8 | kavaClient.setBroadcastMode('async'); 9 | await kavaClient.initChain(); 10 | 11 | // Query all Auctions 12 | const allAuctions = await kavaClient.getAuctions(); 13 | console.log('All Auctions:', allAuctions); 14 | 15 | // Query only Auctions that meet our filter 16 | const args = { 17 | type: 'collateral', 18 | phase: 'forward', 19 | denom: 'bnb', 20 | owner: 'kava1g0qywkx6mt5jmvefv6hs7c7h333qas5ks63a6t', 21 | page: 1, 22 | limit: 1000, 23 | }; 24 | const filteredAuctions = await kavaClient.getAuctions(args); 25 | console.log('Filtered Auctions:', filteredAuctions); 26 | }; 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /example/query_cdps.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const KavaClient = require('../src/client').KavaClient; 3 | 4 | var main = async () => { 5 | // Start new Kava client 6 | kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 7 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 8 | kavaClient.setBroadcastMode('async'); 9 | await kavaClient.initChain(); 10 | 11 | // Query all CDPs 12 | const allCdps = await kavaClient.getCDPs(); 13 | console.log('All CDPs:', allCdps); 14 | 15 | // Query only CDPs that meet our filter 16 | const args = { 17 | collateral_type: 'bnb-a', 18 | id: '52', 19 | ratio: '3.15', 20 | owner: 'kava1g0qywkx6mt5jmvefv6hs7c7h333qas5ks63a6t', 21 | page: 1, 22 | limit: 1000, 23 | }; 24 | const filteredCDPs = await kavaClient.getCDPs(args); 25 | console.log('Filtered CDPs:', filteredCDPs); 26 | }; 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /example/query_swaps.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const KavaClient = require('../src/client').KavaClient; 3 | 4 | var main = async () => { 5 | // Start new Kava client 6 | kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 7 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 8 | kavaClient.setBroadcastMode('async'); 9 | await kavaClient.initChain(); 10 | 11 | // Query all swaps 12 | const allSwaps = await kavaClient.getSwaps(); 13 | console.log('All swaps:', allSwaps); 14 | 15 | // Query only swaps that meet our filter 16 | const args = { 17 | direction: 'Incoming', 18 | status: 'Completed', 19 | involve: '', 20 | expiration: '1273092', 21 | page: 1, 22 | limit: 1000, 23 | }; 24 | const filteredSwaps = await kavaClient.getSwaps(args); 25 | console.log('Filtered swaps:', filteredSwaps); 26 | }; 27 | 28 | main(); 29 | -------------------------------------------------------------------------------- /example/static/env.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | require('dotenv').config(); 3 | 4 | const KavaAccount = { 5 | Local: { 6 | Address: process.env.KAVA_ADDRESS, 7 | Mnemonic: process.env.KAVA_MNEMONIC, 8 | }, 9 | Testnet: { 10 | Address: '', 11 | Mnemonic: '', 12 | }, 13 | Mainnet: { 14 | Address: '', 15 | Mnemonic: '', 16 | }, 17 | }; 18 | 19 | const KavaEndpoints = { 20 | Local: 'http://localhost:1317', 21 | Testnet: 'https://kava-testnet-8000.kava.io', 22 | Mainnet: 'https://kava3.data.kava.io', 23 | }; 24 | 25 | const KavaDeputy = { 26 | Local: { 27 | Address: process.env.DEPUTY_ADDRESS, 28 | Mnemonic: process.env.DEPUTY_MNEMONIC, 29 | }, 30 | Testnet: 'kava1tfvn5t8qwngqd2q427za2mel48pcus3z9u73fl', 31 | Mainnet: 'kava1r4v2zdhdalfj2ydazallqvrus9fkphmglhn6u6', 32 | }; 33 | 34 | const BinanceAccount = { 35 | Local: { 36 | Address: process.env.BINANCE_ADDRESS, 37 | Mnemonic: process.env.BINANCE_MNEMONIC, 38 | }, 39 | Testnet: { 40 | Address: '', 41 | Mnemonic: '', 42 | }, 43 | Mainnet: { 44 | Address: '', 45 | Mnemonic: '', 46 | }, 47 | }; 48 | 49 | const BinanceEndpoints = { 50 | Local: process.env.BINANCE_LOCAL_ENDPOINT, 51 | Testnet: 'https://testnet-dex.binance.org', 52 | Mainnet: 'https://dex.binance.org/', 53 | }; 54 | 55 | const BinanceDeputy = { 56 | Local: process.env.BINANCE_DEPUTY_ADDRESS, 57 | Testnet: 'tbnb1et8vmd0dgvswjnyaf73ez8ye0jehc8a7t7fljv', 58 | Mainnet: 'bnb1jh7uv2rm6339yue8k4mj9406k3509kr4wt5nxn', 59 | }; 60 | 61 | export const env = { 62 | KavaAccount, 63 | KavaEndpoints, 64 | KavaDeputy, 65 | BinanceAccount, 66 | BinanceEndpoints, 67 | BinanceDeputy, 68 | }; 69 | -------------------------------------------------------------------------------- /example/swap.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const KavaClient = require('../src/client').KavaClient; 3 | const kavaUtils = require('../src/utils').utils; 4 | 5 | var main = async () => { 6 | // Start new Kava client 7 | kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 8 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 9 | await kavaClient.initChain(); 10 | 11 | // deposit coins to the swap bnb:usdx pool 12 | const tokenA = kavaUtils.formatCoins(20, 'bnb'); 13 | const tokenB = kavaUtils.formatCoins(10, 'usdx'); 14 | const slippage = '0.010000000000000000'; 15 | // create a deadline of 30 seconds from now for the max amount of time to wait 16 | // for the transaction to be processed 17 | const deadline = kavaUtils.calculateUnixTime(30); 18 | const depositRes = await kavaClient.swap.deposit( 19 | tokenA, 20 | tokenB, 21 | slippage, 22 | deadline 23 | ); 24 | console.log('Deposit tx:', depositRes); 25 | 26 | await sleep(5000); // Wait 5 seconds 27 | 28 | // withdraw coins from the swap bnb:usdx pool 29 | const shares = 100; 30 | const minTokenA = kavaUtils.formatCoins(20, 'bnb'); 31 | const minTokenB = kavaUtils.formatCoins(10, 'usdx'); 32 | const deadline = kavaUtils.calculateUnixTime(30); 33 | const withdrawRes = await kavaClient.swap.withdraw(tokenA, tokenB, deadline); 34 | console.log('Withdraw tx:', withdrawRes); 35 | 36 | await sleep(5000); // Wait 5 seconds 37 | 38 | // Attempt a swap to a valid tradable pool with an exact tokenA 39 | // coins to deposit to the pool 40 | const exactTokenA = kavaUtils.formatCoins(100, 'bnb'); 41 | // coins to withdraw from the pool 42 | const tokenB = kavaUtils.formatCoins(10, 'usdx'); 43 | const slippage = '0.010000000000000000'; 44 | const deadline = kavaUtils.calculateUnixTime(30); 45 | 46 | const swapExactForRes = await kavaClient.swap.newMsgSwapExactForTokens( 47 | exactTokenA, 48 | tokenB, 49 | slippage, 50 | deadline 51 | ); 52 | console.log('Swap Exact For tx:', swapExactForRes); 53 | 54 | await sleep(5000); // Wait 5 seconds 55 | 56 | // Attempt a swap to a valid tradable pool with an exact tokenB 57 | // coins to deposit to the pool 58 | const tokenA = kavaUtils.formatCoins(100, 'bnb'); 59 | // coins to withdraw from the pool 60 | const exactTokenB = kavaUtils.formatCoins(10, 'usdx'); 61 | const slippage = '0.010000000000000000'; 62 | const deadline = kavaUtils.calculateUnixTime(30); 63 | 64 | const swapForExactRes = await kavaClient.swap.newMsgSwapForExactTokens( 65 | tokenA, 66 | exactTokenB, 67 | slippage, 68 | deadline 69 | ); 70 | console.log('Swap For Exact tx:', swapForExactRes); 71 | 72 | await sleep(5000); // Wait 5 seconds 73 | 74 | // claim swap rewards 75 | const multiplierName = 'large'; 76 | const denomsToClaim = ['swp']; 77 | 78 | const args = { owner: Env.KavaAccount.Testnet.Address, type: 'swap' }; 79 | const claims = await kavaClient.getRewards(args); 80 | if (claims) { 81 | const claimRes = await kavaClient.claimSwapReward( 82 | multiplierName, 83 | denomsToClaim 84 | ); 85 | console.log('Claim tx:', claimRes); 86 | } 87 | 88 | await sleep(5000); // Wait 5 seconds 89 | }; 90 | 91 | // Sleep is a wait function 92 | function sleep(ms) { 93 | return new Promise((resolve) => setTimeout(resolve, ms)); 94 | } 95 | 96 | main(); 97 | -------------------------------------------------------------------------------- /example/swap_incoming.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const kavaUtils = require('../src/utils').utils; 3 | const KavaClient = require('../src/client').KavaClient; 4 | const BnbApiClient = require('@binance-chain/javascript-sdk'); 5 | const bnbCrypto = BnbApiClient.crypto; 6 | 7 | const BNB_CONVERSION_FACTOR = 10 ** 8; 8 | 9 | var main = async () => { 10 | await incomingSwap(); 11 | }; 12 | 13 | var incomingSwap = async () => { 14 | // Start new Kava client 15 | kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 16 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 17 | kavaClient.setBroadcastMode('async'); 18 | await kavaClient.initChain(); 19 | 20 | // Start Binance Chain client 21 | const bnbClient = await new BnbApiClient(Env.BinanceEndpoints.Testnet); 22 | bnbClient.chooseNetwork('testnet'); 23 | const privateKey = bnbCrypto.getPrivateKeyFromMnemonic( 24 | Env.BinanceAccount.Testnet.Mnemonic 25 | ); 26 | bnbClient.setPrivateKey(privateKey); 27 | await bnbClient.initChain(); 28 | 29 | // ------------------------------------------------------------------------------- 30 | // Binance Chain blockchain interaction 31 | // ------------------------------------------------------------------------------- 32 | // Assets involved in the swap 33 | const asset = 'BNB'; 34 | const amount = 1 * BNB_CONVERSION_FACTOR; 35 | 36 | // Addresses involved in the swap 37 | const sender = Env.BinanceAccount.Testnet.Address; // user's address on Binance Chain 38 | const recipient = Env.BinanceDeputy.Testnet; // deputy's address on Binance Chain 39 | const senderOtherChain = Env.KavaDeputy.Testnet; // deputy's address on Kava 40 | const recipientOtherChain = Env.KavaAccount.Testnet.Address; // user's address on Kava 41 | 42 | // Format asset/amount parameters as tokens, expectedIncome 43 | const tokens = [ 44 | { 45 | denom: asset, 46 | amount: amount, 47 | }, 48 | ]; 49 | const expectedIncome = [String(amount), ':', asset].join(''); 50 | 51 | // Number of blocks that swap will be active 52 | const heightSpan = 10001; 53 | 54 | // Generate random number hash from timestamp and hex-encoded random number 55 | let randomNumber = kavaUtils.generateRandomNumber(); 56 | const timestamp = Math.floor(Date.now() / 1000); 57 | const randomNumberHash = kavaUtils.calculateRandomNumberHash( 58 | randomNumber, 59 | timestamp 60 | ); 61 | console.log('Secret random number:', randomNumber); 62 | 63 | printSwapIDs(randomNumberHash, sender, senderOtherChain); 64 | 65 | // Send create swap tx using Binance Chain client 66 | const res = await bnbClient.swap.HTLT( 67 | sender, 68 | recipient, 69 | recipientOtherChain, 70 | senderOtherChain, 71 | randomNumberHash, 72 | timestamp, 73 | tokens, 74 | expectedIncome, 75 | heightSpan, 76 | true 77 | ); 78 | 79 | if (res && res.status == 200) { 80 | console.log('\nCreate swap tx hash (Binance Chain): ', res.result[0].hash); 81 | } else { 82 | console.log('Tx error:', res); 83 | return; 84 | } 85 | // Wait for deputy to see the new swap on Binance Chain and relay it to Kava 86 | console.log('Waiting for deputy to witness and relay the swap...'); 87 | 88 | // Calculate the expected swap ID on Kava 89 | const expectedKavaSwapID = kavaUtils.calculateSwapID( 90 | randomNumberHash, 91 | senderOtherChain, 92 | sender 93 | ); 94 | 95 | await sleep(45000); // 45 seconds 96 | await kavaClient.getSwap(expectedKavaSwapID); 97 | 98 | // ------------------------------------------------------------------------------- 99 | // Kava blockchain interaction 100 | // ------------------------------------------------------------------------------- 101 | 102 | // Send claim swap tx using Kava client 103 | const txHashClaim = await kavaClient.claimSwap( 104 | expectedKavaSwapID, 105 | randomNumber 106 | ); 107 | console.log('Claim swap tx hash (Kava): '.concat(txHashClaim)); 108 | 109 | // Check the claim tx hash 110 | const txRes = await kavaClient.checkTxHash(txHashClaim, 15000); 111 | console.log('\nTx result:', txRes.raw_log); 112 | }; 113 | 114 | // Print swap IDs 115 | var printSwapIDs = (randomNumberHash, sender, senderOtherChain) => { 116 | // Calculate the expected swap ID on origin chain 117 | const originChainSwapID = kavaUtils.calculateSwapID( 118 | randomNumberHash, 119 | sender, 120 | senderOtherChain 121 | ); 122 | 123 | // Calculate the expected swap ID on destination chain 124 | const destChainSwapID = kavaUtils.calculateSwapID( 125 | randomNumberHash, 126 | senderOtherChain, 127 | sender 128 | ); 129 | 130 | console.log('Expected Bnbchain swap ID:', originChainSwapID); 131 | console.log('Expected Kava swap ID:', destChainSwapID); 132 | }; 133 | 134 | // Sleep is a wait function 135 | function sleep(ms) { 136 | return new Promise((resolve) => setTimeout(resolve, ms)); 137 | } 138 | 139 | main(); 140 | -------------------------------------------------------------------------------- /example/swap_outgoing.ts: -------------------------------------------------------------------------------- 1 | const Env = require('./static/env').env; 2 | const kavaUtils = require('../src/utils').utils; 3 | const KavaClient = require('../src/client').KavaClient; 4 | const BnbApiClient = require('@binance-chain/javascript-sdk'); 5 | const bnbCrypto = BnbApiClient.crypto; 6 | 7 | var main = async () => { 8 | await outgoingSwap(); 9 | }; 10 | 11 | var outgoingSwap = async () => { 12 | // Start new Kava client 13 | kavaClient = new KavaClient(Env.KavaEndpoints.Testnet); 14 | kavaClient.setWallet(Env.KavaAccount.Testnet.Mnemonic); 15 | kavaClient.setBroadcastMode('async'); 16 | await kavaClient.initChain(); 17 | 18 | // Start Binance Chain client 19 | const bnbClient = await new BnbApiClient(Env.BinanceEndpoints.Testnet); 20 | bnbClient.chooseNetwork('testnet'); 21 | const privateKey = bnbCrypto.getPrivateKeyFromMnemonic( 22 | Env.BinanceAccount.Testnet.Mnemonic 23 | ); 24 | bnbClient.setPrivateKey(privateKey); 25 | await bnbClient.initChain(); 26 | 27 | // ------------------------------------------------------------------------------- 28 | // Kava blockchain interaction 29 | // ------------------------------------------------------------------------------- 30 | const sender = Env.KavaAccount.Testnet.Address; // user's address on Binance Chain 31 | const recipient = Env.KavaDeputy.Testnet; // deputy's address on kava 32 | const recipientOtherChain = Env.BinanceAccount.Testnet.Address; // user's address on bnbchain 33 | const senderOtherChain = Env.BinanceDeputy.Testnet; // deputy's address on bnbchain 34 | 35 | // Set up params 36 | const asset = 'bnb'; 37 | const amount = 10000000; 38 | 39 | const coins = kavaUtils.formatCoins(amount, asset); 40 | const heightSpan = '250'; 41 | 42 | // Generate random number hash from timestamp and hex-encoded random number 43 | const randomNumber = kavaUtils.generateRandomNumber(); 44 | const timestamp = Math.floor(Date.now() / 1000); 45 | const randomNumberHash = kavaUtils.calculateRandomNumberHash( 46 | randomNumber, 47 | timestamp 48 | ); 49 | console.log('\nSecret random number:', randomNumber.toUpperCase()); 50 | 51 | printSwapIDs(randomNumberHash, sender, senderOtherChain); 52 | 53 | const txHash = await kavaClient.createSwap( 54 | recipient, 55 | recipientOtherChain, 56 | senderOtherChain, 57 | randomNumberHash, 58 | timestamp, 59 | coins, 60 | heightSpan 61 | ); 62 | 63 | console.log('\nTx hash (Create swap on Kava):', txHash); 64 | 65 | // Wait for deputy to see the new swap on Kava and relay it to Binance Chain 66 | console.log('Waiting for deputy to witness and relay the swap...'); 67 | await sleep(45000); // 45 seconds 68 | 69 | // ------------------------------------------------------------------------------- 70 | // Binance Chain blockchain interaction 71 | // ------------------------------------------------------------------------------- 72 | // Calculate the expected swap ID on Bnbchain 73 | const expectedBnbchainSwapID = kavaUtils.calculateSwapID( 74 | randomNumberHash, 75 | senderOtherChain, 76 | sender 77 | ); 78 | 79 | const res = await bnbClient.swap.claimHTLT( 80 | Env.BinanceAccount.Testnet.Address, 81 | expectedBnbchainSwapID, 82 | randomNumber 83 | ); // Binance-chain 84 | if (res && res.status == 200) { 85 | console.log('Claim swap tx hash (Binance Chain): ', res.result[0].hash); 86 | } else { 87 | console.log('Tx error:', res); 88 | return; 89 | } 90 | }; 91 | 92 | // Print swap IDs 93 | var printSwapIDs = (randomNumberHash, sender, senderOtherChain) => { 94 | // Calculate the expected swap ID on origin chain 95 | const originChainSwapID = kavaUtils.calculateSwapID( 96 | randomNumberHash, 97 | sender, 98 | senderOtherChain 99 | ); 100 | 101 | // Calculate the expected swap ID on destination chain 102 | const destChainSwapID = kavaUtils.calculateSwapID( 103 | randomNumberHash, 104 | senderOtherChain, 105 | sender 106 | ); 107 | 108 | console.log('Expected Kava swap ID:', originChainSwapID.toUpperCase()); 109 | console.log('Expected BnbChain swap ID:', destChainSwapID.toUpperCase()); 110 | }; 111 | 112 | // Sleep is a wait function 113 | function sleep(ms) { 114 | return new Promise((resolve) => setTimeout(resolve, ms)); 115 | } 116 | 117 | main(); 118 | -------------------------------------------------------------------------------- /example/transfer.ts: -------------------------------------------------------------------------------- 1 | import { utils } from '../src/utils'; 2 | import { env } from './static/env'; 3 | import { KavaClient } from '../src/client'; 4 | 5 | const KAVA_CONVERSION_FACTOR = 10 ** 6; 6 | 7 | var main = async () => { 8 | const recipient = 'kava1g0qywkx6mt5jmvefv6hs7c7h333qas5ks63a6t'; 9 | 10 | // Start new Kava client 11 | const kavaClient = new KavaClient(env.KavaEndpoints.Testnet); 12 | kavaClient.setWallet(env.KavaAccount.Testnet.Mnemonic); 13 | kavaClient.setBroadcastMode('async'); 14 | await kavaClient.initChain(); 15 | 16 | // First let's check our account balances 17 | let balances = await kavaClient.getBalances(env.KavaAccount.Testnet.Address); 18 | console.log('Balances:', balances); 19 | // Print our KAVA balance (if we have one) 20 | let kavaBalance = balances?.find( 21 | (item) => item.denom.toUpperCase() === 'UKAVA' 22 | ); 23 | if (kavaBalance) { 24 | console.log( 25 | '\tBalance (kava):', 26 | Number(kavaBalance.amount) / KAVA_CONVERSION_FACTOR 27 | ); 28 | } 29 | 30 | // Transfer 1 kava to recipient's address 31 | const coins = utils.formatCoins(1 * KAVA_CONVERSION_FACTOR, 'ukava'); 32 | const txHash = await kavaClient.transfer(recipient, coins); 33 | console.log('Tx hash:', txHash); 34 | 35 | // Check the resulting tx hash 36 | const txRes = await kavaClient.checkTxHash(txHash, 15000); // 15 seconds 37 | console.log('Tx result:', txRes); 38 | }; 39 | 40 | main(); 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@kava-labs/javascript-sdk", 3 | "version": "14.0.2", 4 | "description": "Supports interaction with the Kava blockchain via a REST api", 5 | "main": "lib/index.js", 6 | "types": "lib/index.d.ts", 7 | "scripts": { 8 | "build": "make clean && make -j1 all && tsc", 9 | "format": "prettier --write \"src/**/*.ts\"", 10 | "postversion": "git push && git push --tags", 11 | "prepare": "npm run build && husky install", 12 | "version": "npm run format && git add -A src", 13 | "check": "tsc -p tsconfig.json" 14 | }, 15 | "dependencies": { 16 | "@cosmjs/proto-signing": "^0.32.4", 17 | "@cosmjs/stargate": "^0.32.4", 18 | "@kava-labs/sig": "^0.1.0", 19 | "axios": "^1.6.5", 20 | "bech32": "^1.1.3", 21 | "big.js": "^5.2.2", 22 | "bip39": "^3.0.2", 23 | "crypto-js": "^4.0.0", 24 | "ethers": "^5.6.2", 25 | "long": "^5.2.0", 26 | "protobufjs": "^6.11.2", 27 | "url": "^0.11.0" 28 | }, 29 | "devDependencies": { 30 | "@binance-chain/javascript-sdk": "^4.2.2", 31 | "@typescript-eslint/eslint-plugin": "^5.6.0", 32 | "@typescript-eslint/parser": "^5.6.0", 33 | "dotenv": "^14.3.0", 34 | "eslint": "^8.4.1", 35 | "husky": "^7.0.1", 36 | "jest": "^27.3.1", 37 | "lint-staged": "^11.1.1", 38 | "prettier": "2.0.5", 39 | "ts-jest": "^27.0.7", 40 | "ts-proto": "^1.150.0", 41 | "typescript": "^4.9.3" 42 | }, 43 | "lint-staged": { 44 | "*.{js,jsx,ts,tsx}": "eslint --cache --fix", 45 | "*.{js,ts,jsx,tsx,css,md,yml,json,html}": "prettier --write" 46 | }, 47 | "husky": { 48 | "hooks": { 49 | "pre-commit": "lint-staged" 50 | } 51 | }, 52 | "jest": { 53 | "preset": "ts-jest", 54 | "testEnvironment": "node" 55 | }, 56 | "repository": { 57 | "type": "git", 58 | "url": "git+https://github.com/Kava-Labs/javascript-sdk.git" 59 | }, 60 | "author": "Kava Labs", 61 | "license": "Apache-2.0", 62 | "bugs": { 63 | "url": "https://github.com/Kava-Labs/javascript-sdk/issues" 64 | }, 65 | "homepage": "https://github.com/Kava-Labs/javascript-sdk#readme" 66 | } 67 | -------------------------------------------------------------------------------- /src/client/hard/index.ts: -------------------------------------------------------------------------------- 1 | import { tx } from '../../tx'; 2 | import { msg } from '../../msg'; 3 | import { KavaClient } from '..'; 4 | import { Coin } from '../../types/Coin'; 5 | 6 | const DEFAULT_GAS = 300000; 7 | 8 | const api = { 9 | getParams: '/hard/parameters', 10 | getModAccounts: '/hard/accounts', 11 | getDeposits: '/hard/deposits', 12 | getTotalDeposited: '/hard/total-deposited', 13 | getBorrows: '/hard/borrows', 14 | getTotalBorrowed: '/hard/total-borrowed', 15 | }; 16 | 17 | export class Hard { 18 | // @ts-ignore 19 | public kavaClient: KavaClient; 20 | public static instance: Hard; 21 | 22 | constructor(kavaClient: KavaClient) { 23 | if (!Hard.instance) { 24 | this.kavaClient = kavaClient; 25 | Hard.instance = this; 26 | } 27 | return Hard.instance; 28 | } 29 | 30 | /** 31 | * Get the params of the hard module 32 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 33 | * @return {Promise} 34 | */ 35 | async getParams(timeout = 2000) { 36 | const res = await tx.getTx(api.getParams, this.kavaClient.baseURI, timeout); 37 | if (res && res.data) { 38 | return res.data.result; 39 | } 40 | } 41 | 42 | /** 43 | * Get module accounts associated with the hard module 44 | * @param {Object} args optional arguments {name: "hard"|"hard_lp_distribution"} 45 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 46 | * @return {Promise} 47 | */ 48 | async getModAccounts(args = {}, timeout = 2000) { 49 | const res = await tx.getTx( 50 | api.getModAccounts, 51 | this.kavaClient.baseURI, 52 | timeout, 53 | args 54 | ); 55 | if (res && res.data) { 56 | return res.data.result; 57 | } 58 | } 59 | 60 | /** 61 | * Get hard deposits 62 | * @param {Object} args optional arguments {deposit_denom: "btc", deposit_type: "btc-a", owner: "kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny"} 63 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 64 | * @return {Promise} 65 | */ 66 | async getDeposits(args = {}, timeout = 2000) { 67 | const res = await tx.getTx( 68 | api.getDeposits, 69 | this.kavaClient.baseURI, 70 | timeout, 71 | args 72 | ); 73 | if (res && res.data) { 74 | return res.data.result; 75 | } 76 | } 77 | 78 | /** 79 | * Get hard total-deposited 80 | * @param {Object} args optional arguments {denom: "btc"} 81 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 82 | * @return {Promise} 83 | */ 84 | async getTotalDeposited(args = {}, timeout = 2000) { 85 | const res = await tx.getTx( 86 | api.getTotalDeposited, 87 | this.kavaClient.baseURI, 88 | timeout, 89 | args 90 | ); 91 | if (res && res.data) { 92 | return res.data.result; 93 | } 94 | } 95 | 96 | /** 97 | * Get hard borrows 98 | * @param {Object} args optional arguments {owner: "kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny" denom: "btc" } 99 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 100 | * @return {Promise} 101 | */ 102 | async getBorrows(args = {}, timeout = 2000) { 103 | const res = await tx.getTx( 104 | api.getBorrows, 105 | this.kavaClient.baseURI, 106 | timeout, 107 | args 108 | ); 109 | if (res && res.data) { 110 | return res.data.result; 111 | } 112 | } 113 | 114 | /** 115 | * Get hard total-borrowed 116 | * @param {Object} args optional arguments {denom: "btc"} 117 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 118 | * @return {Promise} 119 | */ 120 | async getTotalBorrowed(args = {}, timeout = 2000) { 121 | const res = await tx.getTx( 122 | api.getTotalBorrowed, 123 | this.kavaClient.baseURI, 124 | timeout, 125 | args 126 | ); 127 | if (res && res.data) { 128 | return res.data.result; 129 | } 130 | } 131 | 132 | /** 133 | * Deposit funds to a liquidity pool 134 | * @param {Object} amount the coins to be deposited 135 | * @param {Number} gas optional gas amount 136 | * @param {String} sequence optional account sequence 137 | * @return {Promise} 138 | */ 139 | async deposit(amount: Coin[], gas = DEFAULT_GAS, sequence = null) { 140 | if (!this.kavaClient.wallet) { 141 | throw Error('Wallet has not yet been initialized'); 142 | } 143 | const msgDeposit = msg.hard.newMsgDeposit( 144 | this.kavaClient.wallet.address, 145 | amount 146 | ); 147 | const fee = { amount: [], gas: String(gas) }; 148 | return await this.kavaClient.sendTx([msgDeposit], fee, sequence); 149 | } 150 | 151 | /** 152 | * Withdraw funds from a liquidity pool 153 | * @param {Object} amount the coins to be deposited 154 | * @param {Number} gas optional gas amount 155 | * @param {String} sequence optional account sequence 156 | * @return {Promise} 157 | */ 158 | async withdraw(amount: Coin[], gas = DEFAULT_GAS, sequence = null) { 159 | if (!this.kavaClient.wallet) { 160 | throw Error('Wallet has not yet been initialized'); 161 | } 162 | const msgWithdraw = msg.hard.newMsgWithdraw( 163 | this.kavaClient.wallet.address, 164 | amount 165 | ); 166 | const fee = { amount: [], gas: String(gas) }; 167 | return await this.kavaClient.sendTx([msgWithdraw], fee, sequence); 168 | } 169 | 170 | /** 171 | * Borrow available funds from a liquidity pool 172 | * @param {Object} amount the coins to be deposited 173 | * @param {Number} gas optional gas amount 174 | * @param {String} sequence optional account sequence 175 | * @return {Promise} 176 | */ 177 | async borrow(amount: Coin[], gas = DEFAULT_GAS, sequence = null) { 178 | if (!this.kavaClient.wallet) { 179 | throw Error('Wallet has not yet been initialized'); 180 | } 181 | const msgBorrow = msg.hard.newMsgBorrow( 182 | this.kavaClient.wallet.address, 183 | amount 184 | ); 185 | const fee = { amount: [], gas: String(gas) }; 186 | return await this.kavaClient.sendTx([msgBorrow], fee, sequence); 187 | } 188 | 189 | /** 190 | * Repay funds borrowed from a liquidity pool 191 | * @param {Object} amount the coins to be deposited 192 | * @param {Number} gas optional gas amount 193 | * @param {String} sequence optional account sequence 194 | * @return {Promise} 195 | */ 196 | async repay(amount: Coin[], gas = DEFAULT_GAS, sequence = null) { 197 | if (!this.kavaClient.wallet) { 198 | throw Error('Wallet has not yet been initialized'); 199 | } 200 | const msgRepay = msg.hard.newMsgRepay( 201 | this.kavaClient.wallet.address, 202 | this.kavaClient.wallet.address, 203 | amount 204 | ); 205 | const fee = { amount: [], gas: String(gas) }; 206 | return await this.kavaClient.sendTx([msgRepay], fee, sequence); 207 | } 208 | 209 | /** 210 | * Attempt to liquidate a borrower that's over their loan-to-value ratio 211 | * @param {String} borrower the borrower to be liquidated 212 | * @param {Number} gas optional gas amount 213 | * @param {String} sequence optional account sequence 214 | * @return {Promise} 215 | */ 216 | async liquidate(borrower: string, gas = DEFAULT_GAS, sequence = null) { 217 | if (!this.kavaClient.wallet) { 218 | throw Error('Wallet has not yet been initialized'); 219 | } 220 | const msgLiquidate = msg.hard.newMsgLiquidate( 221 | this.kavaClient.wallet.address, 222 | borrower 223 | ); 224 | const fee = { amount: [], gas: String(gas) }; 225 | return await this.kavaClient.sendTx([msgLiquidate], fee, sequence); 226 | } 227 | } 228 | 229 | module.exports.Hard = Hard; 230 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires, @typescript-eslint/no-explicit-any */ 2 | const sig = require('@kava-labs/sig'); 3 | import { tx } from '../tx'; 4 | import { msg } from '../msg'; 5 | import { Hard } from './hard'; 6 | import { Swap } from './swap'; 7 | import { DenomToClaim } from '../types/DenomToClaim'; 8 | import { VoteType } from '../types/VoteType'; 9 | import { Wallet } from '../types/Wallet'; 10 | import { Coin } from '../types/Coin'; 11 | import { Slip10RawIndex, HdPath } from '@cosmjs/crypto'; 12 | import { DirectSecp256k1HdWallet, Registry } from '@cosmjs/proto-signing'; 13 | import { SigningStargateClient } from '@cosmjs/stargate'; 14 | import { MsgPostPrice } from '../proto/kava/pricefeed/v1beta1/tx'; 15 | import { Decimal } from '@cosmjs/math'; 16 | import { MsgRefundAtomicSwap } from '../proto/kava/bep3/v1beta1/tx'; 17 | 18 | const KAVA_PREFIX = 'kava'; 19 | const DERIVATION_PATH = "m/44'/459'/0'/0/0"; 20 | const DERIVATION_PATH_LEGACY = "m/44'/118'/0'/0/0"; 21 | const DEFAULT_FEE = { amount: [], gas: String(300000) }; 22 | const DEFAULT_CDP_FEE = { amount: [], gas: String(650000) }; 23 | 24 | const POST_PRICE_MSG_URL = '/kava.pricefeed.v1beta1.MsgPostPrice'; 25 | const REFUND_SWAP_MSG_URL = '/kava.bep3.v1beta1.MsgRefundAtomicSwap'; 26 | 27 | const api = { 28 | txs: '/cosmos/tx/v1beta1/txs', 29 | nodeInfo: '/cosmos/base/tendermint/v1beta1/node_info', 30 | getBlock: '/blocks', 31 | getLatestBlock: '/blocks/latest', 32 | getLatestValidatorSet: '/validatorsets/latest', 33 | getValidatorSet: '/validatorsets/', 34 | getParamsPricefeed: '/pricefeed/parameters', 35 | getParamsAuction: '/auction/parameters', 36 | getParamsCDP: '/cdp/parameters', 37 | getParamsBEP3: '/bep3/parameters', 38 | getParamsIncentive: '/incentive/parameters', 39 | getParamsCommittee: '/committee/parameters', 40 | getParamsIssuance: '/issuance/parameters', 41 | getAccount: '/auth/accounts', 42 | getBalances: '/bank/balances', 43 | getSupply: '/supply/total', 44 | getMarkets: 'pricefeed/markets', 45 | getOracles: 'pricefeed/oracles', 46 | getPrice: '/pricefeed/price', 47 | getRawPrices: '/kava/pricefeed/v1beta1/rawprices', 48 | getSwap: 'bep3/swap', 49 | getSwaps: 'kava/bep3/v1beta1/atomicswaps', 50 | getAssetSupply: 'bep3/supply', 51 | getAssetSupplies: 'bep3/supplies', 52 | getCDP: 'cdp/cdps/cdp', 53 | getCDPs: '/cdp/cdps', 54 | getCDPsByCollateralType: '/cdp/cdps/collateralType', 55 | getCDPsRatio: '/cdp/cdps/ratio', 56 | getDeposits: '/cdp/cdps/deposits', 57 | getAuction: '/auction/auctions', 58 | getAuctions: '/auction/auctions', 59 | getRewards: '/incentive/rewards', 60 | getCommittee: '/committee/committees', // endpoint also used by getCommitteeProposals 61 | getCommittees: '/committee/committees', 62 | getProposal: '/committee/proposals', // endpoint also used by getProposer, getProposalTally, getProposalVotes 63 | getDistributionRewards: '/distribution/delegators', 64 | getDelegations: '/staking/delegators', 65 | }; 66 | 67 | /** 68 | * The Kava client. 69 | */ 70 | export class KavaClient { 71 | public baseURI: string; 72 | public rpcURI: string; 73 | public broadcastMode: string; 74 | public hard: Hard; 75 | public swap: Swap; 76 | public wallet?: Wallet; 77 | public chainID?: string; 78 | public accNum?: string; 79 | public newWallet?: DirectSecp256k1HdWallet; 80 | public rpcClient?: SigningStargateClient; 81 | 82 | /** 83 | * @param {String} server Kava public url 84 | */ 85 | constructor(server: string, rcpUri: string) { 86 | if (!server) { 87 | throw new Error('Kava server should not be null'); 88 | } 89 | this.rpcURI = rcpUri; 90 | this.baseURI = server; 91 | this.broadcastMode = 'sync'; // default broadcast mode 92 | this.hard = new Hard(this); 93 | this.swap = new Swap(this); 94 | } 95 | 96 | /** 97 | * Initialize the client with the chain's ID. Asynchronous. 98 | * @return {Promise} 99 | */ 100 | async initChain() { 101 | if (!this.chainID) { 102 | const res = await tx.getTx(api.nodeInfo, this.baseURI); 103 | this.chainID = res?.data?.default_node_info?.network; 104 | } 105 | return this; 106 | } 107 | 108 | /** 109 | * Manually set the chain's ID 110 | * @param {String} chainID Kava chain ID 111 | */ 112 | setChainID(chainID: string) { 113 | if (!chainID) { 114 | throw new Error('chainID cannot be undefined'); 115 | } 116 | this.chainID = chainID; 117 | return this; 118 | } 119 | 120 | /** 121 | * Manually set the wallet's account number 122 | * @param {String} accNum Account number of the Kava address 123 | */ 124 | setAccountNumber(accNum: string) { 125 | if (!accNum) { 126 | throw new Error('account number cannot be undefined'); 127 | } 128 | this.accNum = String(accNum); 129 | return this; 130 | } 131 | 132 | /** 133 | * Set broadcast mode 134 | * @param {String} mode transaction broadcast mode 135 | */ 136 | setBroadcastMode(mode: string) { 137 | if (!mode) { 138 | throw new Error('broadcast mode cannot be undefined'); 139 | } 140 | if (mode != 'async' && mode != 'sync' && mode != 'block') { 141 | throw new Error( 142 | [ 143 | 'invalid broadcast mode ', 144 | mode, 145 | ' - must be async, sync, or block', 146 | ].join(' ') 147 | ); 148 | } 149 | this.broadcastMode = String(mode); 150 | return this; 151 | } 152 | 153 | /** 154 | * Set the client's wallet which is used for signature generation 155 | * @param {String} mnemonic Kava address mnemonic 156 | * @param {String} password optional param for wallet password 157 | * @param {boolean} legacy optional param to use the legacy coin type 158 | * @return {Promise} 159 | */ 160 | setWallet(mnemonic: string, password = '', legacy = false) { 161 | if (!mnemonic) { 162 | throw new Error('mnemonic cannot be undefined'); 163 | } 164 | const derivationPath = legacy ? DERIVATION_PATH_LEGACY : DERIVATION_PATH; 165 | this.wallet = sig.createWalletFromMnemonic( 166 | mnemonic, 167 | password, 168 | KAVA_PREFIX, 169 | derivationPath 170 | ); 171 | return this; 172 | } 173 | 174 | async setNewWallet(mnemonic: string, legacy = false) { 175 | const hdPath: HdPath = [ 176 | Slip10RawIndex.hardened(44), 177 | legacy ? Slip10RawIndex.hardened(118) : Slip10RawIndex.hardened(459), 178 | Slip10RawIndex.hardened(0), 179 | Slip10RawIndex.normal(0), 180 | Slip10RawIndex.normal(0), 181 | ]; 182 | this.newWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { 183 | prefix: 'kava', 184 | hdPaths: [hdPath], 185 | }); 186 | 187 | // register type urls and create rpc client 188 | const registry = new Registry(); 189 | registry.register(POST_PRICE_MSG_URL, MsgPostPrice); 190 | registry.register(REFUND_SWAP_MSG_URL, MsgRefundAtomicSwap); 191 | this.rpcClient = await SigningStargateClient.connectWithSigner( 192 | this.rpcURI, 193 | this.newWallet, 194 | { registry } 195 | ); 196 | } 197 | 198 | /** 199 | * Load account number, sequence, and package with chain ID for signature 200 | * @param {String} sequence Kava address sequence 201 | * @return {Promise} 202 | */ 203 | async prepareSignInfo(sequence: string | null) { 204 | let signInfo; 205 | if (sequence && this.accNum != null) { 206 | // Prepare signing info from manually set values 207 | signInfo = { 208 | chain_id: this.chainID, 209 | account_number: String(this.accNum), 210 | sequence: String(sequence), 211 | }; 212 | } else { 213 | if (!this.wallet) { 214 | throw Error('Wallet has not yet been initialized'); 215 | } 216 | // Load meta data from the account's chain state 217 | const meta = await tx.loadMetaData(this.wallet.address, this.baseURI); 218 | // Select manually set values over automatically pulled values 219 | signInfo = { 220 | chain_id: this.chainID, 221 | account_number: 222 | this.accNum != null 223 | ? String(this.accNum) 224 | : String(meta.account_number), 225 | sequence: sequence ? String(sequence) : String(meta.sequence), 226 | }; 227 | } 228 | return signInfo; 229 | } 230 | 231 | /** 232 | * Sends messages to the Kava blockchain 233 | * @param {Array} msgs an array of msgs to be sent 234 | * @param {Object} fee the transaction's fee that includes gas amount 235 | * @param {String} sequence account sequence 236 | * @return {Promise} 237 | */ 238 | async sendTx(msgs: any[], fee: any, sequence: string | null) { 239 | if (!this.newWallet) { 240 | throw Error('new wallet is not initialized, it is required to send tx.'); 241 | } 242 | if (!this.rpcClient) { 243 | throw Error('rpc client is not initialized, it is required to send tx.'); 244 | } 245 | const [firstAccount] = await this.newWallet.getAccounts(); 246 | const defaultFee = { amount: [], gas: '300000' }; 247 | const result = await this.rpcClient.signAndBroadcast( 248 | firstAccount.address, 249 | msgs, 250 | fee ? fee : defaultFee 251 | ); 252 | return result.transactionHash; 253 | } 254 | 255 | /*************************************************** 256 | * Tendermint 257 | ***************************************************/ 258 | /** 259 | * Get the latest block 260 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 261 | * @return {Promise} 262 | */ 263 | async getLatestBlock(timeout = 2000) { 264 | const res = await tx.getTx(api.getLatestBlock, this.baseURI, timeout); 265 | if (res && res.data) { 266 | return res.data; 267 | } 268 | } 269 | 270 | /** 271 | * Get a block at a specific height 272 | * @param {Number} height the block's height 273 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 274 | * @return {Promise} 275 | */ 276 | async getBlock(height: number, timeout = 2000) { 277 | const path = api.getBlock + '/' + String(height); 278 | const res = await tx.getTx(path, this.baseURI, timeout); 279 | if (res && res.data) { 280 | return res.data; 281 | } 282 | } 283 | 284 | /** 285 | * Get the latest set of validators 286 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 287 | * @return {Promise} 288 | */ 289 | async getLatestValidatorSet(timeout = 2000) { 290 | const res = await tx.getTx( 291 | api.getLatestValidatorSet, 292 | this.baseURI, 293 | timeout 294 | ); 295 | if (res && res.data) { 296 | return res.data; 297 | } 298 | } 299 | 300 | /** 301 | * Get a set of validators at a specific block height 302 | * @param {Number} height the block's height 303 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 304 | * @return {Promise} 305 | */ 306 | async getValidatorSet(height: number, timeout = 2000) { 307 | const path = api.getValidatorSet + '/' + String(height); 308 | const res = await tx.getTx(path, this.baseURI, timeout); 309 | if (res && res.data) { 310 | return res.data; 311 | } 312 | } 313 | 314 | /** 315 | * Checks a transaction hash for on-chain results 316 | * @param {String} txHash the transaction's hash 317 | * @param {Number} timeout milliseconds until the transaction will be considered not found 318 | * @return {Promise} 319 | */ 320 | async checkTxHash(txHash: string, timeout = 10000) { 321 | const path = api.txs + '/' + txHash; 322 | let res; 323 | 324 | // Query the chain for a transaction with this hash 325 | try { 326 | res = await tx.getTx(path, this.baseURI, timeout); 327 | } catch (e) { 328 | throw new Error(`tx not found: ${e}`); 329 | } 330 | 331 | // If the transaction is found, check that it was accepted by the chain 332 | try { 333 | if (res?.data?.code) { 334 | throw new Error(`tx not accepted by chain: "${res.data.raw_log}"`); 335 | } 336 | } catch (e) { 337 | console.log('\n' + e); 338 | } 339 | 340 | return res.data; 341 | } 342 | 343 | /*************************************************** 344 | * Cosmos SDK 345 | ***************************************************/ 346 | /** 347 | * Get information about an account 348 | * @param {String} address account to query 349 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 350 | * @return {Promise} 351 | */ 352 | async getAccount(address: string, timeout = 2000) { 353 | const path = api.getAccount + '/' + address; 354 | const res = await tx.getTx(path, this.baseURI, timeout); 355 | if (res && res.data) { 356 | return res.data.result; 357 | } 358 | } 359 | 360 | /** 361 | * Get an account's balances 362 | * @param {String} address account to query 363 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 364 | * @return {Promise} 365 | */ 366 | async getBalances(address: string, timeout = 2000) { 367 | const path = api.getBalances + '/' + address; 368 | const res = await tx.getTx(path, this.baseURI, timeout); 369 | if (res && res.data) { 370 | return res.data.result as Coin[]; 371 | } 372 | } 373 | 374 | /** 375 | * Get an account's delegators reward 376 | * @param {String} address account to query 377 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 378 | * @return {Promise} 379 | */ 380 | async getDistributionRewards(address: string, timeout = 2000) { 381 | const path = api.getDistributionRewards + '/' + address + '/rewards'; 382 | const res = await tx.getTx(path, this.baseURI, timeout); 383 | if (res && res.data) { 384 | return res.data.result; 385 | } 386 | } 387 | 388 | /** 389 | * Get an account's delegations 390 | * @param {String} address account to query 391 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 392 | * @return {Promise} 393 | */ 394 | async getDelegations(address: string, timeout = 2000) { 395 | const path = api.getDelegations + '/' + address + '/delegations'; 396 | const res = await tx.getTx(path, this.baseURI, timeout); 397 | if (res && res.data) { 398 | return res.data.result; 399 | } 400 | } 401 | 402 | /** 403 | * Get the total supply of coins on the chain 404 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 405 | * @return {Promise} 406 | */ 407 | async getSupply(timeout = 2000) { 408 | const res = await tx.getTx(api.getSupply, this.baseURI, timeout); 409 | if (res && res.data) { 410 | return res.data.result; 411 | } 412 | } 413 | 414 | /** 415 | * Get the total supply of coins on the chain 416 | * @param {String} denom the name of the asset whose total supply will be queried 417 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 418 | * @return {Promise} 419 | */ 420 | async getSupplyOf(denom: string, timeout = 2000) { 421 | const path = api.getSupply + '/' + denom; 422 | const res = await tx.getTx(path, this.baseURI, timeout); 423 | if (res && res.data) { 424 | return res.data.result; 425 | } 426 | } 427 | 428 | /** 429 | * Sends coins to an address 430 | * @param {String} recipient address that will receive coins 431 | * @param {String} coins amount of coins to send 432 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 433 | * @param {String} sequence optional account sequence 434 | * @return {Promise} 435 | */ 436 | async transfer( 437 | recipient: string, 438 | coins: Coin[], 439 | fee = DEFAULT_FEE, 440 | sequence = null 441 | ) { 442 | if (!this.wallet) { 443 | throw Error('Wallet has not yet been initialized'); 444 | } 445 | const msgSend = msg.cosmos.newMsgSend( 446 | this.wallet.address, 447 | recipient, 448 | coins 449 | ); 450 | return await this.sendTx([msgSend], fee, sequence); 451 | } 452 | 453 | /*************************************************** 454 | * Pricefeed 455 | ***************************************************/ 456 | /** 457 | * Get the params of the pricefeed module 458 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 459 | * @return {Promise} 460 | */ 461 | async getParamsPricefeed(timeout = 2000) { 462 | const res = await tx.getTx(api.getParamsPricefeed, this.baseURI, timeout); 463 | if (res && res.data) { 464 | return res.data.result; 465 | } 466 | } 467 | 468 | /** 469 | * Get the current system price of an asset 470 | * @param {String} market asset's market identifier 471 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 472 | * @return {Promise} 473 | */ 474 | async getPrice(market: string, timeout = 2000) { 475 | const path = api.getPrice + '/' + market; 476 | const res = await tx.getTx(path, this.baseURI, timeout); 477 | if (res && res.data) { 478 | return res.data.result; 479 | } 480 | } 481 | 482 | /** 483 | * Get all active oracle prices for an asset 484 | * @param {String} market asset's market identifier 485 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 486 | * @return {Promise} 487 | */ 488 | async getRawPrices(market: string, timeout = 2000) { 489 | const path = api.getRawPrices + '/' + market; 490 | const res = await tx.getTx(path, this.baseURI, timeout); 491 | if (res && res.data) { 492 | return res.data.raw_prices; 493 | } 494 | } 495 | 496 | /** 497 | * Get all active markets 498 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 499 | * @return {Promise} 500 | */ 501 | async getMarkets(timeout = 2000) { 502 | const res = await tx.getTx(api.getMarkets, this.baseURI, timeout); 503 | if (res && res.data) { 504 | return res.data.result; 505 | } 506 | } 507 | 508 | /** 509 | * Get all active oracles for an asset 510 | * @param {String} denom asset's name 511 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 512 | * @return {Promise} 513 | */ 514 | async getOracles(denom: string, timeout = 2000) { 515 | const path = api.getOracles + '/' + denom; 516 | const res = await tx.getTx(path, this.baseURI, timeout); 517 | if (res && res.data) { 518 | return res.data.result; 519 | } 520 | } 521 | 522 | /** 523 | * Allows oracles to post an asset's price to the pricefeed 524 | * @param {String} marketID the asset's on chain market ID, such as 'btc:usd' 525 | * @param {String} price the asset's price 526 | * @param {String} expiry time duration that this price is valid for 527 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 528 | * @param {String} sequence optional account sequence 529 | * @return {Promise} 530 | */ 531 | async postPrice( 532 | marketID: string, 533 | price: string, 534 | expiry: Date, 535 | fee = DEFAULT_FEE, 536 | sequence = null 537 | ) { 538 | if (!this.newWallet) { 539 | throw Error('Wallet has not yet been initialized'); 540 | } 541 | const [account] = await this.newWallet.getAccounts(); 542 | const parsed = Decimal.fromUserInput(price, 18); 543 | const msgPostPrice = { 544 | typeUrl: POST_PRICE_MSG_URL, 545 | value: MsgPostPrice.create({ 546 | from: account.address, 547 | marketId: marketID, 548 | price: parsed.atomics, 549 | expiry, 550 | }), 551 | }; 552 | return await this.sendTx([msgPostPrice], fee, sequence); 553 | } 554 | 555 | /*************************************************** 556 | * Auction 557 | ***************************************************/ 558 | /** 559 | * Get the params of the auction module 560 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 561 | * @return {Promise} 562 | */ 563 | async getParamsAuction(timeout = 2000) { 564 | const res = await tx.getTx(api.getParamsAuction, this.baseURI, timeout); 565 | if (res && res.data) { 566 | return res.data.result; 567 | } 568 | } 569 | 570 | /** 571 | * Get auction by ID 572 | * @param {String} id auctions unique identifier 573 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 574 | * @return {Promise} 575 | */ 576 | async getAuction(id: string, timeout = 2000) { 577 | const path = api.getAuction + '/' + id; 578 | const res = await tx.getTx(path, this.baseURI, timeout); 579 | if (res && res.data) { 580 | return res.data.result; 581 | } 582 | } 583 | 584 | /** 585 | * Get auctions, filterable by args. 586 | * @param {Object} args request args as JSON. Example: {type: "collateral", denom: "btc", owner: "kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny"} 587 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 588 | * @return {Promise} 589 | */ 590 | async getAuctions(args = {}, timeout = 2000) { 591 | const res = await tx.getTx(api.getAuctions, this.baseURI, timeout, args); 592 | if (res && res.data) { 593 | return res.data.result; 594 | } 595 | } 596 | 597 | /** 598 | * Place a bid on an auction 599 | * @param {String} auctionID the unique ID of the auction 600 | * @param {String} amount the coins amount to bid 601 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 602 | * @param {String} sequence optional account sequence 603 | * @return {Promise} 604 | */ 605 | async placeBid( 606 | auctionID: string, 607 | amount: Coin, 608 | fee = DEFAULT_FEE, 609 | sequence = null 610 | ) { 611 | if (!this.wallet) { 612 | throw Error('Wallet has not yet been initialized'); 613 | } 614 | const msgPlaceBid = msg.kava.newMsgPlaceBid( 615 | auctionID, 616 | this.wallet.address, 617 | amount 618 | ); 619 | return await this.sendTx([msgPlaceBid], fee, sequence); 620 | } 621 | 622 | /*************************************************** 623 | * CDP 624 | ***************************************************/ 625 | /** 626 | * Get the params of the cdp module 627 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 628 | * @return {Promise} 629 | */ 630 | async getParamsCDP(timeout = 2000) { 631 | const res = await tx.getTx(api.getParamsCDP, this.baseURI, timeout); 632 | if (res && res.data) { 633 | return res.data.result; 634 | } 635 | } 636 | 637 | /** 638 | * Get CDP if one exists for an owner and asset type 639 | * @param {String} owner address of the CDP's owner 640 | * @param {String} collateralType type of the CDP's collateral asset 641 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 642 | * @return {Promise} 643 | */ 644 | async getCDP(owner: string, collateralType: string, timeout = 2000) { 645 | const path = api.getCDP + '/' + owner + '/' + collateralType; 646 | const res = await tx.getTx(path, this.baseURI, timeout); 647 | if (res && res.data) { 648 | return res.data.result; 649 | } 650 | } 651 | 652 | /** 653 | * Get all CDPs by filterable args 654 | * @param {Object} args request args as JSON. Example: {collateral-type: "btc-a", id: "52", ratio: "2.75", owner: "kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny"} 655 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 656 | * @return {Promise} 657 | */ 658 | async getCDPs(args = {}, timeout = 2000) { 659 | const res = await tx.getTx(api.getCDPs, this.baseURI, timeout, args); 660 | if (res && res.data) { 661 | return res.data.result; 662 | } 663 | } 664 | 665 | /** 666 | * Get all CDPs for an asset type 667 | * @param {String} collateralType type of the CDP's collateral asset 668 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 669 | * @return {Promise} 670 | */ 671 | async getCDPsByCollateralType(collateralType: string, timeout = 2000) { 672 | const path = api.getCDPsByCollateralType + '/' + collateralType; 673 | const res = await tx.getTx(path, this.baseURI, timeout); 674 | if (res && res.data) { 675 | return res.data.result; 676 | } 677 | } 678 | 679 | /** 680 | * Get all CDPs for an asset that are under the collateralization ratio specified 681 | * @param {String} collateralType type of the CDP's collateral asset 682 | * @param {String} ratio upper collateralization ratio limit of the query 683 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 684 | * @return {Promise} 685 | */ 686 | async getCDPsByRatio(collateralType: string, ratio: string, timeout = 2000) { 687 | const path = api.getCDPsRatio + '/' + collateralType + '/' + ratio; 688 | const res = await tx.getTx(path, this.baseURI, timeout); 689 | if (res && res.data) { 690 | return res.data.result; 691 | } 692 | } 693 | 694 | /** 695 | * Get all deposits for the CDP with the specified owner and collateral type 696 | * @param {String} owner the address that owns the CDP 697 | * @param {String} collateralType denom of the CDP's collateral asset 698 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 699 | * @return {Promise} 700 | */ 701 | async getDeposits(owner: string, collateralType: string, timeout = 2000) { 702 | const path = api.getDeposits + '/' + owner + '/' + collateralType; 703 | const res = await tx.getTx(path, this.baseURI, timeout); 704 | if (res && res.data) { 705 | return res.data.result; 706 | } 707 | } 708 | 709 | /** 710 | * Create a collateralized debt position 711 | * @param {String} principal the coins that will be drawn as debt 712 | * @param {String} collateral the coins that will be held as collateral 713 | * @param {String} collateralType the CDP's collateral type 714 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 715 | * @param {String} sequence optional account sequence 716 | * @return {Promise} 717 | */ 718 | async createCDP( 719 | principal: Coin, 720 | collateral: Coin, 721 | collateralType: string, 722 | fee = DEFAULT_CDP_FEE, 723 | sequence = null 724 | ) { 725 | if (!this.wallet) { 726 | throw Error('Wallet has not yet been initialized'); 727 | } 728 | const msgCreateCDP = msg.kava.newMsgCreateCDP( 729 | this.wallet.address, 730 | principal, 731 | collateral, 732 | collateralType 733 | ); 734 | return await this.sendTx([msgCreateCDP], fee, sequence); 735 | } 736 | 737 | /** 738 | * Deposit collateral into a collateralized debt position 739 | * @param {String} owner the owner of the CDP 740 | * @param {String} collateral the coins that will deposited as additional collateral 741 | * @param {String} collateralType the CDP's collateral type 742 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 743 | * @param {String} sequence optional account sequence 744 | * @return {Promise} 745 | */ 746 | async deposit( 747 | owner: string, 748 | collateral: Coin, 749 | collateralType: string, 750 | fee = DEFAULT_CDP_FEE, 751 | sequence = null 752 | ) { 753 | if (!this.wallet) { 754 | throw Error('Wallet has not yet been initialized'); 755 | } 756 | const msgDeposit = msg.kava.newMsgDeposit( 757 | owner, 758 | this.wallet.address, 759 | collateral, 760 | collateralType 761 | ); 762 | return await this.sendTx([msgDeposit], fee, sequence); 763 | } 764 | 765 | /** 766 | * Withdraw collateral from a collateralized debt position 767 | * @param {String} owner the owner of the CDP 768 | * @param {String} collateral the coins that will withdrawn from existing collateral 769 | * @param {String} collateralType the CDP's collateral type 770 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 771 | * @param {String} sequence optional account sequence 772 | * @return {Promise} 773 | */ 774 | async withdraw( 775 | owner: string, 776 | collateral: Coin, 777 | collateralType: string, 778 | fee = DEFAULT_CDP_FEE, 779 | sequence = null 780 | ) { 781 | if (!this.wallet) { 782 | throw Error('Wallet has not yet been initialized'); 783 | } 784 | const msgWithdraw = msg.kava.newMsgWithdraw( 785 | owner, 786 | this.wallet.address, 787 | collateral, 788 | collateralType 789 | ); 790 | return await this.sendTx([msgWithdraw], fee, sequence); 791 | } 792 | 793 | /** 794 | * Draw additional debt from a collateralized debt position 795 | * @param {String} collateralType the CDP's collateral type 796 | * @param {String} principal the coins that will be drawn as additional principal 797 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 798 | * @param {String} sequence optional account sequence 799 | * @return {Promise} 800 | */ 801 | async drawDebt( 802 | collateralType: string, 803 | principal: Coin, 804 | fee = DEFAULT_CDP_FEE, 805 | sequence = null 806 | ) { 807 | if (!this.wallet) { 808 | throw Error('Wallet has not yet been initialized'); 809 | } 810 | const msgDrawDebt = msg.kava.newMsgDrawDebt( 811 | this.wallet.address, 812 | collateralType, 813 | principal 814 | ); 815 | return await this.sendTx([msgDrawDebt], fee, sequence); 816 | } 817 | 818 | /** 819 | * Repay debt by returning principal to a collateralized debt position 820 | * @param {String} collateralType the CDP's collateral type 821 | * @param {String} payment the amount of pricipal to be repaid 822 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 823 | * @param {String} sequence optional account sequence 824 | * @return {Promise} 825 | */ 826 | async repayDebt( 827 | collateralType: string, 828 | payment: Coin, 829 | fee = DEFAULT_CDP_FEE, 830 | sequence = null 831 | ) { 832 | if (!this.wallet) { 833 | throw Error('Wallet has not yet been initialized'); 834 | } 835 | const msgRepayDebt = msg.kava.newMsgRepayDebt( 836 | this.wallet.address, 837 | collateralType, 838 | payment 839 | ); 840 | return await this.sendTx([msgRepayDebt], fee, sequence); 841 | } 842 | 843 | /** 844 | * Attempt to liquidate a borrower that's over their loan-to-value ratio 845 | * @param {String} borrower the borrower to be liquidated 846 | * @param {String} collateralType the collateral type to be liquidated 847 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 848 | * @param {String} sequence optional account sequence 849 | * @return {Promise} 850 | */ 851 | async liquidate( 852 | borrower: string, 853 | collateralType: string, 854 | fee = DEFAULT_CDP_FEE, 855 | sequence = null 856 | ) { 857 | if (!this.wallet) { 858 | throw Error('Wallet has not yet been initialized'); 859 | } 860 | const msgLiquidate = msg.kava.newMsgLiquidate( 861 | this.wallet.address, 862 | borrower, 863 | collateralType 864 | ); 865 | return await this.sendTx([msgLiquidate], fee, sequence); 866 | } 867 | 868 | /*************************************************** 869 | * BEP3 870 | ***************************************************/ 871 | /** 872 | * Get the params of the bep3 module 873 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 874 | * @return {Promise} 875 | */ 876 | async getParamsBEP3(timeout = 2000) { 877 | const res = await tx.getTx(api.getParamsBEP3, this.baseURI, timeout); 878 | if (res && res.data) { 879 | return res.data.result; 880 | } 881 | } 882 | 883 | /** 884 | * Get a swap by its ID 885 | * @param {String} swapID the swap's unique identifier 886 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 887 | * @return {Promise} 888 | */ 889 | async getSwap(swapID: string, timeout = 2000) { 890 | const path = api.getSwap + '/' + swapID; 891 | const res = await tx.getTx(path, this.baseURI, timeout); 892 | if (res && res.data) { 893 | return res.data.result; 894 | } 895 | } 896 | 897 | /** 898 | * Get swaps, filterable by args. 899 | * @param {Object} args request args as JSON. Example: {status: "Open", direction: "Incoming"} 900 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 901 | * @return {Promise} 902 | */ 903 | async getSwaps(args = {}, timeout = 2000) { 904 | const res = await tx.getTx(api.getSwaps, this.baseURI, timeout, args); 905 | if (res && res.data) { 906 | return res.data.atomic_swaps; 907 | } 908 | } 909 | 910 | /** 911 | * Get an asset's total supply by its denom 912 | * @param {String} assetDenom the asset's denom 913 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 914 | * @return {Promise} 915 | */ 916 | async getAssetSupply(assetDenom: string, timeout = 2000) { 917 | const path = api.getAssetSupply + '/' + assetDenom; 918 | const res = await tx.getTx(path, this.baseURI, timeout); 919 | if (res && res.data) { 920 | return res.data.result; 921 | } 922 | } 923 | 924 | /** 925 | * Get all supplies 926 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 927 | * @return {Promise} 928 | */ 929 | async getAssetSupplies(timeout = 2000) { 930 | const res = await tx.getTx(api.getAssetSupplies, this.baseURI, timeout); 931 | if (res && res.data) { 932 | return res.data.result; 933 | } 934 | } 935 | 936 | /** 937 | * Create an atomic swap 938 | * @param {String} recipient the receiver's address on kava 939 | * @param {String} recipientOtherChain the receiver's address on the other chain 940 | * @param {String} senderOtherChain the sender's address on the other chain 941 | * @param {String} randomNumberHash resulting hex-encoded hash from sha256(timestamp, random number) 942 | * @param {String} timestamp the timestamp in unix, must be within 15-30 minutes of current time 943 | * @param {String} amount the amount in coins to be transferred 944 | * @param {String} heightSpan the number of blocks that this swap will be active/claimable 945 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 946 | * @param {String} sequence optional account sequence 947 | * @return {Promise} 948 | */ 949 | async createSwap( 950 | recipient: string, 951 | recipientOtherChain: string, 952 | senderOtherChain: string, 953 | randomNumberHash: string, 954 | timestamp: number, 955 | amount: Coin[], 956 | heightSpan: number, 957 | fee = DEFAULT_FEE, 958 | sequence = null 959 | ) { 960 | if (!this.wallet) { 961 | throw Error('Wallet has not yet been initialized'); 962 | } 963 | const msgCreateAtomicSwap = msg.kava.newMsgCreateAtomicSwap( 964 | this.wallet.address, 965 | recipient, 966 | recipientOtherChain, 967 | senderOtherChain, 968 | randomNumberHash.toUpperCase(), 969 | timestamp, 970 | amount, 971 | heightSpan 972 | ); 973 | return await this.sendTx([msgCreateAtomicSwap], fee, sequence); 974 | } 975 | 976 | /** 977 | * Claim an atomic swap 978 | * @param {String} swapID the swap's unique identifier 979 | * @param {String} randomNumber the secret random number used to generate this swap's random number hash 980 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 981 | * @param {String} sequence optional account sequence 982 | * @return {Promise} 983 | */ 984 | async claimSwap( 985 | swapID: string, 986 | randomNumber: string, 987 | fee = DEFAULT_FEE, 988 | sequence = null 989 | ) { 990 | if (!this.wallet) { 991 | throw Error('Wallet has not yet been initialized'); 992 | } 993 | const msgClaimAtomicSwap = msg.kava.newMsgClaimAtomicSwap( 994 | this.wallet.address, 995 | swapID.toUpperCase(), 996 | randomNumber.toUpperCase() 997 | ); 998 | return await this.sendTx([msgClaimAtomicSwap], fee, sequence); 999 | } 1000 | 1001 | /** 1002 | * Refund an atomic swap 1003 | * @param {String} swapID the swap's unique identifier 1004 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1005 | * @param {String} sequence optional account sequence 1006 | * @return {Promise} 1007 | */ 1008 | async refundSwap(swapID: string, fee = DEFAULT_FEE, sequence = null) { 1009 | if (!this.newWallet) { 1010 | throw Error('New wallet has not yet been initialized'); 1011 | } 1012 | const [account] = await this.newWallet.getAccounts(); 1013 | const msgRefundSwap = { 1014 | typeUrl: REFUND_SWAP_MSG_URL, 1015 | value: MsgRefundAtomicSwap.create({ 1016 | from: account.address, 1017 | swapId: swapID.toUpperCase(), 1018 | }), 1019 | }; 1020 | return await this.sendTx([msgRefundSwap], fee, sequence); 1021 | } 1022 | 1023 | /*************************************************** 1024 | * Incentive 1025 | ***************************************************/ 1026 | /** 1027 | * Get the params of the incentive module 1028 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1029 | * @return {Promise} 1030 | */ 1031 | async getParamsIncentive(timeout = 2000) { 1032 | const res = await tx.getTx(api.getParamsIncentive, this.baseURI, timeout); 1033 | if (res && res.data) { 1034 | return res.data.result; 1035 | } 1036 | } 1037 | 1038 | /** 1039 | * Get the claims of an address for a specific denom 1040 | * @param {Number} args query arguments 1041 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1042 | * @return {Promise} 1043 | */ 1044 | async getRewards(args = {}, timeout = 2000) { 1045 | const path = api.getRewards; 1046 | const res = await tx.getTx(path, this.baseURI, timeout, args); 1047 | if (res && res.data) { 1048 | return res.data.result; 1049 | } 1050 | } 1051 | 1052 | /** 1053 | * Claim USDX minting reward using a specific multiplier 1054 | * @param {String} multiplierName the multiplier to claim with, such as 'small' or 'large' 1055 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1056 | * @param {String} sequence optional account sequence 1057 | * @return {Promise} 1058 | */ 1059 | async claimUSDXMintingReward( 1060 | multiplierName: string, 1061 | fee = DEFAULT_FEE, 1062 | sequence = null 1063 | ) { 1064 | if (!this.wallet) { 1065 | throw Error('Wallet has not yet been initialized'); 1066 | } 1067 | const msgClaimUSDXMintingReward = msg.kava.newMsgClaimUSDXMintingReward( 1068 | this.wallet.address, 1069 | multiplierName 1070 | ); 1071 | return await this.sendTx([msgClaimUSDXMintingReward], fee, sequence); 1072 | } 1073 | 1074 | /** 1075 | * Claim Hard protocol reward using a specific multiplier and denoms 1076 | * @params {Array} choose which denom(s) of your rewards that you would like to claim 1077 | * and at which multiplier you claim them 1078 | * [{ denom: 'hard', multiplierName: 'large'}, { denom: 'ukava', multiplierName: 'small' }] 1079 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1080 | * @param {String} sequence optional account sequence 1081 | * @return {Promise} 1082 | */ 1083 | async claimHardReward( 1084 | denomsToClaim: DenomToClaim[], 1085 | fee = DEFAULT_FEE, 1086 | sequence = null 1087 | ) { 1088 | if (!this.wallet) { 1089 | throw Error('Wallet has not yet been initialized'); 1090 | } 1091 | const msgClaimHardReward = msg.kava.newMsgClaimHardReward( 1092 | this.wallet.address, 1093 | denomsToClaim 1094 | ); 1095 | return await this.sendTx([msgClaimHardReward], fee, sequence); 1096 | } 1097 | 1098 | /** 1099 | * Claim Delegator reward using a specific multiplier and denoms 1100 | * @params {Array} choose which denom(s) of your rewards that you would like to claim 1101 | * and at which multiplier you claim them 1102 | * [{ denom: 'hard', multiplierName: 'large'}, { denom: 'ukava', multiplierName: 'small' }] 1103 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1104 | * @param {String} sequence optional account sequence 1105 | * @return {Promise} 1106 | */ 1107 | async claimDelegatorReward( 1108 | denomsToClaim: DenomToClaim[], 1109 | fee = DEFAULT_FEE, 1110 | sequence = null 1111 | ) { 1112 | if (!this.wallet) { 1113 | throw Error('Wallet has not yet been initialized'); 1114 | } 1115 | const msgClaimDelegatorReward = msg.kava.newMsgClaimDelegatorReward( 1116 | this.wallet.address, 1117 | denomsToClaim 1118 | ); 1119 | return await this.sendTx([msgClaimDelegatorReward], fee, sequence); 1120 | } 1121 | 1122 | /** 1123 | * Claim swap reward using a specific multiplier and denoms 1124 | * @params {Array} choose which denom(s) of your rewards that you would like to claim 1125 | * and at which multiplier you claim them 1126 | * [{ denom: 'hard', multiplierName: 'large'}, { denom: 'ukava', multiplierName: 'small' }] 1127 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1128 | * @param {String} sequence optional account sequence 1129 | * @return {Promise} 1130 | */ 1131 | async claimSwapReward( 1132 | denomsToClaim: DenomToClaim[], 1133 | fee = DEFAULT_FEE, 1134 | sequence = null 1135 | ) { 1136 | if (!this.wallet) { 1137 | throw Error('Wallet has not yet been initialized'); 1138 | } 1139 | const msgClaimSwapReward = msg.kava.newMsgClaimSwapReward( 1140 | this.wallet.address, 1141 | denomsToClaim 1142 | ); 1143 | return await this.sendTx([msgClaimSwapReward], fee, sequence); 1144 | } 1145 | 1146 | /*************************************************** 1147 | * Committee 1148 | ***************************************************/ 1149 | /** 1150 | * Get the params of the committee module 1151 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1152 | * @return {Promise} 1153 | */ 1154 | async getParamsCommittee(timeout = 2000) { 1155 | const res = await tx.getTx(api.getParamsCommittee, this.baseURI, timeout); 1156 | if (res && res.data) { 1157 | return res.data.result; 1158 | } 1159 | } 1160 | 1161 | /** 1162 | * Get a committee by ID 1163 | * @param {Number} committeeID unique identifier of the committee to be queried 1164 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1165 | * @return {Promise} 1166 | */ 1167 | async getCommittee(committeeID: number, timeout = 2000) { 1168 | const path = api.getCommittee + '/' + committeeID; 1169 | const res = await tx.getTx(path, this.baseURI, timeout); 1170 | if (res && res.data) { 1171 | return res.data.result; 1172 | } 1173 | } 1174 | 1175 | /** 1176 | * Get all committees 1177 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1178 | * @return {Promise} 1179 | */ 1180 | async getCommittees(timeout = 2000) { 1181 | const res = await tx.getTx(api.getCommittees, this.baseURI, timeout); 1182 | if (res && res.data) { 1183 | return res.data.result; 1184 | } 1185 | } 1186 | 1187 | /** 1188 | * Get all proposals by a committee 1189 | * @param {Number} committeeID unique identifier of the committee whose proposals will be queried 1190 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1191 | * @return {Promise} 1192 | */ 1193 | async getCommitteeProposals(committeeID: number, timeout = 2000) { 1194 | const path = api.getCommittee + '/' + committeeID + '/proposals'; 1195 | const res = await tx.getTx(path, this.baseURI, timeout); 1196 | if (res && res.data) { 1197 | return res.data.result; 1198 | } 1199 | } 1200 | 1201 | /** 1202 | * Get a proposal by ID 1203 | * @param {Number} proposalID unique identifier of the proposal to be queried 1204 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1205 | * @return {Promise} 1206 | */ 1207 | async getProposal(proposalID: number, timeout = 2000) { 1208 | const path = api.getProposal + '/' + proposalID; 1209 | const res = await tx.getTx(path, this.baseURI, timeout); 1210 | if (res && res.data) { 1211 | return res.data.result; 1212 | } 1213 | } 1214 | 1215 | /** 1216 | * Get a proposal's proposer by proposal ID 1217 | * @param {Number} proposalID unique identifier of the proposal whose proposer will be queried 1218 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1219 | * @return {Promise} 1220 | */ 1221 | async getProposer(proposalID: number, timeout = 2000) { 1222 | const path = api.getProposal + '/' + proposalID + '/proposer'; 1223 | const res = await tx.getTx(path, this.baseURI, timeout); 1224 | if (res && res.data) { 1225 | return res.data.result; 1226 | } 1227 | } 1228 | 1229 | /** 1230 | * Get a proposal's tally by proposal ID 1231 | * @param {Number} proposalID unique identifier of the proposal whose tally will be queried 1232 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1233 | * @return {Promise} 1234 | */ 1235 | async getProposalTally(proposalID: number, timeout = 2000) { 1236 | const path = api.getProposal + '/' + proposalID + '/tally'; 1237 | const res = await tx.getTx(path, this.baseURI, timeout); 1238 | if (res && res.data) { 1239 | return res.data.result; 1240 | } 1241 | } 1242 | 1243 | /** 1244 | * Get a proposal's votes by proposal ID 1245 | * @param {Number} proposalID unique identifier of the proposal whose votes will be queried 1246 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1247 | * @return {Promise} 1248 | */ 1249 | async getProposalVotes(proposalID: number, timeout = 2000) { 1250 | const path = api.getProposal + '/' + proposalID + '/votes'; 1251 | const res = await tx.getTx(path, this.baseURI, timeout); 1252 | if (res && res.data) { 1253 | return res.data.result; 1254 | } 1255 | } 1256 | 1257 | /** 1258 | * Submit a public proposal by a selected committee (must be a member) 1259 | * @param {String} proposal the proposal to be submitted 1260 | * @param {String} committeeID the unique identifier of the committee 1261 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1262 | * @param {String} sequence optional account sequence 1263 | * @return {Promise} 1264 | */ 1265 | async submitCommitteeProposal( 1266 | proposal: string, 1267 | committeeID: string, 1268 | fee = DEFAULT_FEE, 1269 | sequence = null 1270 | ) { 1271 | if (!this.wallet) { 1272 | throw Error('Wallet has not yet been initialized'); 1273 | } 1274 | const msgSubmitProposal = msg.kava.newMsgSubmitProposal( 1275 | proposal, 1276 | this.wallet.address, 1277 | committeeID 1278 | ); 1279 | return await this.sendTx([msgSubmitProposal], fee, sequence); 1280 | } 1281 | 1282 | /** 1283 | * Vote on a public proposal by ID 1284 | * @param {String} proposalID the unique identifier of the proposal 1285 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1286 | * @param {String} sequence optional account sequence 1287 | * @return {Promise} 1288 | */ 1289 | async voteOnCommitteeProposal( 1290 | proposalID: string, 1291 | voteType: VoteType, 1292 | fee = DEFAULT_FEE, 1293 | sequence = null 1294 | ) { 1295 | if (!this.wallet) { 1296 | throw Error('Wallet has not yet been initialized'); 1297 | } 1298 | const msgVote = msg.kava.newMsgVote( 1299 | proposalID, 1300 | this.wallet.address, 1301 | voteType 1302 | ); 1303 | return await this.sendTx([msgVote], fee, sequence); 1304 | } 1305 | 1306 | /*************************************************** 1307 | * Issuance 1308 | ***************************************************/ 1309 | /** 1310 | * Get the params of the issuance module 1311 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 1312 | * @return {Promise} 1313 | */ 1314 | async getParamsIssuance(timeout = 2000) { 1315 | const res = await tx.getTx(api.getParamsIssuance, this.baseURI, timeout); 1316 | if (res && res.data) { 1317 | return res.data.result; 1318 | } 1319 | } 1320 | 1321 | /** 1322 | * Issues (mints) coins to a recipient address 1323 | * @param {String} tokens coins to be issued 1324 | * @param {String} receiver the recipient of the newly issued coins 1325 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1326 | * @param {String} sequence optional account sequence 1327 | * @return {Promise} 1328 | */ 1329 | async issueTokens( 1330 | tokens: Coin[], 1331 | receiver: string, 1332 | fee = DEFAULT_FEE, 1333 | sequence = null 1334 | ) { 1335 | if (!this.wallet) { 1336 | throw Error('Wallet has not yet been initialized'); 1337 | } 1338 | const msgIssueTokens = msg.kava.newMsgIssueTokens( 1339 | this.wallet.address, 1340 | tokens, 1341 | receiver 1342 | ); 1343 | return await this.sendTx([msgIssueTokens], fee, sequence); 1344 | } 1345 | 1346 | /** 1347 | * Redeems tokens 1348 | * @param {String} tokens coins to be redeemed 1349 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1350 | * @param {String} sequence optional account sequence 1351 | * @return {Promise} 1352 | */ 1353 | async redeemTokens(tokens: Coin[], fee = DEFAULT_FEE, sequence = null) { 1354 | if (!this.wallet) { 1355 | throw Error('Wallet has not yet been initialized'); 1356 | } 1357 | const msgRedeemTokens = msg.kava.newMsgRedeemTokens( 1358 | this.wallet.address, 1359 | tokens 1360 | ); 1361 | return await this.sendTx([msgRedeemTokens], fee, sequence); 1362 | } 1363 | 1364 | /** 1365 | * Blocks an address from interacting with a specific token denom 1366 | * @param {String} denom the asset denom the address will be blocked from using 1367 | * @param {String} blockedAddress the address to be blocked 1368 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1369 | * @param {String} sequence optional account sequence 1370 | * @return {Promise} 1371 | */ 1372 | async blockAddress( 1373 | denom: string, 1374 | blockedAddress: string, 1375 | fee = DEFAULT_FEE, 1376 | sequence = null 1377 | ) { 1378 | if (!this.wallet) { 1379 | throw Error('Wallet has not yet been initialized'); 1380 | } 1381 | const msgBlockAddress = msg.kava.newMsgBlockAddress( 1382 | this.wallet.address, 1383 | denom, 1384 | blockedAddress 1385 | ); 1386 | return await this.sendTx([msgBlockAddress], fee, sequence); 1387 | } 1388 | 1389 | /** 1390 | * Unblocks an address that's blocked from interacting with a specific token denom 1391 | * @param {String} denom the asset denom the address will be unblocked from using 1392 | * @param {String} address the address to be unblocked 1393 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1394 | * @param {String} sequence optional account sequence 1395 | * @return {Promise} 1396 | */ 1397 | async unblockAddress( 1398 | denom: string, 1399 | blockedAddress: string, 1400 | fee = DEFAULT_FEE, 1401 | sequence = null 1402 | ) { 1403 | if (!this.wallet) { 1404 | throw Error('Wallet has not yet been initialized'); 1405 | } 1406 | const msgUnblockAddress = msg.kava.newMsgUnblockAddress( 1407 | this.wallet.address, 1408 | denom, 1409 | blockedAddress 1410 | ); 1411 | return await this.sendTx([msgUnblockAddress], fee, sequence); 1412 | } 1413 | 1414 | /** 1415 | * Updates the paused/unpaused status for a specific token denom 1416 | * @param {String} denom the asset denom whose status will be updated 1417 | * @param {String} status bool representing the token's new active/inactive status 1418 | * @param {Object} fee optional fee consisting of { amount: [Coins], gas: String(Number) } 1419 | * @param {String} sequence optional account sequence 1420 | * @return {Promise} 1421 | */ 1422 | async setPauseStatus( 1423 | denom: string, 1424 | status: string, 1425 | fee = DEFAULT_FEE, 1426 | sequence = null 1427 | ) { 1428 | if (!this.wallet) { 1429 | throw Error('Wallet has not yet been initialized'); 1430 | } 1431 | const msgSetPauseStatus = msg.kava.newMsgSetPauseStatus( 1432 | this.wallet.address, 1433 | denom, 1434 | status 1435 | ); 1436 | return await this.sendTx([msgSetPauseStatus], fee, sequence); 1437 | } 1438 | } 1439 | -------------------------------------------------------------------------------- /src/client/swap/index.ts: -------------------------------------------------------------------------------- 1 | import { tx } from '../../tx'; 2 | import { msg } from '../../msg'; 3 | import { KavaClient } from '..'; 4 | import { Coin } from '../../types/Coin'; 5 | 6 | const DEFAULT_GAS = 300000; 7 | 8 | const api = { 9 | getParams: '/swap/parameters', 10 | getDeposits: '/swap/deposits', 11 | getPool: '/swap/pool', 12 | getPools: '/swap/pools', 13 | }; 14 | 15 | export class Swap { 16 | // @ts-ignore 17 | public kavaClient: KavaClient; 18 | public static instance: Swap; 19 | 20 | constructor(kavaClient: KavaClient) { 21 | if (!Swap.instance) { 22 | this.kavaClient = kavaClient; 23 | Swap.instance = this; 24 | } 25 | return Swap.instance; 26 | } 27 | 28 | /** 29 | * Get the params of the swap module 30 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 31 | * @return {Promise} 32 | */ 33 | async getParams(timeout = 2000) { 34 | const res = await tx.getTx(api.getParams, this.kavaClient.baseURI, timeout); 35 | if (res && res.data) { 36 | return res.data.result; 37 | } 38 | } 39 | 40 | /** 41 | * Get swap deposits 42 | * @param {Object} args optional arguments {owner: "kava1l0xsq2z7gqd7yly0g40y5836g0appumark77ny", pool: "bnb:usdx"} 43 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 44 | * @return {Promise} 45 | */ 46 | async getDeposits(args = {}, timeout = 2000) { 47 | const res = await tx.getTx( 48 | api.getDeposits, 49 | this.kavaClient.baseURI, 50 | timeout, 51 | args 52 | ); 53 | if (res && res.data) { 54 | return res.data.result; 55 | } 56 | } 57 | 58 | /** 59 | * Get swap pool 60 | * @param {Object} args required arguments {pool: "bnb:usdx"} 61 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 62 | * @return {Promise} 63 | */ 64 | async getPool(args = {}, timeout = 2000) { 65 | const res = await tx.getTx( 66 | api.getPool, 67 | this.kavaClient.baseURI, 68 | timeout, 69 | args 70 | ); 71 | if (res && res.data) { 72 | return res.data.result; 73 | } 74 | } 75 | 76 | /** 77 | * Get swap pools 78 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 79 | * @return {Promise} 80 | */ 81 | async getPools(args = {}, timeout = 2000) { 82 | const res = await tx.getTx( 83 | api.getPools, 84 | this.kavaClient.baseURI, 85 | timeout, 86 | args 87 | ); 88 | if (res && res.data) { 89 | return res.data.result; 90 | } 91 | } 92 | 93 | /** 94 | * Deposit funds to a liquidity pool 95 | * @param {Object} amount of token a coins to be deposited 96 | * @param {Object} amount of token b coins to be deposited 97 | * @param {string} max slippage you're willing to accept 98 | * @param {string} deadline time to complete the transaction (unix timestamp seconds, UTC timezone) 99 | * @param {Number} gas optional gas amount 100 | * @param {String} sequence optional account sequence 101 | * @return {Promise} 102 | */ 103 | async deposit( 104 | tokenA: Coin, 105 | tokenB: Coin, 106 | slippage: string, 107 | deadline: string, 108 | gas = DEFAULT_GAS, 109 | sequence = null 110 | ) { 111 | if (!this.kavaClient.wallet) { 112 | throw Error('Wallet has not yet been initialized'); 113 | } 114 | const msgDeposit = msg.swap.newMsgDeposit( 115 | this.kavaClient.wallet.address, 116 | tokenA, 117 | tokenB, 118 | slippage, 119 | deadline 120 | ); 121 | const fee = { amount: [], gas: String(gas) }; 122 | return await this.kavaClient.sendTx([msgDeposit], fee, sequence); 123 | } 124 | 125 | /** 126 | * Withdraw funds from a liquidity pool 127 | * @param {Object} amount the coins to be deposited 128 | * @param {Number} number of shares to be withdrawn 129 | * @param {Object} min amount of token a coins to be withdrawn 130 | * @param {Object} min amount of token b coins to be withdrawn 131 | * @param {string} deadline time to complete the transaction (unix timestamp seconds, UTC timezone) 132 | * @param {Number} gas optional gas amount 133 | * @param {String} sequence optional account sequence 134 | * @return {Promise} 135 | */ 136 | async withdraw( 137 | shares: any, 138 | minTokenA: Coin, 139 | minTokenB: Coin, 140 | deadline: string, 141 | gas = DEFAULT_GAS, 142 | sequence = null 143 | ) { 144 | if (!this.kavaClient.wallet) { 145 | throw Error('Wallet has not yet been initialized'); 146 | } 147 | const msgWithdraw = msg.swap.newMsgWithdraw( 148 | this.kavaClient.wallet.address, 149 | shares, 150 | minTokenA, 151 | minTokenB, 152 | deadline 153 | ); 154 | const fee = { amount: [], gas: String(gas) }; 155 | return await this.kavaClient.sendTx([msgWithdraw], fee, sequence); 156 | } 157 | 158 | /** 159 | * Swap an exact number of token a for an estimated amount of token b 160 | * @param {Object} amount of tokens to be be put into the system 161 | * @param {Object} expected amount of coins to be returned 162 | * @param {string} max slippage you're willing to accept 163 | * @param {string} deadline time to complete the transaction (unix timestamp seconds, UTC timezone) 164 | * @param {Number} gas optional gas amount 165 | * @param {String} sequence optional account sequence 166 | * @return {Promise} 167 | */ 168 | async swapExactForTokens( 169 | exactTokenA: Coin, 170 | tokenB: Coin, 171 | slippage: string, 172 | deadline: string, 173 | gas = DEFAULT_GAS, 174 | sequence = null 175 | ) { 176 | if (!this.kavaClient.wallet) { 177 | throw Error('Wallet has not yet been initialized'); 178 | } 179 | const msgSwapExactForTokens = msg.swap.newMsgSwapExactForTokens( 180 | this.kavaClient.wallet.address, 181 | exactTokenA, 182 | tokenB, 183 | slippage, 184 | deadline 185 | ); 186 | const fee = { amount: [], gas: String(gas) }; 187 | return await this.kavaClient.sendTx([msgSwapExactForTokens], fee, sequence); 188 | } 189 | 190 | /** 191 | * Swap an exact number of token b to be returned for an for an estimated amount of token a to input 192 | * @param {Object} expected amount of coins to be put into the system 193 | * @param {Object} amount of tokens to be returned from the system 194 | * @param {string} max slippage you're willing to accept 195 | * @param {string} deadline time to complete the transaction (unix timestamp seconds, UTC timezone) 196 | * @param {Number} gas optional gas amount 197 | * @param {String} sequence optional account sequence 198 | * @return {Promise} 199 | */ 200 | async swapForExactTokens( 201 | tokenA: Coin, 202 | exactTokenB: Coin, 203 | slippage: string, 204 | deadline: string, 205 | gas = DEFAULT_GAS, 206 | sequence = null 207 | ) { 208 | if (!this.kavaClient.wallet) { 209 | throw Error('Wallet has not yet been initialized'); 210 | } 211 | const msgSwapForExactTokens = msg.swap.newMsgSwapForExactTokens( 212 | this.kavaClient.wallet.address, 213 | tokenA, 214 | exactTokenB, 215 | slippage, 216 | deadline 217 | ); 218 | const fee = { amount: [], gas: String(gas) }; 219 | return await this.kavaClient.sendTx([msgSwapForExactTokens], fee, sequence); 220 | } 221 | } 222 | 223 | module.exports.Swap = Swap; 224 | -------------------------------------------------------------------------------- /src/crypto/index.ts: -------------------------------------------------------------------------------- 1 | const sig = require('@kava-labs/sig'); 2 | import bech32 from 'bech32'; 3 | import bip39 from 'bip39'; 4 | 5 | const KAVA_PREFIX = 'kava'; 6 | const MNEMONIC_LEN = 256; 7 | const DECODED_ADDRESS_LEN = 20; 8 | const DERIVATION_PATH = "m/44'/459'/0'/0/0"; 9 | const DERIVATION_PATH_LEGACY = "m/44'/118'/0'/0/0"; 10 | 11 | /** 12 | * Generates mnemonic phrase words using random entropy. 13 | */ 14 | const generateMnemonic = () => bip39.generateMnemonic(MNEMONIC_LEN); 15 | 16 | /** 17 | * Loads a key pair from a mnemonic phrase. 18 | * @param {string} mnemonic the mnemonic from which to generate the key pair 19 | * @param {boolean} legacy optional boolean to use the legacy coin type 20 | */ 21 | const getAddressFromMnemonic = (mnemonic: string, legacy = false) => { 22 | const derivationPath = legacy ? DERIVATION_PATH_LEGACY : DERIVATION_PATH; 23 | const masterKey = sig.createMasterKeyFromMnemonic(mnemonic); 24 | const keyPair = sig.createKeyPairFromMasterKey(masterKey, derivationPath); 25 | return sig.createAddress(keyPair.publicKey, KAVA_PREFIX); 26 | }; 27 | 28 | /** 29 | * Decodes an address in bech32 format. 30 | * @param {string} value the bech32 address to decode 31 | */ 32 | const decodeAddress = (value: string) => { 33 | const decodeAddress = bech32.decode(value); 34 | return Buffer.from(bech32.fromWords(decodeAddress.words)); 35 | }; 36 | 37 | /** 38 | * Checks whether an address is valid. 39 | * @param {string} address the bech32 address to decode 40 | * @param {string} hrp the prefix to check for the bech32 address 41 | * @return {boolean} 42 | */ 43 | const checkAddress = (address: string, hrp: string) => { 44 | try { 45 | if (!address.startsWith(hrp)) { 46 | return false; 47 | } 48 | 49 | const decodedAddress = bech32.decode(address); 50 | const decodedAddressLength = decodeAddress(address).length; 51 | if ( 52 | decodedAddressLength === DECODED_ADDRESS_LEN && 53 | decodedAddress.prefix === hrp 54 | ) { 55 | return true; 56 | } 57 | 58 | return false; 59 | } catch (err) { 60 | return false; 61 | } 62 | }; 63 | 64 | export const crypto = { 65 | generateMnemonic, 66 | getAddressFromMnemonic, 67 | decodeAddress, 68 | checkAddress, 69 | }; 70 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import Kava from './'; 2 | import { utils, crypto, msg, tx } from './'; 3 | 4 | describe('SDK exports', () => { 5 | it('should export Kava as a default', () => { 6 | expect(Kava).toBeDefined(); 7 | }); 8 | it('should contain expected modules', () => { 9 | expect(Kava.utils).toBeDefined(); 10 | expect(Kava.tx).toBeDefined(); 11 | expect(Kava.msg).toBeDefined(); 12 | expect(Kava.crypto).toBeDefined(); 13 | }); 14 | it('should export each module individually', () => { 15 | expect(utils).toBeDefined(); 16 | expect(tx).toBeDefined(); 17 | expect(msg).toBeDefined(); 18 | expect(crypto).toBeDefined(); 19 | }); 20 | 21 | it('derives the correct kava address from ox address', () => { 22 | expect( 23 | utils.kavaToEthAddress('kava1vlpsrmdyuywvaqrv7rx6xga224sqfwz3fyfhwq') 24 | ).toEqual('0x67C301eDA4E11Cce806Cf0CDa323aA556004b851'); 25 | }); 26 | 27 | it('derives the correct 0x address from a kava address', () => { 28 | expect( 29 | utils.ethToKavaAddress('0x7Bbf300890857b8c241b219C6a489431669b3aFA') 30 | ).toEqual('kava10wlnqzyss4accfqmyxwx5jy5x9nfkwh6qm7n4t'); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { tx } from './tx'; 2 | import { msg } from './msg'; 3 | import { utils } from './utils'; 4 | import { crypto } from './crypto'; 5 | import { KavaClient } from './client'; 6 | export * from './types'; 7 | 8 | const Kava = { 9 | tx, 10 | msg, 11 | utils, 12 | crypto, 13 | KavaClient, 14 | }; 15 | 16 | export { tx, msg, utils, crypto, KavaClient }; 17 | 18 | export default Kava; 19 | -------------------------------------------------------------------------------- /src/msg/cosmos/index.test.ts: -------------------------------------------------------------------------------- 1 | import { cosmos } from '.'; 2 | 3 | describe('Cosmos messages', () => { 4 | describe('newMsgSend', () => { 5 | it('should sort the coins passed in by denom', () => { 6 | const coins = [ 7 | { 8 | denom: 'ukava', 9 | amount: '30000000', 10 | }, 11 | { 12 | denom: 'btcb', 13 | amount: '10000000', 14 | }, 15 | { 16 | denom: 'xrpb', 17 | amount: '40000000', 18 | }, 19 | { 20 | denom: 'swp', 21 | amount: '20000000', 22 | }, 23 | ]; 24 | const message = cosmos.newMsgSend('kavafrom', 'kavato', coins); 25 | expect(message).toStrictEqual({ 26 | type: 'cosmos-sdk/MsgSend', 27 | value: { 28 | from_address: 'kavafrom', 29 | to_address: 'kavato', 30 | amount: [ 31 | { 32 | denom: 'btcb', 33 | amount: '10000000', 34 | }, 35 | { 36 | denom: 'swp', 37 | amount: '20000000', 38 | }, 39 | { 40 | denom: 'ukava', 41 | amount: '30000000', 42 | }, 43 | { 44 | denom: 'xrpb', 45 | amount: '40000000', 46 | }, 47 | ], 48 | }, 49 | }); 50 | }); 51 | }); 52 | 53 | describe('newMsgTransfer', () => { 54 | it('should generate a well-formed transfer', () => { 55 | const coin = { 56 | denom: 'ukava', 57 | amount: '10000000', 58 | }; 59 | const message = cosmos.newMsgTransfer( 60 | 'transfer', 61 | 'channel-0', 62 | coin, 63 | 'kava1w66puffhccjck70hw75wu3v92tshw5rmdxp8hb', 64 | 'kava1a22puffhccjck70hw75wu3v92tshw5rmdxp6xz', 65 | 1638988347480000 66 | ); 67 | const expected = { 68 | type: 'cosmos-sdk/MsgTransfer', 69 | value: { 70 | source_port: 'transfer', 71 | source_channel: 'channel-0', 72 | token: { 73 | denom: 'ukava', 74 | amount: '10000000', 75 | }, 76 | sender: 'kava1w66puffhccjck70hw75wu3v92tshw5rmdxp8hb', 77 | receiver: 'kava1a22puffhccjck70hw75wu3v92tshw5rmdxp6xz', 78 | timeoutHeight: 0, 79 | timeoutTimestamp: 1638988347480000, 80 | }, 81 | }; 82 | expect(message).toEqual(expected); 83 | }); 84 | }); 85 | }); 86 | describe('staking delegations messages', () => { 87 | test('newMsgDelegate', () => { 88 | const message = cosmos.newMsgDelegate( 89 | 'kava1w66puffhccjck70hw75wu3v92tshw5rmdxp8hb', 90 | 'kava1a22puffhccjck70hw75wu3v92tshw5rmdxp6xz', 91 | { 92 | denom: 'ukava', 93 | amount: '10000000', 94 | } 95 | ); 96 | const expected = { 97 | type: 'cosmos-sdk/MsgDelegate', 98 | value: { 99 | delegator_address: 'kava1w66puffhccjck70hw75wu3v92tshw5rmdxp8hb', 100 | validator_address: 'kava1a22puffhccjck70hw75wu3v92tshw5rmdxp6xz', 101 | amount: { 102 | denom: 'ukava', 103 | amount: '10000000', 104 | }, 105 | }, 106 | }; 107 | expect(message).toEqual(expected); 108 | }); 109 | test('newMsgUnDelegate', () => { 110 | const message = cosmos.newMsgUnDelegate( 111 | 'kava1w66puffhccjck70hw75wu3v92tshw5rmdxp8hb', 112 | 'kava1a22puffhccjck70hw75wu3v92tshw5rmdxp6xz', 113 | { 114 | denom: 'ukava', 115 | amount: '10000000', 116 | } 117 | ); 118 | const expected = { 119 | type: 'cosmos-sdk/MsgUndelegate', 120 | value: { 121 | delegator_address: 'kava1w66puffhccjck70hw75wu3v92tshw5rmdxp8hb', 122 | validator_address: 'kava1a22puffhccjck70hw75wu3v92tshw5rmdxp6xz', 123 | amount: { 124 | denom: 'ukava', 125 | amount: '10000000', 126 | }, 127 | }, 128 | }; 129 | expect(message).toEqual(expected); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /src/msg/cosmos/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | import { Message } from '../../types/Message'; 3 | import { VoteType } from '../../types/VoteType'; 4 | 5 | const FEE_DEFAULT = { amount: [], gas: '300000' }; 6 | 7 | /** 8 | * Creates a new StdTx from some messages with a default fee 9 | * @param {Object} msgs an array of msgs to be included in the transaction 10 | * @param {Object} fee optional fee 11 | * @param {Object} memo optional memo 12 | * @param {Object} signatures generated when an address signs a data package, required for tx confirmation 13 | * @return {Promise} 14 | */ 15 | function newStdTx( 16 | msgs: Message[], 17 | fee = FEE_DEFAULT, 18 | memo = '', 19 | signatures = null 20 | ) { 21 | return { 22 | type: 'cosmos-sdk/StdTx', 23 | value: { 24 | msg: msgs, 25 | fee: fee, 26 | signatures: signatures, 27 | memo: memo, 28 | }, 29 | }; 30 | } 31 | 32 | function newMsgSend(address: string, to: string, coins: Coin[]) { 33 | const sendTx = { 34 | type: 'cosmos-sdk/MsgSend', 35 | value: { 36 | from_address: address, 37 | to_address: to, 38 | amount: coins.sort((coinA, coinB) => 39 | coinA.denom > coinB.denom ? 1 : -1 40 | ), 41 | }, 42 | }; 43 | return sendTx; 44 | } 45 | 46 | function newMsgVoteGovernance( 47 | proposalID: string, 48 | voter: string, 49 | voteType: VoteType 50 | ) { 51 | return { 52 | type: 'cosmos-sdk/MsgVote', 53 | value: { 54 | voter: voter, 55 | proposal_id: proposalID, 56 | option: voteType, 57 | }, 58 | }; 59 | } 60 | 61 | /** 62 | * Creates an IBC transfer 63 | * @param {String} sourcePort the port identifier, we would expect to always be "transfer" * @param {String} sourcePort the port identifier, we would expect to always be "transfer" 64 | * @param {String} source_channel the channel identifier 65 | * @param {Coin} token 66 | * @param {String} sender address of sender on the origin chain 67 | * @param {String} receiver address of recipient on the destination chain 68 | * @param {Integer} timeoutTimestamp nanoseconds to allow transfer to complete 69 | 70 | */ 71 | function newMsgTransfer( 72 | sourcePort: string, 73 | sourceChannel: string, 74 | token: Coin, 75 | sender: string, 76 | receiver: string, 77 | timeoutTimestamp: number 78 | ) { 79 | return { 80 | type: 'cosmos-sdk/MsgTransfer', 81 | value: { 82 | source_port: sourcePort, 83 | source_channel: sourceChannel, 84 | token: token, 85 | sender: sender, 86 | receiver: receiver, 87 | timeout_height: {}, 88 | timeout_timestamp: timeoutTimestamp.toString(), 89 | }, 90 | }; 91 | } 92 | 93 | export function newMsgDelegate( 94 | delegatorAddress: string, 95 | validatorAddress: string, 96 | amount: Coin 97 | ) { 98 | return { 99 | type: 'cosmos-sdk/MsgDelegate', 100 | value: { 101 | delegator_address: delegatorAddress, 102 | validator_address: validatorAddress, 103 | amount, 104 | }, 105 | }; 106 | } 107 | 108 | export function newMsgUnDelegate( 109 | delegatorAddress: string, 110 | validatorAddress: string, 111 | amount: Coin 112 | ) { 113 | return { 114 | type: 'cosmos-sdk/MsgUndelegate', 115 | value: { 116 | delegator_address: delegatorAddress, 117 | validator_address: validatorAddress, 118 | amount, 119 | }, 120 | }; 121 | } 122 | 123 | export const cosmos = { 124 | newStdTx, 125 | newMsgSend, 126 | newMsgVoteGovernance, 127 | newMsgTransfer, 128 | newMsgDelegate, 129 | newMsgUnDelegate, 130 | }; 131 | -------------------------------------------------------------------------------- /src/msg/earn/index.ts: -------------------------------------------------------------------------------- 1 | import { Strategy, Coin } from '../../types'; 2 | 3 | function newMsgDeposit(depositor: string, amount: Coin, strategy: Strategy) { 4 | return { 5 | type: 'earn/MsgDeposit', 6 | value: { 7 | depositor, 8 | amount, 9 | strategy, 10 | }, 11 | }; 12 | } 13 | 14 | function newMsgWithdraw(from: string, amount: Coin, strategy: Strategy) { 15 | return { 16 | type: 'earn/MsgWithdraw', 17 | value: { 18 | from, 19 | amount, 20 | strategy, 21 | }, 22 | }; 23 | } 24 | 25 | export const earn = { 26 | newMsgDeposit, 27 | newMsgWithdraw, 28 | }; 29 | -------------------------------------------------------------------------------- /src/msg/evmutil/index.ts: -------------------------------------------------------------------------------- 1 | import { InternalEVMAddress, Coin } from '../../types'; 2 | 3 | function newMsgConvertERC20ToCoin( 4 | initiator: InternalEVMAddress, 5 | receiver: string, 6 | kava_erc20_address: InternalEVMAddress, 7 | amount: string 8 | ) { 9 | return { 10 | type: 'evmutil/MsgConvertERC20ToCoin', 11 | value: { 12 | initiator, 13 | receiver, 14 | kava_erc20_address, 15 | amount, 16 | }, 17 | }; 18 | } 19 | 20 | function newMsgConvertCoinToERC20( 21 | initiator: InternalEVMAddress, 22 | receiver: string, 23 | amount: Coin 24 | ) { 25 | return { 26 | type: 'evmutil/MsgConvertCoinToERC20', 27 | value: { 28 | initiator, 29 | receiver, 30 | amount, 31 | }, 32 | }; 33 | } 34 | 35 | function newMsgConvertCosmosCoinToERC20( 36 | initiator: InternalEVMAddress, 37 | receiver: string, 38 | amount: Coin 39 | ) { 40 | return { 41 | type: 'evmutil/MsgConvertCosmosCoinToERC20', 42 | value: { 43 | initiator, 44 | receiver, 45 | amount, 46 | }, 47 | }; 48 | } 49 | 50 | function newMsgConvertCosmosCoinFromERC20( 51 | initiator: InternalEVMAddress, 52 | receiver: string, 53 | amount: Coin 54 | ) { 55 | return { 56 | type: 'evmutil/MsgConvertCosmosCoinFromERC20', 57 | value: { 58 | initiator, 59 | receiver, 60 | amount, 61 | }, 62 | }; 63 | } 64 | 65 | export const evmutil = { 66 | newMsgConvertERC20ToCoin, 67 | newMsgConvertCoinToERC20, 68 | newMsgConvertCosmosCoinToERC20, 69 | newMsgConvertCosmosCoinFromERC20, 70 | }; 71 | -------------------------------------------------------------------------------- /src/msg/hard/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | 3 | function newMsgDeposit(depositor: string, amount: Coin[]) { 4 | return { 5 | type: 'hard/MsgDeposit', 6 | value: { 7 | depositor: depositor, 8 | amount: amount, 9 | }, 10 | }; 11 | } 12 | 13 | function newMsgWithdraw(depositor: string, amount: Coin[]) { 14 | return { 15 | type: 'hard/MsgWithdraw', 16 | value: { 17 | depositor: depositor, 18 | amount: amount, 19 | }, 20 | }; 21 | } 22 | 23 | function newMsgBorrow(borrower: string, amount: Coin[]) { 24 | return { 25 | type: 'hard/MsgBorrow', 26 | value: { 27 | borrower: borrower, 28 | amount: amount, 29 | }, 30 | }; 31 | } 32 | 33 | function newMsgRepay(sender: string, owner: string, amount: Coin[]) { 34 | return { 35 | type: 'hard/MsgRepay', 36 | value: { 37 | sender: sender, 38 | owner: owner, 39 | amount: amount, 40 | }, 41 | }; 42 | } 43 | 44 | function newMsgLiquidate(keeper: string, borrower: string) { 45 | return { 46 | type: 'hard/MsgLiquidate', 47 | value: { 48 | keeper: keeper, 49 | borrower: borrower, 50 | }, 51 | }; 52 | } 53 | 54 | export const hard = { 55 | newMsgDeposit, 56 | newMsgWithdraw, 57 | newMsgBorrow, 58 | newMsgRepay, 59 | newMsgLiquidate, 60 | }; 61 | -------------------------------------------------------------------------------- /src/msg/index.ts: -------------------------------------------------------------------------------- 1 | import { cosmos } from './cosmos'; 2 | import { earn } from './earn'; 3 | import { evmutil } from './evmutil'; 4 | import { hard } from './hard'; 5 | import { kava } from './kava'; 6 | import { liquid } from './liquid'; 7 | import { router } from './router'; 8 | import { savings } from './savings'; 9 | import { swap } from './swap'; 10 | 11 | export const msg = { 12 | cosmos, 13 | earn, 14 | evmutil, 15 | hard, 16 | kava, 17 | liquid, 18 | router, 19 | savings, 20 | swap, 21 | }; 22 | -------------------------------------------------------------------------------- /src/msg/kava/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | import { DenomToClaim } from '../../types/DenomToClaim'; 3 | import { VoteType } from '../../types/VoteType'; 4 | 5 | /*************************************************** 6 | * Auction 7 | ***************************************************/ 8 | 9 | function newMsgPlaceBid(auctionID: string, bidder: string, amount: Coin) { 10 | return { 11 | type: 'auction/MsgPlaceBid', 12 | value: { 13 | auction_id: auctionID, 14 | bidder: bidder, 15 | amount: amount, 16 | }, 17 | }; 18 | } 19 | 20 | /*************************************************** 21 | * BEP3 22 | ***************************************************/ 23 | 24 | // newMsgCreateAtomicSwap creates a new MsgCreateAtomicSwap 25 | function newMsgCreateAtomicSwap( 26 | from: string, 27 | to: string, 28 | recipientOtherChain: string, 29 | senderOtherChain: string, 30 | randomNumberHash: string, 31 | timestamp: number, 32 | amount: Coin[], 33 | heightSpan: number 34 | ) { 35 | return { 36 | type: 'bep3/MsgCreateAtomicSwap', 37 | value: { 38 | from, 39 | to, 40 | recipient_other_chain: recipientOtherChain, 41 | sender_other_chain: senderOtherChain, 42 | random_number_hash: randomNumberHash, 43 | timestamp: String(timestamp), 44 | amount: amount, 45 | height_span: String(heightSpan), 46 | }, 47 | }; 48 | } 49 | 50 | // newMsgClaimAtomicSwap creates a new MsgClaimAtomicSwap 51 | function newMsgClaimAtomicSwap( 52 | from: string, 53 | swapID: string, 54 | randomNumber: string 55 | ) { 56 | return { 57 | type: 'bep3/MsgClaimAtomicSwap', 58 | value: { 59 | from, 60 | swap_id: swapID, 61 | random_number: randomNumber, 62 | }, 63 | }; 64 | } 65 | 66 | // newMsgRefundAtomicSwap creates a new MsgRefundAtomicSwap 67 | function newMsgRefundAtomicSwap(from: string, swapID: string) { 68 | return { 69 | type: 'bep3/MsgRefundAtomicSwap', 70 | value: { 71 | from, 72 | swap_id: swapID, 73 | }, 74 | }; 75 | } 76 | 77 | /*************************************************** 78 | * CDP 79 | ***************************************************/ 80 | 81 | function newMsgCreateCDP( 82 | sender: string, 83 | principal: Coin, 84 | collateral: Coin, 85 | collateralType: string 86 | ) { 87 | return { 88 | type: 'cdp/MsgCreateCDP', 89 | value: { 90 | sender: sender, 91 | principal: principal, 92 | collateral: collateral, 93 | collateral_type: collateralType, 94 | }, 95 | }; 96 | } 97 | 98 | function newMsgDeposit( 99 | owner: string, 100 | depositor: string, 101 | collateral: Coin, 102 | collateralType: string 103 | ) { 104 | return { 105 | type: 'cdp/MsgDeposit', 106 | value: { 107 | owner: owner, 108 | depositor: depositor, 109 | collateral: collateral, 110 | collateral_type: collateralType, 111 | }, 112 | }; 113 | } 114 | 115 | function newMsgWithdraw( 116 | owner: string, 117 | depositor: string, 118 | collateral: Coin, 119 | collateralType: string 120 | ) { 121 | return { 122 | type: 'cdp/MsgWithdraw', 123 | value: { 124 | owner: owner, 125 | depositor: depositor, 126 | collateral: collateral, 127 | collateral_type: collateralType, 128 | }, 129 | }; 130 | } 131 | 132 | function newMsgDrawDebt( 133 | sender: string, 134 | collateralType: string, 135 | principal: Coin 136 | ) { 137 | return { 138 | type: 'cdp/MsgDrawDebt', 139 | value: { 140 | sender: sender, 141 | collateral_type: collateralType, 142 | principal: principal, 143 | }, 144 | }; 145 | } 146 | 147 | function newMsgRepayDebt( 148 | sender: string, 149 | collateralType: string, 150 | payment: Coin 151 | ) { 152 | return { 153 | type: 'cdp/MsgRepayDebt', 154 | value: { 155 | sender: sender, 156 | collateral_type: collateralType, 157 | payment: payment, 158 | }, 159 | }; 160 | } 161 | 162 | function newMsgLiquidate( 163 | keeper: string, 164 | borrower: string, 165 | collateralType: string 166 | ) { 167 | return { 168 | type: 'cdp/MsgLiquidate', 169 | value: { 170 | keeper: keeper, 171 | borrower: borrower, 172 | collateral_type: collateralType, 173 | }, 174 | }; 175 | } 176 | 177 | /*************************************************** 178 | * Committee 179 | ***************************************************/ 180 | 181 | function newMsgSubmitProposal( 182 | pubProposal: string, 183 | proposer: string, 184 | committeeID: string 185 | ) { 186 | return { 187 | type: 'kava/MsgSubmitProposal', 188 | value: { 189 | pub_proposal: pubProposal, 190 | proposer: proposer, 191 | committee_id: String(committeeID), 192 | }, 193 | }; 194 | } 195 | 196 | function newMsgVote(proposalID: string, voter: string, voteType: VoteType) { 197 | return { 198 | type: 'kava/MsgVote', 199 | value: { 200 | proposal_id: String(proposalID), 201 | voter: voter, 202 | vote_type: voteType, 203 | }, 204 | }; 205 | } 206 | 207 | /*************************************************** 208 | * Incentive 209 | ***************************************************/ 210 | 211 | function newMsgClaimUSDXMintingReward(sender: string, multiplierName: string) { 212 | return { 213 | type: 'incentive/MsgClaimUSDXMintingReward', 214 | value: { 215 | sender: sender, 216 | multiplier_name: multiplierName, 217 | }, 218 | }; 219 | } 220 | 221 | function newMsgClaimHardReward(sender: string, denomsToClaim: DenomToClaim[]) { 222 | return { 223 | type: 'incentive/MsgClaimHardReward', 224 | value: { 225 | sender: sender, 226 | denoms_to_claim: denomsToClaim, 227 | }, 228 | }; 229 | } 230 | 231 | function newMsgClaimDelegatorReward( 232 | sender: string, 233 | denomsToClaim: DenomToClaim[] 234 | ) { 235 | return { 236 | type: 'incentive/MsgClaimDelegatorReward', 237 | value: { 238 | sender: sender, 239 | denoms_to_claim: denomsToClaim, 240 | }, 241 | }; 242 | } 243 | 244 | function newMsgClaimSwapReward(sender: string, denomsToClaim: DenomToClaim[]) { 245 | return { 246 | type: 'incentive/MsgClaimSwapReward', 247 | value: { 248 | sender: sender, 249 | denoms_to_claim: denomsToClaim, 250 | }, 251 | }; 252 | } 253 | 254 | function newMsgClaimSavingsReward( 255 | sender: string, 256 | denomsToClaim: DenomToClaim[] 257 | ) { 258 | return { 259 | type: 'incentive/MsgClaimSavingsReward', 260 | value: { 261 | sender: sender, 262 | denoms_to_claim: denomsToClaim, 263 | }, 264 | }; 265 | } 266 | 267 | function newMsgClaimEarnReward(sender: string, denomsToClaim: DenomToClaim[]) { 268 | return { 269 | type: 'incentive/MsgClaimEarnReward', 270 | value: { 271 | sender, 272 | denoms_to_claim: denomsToClaim, 273 | }, 274 | }; 275 | } 276 | 277 | /*************************************************** 278 | * Issuance 279 | ***************************************************/ 280 | 281 | function newMsgIssueTokens(sender: string, tokens: Coin[], receiver: string) { 282 | return { 283 | type: 'issuance/MsgIssueTokens', 284 | value: { 285 | sender: sender, 286 | tokens: tokens, 287 | receiver: receiver, 288 | }, 289 | }; 290 | } 291 | 292 | function newMsgRedeemTokens(sender: string, tokens: Coin[]) { 293 | return { 294 | type: 'issuance/MsgRedeemTokens', 295 | value: { 296 | sender: sender, 297 | tokens: tokens, 298 | }, 299 | }; 300 | } 301 | 302 | function newMsgBlockAddress( 303 | sender: string, 304 | denom: string, 305 | blockedAddress: string 306 | ) { 307 | return { 308 | type: 'issuance/MsgBlockAddress', 309 | value: { 310 | sender: sender, 311 | denom: denom, 312 | blocked_address: blockedAddress, 313 | }, 314 | }; 315 | } 316 | 317 | function newMsgUnblockAddress(sender: string, denom: string, address: string) { 318 | return { 319 | type: 'issuance/MsgUnblockAddress', 320 | value: { 321 | sender: sender, 322 | denom: denom, 323 | address: address, 324 | }, 325 | }; 326 | } 327 | 328 | function newMsgSetPauseStatus(sender: string, denom: string, status: string) { 329 | return { 330 | type: 'issuance/MsgChangePauseStatus', 331 | value: { 332 | sender: sender, 333 | denom: denom, 334 | status: status, 335 | }, 336 | }; 337 | } 338 | 339 | /*************************************************** 340 | * Pricefeed 341 | ***************************************************/ 342 | 343 | function newMsgPostPrice( 344 | from: string, 345 | marketID: string, 346 | price: string, 347 | expiry: string 348 | ) { 349 | return { 350 | type: 'pricefeed/MsgPostPrice', 351 | value: { 352 | from: from, 353 | market_id: marketID, 354 | price: price, 355 | expiry: expiry, 356 | }, 357 | }; 358 | } 359 | 360 | export const kava = { 361 | newMsgPlaceBid, 362 | newMsgCreateAtomicSwap, 363 | newMsgClaimAtomicSwap, 364 | newMsgRefundAtomicSwap, 365 | newMsgCreateCDP, 366 | newMsgDeposit, 367 | newMsgWithdraw, 368 | newMsgDrawDebt, 369 | newMsgRepayDebt, 370 | newMsgLiquidate, 371 | newMsgSubmitProposal, 372 | newMsgVote, 373 | newMsgClaimUSDXMintingReward, 374 | newMsgClaimHardReward, 375 | newMsgClaimDelegatorReward, 376 | newMsgClaimSwapReward, 377 | newMsgClaimSavingsReward, 378 | newMsgClaimEarnReward, 379 | newMsgIssueTokens, 380 | newMsgRedeemTokens, 381 | newMsgBlockAddress, 382 | newMsgUnblockAddress, 383 | newMsgSetPauseStatus, 384 | newMsgPostPrice, 385 | }; 386 | -------------------------------------------------------------------------------- /src/msg/liquid/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | 3 | function newMsgMintDerivative(sender: string, validator: string, amount: Coin) { 4 | return { 5 | type: 'liquid/MsgMintDerivative', 6 | value: { 7 | sender, 8 | validator, 9 | amount, 10 | }, 11 | }; 12 | } 13 | 14 | function newMsgBurnDerivative(sender: string, validator: string, amount: Coin) { 15 | return { 16 | type: 'liquid/MsgBurnDerivative', 17 | value: { 18 | sender, 19 | validator, 20 | amount, 21 | }, 22 | }; 23 | } 24 | 25 | export const liquid = { 26 | newMsgMintDerivative, 27 | newMsgBurnDerivative, 28 | }; 29 | -------------------------------------------------------------------------------- /src/msg/router/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | 3 | function newMsgMintDeposit( 4 | depositor: string, 5 | validator: string, 6 | amount?: Coin 7 | ) { 8 | return { 9 | type: 'router/MsgMintDeposit', 10 | value: { 11 | depositor, 12 | validator, 13 | amount, 14 | }, 15 | }; 16 | } 17 | 18 | function newMsgDelegateMintDeposit( 19 | depositor: string, 20 | validator: string, 21 | amount?: Coin 22 | ) { 23 | return { 24 | type: 'router/MsgDelegateMintDeposit', 25 | value: { 26 | depositor, 27 | validator, 28 | amount, 29 | }, 30 | }; 31 | } 32 | 33 | function newMsgWithdrawBurn(from: string, validator: string, amount?: Coin) { 34 | return { 35 | type: 'router/MsgWithdrawBurn', 36 | value: { 37 | from, 38 | validator, 39 | amount, 40 | }, 41 | }; 42 | } 43 | 44 | function newMsgWithdrawBurnUndelegate( 45 | from: string, 46 | validator: string, 47 | amount?: Coin 48 | ) { 49 | return { 50 | type: 'router/MsgWithdrawBurnUndelegate', 51 | value: { 52 | from, 53 | validator, 54 | amount, 55 | }, 56 | }; 57 | } 58 | 59 | export const router = { 60 | newMsgMintDeposit, 61 | newMsgDelegateMintDeposit, 62 | newMsgWithdrawBurn, 63 | newMsgWithdrawBurnUndelegate, 64 | }; 65 | -------------------------------------------------------------------------------- /src/msg/savings/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | 3 | function newMsgDeposit(depositor: string, amount: Coin[]) { 4 | return { 5 | type: 'savings/MsgDeposit', 6 | value: { 7 | depositor, 8 | amount, 9 | }, 10 | }; 11 | } 12 | 13 | function newMsgWithdraw(depositor: string, amount: Coin[]) { 14 | return { 15 | type: 'savings/MsgWithdraw', 16 | value: { 17 | depositor, 18 | amount, 19 | }, 20 | }; 21 | } 22 | 23 | export const savings = { 24 | newMsgDeposit, 25 | newMsgWithdraw, 26 | }; 27 | -------------------------------------------------------------------------------- /src/msg/swap/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '../../types/Coin'; 2 | 3 | function newMsgDeposit( 4 | depositor: string, 5 | tokenA: Coin, 6 | tokenB: Coin, 7 | slippage: string, 8 | deadline: string 9 | ) { 10 | return { 11 | type: 'swap/MsgDeposit', 12 | value: { 13 | depositor: depositor, 14 | token_a: tokenA, 15 | token_b: tokenB, 16 | slippage: slippage, 17 | deadline: deadline, 18 | }, 19 | }; 20 | } 21 | 22 | function newMsgWithdraw( 23 | from: string, 24 | shares: any, 25 | minTokenA: Coin, 26 | minTokenB: Coin, 27 | deadline: string 28 | ) { 29 | return { 30 | type: 'swap/MsgWithdraw', 31 | value: { 32 | from: from, 33 | shares: shares, 34 | min_token_a: minTokenA, 35 | min_token_b: minTokenB, 36 | deadline: deadline, 37 | }, 38 | }; 39 | } 40 | 41 | function newMsgSwapExactForTokens( 42 | requester: string, 43 | exactTokenA: Coin, 44 | tokenB: Coin, 45 | slippage: string, 46 | deadline: string 47 | ) { 48 | return { 49 | type: 'swap/MsgSwapExactForTokens', 50 | value: { 51 | requester: requester, 52 | exact_token_a: exactTokenA, 53 | token_b: tokenB, 54 | slippage: slippage, 55 | deadline: deadline, 56 | }, 57 | }; 58 | } 59 | 60 | function newMsgSwapForExactTokens( 61 | requester: string, 62 | tokenA: Coin, 63 | exactTokenB: Coin, 64 | slippage: string, 65 | deadline: string 66 | ) { 67 | return { 68 | type: 'swap/MsgSwapForExactTokens', 69 | value: { 70 | requester: requester, 71 | token_a: tokenA, 72 | exact_token_b: exactTokenB, 73 | slippage: slippage, 74 | deadline: deadline, 75 | }, 76 | }; 77 | } 78 | 79 | export const swap = { 80 | newMsgDeposit, 81 | newMsgWithdraw, 82 | newMsgSwapExactForTokens, 83 | newMsgSwapForExactTokens, 84 | }; 85 | -------------------------------------------------------------------------------- /src/tx/index.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/no-var-requires 2 | const sig = require('@kava-labs/sig'); 3 | import axios, { AxiosError } from 'axios'; 4 | import { URL } from 'url'; 5 | 6 | const api = { 7 | getAccount: '/cosmos/auth/v1beta1/accounts', 8 | postTx: '/cosmos/tx/v1beta1/txs', 9 | }; 10 | 11 | /** 12 | * Sends an HTTP GET request to Kava 13 | * @param {String} path the request's url extension 14 | * @param {String} base the request's base url 15 | * @return {Promise} 16 | */ 17 | async function getTx(path: string, base: string, timeout = 5000, args = {}) { 18 | const requestUrl = new URL(path, base).toString(); 19 | 20 | return await retry( 21 | axios.get, 22 | axios, 23 | [applyRequestArgs(requestUrl, args)], 24 | Math.floor(timeout / 1000), 25 | 1000, 26 | false 27 | ); 28 | } 29 | 30 | /** 31 | * Apply args to an HTTP GET request's url 32 | * @param {String} url the request's url (base + path extension) 33 | * @param {String} args the request's http arguments in JSON e.g. {status: 'Open'} 34 | * @return {String} 35 | */ 36 | function applyRequestArgs(url: string, args: Record = {}) { 37 | const search = []; 38 | for (const k in args) { 39 | search.push(`${k}=${args[k]}`); 40 | } 41 | return `${url}?${search.join('&')}`; 42 | } 43 | 44 | type RetryFunction = ( 45 | fn: any, 46 | thisArg: any, 47 | args: string[], 48 | retriesLeft: number, 49 | interval: number, 50 | exponential: boolean 51 | ) => Promise; 52 | 53 | /** 54 | * Retries the given function until it succeeds given a number of retries and an interval between them. They are set 55 | * by default to retry 5 times with 1sec in between. There's also a flag for exponential back-off. 56 | * source (with minor edits): https://gitlab.com/snippets/1775781 57 | * @param {Function} fn - Returns a promise 58 | * @param {Object} thisArg - the object that will be the 'this' argument to the input function 59 | * @param {Array} args - array of arguments to call the input function with 60 | * @param {Number} retriesLeft - Number of retries. If >1 will keep retrying 61 | * @param {Number} interval - milliseconds between retries. If exponential set to true will be doubled each retry 62 | * @param {Boolean} exponential - Flag for exponential back-off mode 63 | * @return {Promise<*>} 64 | */ 65 | const retry: RetryFunction = async ( 66 | fn: any, 67 | thisArg: any, 68 | args: string[], 69 | retriesLeft = 5, 70 | interval = 1000, 71 | exponential = false 72 | ) => { 73 | try { 74 | const result = await fn.apply(thisArg, args); 75 | return result; 76 | } catch (error) { 77 | if (retriesLeft) { 78 | await new Promise((r) => setTimeout(r, interval)); 79 | return retry( 80 | fn, 81 | thisArg, 82 | args, 83 | retriesLeft - 1, 84 | exponential ? interval * 2 : interval, 85 | exponential 86 | ); 87 | } else 88 | throw new Error(`Max retries reached: 89 | error: ${error}`); 90 | } 91 | }; 92 | 93 | /** 94 | * Loads an account's account number and sequence from Kava 95 | * @param {String} address the address to be fetched 96 | * @param {String} base the request's base url 97 | * @param {Number} timeout request is attempted every 1000 milliseconds until millisecond timeout is reached 98 | * @return {Promise} 99 | */ 100 | async function loadMetaData(address: string, base: string, timeout = 2000) { 101 | const path = `${api.getAccount}/${address}`; 102 | const res = await getTx(path, base, timeout); 103 | const account = res?.data?.account; 104 | let seqNum = account?.sequence || '0'; 105 | let accNum = account?.account_number; 106 | const vestingBaseAcct = account?.base_vesting_account?.base_account; 107 | if (vestingBaseAcct) { 108 | seqNum = vestingBaseAcct?.sequence || '0'; 109 | accNum = vestingBaseAcct?.account_number; 110 | } 111 | if (!(accNum || seqNum)) { 112 | throw new Error( 113 | 'account number or sequence number from rest server are undefined' 114 | ); 115 | } 116 | 117 | const signMetaData = { 118 | account_number: accNum, 119 | sequence: seqNum, 120 | }; 121 | 122 | return signMetaData; 123 | } 124 | 125 | /** 126 | * Packages, signs, and verifies a transaction 127 | * @param {Object} tx an unsigned tx object 128 | * @param {Object} signMetaData contains account number, sequence, and chain ID 129 | * @param {Object} wallet the wallet that will be used to sign the tx 130 | * @return {Promise} 131 | */ 132 | function signTx(tx: any, signMetaData: any, wallet: any) { 133 | tx = sig.signTx(tx, signMetaData, wallet); 134 | if (!sig.verifyTx(tx, signMetaData)) { 135 | throw new Error('problem signing tx, generated signature is invalid'); 136 | } 137 | return tx; 138 | } 139 | 140 | /** 141 | * Sends an HTTP POST request containing a signed transaction to Kava 142 | * @param {Object} tx a signed tx 143 | * @param {String} base the request's base url 144 | * @param {String} mode transaction broadcast mode 145 | * @return {Promise} 146 | */ 147 | async function broadcastTx(tx: any, base: string, mode: string) { 148 | let txRes; 149 | try { 150 | const url = new URL(api.postTx, base).toString(); 151 | txRes = await axios.post(url, { tx_bytes: tx, mode: mode }); 152 | } catch (err) { 153 | if (axios.isAxiosError(err)) { 154 | logErr(err); 155 | } 156 | } 157 | 158 | // Check for and handle any tendermint errors 159 | const rsp = txRes?.data?.tx_response; 160 | try { 161 | if (rsp?.code) { 162 | throw new Error(`tx not accepted by chain: ${rsp?.raw_log}`); 163 | } 164 | } catch (err) { 165 | return err; 166 | } 167 | 168 | return rsp?.txhash; 169 | } 170 | 171 | /** 172 | * Parses and logs tx-related errors 173 | * @param {AxiosError} err an error resulting from a tx-related action 174 | */ 175 | const logErr = (err: AxiosError) => { 176 | // Load status, status text, and error 177 | const status = err.response?.status; 178 | const statusText = err.response?.statusText; 179 | const error = err.response?.data; 180 | 181 | // Log status, status text, and error, or if unidentified, log network error 182 | status ? console.log('Status:', status) : null; 183 | statusText ? console.log('Status text:', statusText) : null; 184 | error ? console.log('Error:', error) : null; 185 | if (!status && !statusText && !error) { 186 | console.log('Network error:', err); 187 | } 188 | }; 189 | 190 | export const tx = { 191 | getTx, 192 | loadMetaData, 193 | signTx, 194 | broadcastTx, 195 | logErr, 196 | }; 197 | -------------------------------------------------------------------------------- /src/types/Address.ts: -------------------------------------------------------------------------------- 1 | export type InternalEVMAddress = string; 2 | -------------------------------------------------------------------------------- /src/types/Coin.ts: -------------------------------------------------------------------------------- 1 | export type Coin = { 2 | amount: string; 3 | denom: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/DenomToClaim.ts: -------------------------------------------------------------------------------- 1 | export type DenomToClaim = { 2 | denom: string; 3 | multiplier_name: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/Message.ts: -------------------------------------------------------------------------------- 1 | export type Message = { 2 | type: string; 3 | value: T; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/Strategy.ts: -------------------------------------------------------------------------------- 1 | export enum Strategy { 2 | STRATEGY_TYPE_UNSPECIFIED = 0, 3 | STRATEGY_TYPE_HARD = 1, 4 | STRATEGY_TYPE_SAVINGS = 2, 5 | } 6 | -------------------------------------------------------------------------------- /src/types/VoteType.ts: -------------------------------------------------------------------------------- 1 | export enum VoteType { 2 | YES = 1, 3 | ABSTAIN = 2, 4 | NO = 3, 5 | } 6 | -------------------------------------------------------------------------------- /src/types/Wallet.ts: -------------------------------------------------------------------------------- 1 | export type Wallet = { 2 | address: string; 3 | }; 4 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Address'; 2 | export * from './Coin'; 3 | export * from './DenomToClaim'; 4 | export * from './Strategy'; 5 | export * from './VoteType'; 6 | export * from './Wallet'; 7 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import SHA256 from 'crypto-js/sha256'; 2 | import hexEncoding from 'crypto-js/enc-hex'; 3 | import Big from 'big.js'; 4 | import cryptoRand from 'crypto'; 5 | import { crypto } from '../crypto'; 6 | import { ethers } from 'ethers'; 7 | import bech32 from 'bech32'; 8 | 9 | const RandomNumberLength = 64; 10 | 11 | // Precision is relative to KAVA or 10**6 12 | const precision: Record = { 13 | kava: 1, 14 | ukava: Math.pow(10, 6), 15 | }; 16 | 17 | /** 18 | * Computes a single SHA256 digest. 19 | * @param {string} hex message to hash 20 | * @returns {string} hash output 21 | */ 22 | const sha256 = (hex: string) => { 23 | if (typeof hex !== 'string') throw new Error('sha256 expects a hex string'); 24 | if (hex.length % 2 !== 0) 25 | throw new Error(`invalid hex string length: ${hex}`); 26 | const hexEncoded = hexEncoding.parse(hex); 27 | return SHA256(hexEncoded).toString(); 28 | }; 29 | 30 | /** 31 | * Generates a hex-encoded 256-bit random number 32 | * @returns {string} the hex-encoded number 33 | */ 34 | const generateRandomNumber = () => { 35 | return cryptoRand 36 | .randomBytes(Math.ceil(RandomNumberLength / 2)) 37 | .toString('hex') 38 | .slice(0, RandomNumberLength); 39 | }; 40 | 41 | /** 42 | * Computes sha256 of random number and timestamp 43 | * @param {String} randomNumber 44 | * @param {Number} timestamp 45 | * @returns {string} sha256 result 46 | */ 47 | const calculateRandomNumberHash = (randomNumber: string, timestamp: number) => { 48 | const timestampHexStr = timestamp.toString(16); 49 | let timestampHexStrFormat = timestampHexStr; 50 | for (let i = 0; i < 16 - timestampHexStr.length; i++) { 51 | timestampHexStrFormat = '0' + timestampHexStrFormat; 52 | } 53 | const timestampBytes = Buffer.from(timestampHexStrFormat, 'hex'); 54 | const newBuffer = Buffer.concat([ 55 | Buffer.from(randomNumber, 'hex'), 56 | timestampBytes, 57 | ]); 58 | return sha256(newBuffer.toString('hex')); 59 | }; 60 | 61 | /** 62 | * Computes swapID 63 | * @param {String} randomNumberHash 64 | * @param {String} sender 65 | * @param {String} senderOtherChain 66 | * @returns {string} sha256 result 67 | */ 68 | const calculateSwapID = ( 69 | randomNumberHash: string, 70 | sender: string, 71 | senderOtherChain: string 72 | ) => { 73 | const randomNumberHashBytes = Buffer.from(randomNumberHash, 'hex'); 74 | const senderBytes = crypto.decodeAddress(sender); 75 | const sendOtherChainBytes = Buffer.from( 76 | senderOtherChain.toLowerCase(), 77 | 'utf8' 78 | ); 79 | const newBuffer = Buffer.concat([ 80 | randomNumberHashBytes, 81 | senderBytes, 82 | sendOtherChainBytes, 83 | ]); 84 | return sha256(newBuffer.toString('hex')); 85 | }; 86 | 87 | /** 88 | * Converts coin decimals between kava and ukava 89 | * @param {String} inputAmount value of the input asset 90 | * @param {String} inputDenom denom of the input asset 91 | * @param {String} outputDenom denom of the output asset 92 | * @return {object} coins result 93 | */ 94 | const convertCoinDecimals = ( 95 | inputAmount: string, 96 | inputDenom: string, 97 | outputDenom: string 98 | ) => { 99 | const amount = new Big(inputAmount); 100 | 101 | try { 102 | if (!precision[inputDenom] || !precision[outputDenom]) { 103 | throw new Error('Invalid asset pairing for decimal conversion.'); 104 | } 105 | } catch (err) { 106 | if (err instanceof Error) { 107 | console.log('Error:', err.message); 108 | } 109 | return; 110 | } 111 | 112 | const amountString = amount 113 | .mul(precision[outputDenom]) 114 | .div(precision[inputDenom]) 115 | .toString(); 116 | 117 | return formatCoins(amountString, outputDenom); 118 | }; 119 | 120 | /** 121 | * Formats a denom and amount into Cosmos-SDK compatible sdk.Coin object 122 | * @param {String} amount value of the asset 123 | * @param {String} denom name of the asset 124 | * @return {object} resulting formatted coin 125 | */ 126 | const formatCoin = (amount: string | number, denom: string) => { 127 | return { 128 | denom: String(denom), 129 | amount: String(amount), 130 | }; 131 | }; 132 | 133 | /** 134 | * Formats a denom and amount into Cosmos-SDK compatible sdk.Coins object 135 | * @param {String} amount value of the asset 136 | * @param {String} denom name of the asset 137 | * @return {object} resulting formatted coins 138 | */ 139 | const formatCoins = (amount: string | number, denom: string) => { 140 | return [ 141 | { 142 | denom: String(denom), 143 | amount: String(amount), 144 | }, 145 | ]; 146 | }; 147 | 148 | /** 149 | * Formats an array of denoms and amounts into Cosmos-SDK compatible sdk.Coins object 150 | * @param {String} amounts an array of asset amounts 151 | * @param {String} denoms an array of asset denoms 152 | * @return {object} resulting formatted coins 153 | */ 154 | const formatMultiCoins = (amounts: string[], denoms: string[]) => { 155 | try { 156 | if (amounts.length != denoms.length) { 157 | throw new Error('Every amount must have exactly 1 corresponding denom.'); 158 | } 159 | } catch (err) { 160 | if (err instanceof Error) { 161 | console.log('Error:', err.message); 162 | } 163 | return; 164 | } 165 | 166 | const coins = []; 167 | for (let i = 0; i < amounts.length; i++) { 168 | const coin = formatCoin(amounts[i], denoms[i]); 169 | coins.push(coin); 170 | } 171 | return coins; 172 | }; 173 | 174 | /** 175 | * Takes current day and adds desired amount of seconds and converts to unix time 176 | * @param {number} seconds for the transaction to process 177 | * @return {object} resulting in a unix timestring, defaulting to 1 day 178 | */ 179 | 180 | const calculateUnixTime = (seconds = 86400) => { 181 | return String(Math.floor(Date.now() / 1000) + seconds); 182 | }; 183 | 184 | /** 185 | * 186 | * @param kavaAddress string 187 | * @returns string representing eth address from given kava address 188 | */ 189 | export function kavaToEthAddress(kavaAddress: string) { 190 | return ethers.utils.getAddress( 191 | ethers.utils.hexlify(bech32.fromWords(bech32.decode(kavaAddress).words)) 192 | ); 193 | } 194 | 195 | /** 196 | * 197 | * @param ethereumAddress string 198 | * @returns string representing kava address from give eth address 199 | */ 200 | export function ethToKavaAddress(ethereumAddress: string) { 201 | return bech32.encode( 202 | 'kava', 203 | bech32.toWords( 204 | ethers.utils.arrayify(ethers.utils.getAddress(ethereumAddress)) 205 | ) 206 | ); 207 | } 208 | 209 | export const utils = { 210 | generateRandomNumber, 211 | calculateRandomNumberHash, 212 | calculateSwapID, 213 | calculateUnixTime, 214 | convertCoinDecimals, 215 | formatCoin, 216 | formatCoins, 217 | formatMultiCoins, 218 | ethToKavaAddress, 219 | kavaToEthAddress, 220 | }; 221 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Node 14", 4 | 5 | "compilerOptions": { 6 | "downlevelIteration": true, 7 | "lib": ["es2020"], 8 | "module": "commonjs", 9 | "target": "es5", 10 | "declaration": true, 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "outDir": "./lib", 14 | "skipLibCheck": true, 15 | "strict": true, 16 | "typeRoots": ["./node_modules/@types/", "./src/@types"] 17 | }, 18 | "include": ["src"] 19 | } 20 | --------------------------------------------------------------------------------