├── .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 |
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 |
--------------------------------------------------------------------------------