├── .circleci └── config.yml ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── example └── send.ts ├── package-lock.json ├── package.json ├── scripts ├── deploy.js ├── package-lock.json ├── package.json └── test.js ├── src ├── common │ ├── address.ts │ ├── baseAccount.ts │ ├── codec.ts │ ├── coin.spec.ts │ ├── coin.ts │ ├── decimal.spec.ts │ ├── decimal.ts │ ├── int.ts │ ├── stdTx.spec.ts │ ├── stdTx.ts │ └── stdTxBuilder.ts ├── core │ ├── account.ts │ ├── api.ts │ ├── bech32Config.ts │ ├── bip44.ts │ ├── context.spec.ts │ ├── context.ts │ ├── ledgerWallet.ts │ ├── query.ts │ ├── rest.ts │ ├── rpc.ts │ ├── tx.ts │ ├── txBuilder.ts │ ├── walletProvider.spec.ts │ └── walletProvider.ts ├── crypto │ ├── codec.ts │ ├── index.ts │ ├── secp256k1.spec.ts │ ├── secp256k1.ts │ └── types.ts ├── gaia │ ├── api.ts │ └── rest.ts ├── rpc │ ├── abci.ts │ ├── status.ts │ ├── tendermint.ts │ ├── tx.ts │ └── types.ts ├── utils │ ├── key.spec.ts │ ├── key.ts │ ├── sortJson.spec.ts │ └── sortJson.ts └── x │ ├── bank │ ├── codec.ts │ ├── index.ts │ └── msgs.ts │ ├── distribution │ ├── codec.ts │ ├── index.ts │ └── msgs.ts │ ├── gov │ ├── codec.ts │ ├── index.ts │ └── msgs.ts │ ├── slashing │ ├── codec.ts │ ├── index.ts │ └── msgs.ts │ ├── staking │ ├── codec.ts │ ├── index.ts │ └── msgs.ts │ └── wasm │ ├── codec.ts │ ├── index.ts │ └── msgs.ts ├── tsconfig.json └── typedoc.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | # specify the version you desire here 6 | - image: circleci/node:10.15.3 7 | 8 | # Specify service dependencies here if necessary 9 | # CircleCI maintains a library of pre-built images 10 | # documented at https://circleci.com/docs/2.0/circleci-images/ 11 | # - image: circleci/mongo:3.4.4 12 | 13 | working_directory: ~/cosmosjs 14 | 15 | steps: 16 | - checkout 17 | - run: sudo apt-get install libusb-1.0-0-dev 18 | - run: npm install 19 | - run: 20 | path: ~/cosmosjs/scripts 21 | command: npm install 22 | - run: 23 | path: ~/cosmosjs/scripts 24 | command: sudo npm install -g 25 | - run: 26 | path: ~/cosmosjs/scripts 27 | command: cosmosjs-test 28 | deploy: 29 | docker: 30 | # specify the version you desire here 31 | - image: circleci/node:10.15.3 32 | 33 | # Specify service dependencies here if necessary 34 | # CircleCI maintains a library of pre-built images 35 | # documented at https://circleci.com/docs/2.0/circleci-images/ 36 | # - image: circleci/mongo:3.4.4 37 | 38 | working_directory: ~/cosmosjs 39 | 40 | steps: 41 | - checkout 42 | - run: sudo apt-get install libusb-1.0-0-dev 43 | - run: npm install 44 | - run: 45 | path: ~/cosmosjs/scripts 46 | command: npm install 47 | - run: 48 | path: ~/cosmosjs/scripts 49 | command: sudo npm install -g 50 | - run: 51 | path: ~/cosmosjs/scripts 52 | command: cosmosjs-deploy --npm-token=${NPM_AUTH} 53 | - add_ssh_keys: 54 | fingerprints: 55 | - "d9:b3:6a:a6:8d:39:ef:6a:90:b1:af:d6:80:8b:c3:c7" 56 | - run: git config user.email "yunjh1994@gmail.com" 57 | - run: git config user.name "Thunnini" 58 | - run: npm run deploy-docs 59 | 60 | workflows: 61 | version: 2 62 | test-deploy: 63 | jobs: 64 | - test: 65 | filters: 66 | tags: 67 | only: /.*/ 68 | - deploy: 69 | requires: 70 | - test 71 | filters: 72 | branches: 73 | ignore: /.*/ 74 | tags: 75 | only: /^v\d+\.\d+\.\d+-?[\w.]*$/ 76 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | extends: [ 4 | "plugin:@typescript-eslint/recommended", 5 | ], 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: "module" 9 | }, 10 | rules: { 11 | "@typescript-eslint/explicit-function-return-type": "off", 12 | "@typescript-eslint/no-explicit-any": "off", 13 | "@typescript-eslint/no-non-null-assertion": "off", 14 | "@typescript-eslint/no-inferrable-types": "off", 15 | "@typescript-eslint/no-use-before-define": "off", 16 | "@typescript-eslint/interface-name-prefix": "off", 17 | "@typescript-eslint/no-var-requires": "off", 18 | "@typescript-eslint/no-unused-vars": "off" 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist/ 3 | node_modules/ 4 | .vscode 5 | docs/ 6 | .idea 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .git* 3 | .idea 4 | .vscode/ 5 | .circleci/ 6 | docs/ 7 | node_modules/ 8 | scripts/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | License: Apache2.0 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following 183 | boilerplate notice, with the fields enclosed by brackets "{}" 184 | replaced with your own identifying information. (Don't include 185 | the brackets!) The text should be enclosed in the appropriate 186 | comment syntax for the file format. We also recommend that a 187 | file or class name and description of purpose be included on the 188 | same "printed page" as the copyright notice for easier 189 | identification within third-party archives. 190 | 191 | Copyright 2020 Chainapsis, Inc 192 | 193 | Licensed under the Apache License, Version 2.0 (the "License"); 194 | you may not use this file except in compliance with the License. 195 | You may obtain a copy of the License at 196 | 197 | http://www.apache.org/licenses/LICENSE-2.0 198 | 199 | Unless required by applicable law or agreed to in writing, software 200 | distributed under the License is distributed on an "AS IS" BASIS, 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 202 | See the License for the specific language governing permissions and 203 | limitations under the License. 204 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

Welcome to cosmosjs 👋

3 |

4 | 5 | 6 | Twitter: Chainapsis 7 | 8 |

9 | 10 | > General purpose library for cosmos-sdk 11 | 12 | Our goal is to create a general purpose library for the Cosmos ecosystem. Through this library, blockchains that use cosmos-sdk, as well as Cosmos hub (Gaia), can create their own API for JavaScript client side. 13 | 14 | Documentation can be found [here](https://chainapsis.github.io/cosmosjs/). 15 | 16 | ## Install 17 | ```sh 18 | npm install --save @chainapsis/cosmosjs 19 | ``` 20 | 21 | ## How to use 22 | More examples will be provided [here](https://github.com/chainapsis/cosmosjs/tree/master/example) soon. 23 | ```ts 24 | import { GaiaApi } from "../src/gaia/api"; 25 | import { LedgerWalletProvider } from "../src/core/ledgerWallet"; 26 | import { MsgSend } from "../src/x/bank"; 27 | import { AccAddress } from "../src/common/address"; 28 | import { Coin } from "../src/common/coin"; 29 | import { Int } from "../src/common/int"; 30 | import bigInteger from "big-integer"; 31 | 32 | (async () => { 33 | // Here you can see the type of transport 34 | // https://github.com/LedgerHQ/ledgerjs 35 | const wallet = new LedgerWalletProvider("HID", "cosmos"); 36 | /* 37 | // You should not use local wallet provider in production 38 | const wallet = new LocalWalletProvider( 39 | "anger river nuclear pig enlist fish demand dress library obtain concert nasty wolf episode ring bargain rely off vibrant iron cram witness extra enforce" 40 | ); 41 | */ 42 | 43 | const api = new GaiaApi({ 44 | chainId: "cosmoshub-3", 45 | walletProvider: wallet, 46 | rpc: "http://localhost:26657", 47 | rest: "http://localhost:1317" 48 | }); 49 | 50 | // You should sign in before using your wallet 51 | await api.enable(); 52 | 53 | const key = (await api.getKeys())[0]; 54 | const accAddress = new AccAddress(key.address, "cosmos"); 55 | 56 | await api.sendMsgs( 57 | [ 58 | new MsgSend(accAddress, accAddress, [new Coin("uatom", new Int("1"))]), 59 | new MsgSend(accAddress, accAddress, [new Coin("uatom", new Int("1"))]) 60 | ], 61 | { 62 | // If account number or sequence is omitted, they are calculated automatically 63 | gas: bigInteger(60000), 64 | memo: "test", 65 | fee: new Coin("uatom", new Int("111")) 66 | }, 67 | "commit" 68 | ); 69 | })(); 70 | ``` 71 | 72 | ## Making your own messages 73 | Below is Gaia's basic sending message. 74 | More examples are [here](https://github.com/chainapsis/cosmosjs/tree/master/src/x). 75 | ```ts 76 | import { Amino, Type } from "ts-amino"; 77 | const { Field, Concrete, DefineStruct } = Amino; 78 | import { Msg } from "../../core/tx"; 79 | import { AccAddress } from "../../common/address"; 80 | import { Coin } from "../../common/coin"; 81 | import { Int } from "../../common/int"; 82 | 83 | @Concrete("cosmos-sdk/MsgSend") 84 | @DefineStruct() 85 | export class MsgSend extends Msg { 86 | @Field.Defined(0, { 87 | jsonName: "from_address" 88 | }) 89 | public fromAddress: AccAddress; 90 | 91 | @Field.Defined(1, { 92 | jsonName: "to_address" 93 | }) 94 | public toAddress: AccAddress; 95 | 96 | @Field.Slice( 97 | 2, 98 | { type: Type.Defined }, 99 | { 100 | jsonName: "amount" 101 | } 102 | ) 103 | public amount: Coin[]; 104 | 105 | constructor(fromAddress: AccAddress, toAddress: AccAddress, amount: Coin[]) { 106 | super(); 107 | this.fromAddress = fromAddress; 108 | this.toAddress = toAddress; 109 | this.amount = amount; 110 | } 111 | 112 | public getSigners(): AccAddress[] { 113 | return [this.fromAddress]; 114 | } 115 | 116 | /** 117 | * ValidateBasic does a simple validation check that 118 | * doesn't require access to any other information. 119 | * You can throw error in this when msg is invalid. 120 | */ 121 | public validateBasic(): void { 122 | for (const coin of this.amount) { 123 | if (coin.amount.lte(new Int(0))) { 124 | throw new Error("Send amount is invalid"); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * Get the canonical byte representation of the Msg. 131 | * @return Return sorted by alphabetically amino encoded json by default. 132 | */ 133 | // public getSignBytes(): Uint8Array { 134 | // return Buffer.from(sortJSON(marshalJson(this)), "utf8"); 135 | // } 136 | } 137 | ``` 138 | 139 | ## Making api for your own blockchain 140 | Check out [this](https://github.com/chainapsis/cosmosjs/tree/master/src/gaia). 141 | 142 | ## Run tests 143 | ```sh 144 | npm run test 145 | ``` 146 | 147 | ## Author 148 | 👤 **chainapsis** 149 | * Twitter: [@Chainapsis](https://twitter.com/chainapsis) 150 | * Github: [chainapsis](https://github.com/chainapsis) 151 | -------------------------------------------------------------------------------- /example/send.ts: -------------------------------------------------------------------------------- 1 | import { GaiaApi } from "../src/gaia/api"; 2 | import { LedgerWalletProvider } from "../src/core/ledgerWallet"; 3 | import { MsgSend } from "../src/x/bank"; 4 | import { AccAddress } from "../src/common/address"; 5 | import { Coin } from "../src/common/coin"; 6 | import { Int } from "../src/common/int"; 7 | import bigInteger from "big-integer"; 8 | 9 | (async () => { 10 | // Here you can see the type of transport 11 | // https://github.com/LedgerHQ/ledgerjs 12 | const wallet = new LedgerWalletProvider("HID", "cosmos"); 13 | /* 14 | // You should not use local wallet provider in production 15 | const wallet = new LocalWalletProvider( 16 | "anger river nuclear pig enlist fish demand dress library obtain concert nasty wolf episode ring bargain rely off vibrant iron cram witness extra enforce" 17 | ); 18 | */ 19 | 20 | const api = new GaiaApi({ 21 | chainId: "cosmoshub-3", 22 | walletProvider: wallet, 23 | rpc: "http://localhost:26657", 24 | rest: "http://localhost:1317" 25 | }); 26 | 27 | // You should sign in before using your wallet 28 | await api.enable(); 29 | 30 | const key = (await api.getKeys())[0]; 31 | const accAddress = new AccAddress(key.address, "cosmos"); 32 | 33 | await api.sendMsgs( 34 | [ 35 | new MsgSend(accAddress, accAddress, [new Coin("uatom", new Int("1"))]), 36 | new MsgSend(accAddress, accAddress, [new Coin("uatom", new Int("1"))]) 37 | ], 38 | { 39 | // If account number or sequence is omitted, they are calculated automatically 40 | gas: bigInteger(60000), 41 | memo: "test", 42 | fee: new Coin("uatom", new Int("111")) 43 | }, 44 | "commit" 45 | ); 46 | })(); 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainapsis/cosmosjs", 3 | "version": "v0.0.3-alpha.3", 4 | "homepage": "https://github.com/chainapsis/cosmosjs", 5 | "description": "General purpose library for cosmos-sdk", 6 | "main": "index.js", 7 | "types": "index.d.ts", 8 | "scripts": { 9 | "build": "tsc", 10 | "test": "mocha -r ts-node/register src/**/**/*.spec.ts src/**/*.spec.ts src/*.spec.ts", 11 | "lint": "eslint \"src/**/*\"", 12 | "lint-fix": "eslint --fix \"src/**/*\"", 13 | "prettier": "find ./src -type f | xargs prettier --check", 14 | "prettier-write": "find ./src -type f | xargs prettier --write", 15 | "deploy-docs": "typedoc ./src && touch ./docs/.nojekyll && gh-pages --dist docs -m '[ci skip] Update document' --dotfiles" 16 | }, 17 | "pre-commit": [ 18 | "lint", 19 | "prettier" 20 | ], 21 | "keywords": [ 22 | "cosmos", 23 | "blockchain" 24 | ], 25 | "author": "chainapsis", 26 | "license": "Apache-2.0", 27 | "dependencies": { 28 | "@ledgerhq/hw-transport-node-hid": "^5.15.0", 29 | "@ledgerhq/hw-transport-u2f": "^4.63.2", 30 | "@ledgerhq/hw-transport-webusb": "^4.63.2", 31 | "@chainapsis/ts-amino": "0.0.1-alpha.4", 32 | "axios": "^0.19.0", 33 | "bech32": "^1.1.3", 34 | "big-integer": "^1.6.43", 35 | "bip32": "^2.0.3", 36 | "bip39": "^3.0.2", 37 | "buffer": "^5.2.1", 38 | "crypto-js": "^4.0.0", 39 | "elliptic": "^6.5.2", 40 | "ledger-cosmos-js": "^2.0.2" 41 | }, 42 | "devDependencies": { 43 | "@types/bech32": "^1.1.1", 44 | "@types/crypto-js": "^3.1.47", 45 | "@types/elliptic": "^6.4.12", 46 | "@types/mocha": "^5.2.6", 47 | "@types/node": "^11.13.2", 48 | "@typescript-eslint/eslint-plugin": "^2.3.2", 49 | "@typescript-eslint/parser": "^2.3.2", 50 | "babel-polyfill": "^6.26.0", 51 | "eslint": "^6.5.1", 52 | "gh-pages": "^2.0.1", 53 | "mocha": "^6.1.4", 54 | "node-fetch": "^2.5.0", 55 | "pre-commit": "^1.2.2", 56 | "prettier": "^1.17.1", 57 | "ts-node": "^8.1.0", 58 | "tslint-config-prettier": "^1.18.0", 59 | "tslint-plugin-prettier": "^2.0.1", 60 | "typedoc": "^0.14.2", 61 | "typescript": "^3.4.2" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const shell = require("shelljs") 4 | const program = require('commander'); 5 | const semver = require("semver") 6 | 7 | const version = require(__dirname + "/../package.json").version 8 | 9 | program 10 | .option('-v, --semver ') 11 | .option('--npm-token ') 12 | .action(() => { 13 | shell.cd(__dirname + "/..") 14 | 15 | if (!program.semver) { 16 | shell.echo("Semver not provided: --semver ") 17 | shell.echo("Try to parse semver from git tags") 18 | 19 | if (shell.exec("git rev-parse master").stdout !== shell.exec("git rev-parse HEAD").stdout) { 20 | shell.echo("This is not master branch") 21 | shell.exit(1) 22 | } 23 | 24 | const tags = shell.exec("git tag --points-at HEAD").stdout.split("\n") 25 | for (const tag of tags) { 26 | const re = /^v\d+\.\d+\.\d+-?[\w.]*$/ 27 | if (re.test(tag)) { 28 | program.semver = tag 29 | break; 30 | } 31 | } 32 | } 33 | 34 | if (!semver.valid(program.semver)) { 35 | shell.echo("Invalid version provided " + program.semver) 36 | shell.exit(1) 37 | } 38 | 39 | if (!semver.valid(version)) { 40 | shell.echo("Invalid version " + version) 41 | shell.exit(1) 42 | } 43 | 44 | if (program.semver !== version) { 45 | shell.echo(`Packge version(${version}) and provided version(${program.semver}) don't match`) 46 | shell.exit(1) 47 | } 48 | 49 | if (!program.npmToken) { 50 | shell.echo("You should set npm token: --npm-token ") 51 | shell.exit(1) 52 | } 53 | 54 | shell.echo("Build typescript...") 55 | if (shell.exec("npm run build").code !== 0) { 56 | shell.echo("Fail to build typescript") 57 | shell.exit(1) 58 | } 59 | 60 | if (shell.cp("-f", ["./package.json", "./README.md", "LICENSE"], "./dist").code !== 0) { 61 | shell.echo("Fail to move package.json to folder under dist") 62 | shell.exit(1) 63 | } 64 | 65 | shell.cd("./dist") 66 | 67 | if (shell.exec(`npm config set //registry.npmjs.org/:_authToken ${program.npmToken}`).code !== 0) { 68 | shell.echo("Fail to set npm token") 69 | shell.exit(1) 70 | } 71 | 72 | if (shell.exec("npm publish --verbose --access public").code !== 0) { 73 | shell.echo("Fail to publish to npm") 74 | shell.exit(1) 75 | } 76 | }) 77 | .parse(process.argv) 78 | -------------------------------------------------------------------------------- /scripts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 11 | }, 12 | "brace-expansion": { 13 | "version": "1.1.11", 14 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 15 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 16 | "requires": { 17 | "balanced-match": "^1.0.0", 18 | "concat-map": "0.0.1" 19 | } 20 | }, 21 | "commander": { 22 | "version": "2.20.0", 23 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", 24 | "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" 25 | }, 26 | "concat-map": { 27 | "version": "0.0.1", 28 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 29 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 30 | }, 31 | "fs.realpath": { 32 | "version": "1.0.0", 33 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 34 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 35 | }, 36 | "glob": { 37 | "version": "7.1.4", 38 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", 39 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", 40 | "requires": { 41 | "fs.realpath": "^1.0.0", 42 | "inflight": "^1.0.4", 43 | "inherits": "2", 44 | "minimatch": "^3.0.4", 45 | "once": "^1.3.0", 46 | "path-is-absolute": "^1.0.0" 47 | } 48 | }, 49 | "inflight": { 50 | "version": "1.0.6", 51 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 52 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 53 | "requires": { 54 | "once": "^1.3.0", 55 | "wrappy": "1" 56 | } 57 | }, 58 | "inherits": { 59 | "version": "2.0.4", 60 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 61 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 62 | }, 63 | "interpret": { 64 | "version": "1.2.0", 65 | "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", 66 | "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" 67 | }, 68 | "minimatch": { 69 | "version": "3.0.4", 70 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 71 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 72 | "requires": { 73 | "brace-expansion": "^1.1.7" 74 | } 75 | }, 76 | "once": { 77 | "version": "1.4.0", 78 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 79 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 80 | "requires": { 81 | "wrappy": "1" 82 | } 83 | }, 84 | "path-is-absolute": { 85 | "version": "1.0.1", 86 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 87 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 88 | }, 89 | "path-parse": { 90 | "version": "1.0.6", 91 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", 92 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" 93 | }, 94 | "rechoir": { 95 | "version": "0.6.2", 96 | "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", 97 | "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", 98 | "requires": { 99 | "resolve": "^1.1.6" 100 | } 101 | }, 102 | "resolve": { 103 | "version": "1.11.1", 104 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz", 105 | "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==", 106 | "requires": { 107 | "path-parse": "^1.0.6" 108 | } 109 | }, 110 | "semver": { 111 | "version": "6.1.2", 112 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.2.tgz", 113 | "integrity": "sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ==" 114 | }, 115 | "shelljs": { 116 | "version": "0.8.3", 117 | "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", 118 | "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", 119 | "requires": { 120 | "glob": "^7.0.0", 121 | "interpret": "^1.0.0", 122 | "rechoir": "^0.6.2" 123 | } 124 | }, 125 | "wrappy": { 126 | "version": "1.0.2", 127 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 128 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /scripts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scripts", 3 | "version": "1.0.0", 4 | "bin": { 5 | "cosmosjs-test": "./test.js", 6 | "cosmosjs-deploy": "./deploy.js" 7 | }, 8 | "dependencies": { 9 | "commander": "^2.20.0", 10 | "semver": "^6.1.2", 11 | "shelljs": "^0.8.3" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const shell = require("shelljs") 4 | 5 | shell.cd(__dirname) 6 | shell.cd("../") 7 | if (shell.exec("npm run test").code !== 0) { 8 | shell.echo("Fail to pass tests") 9 | shell.exit(1) 10 | } 11 | -------------------------------------------------------------------------------- /src/common/address.ts: -------------------------------------------------------------------------------- 1 | import { Amino, Type } from "@chainapsis/ts-amino"; 2 | const { Field, DefineType } = Amino; 3 | import bech32 from "bech32"; 4 | import { Address } from "../crypto"; 5 | import { Buffer } from "buffer/"; 6 | 7 | @DefineType() 8 | export class AccAddress { 9 | /** 10 | * Parse the address from bech32. 11 | * @param bech32Addr bech32 address string. 12 | * @param prefix If prefix is not provided, parse the address without verifying with suggested prefix. 13 | */ 14 | public static fromBech32(bech32Addr: string, prefix?: string): AccAddress { 15 | if (prefix === "") { 16 | throw new Error("Empty bech32 prefix"); 17 | } 18 | const { prefix: b32Prefix, words } = bech32.decode(bech32Addr); 19 | if (prefix != null && b32Prefix !== prefix) { 20 | throw new Error("Prefix doesn't match"); 21 | } 22 | return new AccAddress( 23 | bech32.fromWords(words), 24 | prefix != null ? prefix : b32Prefix 25 | ); 26 | } 27 | 28 | @Field.Array(0, { type: Type.Uint8 }) 29 | private readonly address: Uint8Array; 30 | 31 | public readonly bech32Prefix: string; 32 | 33 | constructor(address: Uint8Array | Address, bech32Prefix: string) { 34 | if (!bech32Prefix) { 35 | throw new Error("Empty bech32 prefix"); 36 | } 37 | 38 | this.address = address instanceof Address ? address.toBytes() : address; 39 | this.bech32Prefix = bech32Prefix; 40 | } 41 | 42 | public toBech32(): string { 43 | if (!this.bech32Prefix) { 44 | throw new Error("Empty bech32 prefix"); 45 | } 46 | const words = bech32.toWords(Buffer.from(this.address) as any); 47 | return bech32.encode(this.bech32Prefix, words); 48 | } 49 | 50 | public toBytes(): Uint8Array { 51 | return new Uint8Array(this.address); 52 | } 53 | 54 | public marshalJSON(): Uint8Array { 55 | return Buffer.from(`"${this.toBech32()}"`, "utf8"); 56 | } 57 | } 58 | 59 | @DefineType() 60 | export class ValAddress { 61 | /** 62 | * Parse the address from bech32. 63 | * @param bech32Addr bech32 address string. 64 | * @param prefix If prefix is not provided, parse the address without verifying with suggested prefix. 65 | */ 66 | public static fromBech32(bech32Addr: string, prefix?: string): ValAddress { 67 | if (prefix === "") { 68 | throw new Error("Empty bech32 prefix"); 69 | } 70 | const { prefix: b32Prefix, words } = bech32.decode(bech32Addr); 71 | if (prefix != null && b32Prefix !== prefix) { 72 | throw new Error("Prefix doesn't match"); 73 | } 74 | return new ValAddress( 75 | bech32.fromWords(words), 76 | prefix != null ? prefix : b32Prefix 77 | ); 78 | } 79 | 80 | @Field.Array(0, { type: Type.Uint8 }) 81 | private readonly address: Uint8Array; 82 | 83 | public readonly bech32Prefix: string; 84 | 85 | constructor(address: Uint8Array | Address, bech32Prefix: string) { 86 | if (!bech32Prefix) { 87 | throw new Error("Empty bech32 prefix"); 88 | } 89 | 90 | this.address = address instanceof Address ? address.toBytes() : address; 91 | this.bech32Prefix = bech32Prefix; 92 | } 93 | 94 | public toBech32(): string { 95 | if (!this.bech32Prefix) { 96 | throw new Error("Empty bech32 prefix"); 97 | } 98 | const words = bech32.toWords(Buffer.from(this.address) as any); 99 | return bech32.encode(this.bech32Prefix, words); 100 | } 101 | 102 | public toBytes(): Uint8Array { 103 | return new Uint8Array(this.address); 104 | } 105 | 106 | public marshalJSON(): Uint8Array { 107 | return Buffer.from(`"${this.toBech32()}"`, "utf8"); 108 | } 109 | } 110 | 111 | @DefineType() 112 | export class ConsAddress { 113 | /** 114 | * Parse the address from bech32. 115 | * @param bech32Addr bech32 address string. 116 | * @param prefix If prefix is not provided, parse the address without verifying with suggested prefix. 117 | */ 118 | public static fromBech32(bech32Addr: string, prefix?: string): ConsAddress { 119 | if (prefix === "") { 120 | throw new Error("Empty bech32 prefix"); 121 | } 122 | const { prefix: b32Prefix, words } = bech32.decode(bech32Addr); 123 | if (prefix != null && b32Prefix !== prefix) { 124 | throw new Error("Prefix doesn't match"); 125 | } 126 | return new ConsAddress( 127 | bech32.fromWords(words), 128 | prefix != null ? prefix : b32Prefix 129 | ); 130 | } 131 | 132 | @Field.Array(0, { type: Type.Uint8 }) 133 | private readonly address: Uint8Array; 134 | 135 | public readonly bech32Prefix: string; 136 | 137 | constructor(address: Uint8Array | Address, bech32Prefix: string) { 138 | if (!bech32Prefix) { 139 | throw new Error("Empty bech32 prefix"); 140 | } 141 | 142 | this.address = address instanceof Address ? address.toBytes() : address; 143 | this.bech32Prefix = bech32Prefix; 144 | } 145 | 146 | public toBech32(): string { 147 | if (!this.bech32Prefix) { 148 | throw new Error("Empty bech32 prefix"); 149 | } 150 | const words = bech32.toWords(Buffer.from(this.address) as any); 151 | return bech32.encode(this.bech32Prefix, words); 152 | } 153 | 154 | public toBytes(): Uint8Array { 155 | return new Uint8Array(this.address); 156 | } 157 | 158 | public marshalJSON(): Uint8Array { 159 | return Buffer.from(`"${this.toBech32()}"`, "utf8"); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/common/baseAccount.ts: -------------------------------------------------------------------------------- 1 | import { AccAddress } from "./address"; 2 | import { PubKey, PubKeySecp256k1 } from "../crypto"; 3 | import bigInteger from "big-integer"; 4 | import { Coin } from "./coin"; 5 | import { Int } from "./int"; 6 | import { Buffer } from "buffer/"; 7 | import { Account } from "../core/account"; 8 | 9 | export class BaseAccount implements Account { 10 | public static fromJSON(obj: any): BaseAccount { 11 | if (obj.height) { 12 | obj = obj.result; 13 | } 14 | 15 | const supportedAccountType = [ 16 | "auth/Account", 17 | "auth/BaseVestingAccount", 18 | "auth/ContinuousVestingAccount", 19 | "auth/DelayedVestingAccount", 20 | "cosmos-sdk/Account", 21 | "cosmos-sdk/BaseVestingAccount", 22 | "cosmos-sdk/ContinuousVestingAccount", 23 | "cosmos-sdk/DelayedVestingAccount" 24 | ]; 25 | if (supportedAccountType.indexOf(obj.type) < 0) { 26 | throw new Error(`Unsupported account type: ${obj.type}`); 27 | } 28 | if (obj.value) { 29 | const value = obj.value; 30 | const address = AccAddress.fromBech32(value.address); 31 | const coins: Coin[] = []; 32 | if (value.coins) { 33 | for (const coin of value.coins) { 34 | coins.push(new Coin(coin.denom, new Int(coin.amount))); 35 | } 36 | } 37 | let pubKey: PubKey | undefined; 38 | if (value.public_key) { 39 | if (value.public_key.type === undefined) { 40 | pubKey = new PubKeySecp256k1(Buffer.from(value.public_key, "base64")); 41 | } else if (value.public_key.type !== "tendermint/PubKeySecp256k1") { 42 | throw new Error( 43 | `Unsupported public key type: ${value.public_key.type}` 44 | ); 45 | } else { 46 | pubKey = new PubKeySecp256k1( 47 | Buffer.from(value.public_key.value, "base64") 48 | ); 49 | } 50 | } 51 | 52 | const accountNumber = value.account_number; 53 | const sequence = value.sequence; 54 | 55 | return new BaseAccount(address, pubKey, accountNumber, sequence, coins); 56 | } else { 57 | throw new Error("Invalid base account"); 58 | } 59 | } 60 | 61 | private address: AccAddress; 62 | private pubKey: PubKey | undefined; // If an account haven't sent tx at all, pubKey is undefined. 63 | private accountNumber: bigInteger.BigInteger; 64 | private sequence: bigInteger.BigInteger; 65 | private coins: Coin[]; 66 | 67 | constructor( 68 | address: AccAddress, 69 | pubKey: PubKey | undefined, 70 | accountNumber: bigInteger.BigNumber, 71 | sequence: bigInteger.BigNumber, 72 | coins: Coin[] 73 | ) { 74 | this.address = address; 75 | this.pubKey = pubKey; 76 | if (typeof accountNumber === "string") { 77 | this.accountNumber = bigInteger(accountNumber); 78 | } else if (typeof accountNumber === "number") { 79 | this.accountNumber = bigInteger(accountNumber); 80 | } else { 81 | this.accountNumber = bigInteger(accountNumber); 82 | } 83 | if (typeof sequence === "string") { 84 | this.sequence = bigInteger(sequence); 85 | } else if (typeof sequence === "number") { 86 | this.sequence = bigInteger(sequence); 87 | } else { 88 | this.sequence = bigInteger(sequence); 89 | } 90 | this.coins = coins; 91 | } 92 | 93 | public getAddress(): AccAddress { 94 | return this.address; 95 | } 96 | 97 | public getPubKey(): PubKey | undefined { 98 | return this.pubKey; 99 | } 100 | 101 | public getAccountNumber(): bigInteger.BigInteger { 102 | return this.accountNumber; 103 | } 104 | 105 | public getSequence(): bigInteger.BigInteger { 106 | return this.sequence; 107 | } 108 | 109 | public getCoins(): Coin[] { 110 | return this.coins; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/common/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import * as StdTx from "./stdTx"; 3 | 4 | export function registerCodec(codec: Codec) { 5 | StdTx.registerCodec(codec); 6 | } 7 | -------------------------------------------------------------------------------- /src/common/coin.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { Coin } from "./coin"; 4 | 5 | describe("Test coin", () => { 6 | it("coin parsed from str properly", () => { 7 | let coin = Coin.parse("1000test"); 8 | 9 | assert.strictEqual(coin.denom, "test"); 10 | assert.strictEqual(coin.amount.toString(), "1000"); 11 | 12 | coin = Coin.parse("1000tesT"); 13 | 14 | assert.strictEqual(coin.denom, "tesT"); 15 | assert.strictEqual(coin.amount.toString(), "1000"); 16 | 17 | coin = Coin.parse("1000TEST"); 18 | 19 | assert.strictEqual(coin.denom, "TEST"); 20 | assert.strictEqual(coin.amount.toString(), "1000"); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/common/coin.ts: -------------------------------------------------------------------------------- 1 | import { Amino } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct } = Amino; 3 | import { Int } from "./int"; 4 | import bigInteger from "big-integer"; 5 | 6 | @DefineStruct() 7 | export class Coin { 8 | public static parse(str: string): Coin { 9 | const re = new RegExp("([0-9]+)[ ]*([a-zA-Z]+)"); 10 | const execed = re.exec(str); 11 | if (!execed || execed.length !== 3) { 12 | throw new Error("Invalid coin str"); 13 | } 14 | const denom = execed[2]; 15 | const amount = execed[1]; 16 | return new Coin(denom, amount); 17 | } 18 | 19 | @Field.String(0) 20 | public denom: string; 21 | 22 | @Field.Defined(1) 23 | public amount: Int; 24 | 25 | constructor(denom: string, amount: Int | bigInteger.BigNumber) { 26 | this.denom = denom; 27 | this.amount = amount instanceof Int ? amount : new Int(amount); 28 | } 29 | 30 | public toString(): string { 31 | return `${this.amount.toString()}${this.denom}`; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/common/decimal.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { Dec } from "./decimal"; 4 | import { Int } from "./int"; 5 | 6 | describe("Test decimals", () => { 7 | it("dec should be parsed from str properly", () => { 8 | let dec = new Dec("10.009"); 9 | assert.strictEqual(dec.toString(), "10.009000000000000000"); 10 | assert.strictEqual(dec.toString(2), "10.00"); 11 | 12 | dec = new Dec("-123.45678900"); 13 | assert.strictEqual(dec.toString(), "-123.456789000000000000"); 14 | assert.strictEqual(dec.toString(3), "-123.456"); 15 | 16 | dec = new Dec("10"); 17 | assert.strictEqual(dec.toString(), "10.000000000000000000"); 18 | 19 | assert.throws(() => { 20 | new Dec(""); 21 | }); 22 | assert.throws(() => { 23 | new Dec("0.-75"); 24 | }); 25 | assert.throws(() => { 26 | new Dec("0.489234893284938249348923849283408"); 27 | }); 28 | assert.throws(() => { 29 | new Dec("foobar"); 30 | }); 31 | assert.throws(() => { 32 | new Dec("0.foobar"); 33 | }); 34 | assert.throws(() => { 35 | new Dec("foobar.0"); 36 | }); 37 | }); 38 | 39 | it("dec should be caculated properly", () => { 40 | const tests: { 41 | d1: Dec; 42 | d2: Dec; 43 | expMul: Dec; 44 | expMulTruncate: Dec; 45 | expQuo: Dec; 46 | expQuoRoundUp: Dec; 47 | expQuoTruncate: Dec; 48 | expAdd: Dec; 49 | expSub: Dec; 50 | }[] = [ 51 | { 52 | d1: new Dec(0), 53 | d2: new Dec(0), 54 | expMul: new Dec(0), 55 | expMulTruncate: new Dec(0), 56 | expQuo: new Dec(0), 57 | expQuoRoundUp: new Dec(0), 58 | expQuoTruncate: new Dec(0), 59 | expAdd: new Dec(0), 60 | expSub: new Dec(0) 61 | }, 62 | { 63 | d1: new Dec(0), 64 | d2: new Dec(1), 65 | expMul: new Dec(0), 66 | expMulTruncate: new Dec(0), 67 | expQuo: new Dec(0), 68 | expQuoRoundUp: new Dec(0), 69 | expQuoTruncate: new Dec(0), 70 | expAdd: new Dec(1), 71 | expSub: new Dec(-1) 72 | }, 73 | { 74 | d1: new Dec(-1), 75 | d2: new Dec(0), 76 | expMul: new Dec(0), 77 | expMulTruncate: new Dec(0), 78 | expQuo: new Dec(0), 79 | expQuoRoundUp: new Dec(0), 80 | expQuoTruncate: new Dec(0), 81 | expAdd: new Dec(-1), 82 | expSub: new Dec(-1) 83 | }, 84 | { 85 | d1: new Dec(-1), 86 | d2: new Dec(1), 87 | expMul: new Dec(-1), 88 | expMulTruncate: new Dec(-1), 89 | expQuo: new Dec(-1), 90 | expQuoRoundUp: new Dec(-1), 91 | expQuoTruncate: new Dec(-1), 92 | expAdd: new Dec(0), 93 | expSub: new Dec(-2) 94 | }, 95 | { 96 | d1: new Dec(3), 97 | d2: new Dec(7), 98 | expMul: new Dec(21), 99 | expMulTruncate: new Dec(21), 100 | expQuo: new Dec("428571428571428571", 18), 101 | expQuoRoundUp: new Dec("428571428571428572", 18), 102 | expQuoTruncate: new Dec("428571428571428571", 18), 103 | expAdd: new Dec(10), 104 | expSub: new Dec(-4) 105 | }, 106 | { 107 | d1: new Dec(100), 108 | d2: new Dec(100), 109 | expMul: new Dec(10000), 110 | expMulTruncate: new Dec(10000), 111 | expQuo: new Dec(1), 112 | expQuoRoundUp: new Dec(1), 113 | expQuoTruncate: new Dec(1), 114 | expAdd: new Dec(200), 115 | expSub: new Dec(0) 116 | }, 117 | { 118 | d1: new Dec(3333, 4), 119 | d2: new Dec(333, 4), 120 | expMul: new Dec(1109889, 8), 121 | expMulTruncate: new Dec(1109889, 8), 122 | expQuo: new Dec("10.009009009009009009"), 123 | expQuoRoundUp: new Dec("10.009009009009009010"), 124 | expQuoTruncate: new Dec("10.009009009009009009"), 125 | expAdd: new Dec(3666, 4), 126 | expSub: new Dec(3, 1) 127 | } 128 | ]; 129 | 130 | for (const test of tests) { 131 | const resAdd = test.d1.add(test.d2); 132 | const resSub = test.d1.sub(test.d2); 133 | const resMul = test.d1.mul(test.d2); 134 | const resMulTruncate = test.d1.mulTruncate(test.d2); 135 | 136 | assert.strictEqual( 137 | resAdd.toString(), 138 | test.expAdd.toString(), 139 | "invalid result of add" 140 | ); 141 | assert.strictEqual( 142 | resSub.toString(), 143 | test.expSub.toString(), 144 | "invalid result of sub" 145 | ); 146 | assert.strictEqual( 147 | resMul.toString(), 148 | test.expMul.toString(), 149 | "invalid result of mul" 150 | ); 151 | assert.strictEqual( 152 | resMulTruncate.toString(), 153 | test.expMulTruncate.toString(), 154 | "invalid result of mul" 155 | ); 156 | 157 | if (test.d2.isZero()) { 158 | assert.throws(() => { 159 | test.d1.quo(test.d2); 160 | }); 161 | } else { 162 | const resQuo = test.d1.quo(test.d2); 163 | const resQuoRoundUp = test.d1.quoRoundUp(test.d2); 164 | const resQuoTruncate = test.d1.quoTruncate(test.d2); 165 | 166 | assert.strictEqual( 167 | resQuo.toString(), 168 | test.expQuo.toString(), 169 | "invalid result of quo" 170 | ); 171 | assert.strictEqual( 172 | resQuoRoundUp.toString(), 173 | test.expQuoRoundUp.toString(), 174 | "invalid result of quo round up" 175 | ); 176 | assert.strictEqual( 177 | resQuoTruncate.toString(), 178 | test.expQuoTruncate.toString(), 179 | "invalid result of quo truncate" 180 | ); 181 | } 182 | } 183 | }); 184 | 185 | it("dec should be round up properly", () => { 186 | const tests: { 187 | d1: Dec; 188 | exp: Int; 189 | }[] = [ 190 | { 191 | d1: new Dec("0.25"), 192 | exp: new Int("0") 193 | }, 194 | { 195 | d1: new Dec("0"), 196 | exp: new Int("0") 197 | }, 198 | { 199 | d1: new Dec("1"), 200 | exp: new Int("1") 201 | }, 202 | { 203 | d1: new Dec("0.75"), 204 | exp: new Int("1") 205 | }, 206 | { 207 | d1: new Dec("0.5"), 208 | exp: new Int("0") 209 | }, 210 | { 211 | d1: new Dec("7.5"), 212 | exp: new Int("8") 213 | }, 214 | { 215 | d1: new Dec("0.545"), 216 | exp: new Int("1") 217 | }, 218 | { 219 | d1: new Dec("1.545"), 220 | exp: new Int("2") 221 | } 222 | ]; 223 | 224 | for (const test of tests) { 225 | const resNeg = test.d1.neg().round(); 226 | assert.strictEqual(resNeg.toString(), test.exp.neg().toString()); 227 | 228 | const resPos = test.d1.round(); 229 | assert.strictEqual(resPos.toString(), test.exp.toString()); 230 | } 231 | }); 232 | 233 | it("dec should be round up truncated", () => { 234 | const tests: { 235 | d1: Dec; 236 | exp: Int; 237 | }[] = [ 238 | { 239 | d1: new Dec("0"), 240 | exp: new Int("0") 241 | }, 242 | { 243 | d1: new Dec("0.25"), 244 | exp: new Int("0") 245 | }, 246 | { 247 | d1: new Dec("0.75"), 248 | exp: new Int("0") 249 | }, 250 | { 251 | d1: new Dec("1"), 252 | exp: new Int("1") 253 | }, 254 | { 255 | d1: new Dec("7.5"), 256 | exp: new Int("7") 257 | }, 258 | { 259 | d1: new Dec("7.6"), 260 | exp: new Int("7") 261 | }, 262 | { 263 | d1: new Dec("8.5"), 264 | exp: new Int("8") 265 | }, 266 | { 267 | d1: new Dec("100.000000001"), 268 | exp: new Int("100") 269 | } 270 | ]; 271 | 272 | for (const test of tests) { 273 | const resNeg = test.d1.neg().truncate(); 274 | assert.strictEqual(resNeg.toString(), test.exp.neg().toString()); 275 | 276 | const resPos = test.d1.truncate(); 277 | assert.strictEqual(resPos.toString(), test.exp.toString()); 278 | } 279 | }); 280 | }); 281 | -------------------------------------------------------------------------------- /src/common/decimal.ts: -------------------------------------------------------------------------------- 1 | import bigInteger from "big-integer"; 2 | import { Amino, Type } from "@chainapsis/ts-amino"; 3 | import { Int } from "./int"; 4 | const { Method, DefineStruct } = Amino; 5 | 6 | @DefineStruct() 7 | export class Dec { 8 | public static readonly precision: bigInteger.BigInteger = bigInteger(18); 9 | private static readonly precisionMultipliers: { 10 | [key: string]: bigInteger.BigInteger | undefined; 11 | } = {}; 12 | private static calcPrecisionMultiplier( 13 | prec: bigInteger.BigInteger 14 | ): bigInteger.BigInteger { 15 | if (prec.lt(bigInteger(0))) { 16 | throw new Error("Invalid prec"); 17 | } 18 | if (prec.gt(Dec.precision)) { 19 | throw new Error("Too much precision"); 20 | } 21 | if (Dec.precisionMultipliers[prec.toString()]) { 22 | return Dec.precisionMultipliers[prec.toString()]!; 23 | } 24 | 25 | const zerosToAdd = Dec.precision.minus(prec); 26 | const multiplier = bigInteger(10).pow(zerosToAdd); 27 | Dec.precisionMultipliers[prec.toString()] = multiplier; 28 | return multiplier; 29 | } 30 | 31 | private int: bigInteger.BigInteger; 32 | 33 | /** 34 | * Create a new Dec from integer with decimal place at prec 35 | * @param int - Parse a number | bigInteger | string into a Dec. 36 | * If int is string and contains dot(.), prec is ignored and automatically calculated. 37 | * @param prec - Precision 38 | */ 39 | constructor(int: bigInteger.BigNumber | Int, prec: number = 0) { 40 | if (typeof int === "string") { 41 | if (int.length === 0) { 42 | throw new Error("empty string"); 43 | } 44 | if (!/^(-?\d+\.\d+)$|^(-?\d+)$/.test(int)) { 45 | throw new Error(`invalid decimal: ${int}`); 46 | } 47 | if (int.indexOf(".") >= 0) { 48 | prec = int.length - int.indexOf(".") - 1; 49 | int = int.replace(".", ""); 50 | } 51 | this.int = bigInteger(int); 52 | } else if (typeof int === "number") { 53 | this.int = bigInteger(int); 54 | } else if (int instanceof Int) { 55 | this.int = bigInteger(int.toString()); 56 | } else { 57 | this.int = bigInteger(int); 58 | } 59 | 60 | this.int = this.int.multiply(Dec.calcPrecisionMultiplier(bigInteger(prec))); 61 | } 62 | 63 | public isZero(): boolean { 64 | return this.int.eq(bigInteger(0)); 65 | } 66 | 67 | public isNegative(): boolean { 68 | return this.int.isNegative(); 69 | } 70 | 71 | public isPositive(): boolean { 72 | return this.int.isPositive(); 73 | } 74 | 75 | public equals(d2: Dec): boolean { 76 | return this.int.eq(d2.int); 77 | } 78 | 79 | /** 80 | * Alias for the greater method. 81 | */ 82 | public gt(d2: Dec): boolean { 83 | return this.int.gt(d2.int); 84 | } 85 | 86 | /** 87 | * Alias for the greaterOrEquals method. 88 | */ 89 | public gte(d2: Dec): boolean { 90 | return this.int.geq(d2.int); 91 | } 92 | 93 | /** 94 | * Alias for the lesser method. 95 | */ 96 | public lt(d2: Dec): boolean { 97 | return this.int.lt(d2.int); 98 | } 99 | 100 | /** 101 | * Alias for the lesserOrEquals method. 102 | */ 103 | public lte(d2: Dec): boolean { 104 | return this.int.leq(d2.int); 105 | } 106 | 107 | /** 108 | * reverse the decimal sign. 109 | */ 110 | public neg(): Dec { 111 | return new Dec(this.int.negate(), Dec.precision.toJSNumber()); 112 | } 113 | 114 | /** 115 | * Returns the absolute value of a decimals. 116 | */ 117 | public abs(): Dec { 118 | return new Dec(this.int.abs(), Dec.precision.toJSNumber()); 119 | } 120 | 121 | public add(d2: Dec): Dec { 122 | return new Dec(this.int.add(d2.int), Dec.precision.toJSNumber()); 123 | } 124 | 125 | public sub(d2: Dec): Dec { 126 | return new Dec(this.int.subtract(d2.int), Dec.precision.toJSNumber()); 127 | } 128 | 129 | public mul(d2: Dec): Dec { 130 | return new Dec( 131 | this.mulRaw(d2).chopPrecisionAndRound(), 132 | Dec.precision.toJSNumber() 133 | ); 134 | } 135 | 136 | public mulTruncate(d2: Dec): Dec { 137 | return new Dec( 138 | this.mulRaw(d2).chopPrecisionAndTruncate(), 139 | Dec.precision.toJSNumber() 140 | ); 141 | } 142 | 143 | private mulRaw(d2: Dec): Dec { 144 | return new Dec(this.int.multiply(d2.int), Dec.precision.toJSNumber()); 145 | } 146 | 147 | public quo(d2: Dec): Dec { 148 | return new Dec( 149 | this.quoRaw(d2).chopPrecisionAndRound(), 150 | Dec.precision.toJSNumber() 151 | ); 152 | } 153 | 154 | public quoTruncate(d2: Dec): Dec { 155 | return new Dec( 156 | this.quoRaw(d2).chopPrecisionAndTruncate(), 157 | Dec.precision.toJSNumber() 158 | ); 159 | } 160 | 161 | public quoRoundUp(d2: Dec): Dec { 162 | return new Dec( 163 | this.quoRaw(d2).chopPrecisionAndRoundUp(), 164 | Dec.precision.toJSNumber() 165 | ); 166 | } 167 | 168 | private quoRaw(d2: Dec): Dec { 169 | const precision = Dec.calcPrecisionMultiplier(bigInteger(0)); 170 | 171 | // multiply precision twice 172 | const mul = this.int.multiply(precision).multiply(precision); 173 | return new Dec(mul.divide(d2.int), Dec.precision.toJSNumber()); 174 | } 175 | 176 | public isInteger(): boolean { 177 | const precision = Dec.calcPrecisionMultiplier(bigInteger(0)); 178 | return this.int.remainder(precision).equals(bigInteger(0)); 179 | } 180 | 181 | /** 182 | * Remove a Precision amount of rightmost digits and perform bankers rounding 183 | * on the remainder (gaussian rounding) on the digits which have been removed. 184 | */ 185 | private chopPrecisionAndRound(): bigInteger.BigInteger { 186 | // Remove the negative and add it back when returning 187 | if (this.isNegative()) { 188 | const absoulteDec = this.abs(); 189 | const choped = absoulteDec.chopPrecisionAndRound(); 190 | return choped.negate(); 191 | } 192 | 193 | const precision = Dec.calcPrecisionMultiplier(bigInteger(0)); 194 | const fivePrecision = precision.divide(bigInteger(2)); 195 | 196 | // Get the truncated quotient and remainder 197 | const { quotient, remainder } = this.int.divmod(precision); 198 | 199 | // If remainder is zero 200 | if (remainder.equals(bigInteger(0))) { 201 | return quotient; 202 | } 203 | 204 | if (remainder.lt(fivePrecision)) { 205 | return quotient; 206 | } else if (remainder.gt(fivePrecision)) { 207 | return quotient.add(bigInteger(1)); 208 | } else { 209 | // always round to an even number 210 | if (quotient.divide(bigInteger(2)).equals(bigInteger(0))) { 211 | return quotient; 212 | } else { 213 | return quotient.add(bigInteger(1)); 214 | } 215 | } 216 | } 217 | 218 | private chopPrecisionAndRoundUp(): bigInteger.BigInteger { 219 | // Remove the negative and add it back when returning 220 | if (this.isNegative()) { 221 | const absoulteDec = this.abs(); 222 | // truncate since d is negative... 223 | const choped = absoulteDec.chopPrecisionAndTruncate(); 224 | return choped.negate(); 225 | } 226 | 227 | const precision = Dec.calcPrecisionMultiplier(bigInteger(0)); 228 | 229 | // Get the truncated quotient and remainder 230 | const { quotient, remainder } = this.int.divmod(precision); 231 | 232 | // If remainder is zero 233 | if (remainder.equals(bigInteger(0))) { 234 | return quotient; 235 | } 236 | 237 | return quotient.add(bigInteger(1)); 238 | } 239 | 240 | /** 241 | * Similar to chopPrecisionAndRound, but always rounds down 242 | */ 243 | private chopPrecisionAndTruncate(): bigInteger.BigInteger { 244 | const precision = Dec.calcPrecisionMultiplier(bigInteger(0)); 245 | return this.int.divide(precision); 246 | } 247 | 248 | public toString(prec: number = Dec.precision.toJSNumber()): string { 249 | const precision = Dec.calcPrecisionMultiplier(bigInteger(0)); 250 | const int = this.int.abs(); 251 | const { quotient: interger, remainder: fraction } = int.divmod(precision); 252 | 253 | let fractionStr = fraction.toString(10); 254 | for ( 255 | let i = 0, l = fractionStr.length; 256 | i < Dec.precision.toJSNumber() - l; 257 | i++ 258 | ) { 259 | fractionStr = "0" + fractionStr; 260 | } 261 | fractionStr = fractionStr.substring(0, prec); 262 | 263 | return ( 264 | (this.isNegative() ? "-" : "") + interger.toString(10) + "." + fractionStr 265 | ); 266 | } 267 | 268 | public round(): Int { 269 | return new Int(this.chopPrecisionAndRound()); 270 | } 271 | 272 | public roundUp(): Int { 273 | return new Int(this.chopPrecisionAndRoundUp()); 274 | } 275 | 276 | public truncate(): Int { 277 | return new Int(this.chopPrecisionAndTruncate()); 278 | } 279 | 280 | @Method.AminoMarshaler({ type: Type.String }) 281 | public marshalAmino(): string { 282 | return this.int.toString(10); 283 | } 284 | 285 | public marshalJSON(): string { 286 | return `"${this.int.toString(10)}"`; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/common/int.ts: -------------------------------------------------------------------------------- 1 | import bigInteger from "big-integer"; 2 | import { Amino, Type } from "@chainapsis/ts-amino"; 3 | const { Method, DefineStruct } = Amino; 4 | 5 | @DefineStruct() 6 | export class Int { 7 | private int: bigInteger.BigInteger; 8 | 9 | /** 10 | * @param int - Parse a number | bigInteger | string into a bigInt. 11 | * Remaing parameters only will be used when type of int is string. 12 | * @param base - Default base is 10. 13 | * @param alphabet - Default alphabet is "0123456789abcdefghijklmnopqrstuvwxyz". 14 | * @param caseSensitive - Defaults to false. 15 | */ 16 | constructor( 17 | int: bigInteger.BigNumber, 18 | base?: bigInteger.BigNumber, 19 | alphabet?: string, 20 | caseSensitive?: boolean 21 | ) { 22 | if (typeof int === "string") { 23 | this.int = bigInteger(int, base, alphabet, caseSensitive); 24 | } else if (typeof int === "number") { 25 | this.int = bigInteger(int); 26 | } else { 27 | this.int = bigInteger(int); 28 | } 29 | } 30 | 31 | @Method.AminoMarshaler({ type: Type.String }) 32 | public marshalAmino(): string { 33 | return this.int.toString(10); 34 | } 35 | 36 | public toString(): string { 37 | return this.int.toString(10); 38 | } 39 | 40 | public equals(i: Int): boolean { 41 | return this.int.equals(i.int); 42 | } 43 | 44 | public gt(i: Int): boolean { 45 | return this.int.gt(i.int); 46 | } 47 | 48 | public gte(i: Int): boolean { 49 | return this.int.greaterOrEquals(i.int); 50 | } 51 | 52 | public lt(i: Int): boolean { 53 | return this.int.lt(i.int); 54 | } 55 | 56 | public lte(i: Int): boolean { 57 | return this.int.lesserOrEquals(i.int); 58 | } 59 | 60 | public add(i: Int): Int { 61 | return new Int(this.int.add(i.int)); 62 | } 63 | 64 | public sub(i: Int): Int { 65 | return new Int(this.int.subtract(i.int)); 66 | } 67 | 68 | public mul(i: Int): Int { 69 | return new Int(this.int.multiply(i.int)); 70 | } 71 | 72 | public div(i: Int): Int { 73 | return new Int(this.int.divide(i.int)); 74 | } 75 | 76 | public mod(i: Int): Int { 77 | return new Int(this.int.mod(i.int)); 78 | } 79 | 80 | public neg(): Int { 81 | return new Int(this.int.negate()); 82 | } 83 | } 84 | 85 | @DefineStruct() 86 | export class Uint { 87 | private uint: bigInteger.BigInteger; 88 | 89 | /** 90 | * @param uint - Parse a number | bigInteger | string into a bigUint. 91 | * Remaing parameters only will be used when type of int is string. 92 | * @param base - Default base is 10. 93 | * @param alphabet - Default alphabet is "0123456789abcdefghijklmnopqrstuvwxyz". 94 | * @param caseSensitive - Defaults to false. 95 | */ 96 | constructor( 97 | uint: bigInteger.BigNumber, 98 | base?: bigInteger.BigNumber, 99 | alphabet?: string, 100 | caseSensitive?: boolean 101 | ) { 102 | if (typeof uint === "string") { 103 | this.uint = bigInteger(uint, base, alphabet, caseSensitive); 104 | } else if (typeof uint === "number") { 105 | this.uint = bigInteger(uint); 106 | } else { 107 | this.uint = bigInteger(uint); 108 | } 109 | 110 | if (this.uint.isNegative()) { 111 | throw new TypeError("Uint should not be negative"); 112 | } 113 | } 114 | 115 | @Method.AminoMarshaler({ type: Type.String }) 116 | public marshalAmino(): string { 117 | return this.uint.toString(10); 118 | } 119 | 120 | public toString(): string { 121 | return this.uint.toString(10); 122 | } 123 | 124 | public equals(i: Uint): boolean { 125 | return this.uint.equals(i.uint); 126 | } 127 | 128 | public gt(i: Uint): boolean { 129 | return this.uint.gt(i.uint); 130 | } 131 | 132 | public gte(i: Uint): boolean { 133 | return this.uint.greaterOrEquals(i.uint); 134 | } 135 | 136 | public lt(i: Uint): boolean { 137 | return this.uint.lt(i.uint); 138 | } 139 | 140 | public lte(i: Uint): boolean { 141 | return this.uint.lesserOrEquals(i.uint); 142 | } 143 | 144 | public add(i: Uint): Uint { 145 | return new Uint(this.uint.add(i.uint)); 146 | } 147 | 148 | public sub(i: Uint): Uint { 149 | return new Uint(this.uint.subtract(i.uint)); 150 | } 151 | 152 | public mul(i: Uint): Uint { 153 | return new Uint(this.uint.multiply(i.uint)); 154 | } 155 | 156 | public div(i: Uint): Uint { 157 | return new Uint(this.uint.divide(i.uint)); 158 | } 159 | 160 | public mod(i: Uint): Uint { 161 | return new Uint(this.uint.mod(i.uint)); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/common/stdTx.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { Buffer } from "buffer/"; 4 | import { Coin } from "./coin"; 5 | import { Int } from "./int"; 6 | import { StdSignDoc, StdFee, registerCodec } from "./stdTx"; 7 | import { Msg } from "../core/tx"; 8 | import { AccAddress } from "./address"; 9 | import { Amino } from "@chainapsis/ts-amino"; 10 | const { Field, Concrete, DefineStruct } = Amino; 11 | 12 | @Concrete("test/MsgTest") 13 | @DefineStruct() 14 | class MsgTest extends Msg { 15 | @Field.Defined() 16 | public address: AccAddress; 17 | 18 | constructor(address: Uint8Array) { 19 | super(); 20 | this.address = new AccAddress(address, "cosmos"); 21 | } 22 | 23 | // eslint-disable-next-line @typescript-eslint/no-empty-function 24 | validateBasic(): void {} 25 | } 26 | 27 | describe("Test std tx", () => { 28 | registerCodec(Amino.globalCodec); 29 | 30 | it("std sign doc should generate corrent sign doc", () => { 31 | const signDoc = new StdSignDoc( 32 | Amino.globalCodec, 33 | 1, 34 | "test", 35 | new StdFee([new Coin("test", new Int(10))], 1000), 36 | "test", 37 | [ 38 | new MsgTest( 39 | Buffer.from("5e8f356453a096748ef5966fbe26d65079db30a8", "hex") 40 | ) 41 | ], 42 | 10 43 | ); 44 | 45 | assert.strictEqual( 46 | signDoc.getSignBytes().toString(), 47 | `{"account_number":"1","chain_id":"test","fee":{"amount":[{"amount":"10","denom":"test"}],"gas":"1000"},"memo":"test","msgs":[{"type":"test/MsgTest","value":{"address":"cosmos1t68n2ezn5zt8frh4jehmufkk2puakv9glapyz4"}}],"sequence":"10"}` 48 | ); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/common/stdTx.ts: -------------------------------------------------------------------------------- 1 | import { Tx, Msg, TxEncoder } from "../core/tx"; 2 | import { Amino, Type, Codec } from "@chainapsis/ts-amino"; 3 | const { Field, DefineStruct, DefineType, marshalJson } = Amino; 4 | import { Coin } from "./coin"; 5 | import bigInteger from "big-integer"; 6 | import { PubKey } from "../crypto"; 7 | import { Buffer } from "buffer/"; 8 | import { sortJSON } from "../utils/sortJson"; 9 | import { Context } from "../core/context"; 10 | 11 | export function registerCodec(codec: Codec) { 12 | codec.registerConcrete("cosmos-sdk/StdTx", StdTx.prototype); 13 | } 14 | 15 | @DefineStruct() 16 | export class StdTx implements Tx { 17 | @Field.Slice(0, { type: Type.Interface }, { jsonName: "msg" }) 18 | public msgs: Msg[]; 19 | 20 | @Field.Defined(1) 21 | public fee: StdFee; 22 | 23 | @Field.Slice(2, { type: Type.Defined }) 24 | public signatures: StdSignature[]; 25 | 26 | @Field.String(3) 27 | public memo: string; 28 | 29 | constructor( 30 | msgs: Msg[], 31 | fee: StdFee, 32 | signatures: StdSignature[], 33 | memo: string = "" 34 | ) { 35 | this.msgs = msgs; 36 | this.fee = fee; 37 | this.signatures = signatures; 38 | this.memo = memo; 39 | } 40 | 41 | public getMsgs(): Msg[] { 42 | return this.msgs; 43 | } 44 | 45 | public validateBasic(): void { 46 | for (const msg of this.msgs) { 47 | msg.validateBasic(); 48 | } 49 | } 50 | } 51 | 52 | const defaultTxEncoder: TxEncoder = (context: Context, tx: Tx): Uint8Array => { 53 | return context.get("codec").marshalBinaryLengthPrefixed(tx); 54 | }; 55 | 56 | export { defaultTxEncoder }; 57 | 58 | @DefineStruct() 59 | export class StdFee { 60 | @Field.Slice(0, { type: Type.Defined }) 61 | public amount: Coin[]; 62 | 63 | @Field.Uint64(1) 64 | public gas: bigInteger.BigInteger; 65 | 66 | constructor(amount: Coin[], gas: bigInteger.BigNumber) { 67 | this.amount = amount; 68 | if (typeof gas === "string") { 69 | this.gas = bigInteger(gas, 10); 70 | } else if (typeof gas === "number") { 71 | this.gas = bigInteger(gas); 72 | } else { 73 | this.gas = bigInteger(gas); 74 | } 75 | } 76 | } 77 | 78 | @DefineType() 79 | class RawMessage { 80 | @Field.Slice(0, { type: Type.Uint8 }) 81 | public raw: Uint8Array; 82 | 83 | constructor(raw: Uint8Array) { 84 | this.raw = raw; 85 | } 86 | 87 | public marshalJSON(): string { 88 | return Buffer.from(this.raw).toString("utf8"); 89 | } 90 | } 91 | 92 | @DefineStruct() 93 | export class StdSignDoc { 94 | @Field.Uint64(0, { jsonName: "account_number" }) 95 | public accountNumber: bigInteger.BigInteger; 96 | 97 | @Field.String(1, { jsonName: "chain_id" }) 98 | public chainId: string; 99 | 100 | @Field.Slice(2, { type: Type.Defined }, { jsonName: "fee" }) 101 | public feeRaw: RawMessage; 102 | 103 | @Field.String(3) 104 | public memo: string; 105 | 106 | @Field.Slice(4, { type: Type.Defined }, { jsonName: "msgs" }) 107 | public msgsRaws: RawMessage[]; 108 | 109 | @Field.Uint64() 110 | public sequence: bigInteger.BigInteger; 111 | 112 | constructor( 113 | codec: Codec, 114 | accountNumber: bigInteger.BigNumber, 115 | chainId: string, 116 | fee: StdFee, 117 | memo: string, 118 | msgs: Msg[], 119 | sequence: bigInteger.BigNumber 120 | ) { 121 | if (typeof accountNumber === "string") { 122 | this.accountNumber = bigInteger(accountNumber, 10); 123 | } else if (typeof accountNumber === "number") { 124 | this.accountNumber = bigInteger(accountNumber); 125 | } else { 126 | this.accountNumber = bigInteger(accountNumber); 127 | } 128 | this.chainId = chainId; 129 | this.feeRaw = new RawMessage(Buffer.from(marshalJson(fee), "utf8")); 130 | this.memo = memo; 131 | 132 | this.msgsRaws = []; 133 | for (const msg of msgs) { 134 | this.msgsRaws.push(new RawMessage(msg.getSignBytes(codec))); 135 | } 136 | 137 | if (typeof sequence === "string") { 138 | this.sequence = bigInteger(sequence, 10); 139 | } else if (typeof sequence === "number") { 140 | this.sequence = bigInteger(sequence); 141 | } else { 142 | this.sequence = bigInteger(sequence); 143 | } 144 | } 145 | 146 | public getSignBytes(): Uint8Array { 147 | return Buffer.from(sortJSON(marshalJson(this)), "utf8"); 148 | } 149 | } 150 | 151 | @DefineStruct() 152 | export class StdSignature { 153 | @Field.Interface(0, { jsonName: "pub_key" }) 154 | public pubKey: PubKey; 155 | 156 | @Field.Slice(1, { type: Type.Uint8 }) 157 | public signature: Uint8Array; 158 | 159 | constructor(pubKey: PubKey, signature: Uint8Array) { 160 | this.pubKey = pubKey; 161 | this.signature = signature; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/common/stdTxBuilder.ts: -------------------------------------------------------------------------------- 1 | import { TxBuilder, TxBuilderConfig } from "../core/txBuilder"; 2 | import { Tx, Msg } from "../core/tx"; 3 | import { StdTx, StdFee, StdSignature, StdSignDoc } from "./stdTx"; 4 | import { Context } from "../core/context"; 5 | import { AccAddress } from "./address"; 6 | import bigInteger from "big-integer"; 7 | import { PubKey, PubKeySecp256k1 } from "../crypto"; 8 | import { Coin } from "./coin"; 9 | 10 | function nullableBnToBI( 11 | bn: bigInteger.BigNumber | undefined 12 | ): bigInteger.BigInteger { 13 | let result = bigInteger(-1); 14 | if (bn) { 15 | if (typeof bn === "string") { 16 | result = bigInteger(bn); 17 | } else if (typeof bn === "number") { 18 | result = bigInteger(bn); 19 | } else { 20 | result = bigInteger(bn); 21 | } 22 | } 23 | return result; 24 | } 25 | 26 | export const stdTxBuilder: TxBuilder = async ( 27 | context: Context, 28 | msgs: Msg[], 29 | config: TxBuilderConfig 30 | ): Promise => { 31 | const walletProvider = context.get("walletProvider"); 32 | if (walletProvider.getTxBuilderConfig) { 33 | config = await walletProvider.getTxBuilderConfig(context, config); 34 | 35 | /* 36 | * If config is delivered from third party wallet, it probably can be produced in another scope. 37 | * Though type of fee is `Coin` in third party wallet, it can be not the same with `Coin` type in this scope. 38 | * So, if the fee is likely to be `Coin` type, change the prototype to the `Coin` of the current scope. 39 | */ 40 | const tempFee = config.fee as any; 41 | if (Array.isArray(tempFee)) { 42 | for (let i = 0; i < tempFee.length; i++) { 43 | if (!(tempFee[i] instanceof Coin)) { 44 | const fee = tempFee[i]; 45 | const configFee = config.fee as Coin[]; 46 | configFee[i] = new Coin(fee.denom, bigInteger(fee.amount.toString())); 47 | } 48 | } 49 | } else { 50 | if (!(tempFee instanceof Coin)) { 51 | config.fee = new Coin( 52 | tempFee.denom, 53 | bigInteger(tempFee.amount.toString()) 54 | ); 55 | } 56 | } 57 | 58 | config.gas = bigInteger(config.gas.toString()); 59 | if (config.sequence) { 60 | config.sequence = bigInteger(config.sequence.toString()); 61 | } 62 | if (config.accountNumber) { 63 | config.accountNumber = bigInteger(config.accountNumber.toString()); 64 | } 65 | } 66 | 67 | let fee: Coin[] = []; 68 | if (!Array.isArray(config.fee)) { 69 | fee = [config.fee]; 70 | } else { 71 | fee = config.fee; 72 | } 73 | const stdFee = new StdFee(fee, config.gas); 74 | 75 | const seenSigners: any = {}; 76 | const signers: AccAddress[] = []; 77 | for (const msg of msgs) { 78 | msg.validateBasic(); 79 | for (const signer of msg.getSigners()) { 80 | if (!seenSigners[signer.toBytes().toString()]) { 81 | signers.push(signer); 82 | 83 | seenSigners[signer.toBytes().toString()] = true; 84 | } 85 | } 86 | } 87 | 88 | const keys = await walletProvider.getKeys(context); 89 | 90 | const signatures: StdSignature[] = []; 91 | for (const signer of signers) { 92 | let accountNumber = nullableBnToBI(config.accountNumber); 93 | let sequence = nullableBnToBI(config.sequence); 94 | 95 | if (accountNumber.lt(bigInteger(0)) || sequence.lt(bigInteger(0))) { 96 | const account = await context.get("queryAccount")( 97 | context, 98 | signers[0].toBech32() 99 | ); 100 | if (accountNumber.lt(bigInteger(0))) { 101 | accountNumber = account.getAccountNumber(); 102 | } 103 | if (sequence.lt(bigInteger(0))) { 104 | sequence = account.getSequence(); 105 | } 106 | } 107 | 108 | const signDoc = new StdSignDoc( 109 | context.get("codec"), 110 | accountNumber, 111 | context.get("chainId"), 112 | stdFee, 113 | config.memo, 114 | msgs, 115 | sequence 116 | ); 117 | 118 | const sig = await walletProvider.sign( 119 | context, 120 | signer.toBech32(), 121 | signDoc.getSignBytes() 122 | ); 123 | 124 | let pubKey: PubKey | undefined; 125 | for (const key of keys) { 126 | if (key.bech32Address === signer.toBech32()) { 127 | if (key.algo === "secp256k1") { 128 | pubKey = new PubKeySecp256k1(key.pubKey); 129 | } else { 130 | throw new Error(`Unsupported algo: ${key.algo}`); 131 | } 132 | } 133 | } 134 | 135 | const signature = new StdSignature(pubKey!, sig); 136 | 137 | signatures.push(signature); 138 | } 139 | 140 | const stdTx = new StdTx(msgs, stdFee, signatures, config.memo); 141 | stdTx.validateBasic(); 142 | return stdTx; 143 | }; 144 | -------------------------------------------------------------------------------- /src/core/account.ts: -------------------------------------------------------------------------------- 1 | import { AccAddress } from "../common/address"; 2 | import { PubKey } from "../crypto"; 3 | import bigInteger from "big-integer"; 4 | import { Coin } from "../common/coin"; 5 | import { Context } from "./context"; 6 | 7 | export interface Account { 8 | getAddress(): AccAddress; 9 | getPubKey(): PubKey | undefined; // If an account haven't sent tx at all, pubKey is undefined.; 10 | getAccountNumber(): bigInteger.BigInteger; 11 | getSequence(): bigInteger.BigInteger; 12 | getCoins(): Coin[]; 13 | } 14 | 15 | export type QueryAccount = ( 16 | context: Context, 17 | address: string | Uint8Array 18 | ) => Promise; 19 | -------------------------------------------------------------------------------- /src/core/api.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./context"; 2 | import Axios, { AxiosInstance } from "axios"; 3 | import { TxEncoder, Msg } from "./tx"; 4 | import { TxBuilder, TxBuilderConfig } from "./txBuilder"; 5 | import { Bech32Config } from "./bech32Config"; 6 | import { Key, WalletProvider } from "./walletProvider"; 7 | import { TendermintRPC } from "../rpc/tendermint"; 8 | import { Rest } from "./rest"; 9 | import { QueryAccount } from "./account"; 10 | import { ResultBroadcastTx, ResultBroadcastTxCommit } from "../rpc/tx"; 11 | import { BIP44 } from "./bip44"; 12 | import { Codec } from "@chainapsis/ts-amino"; 13 | 14 | export interface ApiConfig { 15 | chainId: string; 16 | walletProvider: WalletProvider; 17 | /** Endpoint of rpc */ 18 | rpc: string; 19 | /** Endpoint of rest api */ 20 | rest: string; 21 | } 22 | 23 | export interface CoreConfig { 24 | /** 25 | * Encoder for transaction. 26 | */ 27 | txEncoder: TxEncoder; 28 | /** 29 | * Builder for transaction. 30 | */ 31 | txBuilder: TxBuilder; 32 | rpcInstanceFactory?: (rpc: string) => AxiosInstance; 33 | restInstanceFactory?: (rpc: string) => AxiosInstance; 34 | restFactory: (context: Context) => R; 35 | queryAccount: QueryAccount; 36 | bech32Config: Bech32Config; 37 | bip44: BIP44; 38 | registerCodec: (codec: Codec) => void; 39 | } 40 | 41 | export class Api { 42 | private _context: Context; 43 | private _rpc: TendermintRPC; 44 | private _rest: R; 45 | 46 | constructor(config: ApiConfig, coreConfig: CoreConfig) { 47 | this._context = new Context({ 48 | chainId: config.chainId, 49 | txEncoder: coreConfig.txEncoder, 50 | txBuilder: coreConfig.txBuilder, 51 | bech32Config: coreConfig.bech32Config, 52 | walletProvider: config.walletProvider, 53 | rpcInstance: coreConfig.rpcInstanceFactory 54 | ? coreConfig.rpcInstanceFactory(config.rpc) 55 | : Axios.create({ 56 | baseURL: config.rpc 57 | }), 58 | restInstance: coreConfig.restInstanceFactory 59 | ? coreConfig.restInstanceFactory(config.rest) 60 | : Axios.create({ 61 | baseURL: config.rest 62 | }), 63 | queryAccount: coreConfig.queryAccount, 64 | bip44: coreConfig.bip44, 65 | codec: new Codec() 66 | }); 67 | 68 | coreConfig.registerCodec(this.context.get("codec")); 69 | 70 | this._rpc = new TendermintRPC(this.context); 71 | this._rest = coreConfig.restFactory(this.context); 72 | } 73 | 74 | public async enable(): Promise { 75 | await this.wallet.enable(this.context); 76 | return Promise.resolve(); 77 | } 78 | 79 | public async getKeys(): Promise { 80 | return await this.wallet.getKeys(this.context); 81 | } 82 | 83 | /** 84 | * Send msgs. 85 | * @return If mode is commit, this will return [[ResultBroadcastTx]]. 86 | * Or if mode is sync or async, this will return [[ResultBroadcastTxCommit]]. 87 | */ 88 | public async sendMsgs( 89 | msgs: Msg[], 90 | config: TxBuilderConfig, 91 | mode: "commit" | "sync" | "async" = "sync" 92 | ): Promise { 93 | const tx = await this.context.get("txBuilder")(this.context, msgs, config); 94 | const bz = this.context.get("txEncoder")(this.context, tx); 95 | if (mode === "commit") { 96 | return this.rpc.broadcastTxCommit(bz); 97 | } else { 98 | return this.rpc.broadcastTx(bz, mode); 99 | } 100 | } 101 | 102 | get context(): Context { 103 | return this._context; 104 | } 105 | 106 | get wallet(): WalletProvider { 107 | return this.context.get("walletProvider"); 108 | } 109 | 110 | get rpc(): TendermintRPC { 111 | return this._rpc; 112 | } 113 | 114 | get rest(): R { 115 | return this._rest; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/core/bech32Config.ts: -------------------------------------------------------------------------------- 1 | export interface Bech32Config { 2 | bech32PrefixAccAddr: string; 3 | bech32PrefixAccPub: string; 4 | bech32PrefixValAddr: string; 5 | bech32PrefixValPub: string; 6 | bech32PrefixConsAddr: string; 7 | bech32PrefixConsPub: string; 8 | } 9 | 10 | export function defaultBech32Config( 11 | mainPrefix: string, 12 | validatorPrefix: string = "val", 13 | consensusPrefix: string = "cons", 14 | publicPrefix: string = "pub", 15 | operatorPrefix: string = "oper" 16 | ): Bech32Config { 17 | return { 18 | bech32PrefixAccAddr: mainPrefix, 19 | bech32PrefixAccPub: mainPrefix + publicPrefix, 20 | bech32PrefixValAddr: mainPrefix + validatorPrefix + operatorPrefix, 21 | bech32PrefixValPub: 22 | mainPrefix + validatorPrefix + operatorPrefix + publicPrefix, 23 | bech32PrefixConsAddr: mainPrefix + validatorPrefix + consensusPrefix, 24 | bech32PrefixConsPub: 25 | mainPrefix + validatorPrefix + consensusPrefix + publicPrefix 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /src/core/bip44.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This BIP defines a logical hierarchy for deterministic wallets. 3 | */ 4 | export class BIP44 { 5 | /** 6 | * Purpose is a constant set to 44' (or 0x8000002C) following the BIP43 recommendation. 7 | * It indicates that the subtree of this node is used according to this specification. 8 | * Hardened derivation is used at this level. 9 | */ 10 | public readonly purpose: number; 11 | /** 12 | * One master node (seed) can be used for unlimited number of independent cryptocoins such as Bitcoin, Litecoin or Namecoin. However, sharing the same space for various cryptocoins has some disadvantages. 13 | * This level creates a separate subtree for every cryptocoin, avoiding reusing addresses across cryptocoins and improving privacy issues. 14 | * Coin type is a constant, set for each cryptocoin. Cryptocoin developers may ask for registering unused number for their project. 15 | * The list of already allocated coin types is in the chapter "Registered coin types" below. 16 | * Hardened derivation is used at this level. 17 | */ 18 | public readonly coinType: number; 19 | /** 20 | * Constant 0 is used for external chain and constant 1 for internal chain (also known as change addresses). 21 | * External chain is used for addresses that are meant to be visible outside of the wallet (e.g. for receiving payments). 22 | * Internal chain is used for addresses which are not meant to be visible outside of the wallet and is used for return transaction change. 23 | */ 24 | public readonly change: number; 25 | 26 | constructor(purpose: number, coinType: number, change: number = 0) { 27 | this.purpose = purpose; 28 | this.coinType = coinType; 29 | this.change = change; 30 | } 31 | 32 | public withChange(change: number): BIP44 { 33 | return new BIP44(this.purpose, this.coinType, change); 34 | } 35 | 36 | /** 37 | * Return path 38 | * @param account Accounts are numbered from index 0 in sequentially increasing manner. This number is used as child index in BIP32 derivation. 39 | * Public derivation is used at this level. 40 | * @param index Addresses are numbered from index 0 in sequentially increasing manner. This number is used as child index in BIP32 derivation. 41 | * Public derivation is used at this level. 42 | */ 43 | public path(account: number, index: number): number[] { 44 | if (this.purpose !== parseInt(this.purpose.toString(), 10)) { 45 | throw new Error("Purpose should be integer"); 46 | } 47 | if (this.coinType !== parseInt(this.coinType.toString(), 10)) { 48 | throw new Error("CoinType should be integer"); 49 | } 50 | if (account !== parseInt(account.toString(), 10)) { 51 | throw new Error("Account should be integer"); 52 | } 53 | if (this.change !== parseInt(this.change.toString(), 10)) { 54 | throw new Error("Change should be integer"); 55 | } 56 | if (index !== parseInt(index.toString(), 10)) { 57 | throw new Error("Index should be integer"); 58 | } 59 | 60 | return [this.purpose, this.coinType, account, this.change, index]; 61 | } 62 | 63 | public pathString(account: number, index: number): string { 64 | const path = this.path(account, index); 65 | return `m/${path[0]}'/${path[1]}'/${path[2]}'/${path[3]}/${path[4]}`; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/core/context.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { ImmutableContext } from "./context"; 4 | 5 | describe("Test context", () => { 6 | it("context should be immutable", () => { 7 | const context = new ImmutableContext({ 8 | test: "test1" 9 | }); 10 | 11 | const newContext = context.set("test", "test2"); 12 | assert.strictEqual(context.get("test"), "test1"); 13 | assert.strictEqual(newContext.get("test"), "test2"); 14 | assert.notEqual(context, newContext); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/core/context.ts: -------------------------------------------------------------------------------- 1 | import { TxEncoder } from "./tx"; 2 | import { AxiosInstance } from "axios"; 3 | import { TxBuilder } from "./txBuilder"; 4 | import { Bech32Config } from "./bech32Config"; 5 | import { WalletProvider } from "./walletProvider"; 6 | import { QueryAccount } from "./account"; 7 | import { BIP44 } from "./bip44"; 8 | import { Codec } from "@chainapsis/ts-amino"; 9 | 10 | export class ImmutableContext { 11 | constructor(private context: T) {} 12 | 13 | public get(key: K): T[K] { 14 | return this.context[key] as T[K]; 15 | } 16 | 17 | public set(key: K, value: T[K]): ImmutableContext { 18 | return new ImmutableContext({ ...this.context, ...{ [key]: value } }); 19 | } 20 | } 21 | 22 | export interface IContext<> { 23 | chainId: string; 24 | txEncoder: TxEncoder; 25 | txBuilder: TxBuilder; 26 | bech32Config: Bech32Config; 27 | walletProvider: WalletProvider; 28 | rpcInstance: AxiosInstance; 29 | restInstance: AxiosInstance; 30 | queryAccount: QueryAccount; 31 | bip44: BIP44; 32 | codec: Codec; 33 | } 34 | 35 | export class Context extends ImmutableContext {} 36 | -------------------------------------------------------------------------------- /src/core/ledgerWallet.ts: -------------------------------------------------------------------------------- 1 | import { PubKey, PubKeySecp256k1 } from "../crypto"; 2 | import { Context } from "./context"; 3 | import { AccAddress } from "../common/address"; 4 | import { WalletProvider, Key } from "./walletProvider"; 5 | import { Buffer } from "buffer/"; 6 | const TransportWebUSB: any = require("@ledgerhq/hw-transport-webusb").default; 7 | const TransportU2F: any = require("@ledgerhq/hw-transport-u2f").default; 8 | const TransportHID: any = require("@ledgerhq/hw-transport-node-hid").default; 9 | const CosmosApp: any = require("ledger-cosmos-js").default; 10 | 11 | /** 12 | * This wallet provider provides a basic client interface to communicate with a Tendermint/Cosmos App running in a Ledger Nano S/X 13 | * Note: WebUSB support requires Cosmos app >= 1.5.3 14 | */ 15 | export class LedgerWalletProvider implements WalletProvider { 16 | private app: any; 17 | private path: number[] | undefined; 18 | private key: 19 | | { 20 | pubKey: PubKey; 21 | address: Uint8Array; 22 | } 23 | | undefined; 24 | 25 | constructor( 26 | public readonly transport: "WebUSB" | "U2F" | "HID", 27 | public readonly bech32PrefixAccAddr: string, 28 | private readonly account: number = 0, 29 | private readonly index: number = 0 30 | ) {} 31 | 32 | async enable(context: Context): Promise { 33 | let transport; 34 | switch (this.transport) { 35 | case "WebUSB": 36 | transport = await TransportWebUSB.create(); 37 | break; 38 | case "U2F": 39 | transport = await TransportU2F.create(); 40 | break; 41 | case "HID": 42 | transport = await TransportHID.create(); 43 | break; 44 | default: 45 | throw new Error("Unsupported transport"); 46 | } 47 | this.app = new CosmosApp(transport); 48 | 49 | let response = await this.app.getVersion(); 50 | if (response.error_message !== "No errors") { 51 | throw new Error(`[${response.error_message}] ${response.error_message}`); 52 | } 53 | 54 | console.log("Response received!"); 55 | console.log( 56 | `App Version ${response.major}.${response.minor}.${response.patch}` 57 | ); 58 | console.log(`Device Locked: ${response.device_locked}`); 59 | console.log(`Test mode: ${response.test_mode}`); 60 | console.log("Full response:"); 61 | console.log(response); 62 | 63 | this.path = context.get("bip44").path(this.account, this.index); 64 | 65 | response = await this.app.getAddressAndPubKey( 66 | this.path, 67 | context.get("bech32Config").bech32PrefixAccAddr 68 | ); 69 | if (response.error_message !== "No errors") { 70 | throw new Error(`[${response.error_message}] ${response.error_message}`); 71 | } 72 | 73 | this.key = { 74 | address: AccAddress.fromBech32( 75 | this.bech32PrefixAccAddr, 76 | response.bech32_address 77 | ).toBytes(), 78 | pubKey: new PubKeySecp256k1(response.compressed_pk) 79 | }; 80 | 81 | return Promise.resolve(); 82 | } 83 | 84 | getKeys(context: Context): Promise { 85 | if (!this.app || !this.path || !this.key) { 86 | throw new Error("Not approved"); 87 | } 88 | 89 | const bech32Address = new AccAddress( 90 | this.key!.address, 91 | this.bech32PrefixAccAddr 92 | ).toBech32(); 93 | 94 | const key = { 95 | address: this.key.address, 96 | algo: "secp256k1", 97 | pubKey: this.key.pubKey.serialize(), 98 | bech32Address 99 | }; 100 | 101 | return Promise.resolve([key]); 102 | } 103 | 104 | public async sign( 105 | context: Context, 106 | bech32Address: string, 107 | message: Uint8Array 108 | ): Promise { 109 | if (!this.app || !this.path || !this.key) { 110 | throw new Error("Not signed in"); 111 | } 112 | 113 | if ( 114 | new AccAddress(this.key!.address, this.bech32PrefixAccAddr).toBech32() !== 115 | bech32Address 116 | ) { 117 | throw new Error(`Unknown address: ${bech32Address}`); 118 | } 119 | 120 | const response = await this.app.sign(this.path, message); 121 | if (response.error_message !== "No errors") { 122 | throw new Error(`[${response.error_message}] ${response.error_message}`); 123 | } 124 | 125 | // Ledger has encoded the sig in ASN1 DER format, but we need a 64-byte buffer of 126 | // DER-encoded signature from Ledger: 127 | // 0 0x30: a header byte indicating a compound structure 128 | // 1 A 1-byte length descriptor for all what follows (ignore) 129 | // 2 0x02: a header byte indicating an integer 130 | // 3 A 1-byte length descriptor for the R value 131 | // 4 The R coordinate, as a big-endian integer 132 | // 0x02: a header byte indicating an integer 133 | // A 1-byte length descriptor for the S value 134 | // The S coordinate, as a big-endian integer 135 | // = 7 bytes of overhead 136 | let signature: Buffer = response.signature; 137 | if (signature[0] !== 0x30) { 138 | throw new Error( 139 | "Ledger assertion failed: Expected a signature header of 0x30" 140 | ); 141 | } 142 | 143 | // decode DER string format 144 | let rOffset = 4; 145 | let rLen = signature[3]; 146 | const sLen = signature[4 + rLen + 1]; // skip over following 0x02 type prefix for s 147 | let sOffset = signature.length - sLen; 148 | // we can safely ignore the first byte in the 33 bytes cases 149 | if (rLen === 33) { 150 | rOffset++; // chop off 0x00 padding 151 | rLen--; 152 | } 153 | if (sLen === 33) { 154 | sOffset++; 155 | } // as above 156 | const sigR = signature.slice(rOffset, rOffset + rLen); // skip e.g. 3045022100 and pad 157 | const sigS = signature.slice(sOffset); 158 | 159 | signature = Buffer.concat([sigR, sigS]); 160 | if (signature.length !== 64) { 161 | throw new Error( 162 | `Ledger assertion failed: incorrect signature length ${ 163 | signature.length 164 | }` 165 | ); 166 | } 167 | 168 | return Promise.resolve(new Uint8Array(signature)); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/core/query.ts: -------------------------------------------------------------------------------- 1 | import { AccAddress } from "../common/address"; 2 | import { BaseAccount } from "../common/baseAccount"; 3 | 4 | import { AxiosInstance } from "axios"; 5 | 6 | export async function queryAccount( 7 | rpcInstance: AxiosInstance, 8 | account: string | Uint8Array, 9 | bech32PrefixAccAddr?: string, 10 | options?: { 11 | querierRoute?: string; 12 | data?: string; 13 | } 14 | ) { 15 | if (typeof account === "string" && !bech32PrefixAccAddr) { 16 | throw new Error("Empty bech32 prefix"); 17 | } 18 | 19 | const accAddress: AccAddress = 20 | typeof account === "string" 21 | ? AccAddress.fromBech32(account, bech32PrefixAccAddr) 22 | : new AccAddress(account, bech32PrefixAccAddr!); 23 | 24 | const result = await rpcInstance.get("abci_query", { 25 | params: { 26 | path: 27 | "0x" + 28 | Buffer.from( 29 | `custom/${ 30 | options && options.querierRoute ? options.querierRoute : "acc" 31 | }/account` 32 | ).toString("hex"), 33 | data: 34 | options && options.data 35 | ? options.data 36 | : "0x" + 37 | Buffer.from( 38 | JSON.stringify({ 39 | Address: accAddress.toBech32() 40 | }) 41 | ).toString("hex") 42 | } 43 | }); 44 | 45 | if (result.status !== 200) { 46 | throw new Error(result.statusText); 47 | } 48 | 49 | if (result.data) { 50 | const r = result.data; 51 | if (r.result && r.result.response) { 52 | const response = r.result.response; 53 | 54 | if (response.code !== undefined && response.code !== 0) { 55 | throw new Error(response.log); 56 | } 57 | 58 | const value = JSON.parse( 59 | Buffer.from(response.value, "base64").toString() 60 | ); 61 | 62 | return BaseAccount.fromJSON(value); 63 | } 64 | } 65 | 66 | throw new Error("Unknown error"); 67 | } 68 | -------------------------------------------------------------------------------- /src/core/rest.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./context"; 2 | import { AxiosInstance } from "axios"; 3 | 4 | export class Rest { 5 | private _instance: AxiosInstance; 6 | 7 | constructor(private _context: Context) { 8 | this._instance = _context.get("restInstance"); 9 | } 10 | 11 | public get context(): Context { 12 | return this._context; 13 | } 14 | 15 | public get instance(): AxiosInstance { 16 | return this._instance; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/core/rpc.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./context"; 2 | import { AxiosInstance } from "axios"; 3 | 4 | export class RPC { 5 | private _instance: AxiosInstance; 6 | 7 | constructor(private _context: Context) { 8 | this._instance = _context.get("rpcInstance"); 9 | } 10 | 11 | public get context(): Context { 12 | return this._context; 13 | } 14 | 15 | public get instance(): AxiosInstance { 16 | return this._instance; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/core/tx.ts: -------------------------------------------------------------------------------- 1 | import { AccAddress } from "../common/address"; 2 | import { Codec } from "@chainapsis/ts-amino"; 3 | import { Buffer } from "buffer/"; 4 | import { sortJSON } from "../utils/sortJson"; 5 | import { Context } from "./context"; 6 | 7 | export interface Tx { 8 | getMsgs(): Msg[]; 9 | /** 10 | * ValidateBasic does a simple validation check that 11 | * doesn't require access to any other information. 12 | * You can throw error in this when tx is invalid. 13 | */ 14 | validateBasic(): void; 15 | } 16 | 17 | export abstract class Msg { 18 | /** 19 | * ValidateBasic does a simple validation check that 20 | * doesn't require access to any other information. 21 | * You can throw error in this when msg is invalid. 22 | */ 23 | public abstract validateBasic(): void; 24 | /** 25 | * Get the canonical byte representation of the Msg. 26 | * @return Return sorted by alphabetically amino encoded json by default. 27 | */ 28 | public getSignBytes(codec: Codec): Uint8Array { 29 | return Buffer.from(sortJSON(codec.marshalJson(this)), "utf8"); 30 | } 31 | public getSigners(): AccAddress[] { 32 | throw new Error("You should implement getSigners()"); 33 | } 34 | } 35 | 36 | export type TxEncoder = (conext: Context, tx: Tx) => Uint8Array; 37 | -------------------------------------------------------------------------------- /src/core/txBuilder.ts: -------------------------------------------------------------------------------- 1 | import { Context } from "./context"; 2 | import { Tx, Msg } from "./tx"; 3 | import bigInteger from "big-integer"; 4 | import { Coin } from "../common/coin"; 5 | 6 | export interface TxBuilderConfig { 7 | /** 8 | * @param accountNumber - uint64, If this is undefined or negative, tx builder should calculate that automatically or throw error. 9 | * If there are several signers, this should be undefined or negative. 10 | */ 11 | accountNumber?: bigInteger.BigNumber; 12 | /** 13 | * @param sequence - uint64, If this is undefined or negative, tx builder should calculate that automatically or throw error. 14 | * If there are several signers, this should be undefined or negative. 15 | */ 16 | sequence?: bigInteger.BigNumber; 17 | /** 18 | * @param gas - uint64, How much gas will it consume.
19 | * TODO: If this parameter is negative, this means that gas will be set automatically with simulated value. 20 | */ 21 | gas: bigInteger.BigNumber; 22 | /** 23 | * @param gasAdjustment - TODO: If gas parameter is negative(auto), simulated gas will be multiplied with this. 24 | */ 25 | gasAdjustment?: number; 26 | memo: string; 27 | fee: Coin[] | Coin; 28 | gasPrice?: number; 29 | } 30 | 31 | export type TxBuilder = ( 32 | context: Context, 33 | msgs: Msg[], 34 | config: TxBuilderConfig 35 | ) => Promise; 36 | -------------------------------------------------------------------------------- /src/core/walletProvider.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { LocalWalletProvider } from "./walletProvider"; 4 | 5 | import crypto from "crypto"; 6 | import { PubKeySecp256k1 } from "../crypto"; 7 | import { Context } from "./context"; 8 | import { BIP44 } from "./bip44"; 9 | import { Codec } from "@chainapsis/ts-amino"; 10 | import { defaultBech32Config } from "./bech32Config"; 11 | 12 | describe("Test local wallet provider", () => { 13 | it("local wallet provider should generate correct priv key", async () => { 14 | const localWalletProvider = new LocalWalletProvider( 15 | "cosmos", 16 | "anger river nuclear pig enlist fish demand dress library obtain concert nasty wolf episode ring bargain rely off vibrant iron cram witness extra enforce", 17 | 0, 18 | 0, 19 | (array: any): any => { 20 | return crypto.randomBytes(array.length); 21 | } 22 | ); 23 | 24 | const context: Context = new Context({ 25 | chainId: "", 26 | txEncoder: undefined as any, 27 | txBuilder: undefined as any, 28 | bech32Config: defaultBech32Config("cosmos"), 29 | walletProvider: undefined as any, 30 | rpcInstance: undefined as any, 31 | restInstance: undefined as any, 32 | queryAccount: undefined as any, 33 | bip44: new BIP44(44, 118, 0), 34 | codec: new Codec() 35 | }); 36 | 37 | await localWalletProvider.enable(context); 38 | 39 | const keys = await localWalletProvider.getKeys(context); 40 | assert.strictEqual(keys.length, 1); 41 | 42 | const key = keys[0]; 43 | assert.strictEqual(key.algo, "secp256k1"); 44 | assert.strictEqual( 45 | new PubKeySecp256k1(key.pubKey).toAddress().toBech32("cosmos"), 46 | "cosmos1t68n2ezn5zt8frh4jehmufkk2puakv9glapyz4" 47 | ); 48 | assert.strictEqual( 49 | key.bech32Address, 50 | "cosmos1t68n2ezn5zt8frh4jehmufkk2puakv9glapyz4" 51 | ); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /src/core/walletProvider.ts: -------------------------------------------------------------------------------- 1 | import { PrivKey } from "../crypto"; 2 | import { AccAddress } from "../common/address"; 3 | import { generateWalletFromMnemonic, generateSeed, RNG } from "../utils/key"; 4 | import { Context } from "./context"; 5 | import { BIP44 } from "./bip44"; 6 | import { TxBuilderConfig } from "./txBuilder"; 7 | 8 | export interface Key { 9 | bech32Address: string; 10 | address: Uint8Array; 11 | algo: string; 12 | pubKey: Uint8Array; 13 | } 14 | 15 | export interface WalletProvider { 16 | /** 17 | * Request access to the user's accounts. Wallet can ask the user to approve or deny access. If user deny access, it will throw error. 18 | */ 19 | enable(context: Context): Promise; 20 | 21 | /** 22 | * Get array of keys that includes bech32 address string, address bytes and public key from wallet if user have approved the access. 23 | */ 24 | getKeys(context: Context): Promise; 25 | 26 | /** 27 | * Request tx builder config from provider. 28 | * This is optional method. 29 | * If provider supports this method, tx builder will request tx config with prefered tx config that is defined by developer who uses cosmosjs. 30 | * Received tx builder config can be changed in the client. The wallet provider must verify that it is the same as the tx builder config sent earlier or warn the user before signing. 31 | */ 32 | getTxBuilderConfig?( 33 | context: Context, 34 | config: TxBuilderConfig 35 | ): Promise; 36 | 37 | /** 38 | * Request signature from matched address if user have approved the access. 39 | */ 40 | sign( 41 | context: Context, 42 | bech32Address: string, 43 | message: Uint8Array 44 | ): Promise; 45 | } 46 | 47 | /** 48 | * Using the this in the browser is not secure and should only be used for development purposes. 49 | * Use a secure vault outside of the context of the webpage to ensure security when signing transactions in production. 50 | */ 51 | export class LocalWalletProvider implements WalletProvider { 52 | public static generateMnemonic(rng?: RNG): string { 53 | if (!rng) { 54 | throw new Error("You should set rng to generate seed"); 55 | } 56 | return generateSeed(rng); 57 | } 58 | 59 | public static getPrivKeyFromMnemonic( 60 | bip44: BIP44, 61 | mnemonic: string, 62 | account: number = 0, 63 | index: number = 0 64 | ): PrivKey { 65 | return generateWalletFromMnemonic( 66 | mnemonic, 67 | bip44.pathString(account, index) 68 | ); 69 | } 70 | private privKey?: PrivKey; 71 | 72 | constructor( 73 | public readonly bech32PrefixAccAddr: string, 74 | private readonly mnemonic: string = "", 75 | private readonly account: number = 0, 76 | private readonly index: number = 0, 77 | private readonly rng?: RNG 78 | ) { 79 | if (this.mnemonic === "") { 80 | this.mnemonic = LocalWalletProvider.generateMnemonic(this.rng); 81 | } 82 | } 83 | 84 | enable(context: Context): Promise { 85 | this.privKey = LocalWalletProvider.getPrivKeyFromMnemonic( 86 | context.get("bip44"), 87 | this.mnemonic, 88 | this.account, 89 | this.index 90 | ); 91 | return Promise.resolve(); 92 | } 93 | 94 | getKeys(context: Context): Promise { 95 | if (!this.privKey) { 96 | throw new Error("Not approved"); 97 | } 98 | 99 | const pubKey = this.privKey.toPubKey(); 100 | const address = this.privKey.toPubKey().toAddress(); 101 | const bech32Address = new AccAddress( 102 | address, 103 | this.bech32PrefixAccAddr 104 | ).toBech32(); 105 | 106 | const key: Key = { 107 | bech32Address, 108 | address: address.toBytes(), 109 | algo: "secp256k1", 110 | pubKey: pubKey.serialize() 111 | }; 112 | 113 | return Promise.resolve([key]); 114 | } 115 | 116 | sign( 117 | context: Context, 118 | bech32Address: string, 119 | message: Uint8Array 120 | ): Promise { 121 | if (!this.privKey) { 122 | throw new Error("Not approved"); 123 | } 124 | const pubKey = this.privKey.toPubKey(); 125 | const address = pubKey.toAddress(); 126 | const accAddress = new AccAddress(address, this.bech32PrefixAccAddr); 127 | if (accAddress.toBech32() !== bech32Address) { 128 | throw new Error(`Unknown address: ${bech32Address}`); 129 | } 130 | 131 | return Promise.resolve(this.privKey.sign(message)); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/crypto/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { PrivKeySecp256k1, PubKeySecp256k1 } from "./secp256k1"; 3 | 4 | export function registerCodec(codec: Codec) { 5 | codec.registerConcrete( 6 | "tendermint/PrivKeySecp256k1", 7 | PrivKeySecp256k1.prototype 8 | ); 9 | codec.registerConcrete( 10 | "tendermint/PubKeySecp256k1", 11 | PubKeySecp256k1.prototype 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/crypto/index.ts: -------------------------------------------------------------------------------- 1 | import { PrivKeySecp256k1, PubKeySecp256k1 } from "./secp256k1"; 2 | import { Address, PrivKey, PubKey } from "./types"; 3 | import { registerCodec } from "./codec"; 4 | 5 | export { 6 | Address, 7 | PubKey, 8 | PrivKey, 9 | PubKeySecp256k1, 10 | PrivKeySecp256k1, 11 | registerCodec 12 | }; 13 | -------------------------------------------------------------------------------- /src/crypto/secp256k1.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { PrivKeySecp256k1 } from "./secp256k1"; 4 | import { Buffer } from "buffer/"; 5 | import { Amino } from "@chainapsis/ts-amino"; 6 | import { registerCodec } from "./codec"; 7 | 8 | describe("Test secp256k1", () => { 9 | registerCodec(Amino.globalCodec); 10 | 11 | it("secp256k1 should generate correct signature", () => { 12 | const privKey = new PrivKeySecp256k1( 13 | new Uint8Array([ 14 | 110, 15 | 52, 16 | 11, 17 | 156, 18 | 255, 19 | 179, 20 | 122, 21 | 152, 22 | 156, 23 | 165, 24 | 68, 25 | 230, 26 | 187, 27 | 120, 28 | 10, 29 | 44, 30 | 120, 31 | 144, 32 | 29, 33 | 63, 34 | 179, 35 | 55, 36 | 56, 37 | 118, 38 | 133, 39 | 17, 40 | 163, 41 | 6, 42 | 23, 43 | 175, 44 | 160, 45 | 30 46 | ]) 47 | ); 48 | const pubKey = privKey.toPubKey(); 49 | assert.strictEqual( 50 | Buffer.from(pubKey.toBytes()).toString("hex"), 51 | "eb5ae98721037241cbb79688a20d01ed093ebb06c6fd0341be948090ba0fda36a5efa742fe3d" 52 | ); 53 | 54 | const signature = privKey.sign(new Uint8Array([1, 2, 3])); 55 | assert.strictEqual( 56 | Buffer.from(signature).toString("hex"), 57 | "07fd207549d1d550d567b7951897b19daa8fe01dc34baa90cb741742583b090b0af162b3fddd83a2dd5981bfd66243bcba7d64f8f1ce3bc6855b086edc65378a" 58 | ); 59 | 60 | const verified = pubKey.verify(new Uint8Array([1, 2, 3]), signature); 61 | assert.strictEqual(verified, true); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /src/crypto/secp256k1.ts: -------------------------------------------------------------------------------- 1 | import { Amino, Type } from "@chainapsis/ts-amino"; 2 | const { Field, DefineType, marshalBinaryBare } = Amino; 3 | import { Buffer } from "buffer/"; 4 | import EC from "elliptic"; 5 | import CryptoJS from "crypto-js"; 6 | import { Address, PrivKey, PubKey } from "./types"; 7 | 8 | @DefineType() 9 | export class PrivKeySecp256k1 implements PrivKey { 10 | @Field.Array(0, { type: Type.Uint8 }) 11 | private privKey: Uint8Array; 12 | 13 | constructor(privKey: Uint8Array) { 14 | this.privKey = privKey; 15 | } 16 | 17 | /** 18 | * @returns Return amino encoded bytes (including prefix bytes for concrete type). 19 | */ 20 | public toBytes(): Uint8Array { 21 | return marshalBinaryBare(this); 22 | } 23 | 24 | /** 25 | * @returns Return bytes without type info. 26 | */ 27 | public serialize(): Uint8Array { 28 | return new Uint8Array(this.privKey); 29 | } 30 | 31 | public toPubKey(): PubKey { 32 | const secp256k1 = new EC.ec("secp256k1"); 33 | 34 | const key = secp256k1.keyFromPrivate(this.privKey); 35 | return new PubKeySecp256k1( 36 | new Uint8Array(key.getPublic().encodeCompressed("array")) 37 | ); 38 | } 39 | 40 | public equals(privKey: PrivKey): boolean { 41 | return this.toBytes().toString() === privKey.toBytes().toString(); 42 | } 43 | 44 | public sign(msg: Uint8Array): Uint8Array { 45 | const secp256k1 = new EC.ec("secp256k1"); 46 | const key = secp256k1.keyFromPrivate(this.privKey); 47 | 48 | const hash = CryptoJS.SHA256(CryptoJS.lib.WordArray.create(msg)).toString(); 49 | 50 | const signature = key.sign(Buffer.from(hash, "hex"), { 51 | canonical: true 52 | }); 53 | 54 | return new Uint8Array(signature.r.toArray().concat(signature.s.toArray())); 55 | } 56 | 57 | public toString(): string { 58 | return Buffer.from(this.privKey).toString("hex"); 59 | } 60 | } 61 | 62 | @DefineType() 63 | export class PubKeySecp256k1 implements PubKey { 64 | @Field.Array(0, { type: Type.Uint8 }) 65 | private pubKey: Uint8Array; 66 | 67 | constructor(pubKey: Uint8Array) { 68 | this.pubKey = pubKey; 69 | } 70 | 71 | /** 72 | * @returns Return amino encoded bytes (including prefix bytes for concrete type). 73 | */ 74 | public toBytes(): Uint8Array { 75 | return marshalBinaryBare(this); 76 | } 77 | 78 | /** 79 | * @returns Return bytes without type info. 80 | */ 81 | public serialize(): Uint8Array { 82 | return new Uint8Array(this.pubKey); 83 | } 84 | 85 | public toAddress(): Address { 86 | let hash = CryptoJS.SHA256( 87 | CryptoJS.lib.WordArray.create(this.pubKey) 88 | ).toString(); 89 | hash = CryptoJS.RIPEMD160(CryptoJS.enc.Hex.parse(hash)).toString(); 90 | 91 | return new Address(Buffer.from(hash, "hex")); 92 | } 93 | 94 | public equals(pubKey: PubKey): boolean { 95 | return this.toBytes().toString() === pubKey.toBytes().toString(); 96 | } 97 | 98 | public verify(msg: Uint8Array, sig: Uint8Array): boolean { 99 | const secp256k1 = new EC.ec("secp256k1"); 100 | const key = secp256k1.keyFromPublic(this.pubKey); 101 | 102 | const hash = CryptoJS.SHA256(CryptoJS.lib.WordArray.create(msg)).toString(); 103 | 104 | const signature = { 105 | r: sig.slice(0, 32), 106 | s: sig.slice(32) 107 | }; 108 | 109 | return key.verify(Buffer.from(hash, "hex"), signature); 110 | } 111 | 112 | public toString(): string { 113 | return Buffer.from(this.pubKey).toString("hex"); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/crypto/types.ts: -------------------------------------------------------------------------------- 1 | import { Amino, Type } from "@chainapsis/ts-amino"; 2 | const { Field, DefineType } = Amino; 3 | import bech32 from "bech32"; 4 | 5 | @DefineType() 6 | export class Address { 7 | public static fromBech32(prefix: string, bech32Addr: string): Address { 8 | const { prefix: b32Prefix, words } = bech32.decode(bech32Addr); 9 | if (b32Prefix !== prefix) { 10 | throw new Error("Prefix doesn't match"); 11 | } 12 | return new Address(bech32.fromWords(words)); 13 | } 14 | 15 | @Field.Array(0, { type: Type.Uint8 }) 16 | private address: Uint8Array; 17 | 18 | constructor(address: Uint8Array) { 19 | this.address = address; 20 | } 21 | 22 | public toBech32(prefix: string): string { 23 | const words = bech32.toWords(Buffer.from(this.address) as any); 24 | return bech32.encode(prefix, words); 25 | } 26 | 27 | public toBytes(): Uint8Array { 28 | // Return uint8array newly, because address could have been made from buffer. 29 | return new Uint8Array(this.address); 30 | } 31 | } 32 | 33 | export interface PubKey { 34 | toAddress(): Address; 35 | /** 36 | * @returns Return amino encoded bytes (including prefix bytes for concrete type). 37 | */ 38 | toBytes(): Uint8Array; 39 | /** 40 | * @returns Return bytes without type info. 41 | */ 42 | serialize(): Uint8Array; 43 | verify(msg: Uint8Array, sig: Uint8Array): boolean; 44 | equals(pubKey: PubKey): boolean; 45 | } 46 | 47 | export interface PrivKey { 48 | /** 49 | * @returns Return amino encoded bytes (including prefix bytes for concrete type). 50 | */ 51 | toBytes(): Uint8Array; 52 | /** 53 | * @returns Return bytes without type info. 54 | */ 55 | serialize(): Uint8Array; 56 | sign(msg: Uint8Array): Uint8Array; 57 | toPubKey(): PubKey; 58 | equals(privKey: PrivKey): boolean; 59 | } 60 | -------------------------------------------------------------------------------- /src/gaia/api.ts: -------------------------------------------------------------------------------- 1 | import { Api, ApiConfig, CoreConfig } from "../core/api"; 2 | import { GaiaRest } from "./rest"; 3 | import * as CmnCdc from "../common/codec"; 4 | import * as Bank from "../x/bank"; 5 | import * as Distribution from "../x/distribution"; 6 | import * as Gov from "../x/gov"; 7 | import * as Slashing from "../x/slashing"; 8 | import * as Staking from "../x/staking"; 9 | import * as Wasm from "../x/wasm"; 10 | import { defaultTxEncoder } from "../common/stdTx"; 11 | import { stdTxBuilder } from "../common/stdTxBuilder"; 12 | import { Context } from "../core/context"; 13 | import { Account } from "../core/account"; 14 | import { BIP44 } from "../core/bip44"; 15 | import { defaultBech32Config } from "../core/bech32Config"; 16 | import { Codec } from "@chainapsis/ts-amino"; 17 | import { queryAccount } from "../core/query"; 18 | import * as Crypto from "../crypto"; 19 | 20 | export class GaiaApi extends Api { 21 | constructor( 22 | config: ApiConfig, 23 | coreConfig: Partial> = {} 24 | ) { 25 | super(config, { 26 | ...{ 27 | txEncoder: defaultTxEncoder, 28 | txBuilder: stdTxBuilder, 29 | restFactory: (context: Context) => { 30 | return new GaiaRest(context); 31 | }, 32 | queryAccount: ( 33 | context: Context, 34 | address: string | Uint8Array 35 | ): Promise => { 36 | return queryAccount( 37 | context.get("rpcInstance"), 38 | address, 39 | coreConfig.bech32Config 40 | ? coreConfig.bech32Config.bech32PrefixAccAddr 41 | : "cosmos" 42 | ); 43 | }, 44 | bech32Config: defaultBech32Config("cosmos"), 45 | bip44: new BIP44(44, 118, 0), 46 | registerCodec: (codec: Codec) => { 47 | CmnCdc.registerCodec(codec); 48 | Crypto.registerCodec(codec); 49 | Bank.registerCodec(codec); 50 | Distribution.registerCodec(codec); 51 | Gov.registerCodec(codec); 52 | Slashing.registerCodec(codec); 53 | Staking.registerCodec(codec); 54 | Wasm.registerCodec(codec); 55 | } 56 | }, 57 | ...coreConfig 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/gaia/rest.ts: -------------------------------------------------------------------------------- 1 | import { Rest } from "../core/rest"; 2 | import { Account } from "../core/account"; 3 | import { AccAddress } from "../common/address"; 4 | import { BaseAccount } from "../common/baseAccount"; 5 | 6 | export class GaiaRest extends Rest { 7 | public async getAccount( 8 | account: string | Uint8Array, 9 | bech32PrefixAccAddr?: string 10 | ): Promise { 11 | if (typeof account === "string" && !bech32PrefixAccAddr) { 12 | throw new Error("Empty bech32 prefix"); 13 | } 14 | 15 | const accAddress: AccAddress = 16 | typeof account === "string" 17 | ? AccAddress.fromBech32(account, bech32PrefixAccAddr) 18 | : new AccAddress(account, bech32PrefixAccAddr!); 19 | 20 | const result = await this.instance.get( 21 | `auth/accounts/${accAddress.toBech32()}` 22 | ); 23 | return BaseAccount.fromJSON(result.data); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/rpc/abci.ts: -------------------------------------------------------------------------------- 1 | import { JSONRPC } from "./types"; 2 | import bigInteger from "big-integer"; 3 | import { Buffer } from "buffer/"; 4 | 5 | export class ResultABCIInfo { 6 | public static fromJSON(obj: JSONRPC): ResultABCIInfo { 7 | return new ResultABCIInfo(ABCIResponseInfo.fromJSON(obj.result.response)); 8 | } 9 | 10 | constructor(public readonly response: ABCIResponseInfo) {} 11 | } 12 | 13 | export class ABCIResponseInfo { 14 | public static fromJSON(obj: any): ABCIResponseInfo { 15 | return new ABCIResponseInfo( 16 | obj.data, 17 | obj.version, 18 | obj.app_version, 19 | obj.last_block_height, 20 | obj.last_block_app_hash 21 | ); 22 | } 23 | 24 | constructor( 25 | public readonly data: string, 26 | public readonly version: string | undefined, 27 | public readonly appVersion: string | undefined, 28 | public readonly lastBlockHeight: bigInteger.BigInteger, 29 | public readonly lastBlockAppHash: string 30 | ) {} 31 | } 32 | 33 | export class ResultABCIQuery { 34 | public static fromJSON(obj: JSONRPC): ResultABCIQuery { 35 | return new ResultABCIQuery(ABCIResponseQuery.fromJSON(obj.result.response)); 36 | } 37 | 38 | constructor(public readonly response: ABCIResponseQuery) {} 39 | } 40 | 41 | export class ABCIResponseQuery { 42 | public static fromJSON(obj: any): ABCIResponseQuery { 43 | return new ABCIResponseQuery( 44 | Buffer.from(obj.key, "base64"), 45 | Buffer.from(obj.value, "base64"), 46 | bigInteger(obj.height) 47 | ); 48 | } 49 | 50 | // TODO: add code, proofs and so on... 51 | constructor( 52 | public readonly key: Uint8Array, 53 | public readonly value: Uint8Array, 54 | public readonly height: bigInteger.BigInteger 55 | ) {} 56 | } 57 | 58 | export interface ABCIQueryOptions { 59 | height: bigInteger.BigNumber; 60 | prove: boolean; 61 | } 62 | -------------------------------------------------------------------------------- /src/rpc/status.ts: -------------------------------------------------------------------------------- 1 | import { JSONRPC } from "./types"; 2 | import bigInteger from "big-integer"; 3 | 4 | export class ResultStatus { 5 | public static fromJSON(obj: JSONRPC): ResultStatus { 6 | return new ResultStatus( 7 | NodeInfo.fromJSON(obj.result.node_info), 8 | SyncInfo.fromJSON(obj.result.sync_info), 9 | ValidatorInfo.fromJSON(obj.result.validator_info) 10 | ); 11 | } 12 | 13 | constructor( 14 | public readonly nodeInfo: NodeInfo, 15 | public readonly syncInfo: SyncInfo, 16 | public readonly validatorInfo: ValidatorInfo 17 | ) {} 18 | } 19 | 20 | export class NodeInfo { 21 | public static fromJSON(obj: any): NodeInfo { 22 | return new NodeInfo( 23 | { 24 | p2p: parseInt(obj.protocol_version.p2p, 10), 25 | block: parseInt(obj.protocol_version.block, 10), 26 | app: parseInt(obj.protocol_version.app, 10) 27 | }, 28 | obj.id, 29 | obj.listen_addr, 30 | obj.network, 31 | obj.version, 32 | obj.channels, 33 | obj.moniker, 34 | { 35 | txIndex: obj.other.tx_index === "on" ? true : false, 36 | rpcAddress: obj.other.rpc_address 37 | } 38 | ); 39 | } 40 | 41 | constructor( 42 | public readonly protocolVersion: { 43 | p2p: number; 44 | block: number; 45 | app: number; 46 | }, 47 | public readonly id: string, 48 | public readonly listenAddr: string, 49 | public readonly network: string, 50 | public readonly version: string, 51 | public readonly channels: string, 52 | public readonly moniker: string, 53 | public readonly other: { 54 | txIndex: boolean; 55 | rpcAddress: string; 56 | } 57 | ) {} 58 | } 59 | 60 | export class SyncInfo { 61 | public static fromJSON(obj: any): SyncInfo { 62 | return new SyncInfo( 63 | obj.latest_block_hash, 64 | obj.latest_app_hash, 65 | bigInteger(obj.latest_block_height), 66 | new Date(obj.latest_block_time), 67 | obj.catching_up 68 | ); 69 | } 70 | 71 | constructor( 72 | public readonly latestBlockHash: string, 73 | public readonly latestAppHash: string, 74 | public readonly latestBlockHeight: bigInteger.BigInteger, 75 | public readonly latestBlockTime: Date, 76 | public readonly catchingUp: boolean 77 | ) {} 78 | } 79 | 80 | export class ValidatorInfo { 81 | public static fromJSON(obj: any): ValidatorInfo { 82 | return new ValidatorInfo(obj.address, obj.voting_power); 83 | } 84 | 85 | constructor( 86 | public readonly address: string, 87 | // TODO: Suport validator pubkey when Ed25519 is implemented 88 | // public readonly pubKey:PubKey, 89 | public readonly votingPower: bigInteger.BigInteger 90 | ) {} 91 | } 92 | -------------------------------------------------------------------------------- /src/rpc/tendermint.ts: -------------------------------------------------------------------------------- 1 | import { RPC } from "../core/rpc"; 2 | import { Context } from "../core/context"; 3 | import { Buffer } from "buffer/"; 4 | import bigInteger from "big-integer"; 5 | import { JSONRPC } from "./types"; 6 | import { ResultStatus } from "./status"; 7 | import { ResultABCIInfo, ResultABCIQuery, ABCIQueryOptions } from "./abci"; 8 | import { ResultBroadcastTx, ResultBroadcastTxCommit } from "./tx"; 9 | 10 | export class TendermintRPC extends RPC { 11 | constructor(context: Context) { 12 | super(context); 13 | } 14 | 15 | public async status(): Promise { 16 | const result = await this.instance.get("/status"); 17 | if (result.data.error) { 18 | const error = result.data.error; 19 | throw new Error( 20 | `code: ${error.code}, message: ${error.message}, data: ${error.data}` 21 | ); 22 | } 23 | return ResultStatus.fromJSON(result.data); 24 | } 25 | 26 | public async abciInfo(): Promise { 27 | const result = await this.instance.get("/abci_info"); 28 | if (result.data.error) { 29 | const error = result.data.error; 30 | throw new Error( 31 | `code: ${error.code}, message: ${error.message}, data: ${error.data}` 32 | ); 33 | } 34 | return ResultABCIInfo.fromJSON(result.data); 35 | } 36 | 37 | public async abciQuery( 38 | path: string, 39 | data: Uint8Array | string, 40 | opts?: ABCIQueryOptions 41 | ): Promise { 42 | const height = opts ? bigInteger(opts.height as any) : bigInteger(0); 43 | const prove = opts ? opts.prove : false; 44 | let bz: Buffer; 45 | if (typeof data === "string") { 46 | bz = 47 | data.indexOf("0x") === 0 48 | ? Buffer.from(data.replace("0x", ""), "hex") 49 | : Buffer.from(data); 50 | } else { 51 | bz = Buffer.from(data); 52 | } 53 | 54 | const result = await this.instance.get( 55 | `/abci_query?path="${path}&data=0x${bz.toString( 56 | "hex" 57 | )}&prove=${prove}&height=${height.toString()}"` 58 | ); 59 | if (result.data.error) { 60 | const error = result.data.error; 61 | throw new Error( 62 | `code: ${error.code}, message: ${error.message}, data: ${error.data}` 63 | ); 64 | } 65 | return ResultABCIQuery.fromJSON(result.data); 66 | } 67 | 68 | public async broadcastTx( 69 | tx: Uint8Array, 70 | mode: "sync" | "async" 71 | ): Promise { 72 | const hex = Buffer.from(tx).toString("hex"); 73 | const result = await this.instance.get(`/broadcast_tx_${mode}`, { 74 | params: { 75 | tx: "0x" + hex 76 | } 77 | }); 78 | if (result.data.error) { 79 | const error = result.data.error; 80 | throw new Error( 81 | `code: ${error.code}, message: ${error.message}, data: ${error.data}` 82 | ); 83 | } 84 | return ResultBroadcastTx.fromJSON(result.data, mode); 85 | } 86 | 87 | public async broadcastTxCommit( 88 | tx: Uint8Array 89 | ): Promise { 90 | const hex = Buffer.from(tx).toString("hex"); 91 | const result = await this.instance.get(`/broadcast_tx_commit`, { 92 | params: { 93 | tx: "0x" + hex 94 | } 95 | }); 96 | if (result.data.error) { 97 | const error = result.data.error; 98 | throw new Error( 99 | `code: ${error.code}, message: ${error.message}, data: ${error.data}` 100 | ); 101 | } 102 | return ResultBroadcastTxCommit.fromJSON(result.data); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/rpc/tx.ts: -------------------------------------------------------------------------------- 1 | import { JSONRPC } from "./types"; 2 | import { Buffer } from "buffer/"; 3 | import bigInteger from "big-integer"; 4 | 5 | export class ResultBroadcastTx { 6 | public static fromJSON( 7 | obj: JSONRPC, 8 | mode: "async" | "sync" 9 | ): ResultBroadcastTx { 10 | const result = obj.result; 11 | 12 | return new ResultBroadcastTx( 13 | mode, 14 | result.code, 15 | Buffer.from(result.data, "hex"), 16 | result.log, 17 | Buffer.from(result.hash, "hex") 18 | ); 19 | } 20 | 21 | constructor( 22 | public readonly mode: "async" | "sync", 23 | public readonly code: number, 24 | public readonly data: Uint8Array, 25 | public readonly log: string, 26 | public readonly hash: Uint8Array 27 | ) {} 28 | } 29 | 30 | export class ResultBroadcastTxCommit { 31 | public static fromJSON(obj: JSONRPC): ResultBroadcastTxCommit { 32 | const result = obj.result; 33 | 34 | return new ResultBroadcastTxCommit( 35 | "commit", 36 | ResponseCheckTx.fromJSON(result.check_tx), 37 | ResponseDeliverTx.fromJSON(result.deliver_tx), 38 | Buffer.from(result.hash, "hex"), 39 | bigInteger(result.height) 40 | ); 41 | } 42 | 43 | constructor( 44 | public readonly mode: "commit", 45 | public readonly checkTx: ResponseCheckTx, 46 | public readonly deliverTx: ResponseDeliverTx, 47 | public readonly hash: Uint8Array, 48 | public readonly height: bigInteger.BigInteger 49 | ) {} 50 | } 51 | 52 | export class ResponseCheckTx { 53 | public static fromJSON(obj: any): ResponseCheckTx { 54 | const tags: Array<{ 55 | key: Uint8Array; 56 | value: Uint8Array; 57 | }> = []; 58 | if (obj.tags) { 59 | for (const tag of obj.tags) { 60 | tags.push({ 61 | key: Buffer.from(tag.key, "base64"), 62 | value: Buffer.from(tag.value, "base64") 63 | }); 64 | } 65 | } 66 | 67 | return new ResponseCheckTx( 68 | obj.code, 69 | obj.data ? Buffer.from(obj.data, "base64") : Buffer.from([]), 70 | obj.log, 71 | obj.info, 72 | bigInteger(obj.gas_wanted), 73 | bigInteger(obj.gas_used), 74 | tags, 75 | obj.codespace 76 | ); 77 | } 78 | 79 | constructor( 80 | public readonly code: number | undefined, 81 | public readonly data: Uint8Array, 82 | public readonly log: string | undefined, 83 | public readonly info: string | undefined, 84 | public readonly gasWanted: bigInteger.BigInteger, 85 | public readonly gasUsed: bigInteger.BigInteger, 86 | public readonly tags: Array<{ 87 | key: Uint8Array; 88 | value: Uint8Array; 89 | }>, 90 | public readonly codespace: string | undefined 91 | ) {} 92 | } 93 | 94 | export class ResponseDeliverTx { 95 | public static fromJSON(obj: any): ResponseDeliverTx { 96 | const tags: Array<{ 97 | key: Uint8Array; 98 | value: Uint8Array; 99 | }> = []; 100 | if (obj.tags) { 101 | for (const tag of obj.tags) { 102 | tags.push({ 103 | key: Buffer.from(tag.key, "base64"), 104 | value: Buffer.from(tag.value, "base64") 105 | }); 106 | } 107 | } 108 | 109 | return new ResponseDeliverTx( 110 | obj.code, 111 | obj.data ? Buffer.from(obj.data, "base64") : Buffer.from([]), 112 | obj.log, 113 | obj.info, 114 | bigInteger(obj.gas_wanted), 115 | bigInteger(obj.gas_used), 116 | tags, 117 | obj.codespace 118 | ); 119 | } 120 | 121 | constructor( 122 | public readonly code: number | undefined, 123 | public readonly data: Uint8Array, 124 | public readonly log: string | undefined, 125 | public readonly info: string | undefined, 126 | public readonly gasWanted: bigInteger.BigInteger, 127 | public readonly gasUsed: bigInteger.BigInteger, 128 | public readonly tags: Array<{ 129 | key: Uint8Array; 130 | value: Uint8Array; 131 | }>, 132 | public readonly codespace: string | undefined 133 | ) {} 134 | } 135 | -------------------------------------------------------------------------------- /src/rpc/types.ts: -------------------------------------------------------------------------------- 1 | export interface JSONRPC { 2 | jsonrpc: string; 3 | id: string; 4 | result: any; 5 | error: 6 | | { 7 | code: number; 8 | message: string; 9 | data: string; 10 | } 11 | | undefined; 12 | } 13 | -------------------------------------------------------------------------------- /src/utils/key.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import * as Key from "./key"; 4 | 5 | import crypto from "crypto"; 6 | 7 | describe("Test wallet", () => { 8 | it("generate key and mnemonic", () => { 9 | const { privKey, mnemonic } = Key.generateWallet( 10 | (array: any): any => { 11 | return crypto.randomBytes(array.length); 12 | } 13 | ); 14 | assert.strictEqual( 15 | mnemonic.split(" ").length, 16 | 24, 17 | "should generate 24 words by default" 18 | ); 19 | 20 | const recoveredPrivKey = Key.generateWalletFromMnemonic(mnemonic); 21 | assert.strictEqual(recoveredPrivKey.toString(), privKey.toString()); 22 | assert.strictEqual( 23 | recoveredPrivKey.toPubKey().toString(), 24 | privKey.toPubKey().toString() 25 | ); 26 | assert.strictEqual( 27 | recoveredPrivKey 28 | .toPubKey() 29 | .toAddress() 30 | .toBech32("cosmos"), 31 | privKey 32 | .toPubKey() 33 | .toAddress() 34 | .toBech32("cosmos") 35 | ); 36 | }); 37 | 38 | it("recover key from mnemonic", () => { 39 | const privKey = Key.generateWalletFromMnemonic( 40 | "anger river nuclear pig enlist fish demand dress library obtain concert nasty wolf episode ring bargain rely off vibrant iron cram witness extra enforce", 41 | "m/44'/118'/0'/0/0" 42 | ); 43 | 44 | assert.strictEqual( 45 | privKey 46 | .toPubKey() 47 | .toAddress() 48 | .toBech32("cosmos"), 49 | "cosmos1t68n2ezn5zt8frh4jehmufkk2puakv9glapyz4" 50 | ); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /src/utils/key.ts: -------------------------------------------------------------------------------- 1 | const bip39 = require("bip39"); 2 | const bip32 = require("bip32"); 3 | import { Buffer } from "buffer/"; 4 | import { PrivKey, PrivKeySecp256k1 } from "../crypto"; 5 | 6 | export type RNG = < 7 | T extends 8 | | Int8Array 9 | | Int16Array 10 | | Int32Array 11 | | Uint8Array 12 | | Uint16Array 13 | | Uint32Array 14 | | Uint8ClampedArray 15 | | Float32Array 16 | | Float64Array 17 | | DataView 18 | | null 19 | >( 20 | array: T 21 | ) => T; 22 | 23 | export function generateWallet( 24 | rng: RNG, 25 | path: string = `m/44'/118'/0'/0/0`, 26 | password: string = "", 27 | strength: number = 256 28 | ): { privKey: PrivKey; mnemonic: string } { 29 | const mnemonic = generateSeed(rng, strength); 30 | const privKey = generateWalletFromMnemonic(mnemonic, path, password); 31 | 32 | return { 33 | privKey, 34 | mnemonic 35 | }; 36 | } 37 | 38 | export function generateWalletFromMnemonic( 39 | mnemonic: string, 40 | path: string = `m/44'/118'/0'/0/0`, 41 | password: string = "" 42 | ): PrivKey { 43 | // bip39.validateMnemonic(mnemonic); 44 | 45 | const seed = bip39.mnemonicToSeedSync(mnemonic, password); 46 | const masterKey = bip32.fromSeed(seed); 47 | const hd = masterKey.derivePath(path); 48 | 49 | const privateKey = hd.privateKey; 50 | if (!privateKey) { 51 | throw new Error("null hd key"); 52 | } 53 | return new PrivKeySecp256k1(privateKey); 54 | } 55 | 56 | export function generateSeed(rng: RNG, strength: number = 128): string { 57 | if (strength % 32 !== 0) { 58 | throw new TypeError("invalid entropy"); 59 | } 60 | let bytes = new Uint8Array(strength / 8); 61 | bytes = rng(bytes); 62 | return bip39.entropyToMnemonic(Buffer.from(bytes).toString("hex")); 63 | } 64 | -------------------------------------------------------------------------------- /src/utils/sortJson.spec.ts: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import "mocha"; 3 | import { sortJSON } from "./sortJson"; 4 | 5 | describe("Test std tx", () => { 6 | it("test json is sorted by alphabetically", () => { 7 | const obj = { 8 | b: { 9 | b: "b", 10 | a: "a", 11 | c: "c" 12 | }, 13 | a: { 14 | e: ["a", "b"], 15 | f: { 16 | c: "c", 17 | b: "b" 18 | } 19 | } 20 | }; 21 | 22 | assert.strictEqual( 23 | sortJSON(JSON.stringify(obj)), 24 | `{"a":{"e":["a","b"],"f":{"b":"b","c":"c"}},"b":{"a":"a","b":"b","c":"c"}}` 25 | ); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/utils/sortJson.ts: -------------------------------------------------------------------------------- 1 | export function sortJSON(json: string): string { 2 | const obj = JSON.parse(json); 3 | if (obj && typeof obj === "object") { 4 | if (!Array.isArray(obj)) { 5 | let result = "{"; 6 | const keys = Object.keys(obj).sort(); 7 | let writeComma = false; 8 | for (const key of keys) { 9 | if (writeComma) { 10 | result += ","; 11 | writeComma = false; 12 | } 13 | result += `"${key}":`; 14 | result += sortJSON(JSON.stringify(obj[key])); 15 | writeComma = true; 16 | } 17 | result += "}"; 18 | return result; 19 | } else { 20 | let result = "["; 21 | let writeComma = false; 22 | for (const o of obj) { 23 | if (writeComma) { 24 | result += ","; 25 | writeComma = false; 26 | } 27 | result += sortJSON(JSON.stringify(o)); 28 | writeComma = true; 29 | } 30 | result += "]"; 31 | return result; 32 | } 33 | } 34 | return json; 35 | } 36 | -------------------------------------------------------------------------------- /src/x/bank/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { MsgSend } from "./msgs"; 3 | 4 | export function registerCodec(codec: Codec) { 5 | codec.registerConcrete("cosmos-sdk/MsgSend", MsgSend.prototype); 6 | } 7 | -------------------------------------------------------------------------------- /src/x/bank/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msgs"; 2 | export * from "./codec"; 3 | -------------------------------------------------------------------------------- /src/x/bank/msgs.ts: -------------------------------------------------------------------------------- 1 | import { Amino, Type } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct } = Amino; 3 | import { Msg } from "../../core/tx"; 4 | import { AccAddress } from "../../common/address"; 5 | import { Coin } from "../../common/coin"; 6 | import { Int } from "../../common/int"; 7 | 8 | @DefineStruct() 9 | export class MsgSend extends Msg { 10 | @Field.Defined(0, { 11 | jsonName: "from_address" 12 | }) 13 | public fromAddress: AccAddress; 14 | 15 | @Field.Defined(1, { 16 | jsonName: "to_address" 17 | }) 18 | public toAddress: AccAddress; 19 | 20 | @Field.Slice( 21 | 2, 22 | { type: Type.Defined }, 23 | { 24 | jsonName: "amount" 25 | } 26 | ) 27 | public amount: Coin[]; 28 | 29 | constructor(fromAddress: AccAddress, toAddress: AccAddress, amount: Coin[]) { 30 | super(); 31 | this.fromAddress = fromAddress; 32 | this.toAddress = toAddress; 33 | this.amount = amount; 34 | } 35 | 36 | public getSigners(): AccAddress[] { 37 | return [this.fromAddress]; 38 | } 39 | 40 | public validateBasic(): void { 41 | for (const coin of this.amount) { 42 | if (coin.amount.lte(new Int(0))) { 43 | throw new Error("Send amount is invalid"); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/x/distribution/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { 3 | MsgSetWithdrawAddress, 4 | MsgWithdrawDelegatorReward, 5 | MsgWithdrawValidatorCommission 6 | } from "./msgs"; 7 | 8 | export function registerCodec(codec: Codec) { 9 | codec.registerConcrete( 10 | "cosmos-sdk/MsgModifyWithdrawAddress", 11 | MsgSetWithdrawAddress.prototype 12 | ); 13 | codec.registerConcrete( 14 | "cosmos-sdk/MsgWithdrawDelegationReward", 15 | MsgWithdrawDelegatorReward.prototype 16 | ); 17 | codec.registerConcrete( 18 | "cosmos-sdk/MsgWithdrawValidatorCommission", 19 | MsgWithdrawValidatorCommission.prototype 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/x/distribution/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msgs"; 2 | export * from "./codec"; 3 | -------------------------------------------------------------------------------- /src/x/distribution/msgs.ts: -------------------------------------------------------------------------------- 1 | import { Amino } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct } = Amino; 3 | import { Msg } from "../../core/tx"; 4 | import { AccAddress, ValAddress } from "../../common/address"; 5 | 6 | @DefineStruct() 7 | export class MsgSetWithdrawAddress extends Msg { 8 | @Field.Defined(0, { 9 | jsonName: "delegator_address" 10 | }) 11 | public delegatorAddress: AccAddress; 12 | 13 | @Field.Defined(1, { 14 | jsonName: "withdraw_address" 15 | }) 16 | public withdrawAddress: AccAddress; 17 | 18 | constructor(delegatorAddress: AccAddress, withdrawAddress: AccAddress) { 19 | super(); 20 | this.delegatorAddress = delegatorAddress; 21 | this.withdrawAddress = withdrawAddress; 22 | } 23 | 24 | // eslint-disable-next-line @typescript-eslint/no-empty-function 25 | public validateBasic(): void {} 26 | 27 | public getSigners(): AccAddress[] { 28 | return [this.delegatorAddress]; 29 | } 30 | } 31 | 32 | @DefineStruct() 33 | export class MsgWithdrawDelegatorReward extends Msg { 34 | @Field.Defined(0, { 35 | jsonName: "delegator_address" 36 | }) 37 | public delegatorAddress: AccAddress; 38 | 39 | @Field.Defined(1, { 40 | jsonName: "validator_address" 41 | }) 42 | public validatorAddress: ValAddress; 43 | 44 | constructor(delegatorAddress: AccAddress, validatorAddress: ValAddress) { 45 | super(); 46 | this.delegatorAddress = delegatorAddress; 47 | this.validatorAddress = validatorAddress; 48 | } 49 | 50 | // eslint-disable-next-line @typescript-eslint/no-empty-function 51 | public validateBasic(): void {} 52 | 53 | public getSigners(): AccAddress[] { 54 | return [this.delegatorAddress]; 55 | } 56 | } 57 | 58 | @DefineStruct() 59 | export class MsgWithdrawValidatorCommission extends Msg { 60 | @Field.Defined(0, { 61 | jsonName: "validator_address" 62 | }) 63 | public validatorAddress: ValAddress; 64 | 65 | public bech32PrefixAccAddr: string; 66 | 67 | constructor(validatorAddress: ValAddress, bech32PrefixAccAddr: string) { 68 | super(); 69 | this.validatorAddress = validatorAddress; 70 | this.bech32PrefixAccAddr = bech32PrefixAccAddr; 71 | } 72 | 73 | // eslint-disable-next-line @typescript-eslint/no-empty-function 74 | public validateBasic(): void {} 75 | 76 | public getSigners(): AccAddress[] { 77 | return [ 78 | new AccAddress(this.validatorAddress.toBytes(), this.bech32PrefixAccAddr) 79 | ]; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/x/gov/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { MsgSubmitProposal, MsgDeposit, MsgVote } from "./msgs"; 3 | 4 | export function registerCodec(codec: Codec) { 5 | codec.registerConcrete( 6 | "cosmos-sdk/MsgSubmitProposal", 7 | MsgSubmitProposal.prototype 8 | ); 9 | codec.registerConcrete("cosmos-sdk/MsgDeposit", MsgDeposit.prototype); 10 | codec.registerConcrete("cosmos-sdk/MsgVote", MsgVote.prototype); 11 | } 12 | -------------------------------------------------------------------------------- /src/x/gov/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msgs"; 2 | export * from "./codec"; 3 | -------------------------------------------------------------------------------- /src/x/gov/msgs.ts: -------------------------------------------------------------------------------- 1 | import { Amino, Type } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct, DefineType } = Amino; 3 | import { Msg } from "../../core/tx"; 4 | import { AccAddress } from "../../common/address"; 5 | import { Coin } from "../../common/coin"; 6 | import { Int } from "../../common/int"; 7 | import bigInteger from "big-integer"; 8 | 9 | @DefineType() 10 | export class ProposalKind { 11 | public static get nil(): number { 12 | return 0; 13 | } 14 | public static get text(): number { 15 | return 1; 16 | } 17 | public static get parameterChange(): number { 18 | return 2; 19 | } 20 | public static get softwareUpgread(): number { 21 | return 3; 22 | } 23 | 24 | @Field.Uint8(0) 25 | private _kind: number; 26 | 27 | constructor(kind: 0 | 1 | 2 | 3) { 28 | this._kind = kind; 29 | } 30 | 31 | public validateBasic(): void { 32 | if (this.kind < 0 || this.kind > 3) { 33 | throw new Error(`Invalid proposal kind: ${this.kind}`); 34 | } 35 | } 36 | 37 | public get kind(): number { 38 | return this._kind; 39 | } 40 | } 41 | 42 | @DefineType() 43 | export class VoteOption { 44 | public static get empty(): number { 45 | return 0; 46 | } 47 | public static get yes(): number { 48 | return 1; 49 | } 50 | public static get abstain(): number { 51 | return 2; 52 | } 53 | public static get no(): number { 54 | return 3; 55 | } 56 | public static get noWithVeto(): number { 57 | return 4; 58 | } 59 | 60 | @Field.Uint8(0) 61 | private _option: number; 62 | 63 | constructor(option: 0 | 1 | 2 | 3 | 4) { 64 | this._option = option; 65 | } 66 | 67 | public validateBasic(): void { 68 | if (this.option < 0 || this.option > 4) { 69 | throw new Error(`Invalid proposal kind: ${this.option}`); 70 | } 71 | } 72 | 73 | public get option(): number { 74 | return this._option; 75 | } 76 | } 77 | 78 | @DefineStruct() 79 | export class MsgSubmitProposal extends Msg { 80 | @Field.String(0, { 81 | jsonName: "title" 82 | }) 83 | public title: string; 84 | 85 | @Field.String(1, { 86 | jsonName: "description" 87 | }) 88 | public description: string; 89 | 90 | @Field.Defined(2, { 91 | jsonName: "proposal_type" 92 | }) 93 | public proposalType: ProposalKind; 94 | 95 | @Field.Defined(3, { 96 | jsonName: "proposer" 97 | }) 98 | public proposer: AccAddress; 99 | 100 | @Field.Slice( 101 | 4, 102 | { 103 | type: Type.Defined 104 | }, 105 | { 106 | jsonName: "initial_deposit" 107 | } 108 | ) 109 | public initialDeposit: Coin[]; 110 | 111 | constructor( 112 | title: string, 113 | description: string, 114 | proposalType: ProposalKind, 115 | proposer: AccAddress, 116 | initialDeposit: Coin[] 117 | ) { 118 | super(); 119 | this.title = title; 120 | this.description = description; 121 | this.proposalType = proposalType; 122 | this.proposer = proposer; 123 | this.initialDeposit = initialDeposit; 124 | } 125 | 126 | public getSigners(): AccAddress[] { 127 | return [this.proposer]; 128 | } 129 | 130 | public validateBasic(): void { 131 | this.proposalType.validateBasic(); 132 | } 133 | } 134 | 135 | @DefineStruct() 136 | export class MsgDeposit extends Msg { 137 | @Field.Uint64(0, { 138 | jsonName: "proposal_id" 139 | }) 140 | public proposalId: bigInteger.BigInteger; 141 | 142 | @Field.Defined(1, { 143 | jsonName: "depositor" 144 | }) 145 | public depositor: AccAddress; 146 | 147 | @Field.Slice( 148 | 2, 149 | { type: Type.Defined }, 150 | { 151 | jsonName: "amount" 152 | } 153 | ) 154 | public amount: Coin[]; 155 | 156 | constructor( 157 | proposalId: bigInteger.BigNumber, 158 | depositor: AccAddress, 159 | amount: Coin[] 160 | ) { 161 | super(); 162 | if (typeof proposalId === "string") { 163 | this.proposalId = bigInteger(proposalId); 164 | } else if (typeof proposalId === "number") { 165 | this.proposalId = bigInteger(proposalId); 166 | } else { 167 | this.proposalId = bigInteger(proposalId); 168 | } 169 | this.depositor = depositor; 170 | this.amount = amount; 171 | } 172 | 173 | public getSigners(): AccAddress[] { 174 | return [this.depositor]; 175 | } 176 | 177 | public validateBasic(): void { 178 | for (const coin of this.amount) { 179 | if (coin.amount.lte(new Int(0))) { 180 | throw new Error("Deposit amount is invalid"); 181 | } 182 | } 183 | } 184 | } 185 | 186 | @DefineStruct() 187 | export class MsgVote extends Msg { 188 | @Field.Uint64(0, { 189 | jsonName: "proposal_id" 190 | }) 191 | public proposalId: bigInteger.BigInteger; 192 | 193 | @Field.Defined(1, { 194 | jsonName: "voter" 195 | }) 196 | public voter: AccAddress; 197 | 198 | @Field.Defined(2, { 199 | jsonName: "option" 200 | }) 201 | public option: VoteOption; 202 | 203 | constructor( 204 | proposalId: bigInteger.BigNumber, 205 | voter: AccAddress, 206 | option: VoteOption 207 | ) { 208 | super(); 209 | if (typeof proposalId === "string") { 210 | this.proposalId = bigInteger(proposalId); 211 | } else if (typeof proposalId === "number") { 212 | this.proposalId = bigInteger(proposalId); 213 | } else { 214 | this.proposalId = bigInteger(proposalId); 215 | } 216 | this.voter = voter; 217 | this.option = option; 218 | } 219 | 220 | public getSigners(): AccAddress[] { 221 | return [this.voter]; 222 | } 223 | 224 | public validateBasic(): void { 225 | this.option.validateBasic(); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/x/slashing/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { MsgUnjail } from "./msgs"; 3 | 4 | export function registerCodec(codec: Codec) { 5 | codec.registerConcrete("cosmos-sdk/MsgUnjail", MsgUnjail.prototype); 6 | } 7 | -------------------------------------------------------------------------------- /src/x/slashing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msgs"; 2 | export * from "./codec"; 3 | -------------------------------------------------------------------------------- /src/x/slashing/msgs.ts: -------------------------------------------------------------------------------- 1 | import { Amino } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct } = Amino; 3 | import { Msg } from "../../core/tx"; 4 | import { AccAddress, ValAddress } from "../../common/address"; 5 | 6 | @DefineStruct() 7 | export class MsgUnjail extends Msg { 8 | @Field.Defined(0, { 9 | jsonName: "address" 10 | }) 11 | public validatorAddr: ValAddress; 12 | 13 | public bech32PrefixAccAddr: string; 14 | 15 | constructor(validatorAddr: ValAddress, bech32PrefixAccAddr: string) { 16 | super(); 17 | this.validatorAddr = validatorAddr; 18 | this.bech32PrefixAccAddr = bech32PrefixAccAddr; 19 | } 20 | 21 | // eslint-disable-next-line @typescript-eslint/no-empty-function 22 | public validateBasic(): void {} 23 | 24 | public getSigners(): AccAddress[] { 25 | return [ 26 | new AccAddress(this.validatorAddr.toBytes(), this.bech32PrefixAccAddr) 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/x/staking/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { 3 | MsgCreateValidator, 4 | MsgEditValidator, 5 | MsgDelegate, 6 | MsgBeginRedelegate, 7 | MsgUndelegate 8 | } from "./msgs"; 9 | 10 | export function registerCodec(codec: Codec) { 11 | codec.registerConcrete( 12 | "cosmos-sdk/MsgCreateValidator", 13 | MsgCreateValidator.prototype 14 | ); 15 | codec.registerConcrete( 16 | "cosmos-sdk/MsgEditValidator", 17 | MsgEditValidator.prototype 18 | ); 19 | codec.registerConcrete("cosmos-sdk/MsgDelegate", MsgDelegate.prototype); 20 | codec.registerConcrete( 21 | "cosmos-sdk/MsgBeginRedelegate", 22 | MsgBeginRedelegate.prototype 23 | ); 24 | codec.registerConcrete("cosmos-sdk/MsgUndelegate", MsgUndelegate.prototype); 25 | } 26 | -------------------------------------------------------------------------------- /src/x/staking/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msgs"; 2 | export * from "./codec"; 3 | -------------------------------------------------------------------------------- /src/x/staking/msgs.ts: -------------------------------------------------------------------------------- 1 | import { Amino } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct } = Amino; 3 | import { Msg } from "../../core/tx"; 4 | import { AccAddress, ValAddress } from "../../common/address"; 5 | import { Coin } from "../../common/coin"; 6 | import { Int } from "../../common/int"; 7 | import { Dec } from "../../common/decimal"; 8 | import { PubKey } from "../../crypto"; 9 | 10 | @DefineStruct() 11 | export class Description { 12 | @Field.String(0, { 13 | jsonName: "moniker" 14 | }) 15 | public moniker: string; 16 | 17 | @Field.String(1, { 18 | jsonName: "identity" 19 | }) 20 | public identity: string; 21 | 22 | @Field.String(2, { 23 | jsonName: "website" 24 | }) 25 | public website: string; 26 | 27 | @Field.String(3, { 28 | jsonName: "details" 29 | }) 30 | public details: string; 31 | 32 | constructor( 33 | moniker: string, 34 | identity: string, 35 | website: string, 36 | details: string 37 | ) { 38 | this.moniker = moniker; 39 | this.identity = identity; 40 | this.website = website; 41 | this.details = details; 42 | } 43 | } 44 | 45 | @DefineStruct() 46 | export class CommissionMsg { 47 | @Field.Defined(0, { 48 | jsonName: "rate" 49 | }) 50 | public rate: Dec; 51 | 52 | @Field.Defined(1, { 53 | jsonName: "max_rate" 54 | }) 55 | public maxRate: Dec; 56 | 57 | @Field.Defined(2, { 58 | jsonName: "max_change_rate" 59 | }) 60 | public maxChangeRate: Dec; 61 | 62 | constructor(rate: Dec, maxRate: Dec, maxChangeRate: Dec) { 63 | this.rate = rate; 64 | this.maxRate = maxRate; 65 | this.maxChangeRate = maxChangeRate; 66 | } 67 | } 68 | 69 | @DefineStruct() 70 | export class MsgCreateValidator extends Msg { 71 | @Field.Defined(0, { 72 | jsonName: "description" 73 | }) 74 | public description: Description; 75 | 76 | @Field.Defined(1, { 77 | jsonName: "commission" 78 | }) 79 | public commission: CommissionMsg; 80 | 81 | @Field.Defined(2, { 82 | jsonName: "min_self_delegation" 83 | }) 84 | public minSelfDelegation: Int; 85 | 86 | @Field.Defined(3, { 87 | jsonName: "delegator_address" 88 | }) 89 | public delegatorAddress: AccAddress; 90 | 91 | @Field.Defined(4, { 92 | jsonName: "validator_address" 93 | }) 94 | public validatorAddress: ValAddress; 95 | 96 | @Field.Interface(5, { 97 | jsonName: "pubkey" 98 | }) 99 | public pubKey: PubKey; 100 | 101 | @Field.Defined(6, { 102 | jsonName: "value" 103 | }) 104 | public value: Coin; 105 | 106 | constructor( 107 | description: Description, 108 | commission: CommissionMsg, 109 | minSelfDelegation: Int, 110 | delegatorAddress: AccAddress, 111 | validatorAddress: ValAddress, 112 | pubKey: PubKey, 113 | value: Coin 114 | ) { 115 | super(); 116 | this.description = description; 117 | this.commission = commission; 118 | this.minSelfDelegation = minSelfDelegation; 119 | this.delegatorAddress = delegatorAddress; 120 | this.validatorAddress = validatorAddress; 121 | this.pubKey = pubKey; 122 | this.value = value; 123 | } 124 | 125 | // eslint-disable-next-line @typescript-eslint/no-empty-function 126 | public validateBasic(): void {} 127 | 128 | public getSigners(): AccAddress[] { 129 | const addr = [this.delegatorAddress]; 130 | 131 | if ( 132 | this.delegatorAddress.toBytes().toString() !== 133 | this.validatorAddress.toBytes().toString() 134 | ) { 135 | addr.push( 136 | new AccAddress( 137 | this.validatorAddress.toBytes(), 138 | this.delegatorAddress.bech32Prefix 139 | ) 140 | ); 141 | } 142 | 143 | return addr; 144 | } 145 | } 146 | 147 | @DefineStruct() 148 | export class MsgEditValidator extends Msg { 149 | @Field.Defined(0, { 150 | jsonName: "description" 151 | }) 152 | public description: Description; 153 | 154 | @Field.Defined(1, { 155 | jsonName: "address" 156 | }) 157 | public validatorAddress: ValAddress; 158 | 159 | @Field.Defined(2, { 160 | jsonName: "commission_rate" 161 | }) 162 | public commisionRate: Dec; 163 | 164 | @Field.Defined(3, { 165 | jsonName: "min_self_delegation" 166 | }) 167 | public minSelfDelegation: Int; 168 | 169 | public bech32PrefixAccAddr: string; 170 | 171 | constructor( 172 | description: Description, 173 | validatorAddress: ValAddress, 174 | commisionRate: Dec, 175 | minSelfDelegation: Int, 176 | bech32PrefixAccAddr: string 177 | ) { 178 | super(); 179 | this.description = description; 180 | this.validatorAddress = validatorAddress; 181 | this.commisionRate = commisionRate; 182 | this.minSelfDelegation = minSelfDelegation; 183 | 184 | this.bech32PrefixAccAddr = bech32PrefixAccAddr; 185 | } 186 | 187 | // eslint-disable-next-line @typescript-eslint/no-empty-function 188 | public validateBasic(): void {} 189 | 190 | public getSigners(): AccAddress[] { 191 | return [ 192 | new AccAddress(this.validatorAddress.toBytes(), this.bech32PrefixAccAddr) 193 | ]; 194 | } 195 | } 196 | 197 | @DefineStruct() 198 | export class MsgDelegate extends Msg { 199 | @Field.Defined(0, { 200 | jsonName: "delegator_address" 201 | }) 202 | public delegatorAddress: AccAddress; 203 | 204 | @Field.Defined(1, { 205 | jsonName: "validator_address" 206 | }) 207 | public validatorAddress: ValAddress; 208 | 209 | @Field.Defined(2, { 210 | jsonName: "amount" 211 | }) 212 | public amount: Coin; 213 | 214 | constructor( 215 | delegatorAddress: AccAddress, 216 | validatorAddress: ValAddress, 217 | amount: Coin 218 | ) { 219 | super(); 220 | this.delegatorAddress = delegatorAddress; 221 | this.validatorAddress = validatorAddress; 222 | this.amount = amount; 223 | } 224 | 225 | // eslint-disable-next-line @typescript-eslint/no-empty-function 226 | public validateBasic(): void {} 227 | 228 | public getSigners(): AccAddress[] { 229 | return [this.delegatorAddress]; 230 | } 231 | } 232 | 233 | @DefineStruct() 234 | export class MsgBeginRedelegate extends Msg { 235 | @Field.Defined(0, { 236 | jsonName: "delegator_address" 237 | }) 238 | public delegatorAddress: AccAddress; 239 | 240 | @Field.Defined(1, { 241 | jsonName: "validator_src_address" 242 | }) 243 | public validatorSrcAddress: ValAddress; 244 | 245 | @Field.Defined(2, { 246 | jsonName: "validator_dst_address" 247 | }) 248 | public validatorDstAddress: ValAddress; 249 | 250 | @Field.Defined(3, { 251 | jsonName: "amount" 252 | }) 253 | public amount: Coin; 254 | 255 | constructor( 256 | delegatorAddress: AccAddress, 257 | validatorSrcAddress: ValAddress, 258 | validatorDstAddress: ValAddress, 259 | amount: Coin 260 | ) { 261 | super(); 262 | this.delegatorAddress = delegatorAddress; 263 | this.validatorSrcAddress = validatorSrcAddress; 264 | this.validatorDstAddress = validatorDstAddress; 265 | this.amount = amount; 266 | } 267 | 268 | // eslint-disable-next-line @typescript-eslint/no-empty-function 269 | public validateBasic(): void {} 270 | 271 | public getSigners(): AccAddress[] { 272 | return [this.delegatorAddress]; 273 | } 274 | } 275 | 276 | @DefineStruct() 277 | export class MsgUndelegate extends Msg { 278 | @Field.Defined(0, { 279 | jsonName: "delegator_address" 280 | }) 281 | public delegatorAddress: AccAddress; 282 | 283 | @Field.Defined(1, { 284 | jsonName: "validator_address" 285 | }) 286 | public validatorAddress: ValAddress; 287 | 288 | @Field.Defined(2, { 289 | jsonName: "amount" 290 | }) 291 | public amount: Coin; 292 | 293 | constructor( 294 | delegatorAddress: AccAddress, 295 | validatorAddress: ValAddress, 296 | amount: Coin 297 | ) { 298 | super(); 299 | this.delegatorAddress = delegatorAddress; 300 | this.validatorAddress = validatorAddress; 301 | this.amount = amount; 302 | } 303 | 304 | // eslint-disable-next-line @typescript-eslint/no-empty-function 305 | public validateBasic(): void {} 306 | 307 | public getSigners(): AccAddress[] { 308 | return [this.delegatorAddress]; 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/x/wasm/codec.ts: -------------------------------------------------------------------------------- 1 | import { Codec } from "@chainapsis/ts-amino"; 2 | import { MsgExecuteContract, MsgInstantiateContract } from "./msgs"; 3 | 4 | export function registerCodec(codec: Codec) { 5 | codec.registerConcrete( 6 | "wasm/MsgExecuteContract", 7 | MsgExecuteContract.prototype 8 | ); 9 | codec.registerConcrete( 10 | "wasm/MsgInstantiateContract", 11 | MsgInstantiateContract.prototype 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /src/x/wasm/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msgs"; 2 | export * from "./codec"; 3 | -------------------------------------------------------------------------------- /src/x/wasm/msgs.ts: -------------------------------------------------------------------------------- 1 | import { Amino, Type } from "@chainapsis/ts-amino"; 2 | const { Field, DefineStruct, DefineType } = Amino; 3 | import { Msg } from "../../core/tx"; 4 | import { AccAddress } from "../../common/address"; 5 | import { Coin } from "../../common/coin"; 6 | import { Int } from "../../common/int"; 7 | 8 | import { Buffer } from "buffer/"; 9 | 10 | @DefineType() 11 | class RawMessage { 12 | @Field.Slice(0, { type: Type.Uint8 }) 13 | public raw: Uint8Array; 14 | 15 | constructor(raw: Uint8Array) { 16 | this.raw = raw; 17 | } 18 | 19 | public marshalJSON(): string { 20 | return Buffer.from(this.raw).toString("utf8"); 21 | } 22 | } 23 | 24 | @DefineStruct() 25 | export class MsgExecuteContract extends Msg { 26 | @Field.Defined(0, { 27 | jsonName: "sender" 28 | }) 29 | public sender: AccAddress; 30 | 31 | @Field.Defined(1, { 32 | jsonName: "contract" 33 | }) 34 | public contract: AccAddress; 35 | 36 | @Field.Slice(2, { type: Type.Defined }, { jsonName: "msg" }) 37 | public msg: RawMessage; 38 | 39 | @Field.Slice( 40 | 3, 41 | { type: Type.Defined }, 42 | { 43 | jsonName: "sent_funds" 44 | } 45 | ) 46 | public sentFunds: Coin[]; 47 | 48 | constructor( 49 | sender: AccAddress, 50 | contract: AccAddress, 51 | msg: object, 52 | sentFunds: Coin[] 53 | ) { 54 | super(); 55 | this.sender = sender; 56 | this.contract = contract; 57 | this.msg = new RawMessage(Buffer.from(JSON.stringify(msg), "utf8")); 58 | this.sentFunds = sentFunds; 59 | } 60 | 61 | public getSigners(): AccAddress[] { 62 | return [this.sender]; 63 | } 64 | 65 | public validateBasic(): void { 66 | for (const coin of this.sentFunds) { 67 | if (coin.amount.lte(new Int(0))) { 68 | throw new Error("Send amount is invalid"); 69 | } 70 | } 71 | } 72 | } 73 | 74 | @DefineStruct() 75 | export class MsgInstantiateContract extends Msg { 76 | @Field.Defined(0, { 77 | jsonName: "sender" 78 | }) 79 | public sender: AccAddress; 80 | 81 | @Field.Defined(1, { 82 | jsonName: "admin", 83 | jsonOmitEmpty: true 84 | }) 85 | public admin: AccAddress | undefined; 86 | 87 | @Field.Uint64(2, { 88 | jsonName: "code_id" 89 | }) 90 | public codeId: number; 91 | 92 | @Field.String(3, { 93 | jsonName: "label" 94 | }) 95 | public label: string; 96 | 97 | @Field.Slice(4, { type: Type.Defined }, { jsonName: "init_msg" }) 98 | public initMsg: RawMessage; 99 | 100 | @Field.Slice( 101 | 5, 102 | { type: Type.Defined }, 103 | { 104 | jsonName: "init_funds" 105 | } 106 | ) 107 | public initFunds: Coin[]; 108 | 109 | constructor( 110 | sender: AccAddress, 111 | admin: AccAddress | undefined, 112 | codeId: number, 113 | label: string, 114 | initMsg: object, 115 | initFunds: Coin[] 116 | ) { 117 | super(); 118 | this.sender = sender; 119 | this.admin = admin; 120 | this.codeId = codeId; 121 | this.label = label; 122 | this.initMsg = new RawMessage(Buffer.from(JSON.stringify(initMsg), "utf8")); 123 | this.initFunds = initFunds; 124 | } 125 | 126 | public getSigners(): AccAddress[] { 127 | return [this.sender]; 128 | } 129 | 130 | public validateBasic(): void { 131 | for (const coin of this.initFunds) { 132 | if (coin.amount.lte(new Int(0))) { 133 | throw new Error("Send amount is invalid"); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist", /* Redirect output structure to the directory. */ 15 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "incremental": true, /* Enable incremental compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | "baseUrl": ".", 62 | "paths": { 63 | "*": [ 64 | "src/typings/*" 65 | ] 66 | } 67 | }, 68 | "include": [ 69 | "src/**/*.ts", 70 | "src/**/*.js", 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "out": "docs", 3 | "excludeNotExported": true, 4 | "excludeExternals": true, 5 | "excludePrivate": true, 6 | "exclude": "**/*+(index|.spec|.e2e).ts" 7 | } 8 | --------------------------------------------------------------------------------