├── .prettierignore ├── src ├── types │ ├── msg │ │ ├── gov │ │ │ ├── index.ts │ │ │ └── submitProposal.ts │ │ ├── dex │ │ │ ├── index.ts │ │ │ ├── cancelOrder.ts │ │ │ ├── listMiniMsg.ts │ │ │ └── newOrder.ts │ │ ├── claim │ │ │ ├── index.ts │ │ │ ├── claimTypes.ts │ │ │ ├── transferOutMsg.ts │ │ │ ├── claimMsg.ts │ │ │ └── bindMsg.ts │ │ ├── stake │ │ │ ├── index.ts │ │ │ ├── bscUndelegateMsg.ts │ │ │ ├── bscDelegateMsg.ts │ │ │ ├── bscRedelegateMsg.ts │ │ │ └── stakeMigrationMsg.ts │ │ ├── index.ts │ │ ├── base │ │ │ └── index.ts │ │ ├── token │ │ │ ├── index.ts │ │ │ ├── timeUnlock.ts │ │ │ ├── setTokenUri.ts │ │ │ ├── burnToken.ts │ │ │ ├── freezeToken.ts │ │ │ ├── unFreezeToken.ts │ │ │ ├── issueToken.ts │ │ │ ├── mintToken.ts │ │ │ ├── issueMiniToken.ts │ │ │ ├── issueTinyToken.ts │ │ │ ├── timelock.ts │ │ │ └── timeRelock.ts │ │ └── send.ts │ ├── index.ts │ ├── tx │ │ ├── index.ts │ │ ├── abciResponse.ts │ │ ├── txResult.ts │ │ └── stdTx.ts │ └── rpc │ │ └── index.ts ├── utils │ ├── index.ts │ ├── encoderHelper.ts │ ├── request.ts │ ├── rpcHelper.ts │ └── validateHelper.ts ├── amino │ ├── index.ts │ ├── encoder │ │ ├── varint.ts │ │ └── index.ts │ └── decoder │ │ └── index.ts ├── index.ts ├── client │ ├── gov │ │ ├── proposalType.ts │ │ └── index.ts │ ├── stake │ │ └── index.ts │ └── swap │ │ └── index.ts ├── ledger │ └── index.ts ├── declarations.ts ├── tx │ └── index.ts └── rpc │ └── baseRpc.ts ├── .prettierrc ├── .eslintignore ├── SECURITY.md ├── .babelrc.js ├── .editorconfig ├── jest.config.js ├── docs ├── api-docs │ └── classes │ │ ├── stake.md │ │ ├── transaction.md │ │ ├── bridge.md │ │ ├── rpcclient.md │ │ ├── ledgerapp.md │ │ └── tokenmanagement.md ├── transaction-types.md ├── README.md └── examples.md ├── .github └── workflows │ ├── test.yml │ └── publish.yml ├── tests-ledger ├── index.html ├── run-browser-tests.sh └── index-browser.js ├── __tests__ ├── bridge.test.ts ├── decoder.test.ts ├── stake.test.ts ├── utils.ts ├── crypto.test.ts ├── fixtures │ ├── cancelOrder.json │ ├── placeOrder.json │ └── transfer.json ├── dex.test.ts ├── encoder.test.ts └── rpc.test.ts ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── README.md └── package.json /.prettierignore: -------------------------------------------------------------------------------- 1 | /typings 2 | /lib 3 | -------------------------------------------------------------------------------- /src/types/msg/gov/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./submitProposal" 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "proseWrap": "always" 4 | } 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | typings 3 | build 4 | node_modules 5 | tests-ledger 6 | docs -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./msg" 2 | export * from "./tx" 3 | export * from "./rpc" 4 | -------------------------------------------------------------------------------- /src/types/tx/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./abciResponse" 2 | export * from "./stdTx" 3 | export * from "./txResult" 4 | -------------------------------------------------------------------------------- /src/types/msg/dex/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cancelOrder" 2 | export * from "./newOrder" 3 | export * from "./listMiniMsg" 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | [Find us on Bugcrowd](https://bugcrowd.com/binance) 6 | -------------------------------------------------------------------------------- /src/types/msg/claim/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bindMsg" 2 | export * from "./claimMsg" 3 | export * from "./claimTypes" 4 | export * from "./transferOutMsg" 5 | -------------------------------------------------------------------------------- /src/types/msg/stake/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./bscDelegateMsg" 2 | export * from "./bscUndelegateMsg" 3 | export * from "./bscRedelegateMsg" 4 | export * from "./stakeMigrationMsg" 5 | -------------------------------------------------------------------------------- /src/types/tx/abciResponse.ts: -------------------------------------------------------------------------------- 1 | export interface abciQueryResponseResult { 2 | response: abciQueryResponse 3 | } 4 | 5 | interface abciQueryResponse { 6 | value: string 7 | } 8 | -------------------------------------------------------------------------------- /src/types/msg/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./base" 2 | export * from "./dex" 3 | export * from "./token" 4 | export * from "./send" 5 | export * from "./claim" 6 | export * from "./stake" 7 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./cryptoHelper" 2 | export * from "./encoderHelper" 3 | export * from "./validateHelper" 4 | export * from "./rpcHelper" 5 | export * from "./request" 6 | -------------------------------------------------------------------------------- /src/amino/index.ts: -------------------------------------------------------------------------------- 1 | import * as decoder from "./decoder" 2 | import * as encoder from "./encoder" 3 | 4 | //backward compatibility 5 | export { decoder, encoder } 6 | 7 | export * from "./decoder" 8 | export * from "./encoder" 9 | -------------------------------------------------------------------------------- /.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env", "@babel/preset-typescript"], 3 | plugins: [ 4 | "@babel/plugin-transform-async-to-generator", 5 | "@babel/plugin-proposal-class-properties", 6 | "@babel/plugin-transform-runtime", 7 | ], 8 | } 9 | -------------------------------------------------------------------------------- /src/types/msg/base/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export interface SignMsg {} 3 | export interface Msg {} 4 | 5 | export abstract class BaseMsg { 6 | public abstract getSignMsg(): SignMsg 7 | public abstract getMsg(): Msg 8 | public static defaultMsg(): object { 9 | return {} 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/types/msg/token/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./burnToken" 2 | export * from "./freezeToken" 3 | export * from "./unFreezeToken" 4 | export * from "./issueToken" 5 | export * from "./mintToken" 6 | export * from "./timelock" 7 | export * from "./timeRelock" 8 | export * from "./timeUnlock" 9 | export * from "./issueMiniToken" 10 | export * from "./issueTinyToken" 11 | export * from "./setTokenUri" 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | max_line_length = 80 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | max_line_length = 0 14 | trim_trailing_whitespace = false 15 | 16 | [Makefile] 17 | max_line_length = 0 18 | indent_style = tab 19 | indent_size = 4 20 | 21 | [COMMIT_EDITMSG] 22 | max_line_length = 0 23 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "./declarations" 2 | 3 | import * as amino from "./amino" 4 | import { BncClient } from "./client" 5 | import * as crypto from "./crypto" 6 | import * as types from "./types" 7 | import * as utils from "./utils" 8 | 9 | export { default as ledger } from "./ledger" 10 | export { default as rpc } from "./rpc" 11 | export { default as Transaction } from "./tx" 12 | 13 | export { BncClient, crypto, amino, types, utils } 14 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ["**/*test.(ts|js)"], 3 | testPathIgnorePatterns: [ 4 | "/node_modules/", 5 | "/lib/", 6 | "/typings", 7 | ], 8 | transform: { 9 | "^.+\\.(ts|js)?$": "ts-jest", 10 | }, 11 | rootDir: "", 12 | moduleFileExtensions: ["ts", "js", "json", "node"], 13 | testEnvironment: "node", 14 | globals: { 15 | "ts-jest": { 16 | diagnostics: false, 17 | }, 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /docs/api-docs/classes/stake.md: -------------------------------------------------------------------------------- 1 | 2 | # Class: Stake 3 | 4 | Stake 5 | 6 | ## Hierarchy 7 | 8 | * **Stake** 9 | 10 | ## Index 11 | 12 | ### Constructors 13 | 14 | * [constructor](stake.md#constructor) 15 | 16 | ## Constructors 17 | 18 | ### constructor 19 | 20 | \+ **new Stake**(`bncClient`: [BncClient](bncclient.md)): *[Stake](stake.md)* 21 | 22 | **Parameters:** 23 | 24 | Name | Type | Description | 25 | ------ | ------ | ------ | 26 | `bncClient` | [BncClient](bncclient.md) | | 27 | 28 | **Returns:** *[Stake](stake.md)* 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: 12.x 16 | 17 | - name: Dependencies 18 | run: yarn install --frozen-lockfile 19 | 20 | # - name: Test 21 | # run: yarn test 22 | 23 | - name: Lint 24 | run: yarn lint 25 | 26 | - name: Check types 27 | run: yarn tsc --noEmit 28 | -------------------------------------------------------------------------------- /src/client/gov/proposalType.ts: -------------------------------------------------------------------------------- 1 | const ProposalTypeNil = 0x00 2 | const ProposalTypeText = 0x01 3 | const ProposalTypeParameterChange = 0x02 4 | const ProposalTypeSoftwareUpgrade = 0x03 5 | const ProposalTypeListTradingPair = 0x04 6 | const ProposalTypeFeeChange = 0x05 7 | const ProposalTypeCreateValidator = 0x06 8 | const ProposalTypeRemoveValidator = 0x07 9 | 10 | export default { 11 | ProposalTypeNil, 12 | ProposalTypeText, 13 | ProposalTypeParameterChange, 14 | ProposalTypeSoftwareUpgrade, 15 | ProposalTypeListTradingPair, 16 | ProposalTypeFeeChange, 17 | ProposalTypeCreateValidator, 18 | ProposalTypeRemoveValidator, 19 | } as const 20 | -------------------------------------------------------------------------------- /tests-ledger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | QUnit Test Runner 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /__tests__/bridge.test.ts: -------------------------------------------------------------------------------- 1 | import { getClient } from "./utils" 2 | 3 | describe("bridge", () => { 4 | beforeEach(() => { 5 | jest.setTimeout(50000) 6 | }) 7 | 8 | it("transfer from bc to bsc1", async () => { 9 | const client = await getClient( 10 | true, 11 | false, 12 | "https://testnet-kongo.defibit.io/" 13 | ) 14 | const toAddress = "0xc1c87c37be3Ef20273A4E8982293EEb6E08C620C" 15 | const from = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd" 16 | const result = await client.bridge.transferFromBcToBsc({ 17 | toAddress, 18 | fromAddress: from, 19 | amount: 9, 20 | symbol: "BNB", 21 | expireTime: 1597543193, 22 | }) 23 | expect(result.status).toBe(200) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/utils/encoderHelper.ts: -------------------------------------------------------------------------------- 1 | import is from "is-it-check" 2 | 3 | // typeToTyp3 4 | //amino type convert 5 | export default (type: any): 0 | 1 | 2 => { 6 | if (is.boolean(type)) { 7 | return 0 8 | } 9 | 10 | if (is.number(type)) { 11 | if (is.integer(type)) { 12 | return 0 13 | } else { 14 | return 1 15 | } 16 | } 17 | 18 | if (is.string(type) || is.array(type) || is.object(type)) { 19 | return 2 20 | } 21 | 22 | throw new Error(`Invalid type "${type}"`) // Is this what's expected? 23 | } 24 | 25 | export const size = function ( 26 | items: T[], 27 | iter: (it: T, index: number, acc: number) => number, 28 | acc: number 29 | ): number { 30 | if (acc === undefined) acc = 0 31 | for (let i = 0; i < items.length; ++i) acc += iter(items[i], i, acc) 32 | return acc 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: 12.x 18 | 19 | - name: Dependencies 20 | run: yarn install --frozen-lockfile 21 | 22 | - name: Build 23 | run: yarn build 24 | 25 | - name: Build documentation 26 | run: yarn build:docs 27 | 28 | - name: Publish 29 | id: semantic 30 | uses: cycjimmy/semantic-release-action@v2 31 | with: 32 | semantic_version: 17 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN_SEMANTIC_RELEASE }} 35 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests-ledger/ledger-bundle.js 2 | tests-ledger/buffer-polyfill.js 3 | 4 | *.pem 5 | 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, build with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | # See https://help.github.com/ignore-files/ for more about ignoring files. 19 | 20 | # dependencies 21 | /vendor 22 | /node_modules 23 | 24 | # go cache 25 | /.cache 26 | 27 | # testing 28 | /coverage 29 | 30 | # production 31 | /build 32 | 33 | # build result 34 | /lib 35 | /typings 36 | 37 | # editor 38 | /.vscode 39 | 40 | # misc 41 | .DS_Store 42 | .env.local 43 | .env.development.local 44 | .env.test.local 45 | .env.production.local 46 | 47 | npm-debug.log* 48 | yarn-debug.log* 49 | yarn-error.log* 50 | package-lock.json 51 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "allowJs": false, 5 | "target": "es5", 6 | "module": "ESNext", 7 | "lib": ["dom", "esnext"], 8 | "importHelpers": true, 9 | "declaration": true, 10 | "sourceMap": true, 11 | "rootDir": "./src", 12 | "outDir": "lib", 13 | "strict": true, 14 | "skipLibCheck": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | "moduleResolution": "node", 20 | "removeComments": false, 21 | "baseUrl": "./", 22 | "paths": { 23 | "*": ["src/*", "node_modules/*"] 24 | }, 25 | "esModuleInterop": true 26 | }, 27 | "typedocOptions": { 28 | "mode": "file", 29 | "out": "docs", 30 | "exclude": ["src/declarations.ts"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/types/tx/txResult.ts: -------------------------------------------------------------------------------- 1 | import { StdTx } from "./stdTx" 2 | 3 | export interface ResultTx { 4 | hash: Buffer | string 5 | height: number 6 | index: number 7 | tx_result: ResponseDeliverTx 8 | tx: Buffer | StdTx 9 | proof?: TxProof 10 | } 11 | 12 | export interface ResponseDeliverTx { 13 | code?: number 14 | data?: string 15 | log?: string 16 | info?: string 17 | gas_wanted?: number 18 | gas_used?: number 19 | events?: Event[] 20 | tags?: KVPair[] 21 | codespace?: string 22 | } 23 | 24 | export interface TxProof { 25 | rootHash: Buffer 26 | data: Buffer 27 | proof: { 28 | total: number 29 | index: number 30 | leaf_hash: Buffer 31 | aunts: Buffer[] 32 | } 33 | } 34 | 35 | export interface Event { 36 | type?: string 37 | attributes?: KVPair[] 38 | } 39 | 40 | export interface KVPair { 41 | key: string 42 | value: string 43 | } 44 | -------------------------------------------------------------------------------- /tests-ledger/run-browser-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Building SDK" 4 | yarn build 5 | echo "" 6 | 7 | echo "Browserifying Buffer polyfill" 8 | yarn browserify node_modules/buffer/index.js --im --s=Buffer -o tests-ledger/buffer-polyfill.js 9 | echo "" 10 | 11 | echo "Browserifying Ledger sources" 12 | yarn browserify tests-ledger/index-browser.js --im --s=SDK -o tests-ledger/ledger-bundle.js 13 | echo "" 14 | 15 | if [[ ! -e ./cert.pem ]]; then 16 | echo "Generating https keypair (https is required for U2F communication)" 17 | echo "" 18 | echo "READ THIS: Please enter 'US' in the Country Name field, and press ENTER to leave the rest blank." 19 | echo "" 20 | openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem 21 | echo "" 22 | fi 23 | 24 | echo "Starting the https web server. Please accept the browser security warning" 25 | yarn http-server tests-ledger/ -o --ssl 26 | echo "" 27 | -------------------------------------------------------------------------------- /src/types/msg/claim/claimTypes.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from ".." 2 | 3 | export enum ClaimTypes { 4 | ClaimTypeSkipSequence = 0x1, 5 | ClaimTypeUpdateBind = 0x2, 6 | ClaimTypeTransferOutRefund = 0x3, 7 | ClaimTypeTransferIn = 0x4, 8 | } 9 | 10 | export enum RefundReason { 11 | UnboundToken = 1, 12 | Timeout = 2, 13 | InsufficientBalance = 3, 14 | Unkown = 4, 15 | } 16 | 17 | export enum BindStatus { 18 | BindStatusSuccess = 0, 19 | BindStatusRejected = 1, 20 | BindStatusTimeout = 2, 21 | BindStatusInvalidParameter = 3, 22 | } 23 | 24 | export interface TransferInClaim { 25 | contract_address: string 26 | refund_addresses: string[] 27 | receiver_addresses: string[] 28 | amounts: number[] 29 | symbol: string 30 | relay_fee: Coin 31 | expire_time: number 32 | } 33 | 34 | export interface TransferOutRefundClaim { 35 | transfer_out_sequence: number 36 | refund_address: string 37 | amount: Coin 38 | refund_reason: RefundReason 39 | } 40 | 41 | export interface UpdateBindClaim { 42 | status: BindStatus 43 | symbol: string 44 | contract_address: string 45 | } 46 | 47 | export interface SkipSequenceClaim { 48 | claim_type: ClaimTypes 49 | sequenceToSkip: number 50 | } 51 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: "@typescript-eslint/parser", 4 | env: { 5 | es6: true, 6 | node: true, 7 | browser: true, 8 | jest: true, 9 | }, 10 | extends: [ 11 | "plugin:@typescript-eslint/recommended", 12 | "plugin:import/errors", 13 | "plugin:import/warnings", 14 | "plugin:import/typescript", 15 | "prettier/@typescript-eslint", 16 | "plugin:prettier/recommended", 17 | ], 18 | rules: { 19 | "no-console": ["error"], 20 | "import/named": ["off"], 21 | "import/no-unresolved": ["off"], 22 | "import/default": ["off"], 23 | "import/order": [ 24 | "error", 25 | { 26 | "newlines-between": "always", 27 | groups: [ 28 | ["builtin", "external"], 29 | "internal", 30 | "parent", 31 | "sibling", 32 | "index", 33 | ], 34 | alphabetize: { order: "asc", caseInsensitive: true }, 35 | }, 36 | ], 37 | "no-buffer-constructor": 1, 38 | "linebreak-style": ["error", "unix"], 39 | "@typescript-eslint/explicit-function-return-type": ["off"], 40 | "@typescript-eslint/no-this-alias": ["off"], 41 | "@typescript-eslint/explicit-module-boundary-types": ["off"], 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /tests-ledger/index-browser.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | * BNB Beacon Chain Ledger App Interface 3 | * (c) 2018-2019 All BNB Chain Developers 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | ********************************************************************************/ 17 | 18 | require("babel-polyfill") 19 | 20 | const SDK = module.exports 21 | const Ledger = (SDK.Ledger = {}) 22 | 23 | SDK.crypto = require("../lib/crypto") 24 | Ledger.app = require("../lib/ledger/ledger-app") 25 | 26 | Ledger.transports = { 27 | u2f: require("@ledgerhq/hw-transport-u2f").default, 28 | wble: require("@ledgerhq/hw-transport-web-ble").default, 29 | } 30 | -------------------------------------------------------------------------------- /__tests__/decoder.test.ts: -------------------------------------------------------------------------------- 1 | import * as amino from "../src/amino" 2 | import { unMarshalBinaryLengthPrefixed } from "../src/amino" 3 | import { AminoPrefix, StdTx, SendMsg } from "../src/types/" 4 | 5 | class Msg { 6 | constructor(opts) { 7 | opts = opts || {} 8 | this.string = opts.address || "" 9 | this.buf = opts.buf || Buffer.alloc(0) 10 | this.price = opts.price || 0 11 | this.bool = opts.timeinforce || false 12 | this.quantity = opts.quantity || 0 13 | this.coin = [] 14 | } 15 | } 16 | 17 | Msg.prototype.msgType = AminoPrefix.NewOrderMsg 18 | 19 | describe("decoder", () => { 20 | it("decode type", () => { 21 | const opt = { 22 | address: "tbnb1l6vgk5yyxcalm06gdsg55ay4pjkfueazkvwh58", 23 | buf: Buffer.from("1213"), 24 | price: 100000000, 25 | timeinforce: true, 26 | quantity: 0, 27 | coins: [ 28 | { 29 | denom: "BNB", 30 | amount: 1000000000, 31 | }, 32 | ], 33 | aminoPrefix: AminoPrefix.NewOrderMsg, 34 | } 35 | 36 | const msgObj = new Msg(opt) 37 | 38 | const result = new Msg() 39 | 40 | let bytes = amino.marshalBinary(msgObj) 41 | bytes = Buffer.from(bytes, "hex") 42 | amino.unMarshalBinaryLengthPrefixed(bytes, result) 43 | expect(JSON.stringify(result)).toBe(JSON.stringify(msgObj)) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /src/types/msg/token/timeUnlock.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedTimeUnlockMsg extends SignMsg { 6 | from: string 7 | time_lock_id: number 8 | } 9 | 10 | export interface TimeUnlockData extends Msg { 11 | from: Buffer 12 | time_lock_id: number 13 | aminoPrefix: AminoPrefix 14 | } 15 | 16 | export class TimeUnlockMsg extends BaseMsg { 17 | private from: string 18 | private time_lock_id: number 19 | 20 | constructor({ 21 | address, 22 | time_lock_id, 23 | }: { 24 | address: string 25 | time_lock_id: number 26 | }) { 27 | super() 28 | this.from = address 29 | this.time_lock_id = time_lock_id 30 | } 31 | 32 | getSignMsg() { 33 | const signMsg: SignedTimeUnlockMsg = { 34 | from: this.from, 35 | time_lock_id: this.time_lock_id, 36 | } 37 | 38 | return signMsg 39 | } 40 | 41 | getMsg() { 42 | const data: TimeUnlockData = { 43 | from: crypto.decodeAddress(this.from), 44 | time_lock_id: this.time_lock_id, 45 | aminoPrefix: AminoPrefix.TimeUnlockMsg, 46 | } 47 | 48 | return data 49 | } 50 | 51 | static defaultMsg() { 52 | return { 53 | from: Buffer.from(""), 54 | time_lock_id: 0, 55 | aminoPrefix: AminoPrefix.TimeUnlockMsg, 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/types/msg/token/setTokenUri.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedSetUri extends SignMsg { 6 | from: string 7 | symbol: string 8 | token_uri: string 9 | } 10 | 11 | export interface SetUriData extends Msg { 12 | from: Buffer 13 | symbol: string 14 | token_uri: string 15 | aminoPrefix: AminoPrefix 16 | } 17 | 18 | export class SetTokenUriMsg extends BaseMsg { 19 | private from: string 20 | private symbol: string 21 | private token_uri: string 22 | 23 | constructor({ from, symbol, token_uri }: SignedSetUri) { 24 | super() 25 | this.from = from 26 | this.symbol = symbol 27 | this.token_uri = token_uri 28 | } 29 | 30 | getSignMsg() { 31 | const signMsg: SignedSetUri = { 32 | from: this.from, 33 | symbol: this.symbol, 34 | token_uri: this.token_uri, 35 | } 36 | 37 | return signMsg 38 | } 39 | 40 | getMsg() { 41 | const data: SetUriData = { 42 | from: crypto.decodeAddress(this.from), 43 | symbol: this.symbol, 44 | token_uri: this.token_uri, 45 | aminoPrefix: AminoPrefix.SetURIMsg, 46 | } 47 | 48 | return data 49 | } 50 | 51 | static defaultMsg() { 52 | return { 53 | from: Buffer.from(""), 54 | symbol: "", 55 | token_uri: "", 56 | aminoPrefix: AminoPrefix.SetURIMsg, 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/request.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosInstance, Method, AxiosRequestConfig } from "axios" 2 | 3 | export class HttpRequest { 4 | private httpClient: AxiosInstance 5 | 6 | constructor(baseURL: string) { 7 | this.httpClient = axios.create({ baseURL }) 8 | } 9 | 10 | get(path: string, params?: any, opts?: AxiosRequestConfig) { 11 | return this.request("get", path, params, opts) 12 | } 13 | 14 | post(path: string, body?: any, opts?: AxiosRequestConfig) { 15 | return this.request("post", path, body, opts) 16 | } 17 | 18 | request( 19 | method: Method, 20 | path: string, 21 | params?: any, 22 | opts: AxiosRequestConfig = {} 23 | ) { 24 | const options = { 25 | method, 26 | url: path, 27 | ...opts, 28 | } 29 | 30 | if (params) { 31 | if (method === "get") { 32 | options.params = params 33 | } else { 34 | options.data = params 35 | } 36 | } 37 | 38 | return this.httpClient 39 | .request(options) 40 | .then((response) => { 41 | return { result: response.data, status: response.status } 42 | }) 43 | .catch((err) => { 44 | let error = err 45 | try { 46 | const msgObj = err.response && err.response.data 47 | error = new Error(msgObj.message) 48 | error.code = msgObj.code 49 | } catch (err) { 50 | throw error 51 | } 52 | throw error 53 | }) 54 | } 55 | } 56 | 57 | export default HttpRequest 58 | -------------------------------------------------------------------------------- /src/types/msg/token/burnToken.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedBurnToken extends SignMsg { 6 | from: string 7 | symbol: string 8 | amount: number 9 | } 10 | 11 | export interface BurnTokenData extends Msg { 12 | from: Buffer 13 | symbol: string 14 | amount: number 15 | aminoPrefix: AminoPrefix 16 | } 17 | 18 | export class BurnTokenMsg extends BaseMsg { 19 | private from: string 20 | private symbol: string 21 | private amount: number 22 | 23 | constructor({ 24 | address, 25 | sybmol, 26 | amount, 27 | }: { 28 | address: string 29 | sybmol: string 30 | amount: number 31 | }) { 32 | super() 33 | this.from = address 34 | this.symbol = sybmol 35 | this.amount = amount 36 | } 37 | 38 | getSignMsg() { 39 | const signMsg: SignedBurnToken = { 40 | from: this.from, 41 | symbol: this.symbol, 42 | amount: this.amount, 43 | } 44 | 45 | return signMsg 46 | } 47 | 48 | getMsg() { 49 | const data: BurnTokenData = { 50 | from: crypto.decodeAddress(this.from), 51 | symbol: this.symbol, 52 | amount: this.amount, 53 | aminoPrefix: AminoPrefix.BurnMsg, 54 | } 55 | 56 | return data 57 | } 58 | 59 | static defaultMsg() { 60 | return { 61 | from: Buffer.from(""), 62 | symbol: "", 63 | amount: 0, 64 | aminoPrefix: AminoPrefix.BurnMsg, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/types/msg/token/freezeToken.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedFreezeToken extends SignMsg { 6 | from: string 7 | symbol: string 8 | amount: number 9 | } 10 | 11 | export interface FreezeTokenData extends Msg { 12 | from: Buffer 13 | symbol: string 14 | amount: number 15 | aminoPrefix: AminoPrefix 16 | } 17 | 18 | export class FreezeTokenMsg extends BaseMsg { 19 | private from: string 20 | private symbol: string 21 | private amount: number 22 | 23 | constructor({ 24 | address, 25 | sybmol, 26 | amount, 27 | }: { 28 | address: string 29 | sybmol: string 30 | amount: number 31 | }) { 32 | super() 33 | this.from = address 34 | this.symbol = sybmol 35 | this.amount = amount 36 | } 37 | 38 | getSignMsg() { 39 | const signMsg: SignedFreezeToken = { 40 | from: this.from, 41 | symbol: this.symbol, 42 | amount: this.amount, 43 | } 44 | 45 | return signMsg 46 | } 47 | 48 | getMsg() { 49 | const data: FreezeTokenData = { 50 | from: crypto.decodeAddress(this.from), 51 | symbol: this.symbol, 52 | amount: this.amount, 53 | aminoPrefix: AminoPrefix.FreezeMsg, 54 | } 55 | 56 | return data 57 | } 58 | 59 | static defaultMsg() { 60 | return { 61 | from: Buffer.from(""), 62 | symbol: "", 63 | amount: 0, 64 | aminoPrefix: AminoPrefix.FreezeMsg, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/types/msg/dex/cancelOrder.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from "../../../crypto" 2 | import { AminoPrefix } from "../../tx" 3 | import { BaseMsg, Msg, SignMsg } from "../base" 4 | 5 | export interface SignedCancelOrder extends SignMsg { 6 | sender: string 7 | symbol: string 8 | refid: string 9 | } 10 | 11 | export interface CancelOrderData extends Msg { 12 | sender: Buffer 13 | symbol: string 14 | refid: string 15 | aminoPrefix: AminoPrefix 16 | } 17 | 18 | export class CancelOrderMsg extends BaseMsg { 19 | private address: string 20 | private symbol: string 21 | private orderId: string 22 | public readonly aminoPrefix: AminoPrefix = AminoPrefix.CancelOrderMsg 23 | 24 | constructor(address: string, sybmol: string, orderId: string) { 25 | super() 26 | this.address = address 27 | this.symbol = sybmol 28 | this.orderId = orderId 29 | } 30 | 31 | getSignMsg() { 32 | const signMsg: SignedCancelOrder = { 33 | sender: this.address, 34 | symbol: this.symbol, 35 | refid: this.orderId, 36 | } 37 | 38 | return signMsg 39 | } 40 | 41 | getMsg() { 42 | const data: CancelOrderData = { 43 | sender: crypto.decodeAddress(this.address), 44 | symbol: this.symbol, 45 | refid: this.orderId, 46 | aminoPrefix: AminoPrefix.CancelOrderMsg, 47 | } 48 | 49 | return data 50 | } 51 | 52 | static defaultMsg() { 53 | return { 54 | sender: Buffer.from(""), 55 | symbol: "", 56 | refid: "", 57 | aminoPrefix: AminoPrefix.CancelOrderMsg, 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/types/msg/token/unFreezeToken.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedUnFreezeToken extends SignMsg { 6 | from: string 7 | symbol: string 8 | amount: number 9 | } 10 | 11 | export interface UnFreezeTokenData extends Msg { 12 | from: Buffer 13 | symbol: string 14 | amount: number 15 | aminoPrefix: AminoPrefix 16 | } 17 | 18 | export class UnFreezeTokenMsg extends BaseMsg { 19 | private from: string 20 | private symbol: string 21 | private amount: number 22 | 23 | constructor({ 24 | address, 25 | sybmol, 26 | amount, 27 | }: { 28 | address: string 29 | sybmol: string 30 | amount: number 31 | }) { 32 | super() 33 | this.from = address 34 | this.symbol = sybmol 35 | this.amount = amount 36 | } 37 | 38 | getSignMsg() { 39 | const signMsg: SignedUnFreezeToken = { 40 | from: this.from, 41 | symbol: this.symbol, 42 | amount: this.amount, 43 | } 44 | 45 | return signMsg 46 | } 47 | 48 | getMsg() { 49 | const data: UnFreezeTokenData = { 50 | from: crypto.decodeAddress(this.from), 51 | symbol: this.symbol, 52 | amount: this.amount, 53 | aminoPrefix: AminoPrefix.UnfreezeMsg, 54 | } 55 | 56 | return data 57 | } 58 | 59 | static defaultMsg() { 60 | return { 61 | from: Buffer.from(""), 62 | symbol: "", 63 | amount: 0, 64 | aminoPrefix: AminoPrefix.UnfreezeMsg, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/types/msg/token/issueToken.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface IssueParams { 6 | name: string 7 | symbol: string 8 | total_supply: number 9 | mintable: boolean 10 | } 11 | 12 | export interface SignedIssueTokenMsg extends SignMsg, IssueParams { 13 | from: string 14 | } 15 | 16 | export interface IssueTokenData extends Msg, IssueParams { 17 | from: Buffer 18 | aminoPrefix: AminoPrefix 19 | } 20 | 21 | export class IssueTokenMsg extends BaseMsg { 22 | private from: string 23 | private params: IssueParams 24 | 25 | constructor(params: IssueParams, address: string) { 26 | super() 27 | this.from = address 28 | this.params = params 29 | } 30 | 31 | getSignMsg() { 32 | const signMsg: SignedIssueTokenMsg = { 33 | from: this.from, 34 | ...this.params, 35 | } 36 | 37 | return signMsg 38 | } 39 | 40 | getMsg() { 41 | const data: IssueTokenData = { 42 | from: crypto.decodeAddress(this.from), 43 | name: this.params.name, 44 | symbol: this.params.symbol, 45 | total_supply: this.params.total_supply, 46 | mintable: this.params.mintable, 47 | aminoPrefix: AminoPrefix.IssueMsg, 48 | } 49 | 50 | return data 51 | } 52 | 53 | static defaultMsg() { 54 | return { 55 | from: Buffer.from, 56 | name: "", 57 | symbol: "", 58 | total_supply: 0, 59 | mintable: false, 60 | aminoPrefix: AminoPrefix.IssueMsg, 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/types/msg/token/mintToken.ts: -------------------------------------------------------------------------------- 1 | import Big from "big.js" 2 | 3 | import { BaseMsg, Msg, SignMsg } from "../" 4 | import * as crypto from "../../../crypto" 5 | import { AminoPrefix } from "../../tx" 6 | 7 | export interface SignedMintTokenMsg extends SignMsg { 8 | from: string 9 | symbol: string 10 | amount: number 11 | } 12 | 13 | export interface MintTokenData extends Msg { 14 | from: Buffer 15 | symbol: string 16 | amount: number 17 | aminoPrefix: AminoPrefix 18 | } 19 | 20 | export class MintTokenMsg extends BaseMsg { 21 | private from: string 22 | private symbol: string 23 | private amount: number 24 | 25 | constructor({ 26 | address, 27 | sybmol, 28 | amount, 29 | }: { 30 | address: string 31 | sybmol: string 32 | amount: number 33 | }) { 34 | super() 35 | this.from = address 36 | this.symbol = sybmol 37 | this.amount = amount 38 | } 39 | 40 | getSignMsg() { 41 | const signMsg: SignedMintTokenMsg = { 42 | from: this.from, 43 | amount: Number(new Big(this.amount).mul(Math.pow(10, 8)).toString()), 44 | symbol: this.symbol, 45 | } 46 | 47 | return signMsg 48 | } 49 | 50 | getMsg() { 51 | const data: MintTokenData = { 52 | from: crypto.decodeAddress(this.from), 53 | symbol: this.symbol, 54 | amount: Number(new Big(this.amount).mul(Math.pow(10, 8)).toString()), 55 | aminoPrefix: AminoPrefix.MintMsg, 56 | } 57 | 58 | return data 59 | } 60 | 61 | static defaultMsg() { 62 | return { 63 | from: Buffer.from(""), 64 | symbol: "", 65 | amount: 0, 66 | aminoPrefix: AminoPrefix.MintMsg, 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/types/msg/token/issueMiniToken.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedIssueMiniTokenMsg extends SignMsg { 6 | name: string 7 | symbol: string 8 | total_supply: number 9 | mintable: boolean 10 | from: string 11 | token_uri: string | undefined 12 | } 13 | 14 | export interface IssueMiniTokenData extends Msg { 15 | name: string 16 | symbol: string 17 | total_supply: number 18 | mintable: boolean 19 | from: Buffer 20 | token_uri: string | undefined 21 | aminoPrefix: AminoPrefix 22 | } 23 | 24 | export class IssueMiniTokenMsg extends BaseMsg { 25 | private params: SignedIssueMiniTokenMsg 26 | 27 | constructor(params: SignedIssueMiniTokenMsg) { 28 | super() 29 | this.params = params 30 | } 31 | 32 | getSignMsg() { 33 | const signMsg: SignedIssueMiniTokenMsg = { 34 | ...this.params, 35 | } 36 | 37 | return signMsg 38 | } 39 | 40 | getMsg() { 41 | const data: IssueMiniTokenData = { 42 | from: crypto.decodeAddress(this.params.from), 43 | name: this.params.name, 44 | symbol: this.params.symbol, 45 | total_supply: this.params.total_supply, 46 | mintable: this.params.mintable, 47 | token_uri: this.params.token_uri, 48 | aminoPrefix: AminoPrefix.IssueMiniMsg, 49 | } 50 | 51 | return data 52 | } 53 | 54 | static defaultMsg() { 55 | return { 56 | from: Buffer.from, 57 | name: "", 58 | symbol: "", 59 | total_supply: 0, 60 | mintable: false, 61 | token_uri: "", 62 | aminoPrefix: AminoPrefix.IssueMiniMsg, 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/types/msg/token/issueTinyToken.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedIssueTinyTokenMsg extends SignMsg { 6 | name: string 7 | symbol: string 8 | total_supply: number 9 | mintable: boolean 10 | from: string 11 | token_uri: string | undefined 12 | } 13 | 14 | export interface IssueTinyTokenData extends Msg { 15 | name: string 16 | symbol: string 17 | total_supply: number 18 | mintable: boolean 19 | from: Buffer 20 | token_uri: string | undefined 21 | aminoPrefix: AminoPrefix 22 | } 23 | 24 | export class IssueTinyTokenMsg extends BaseMsg { 25 | private params: SignedIssueTinyTokenMsg 26 | 27 | constructor(params: SignedIssueTinyTokenMsg) { 28 | super() 29 | this.params = params 30 | } 31 | 32 | getSignMsg() { 33 | const signMsg: SignedIssueTinyTokenMsg = { 34 | ...this.params, 35 | } 36 | 37 | return signMsg 38 | } 39 | 40 | getMsg() { 41 | const data: IssueTinyTokenData = { 42 | from: crypto.decodeAddress(this.params.from), 43 | name: this.params.name, 44 | symbol: this.params.symbol, 45 | total_supply: this.params.total_supply, 46 | mintable: this.params.mintable, 47 | token_uri: this.params.token_uri, 48 | aminoPrefix: AminoPrefix.IssueTinyMsg, 49 | } 50 | 51 | return data 52 | } 53 | 54 | static defaultMsg() { 55 | return { 56 | from: Buffer.from, 57 | name: "", 58 | symbol: "", 59 | total_supply: 0, 60 | mintable: false, 61 | token_uri: "", 62 | aminoPrefix: AminoPrefix.IssueTinyMsg, 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/utils/rpcHelper.ts: -------------------------------------------------------------------------------- 1 | import Big, { BigSource } from "big.js" 2 | 3 | import { 4 | AminoPrefix, 5 | NewOrderMsg, 6 | CancelOrderMsg, 7 | SendMsg, 8 | BaseMsg, 9 | BurnTokenMsg, 10 | IssueTokenMsg, 11 | FreezeTokenMsg, 12 | UnFreezeTokenMsg, 13 | TimeLockMsg, 14 | TimeUnlockMsg, 15 | MintTokenMsg, 16 | TimeReLockMsg, 17 | } from "../types" 18 | 19 | export const BASENUMBER = Math.pow(10, 8) 20 | 21 | export const divide = (num: BigSource) => { 22 | return +new Big(num).div(BASENUMBER).toString() 23 | } 24 | 25 | export const convertObjectArrayNum = ( 26 | objArr: Array, 27 | keys: Array 28 | ): void => { 29 | objArr.forEach((item) => { 30 | keys.forEach((key) => { 31 | item[key] = divide(item[key]) as any 32 | }) 33 | }) 34 | } 35 | 36 | //TODO add gov and swap 37 | export const getMsgByAminoPrefix = (aminoPrefix: string) => { 38 | switch (aminoPrefix.toUpperCase()) { 39 | case AminoPrefix.NewOrderMsg: 40 | return NewOrderMsg 41 | case AminoPrefix.CancelOrderMsg: 42 | return CancelOrderMsg 43 | case AminoPrefix.MsgSend: 44 | return SendMsg 45 | case AminoPrefix.IssueMsg: 46 | return IssueTokenMsg 47 | case AminoPrefix.FreezeMsg: 48 | return FreezeTokenMsg 49 | case AminoPrefix.UnfreezeMsg: 50 | return UnFreezeTokenMsg 51 | case AminoPrefix.BurnMsg: 52 | return BurnTokenMsg 53 | case AminoPrefix.MintMsg: 54 | return MintTokenMsg 55 | case AminoPrefix.TimeLockMsg: 56 | return TimeLockMsg 57 | case AminoPrefix.TimeRelockMsg: 58 | return TimeReLockMsg 59 | case AminoPrefix.TimeUnlockMsg: 60 | return TimeUnlockMsg 61 | default: 62 | return BaseMsg 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/validateHelper.ts: -------------------------------------------------------------------------------- 1 | import Big, { BigSource } from "big.js" 2 | 3 | import { Coin } from "../types" 4 | 5 | const MAX_INT64 = Math.pow(2, 63) 6 | 7 | /** 8 | * validate the input number. 9 | * @param {Number} value 10 | */ 11 | export const checkNumber = (value: BigSource, name = "input number") => { 12 | if (new Big(value).lte(0)) { 13 | throw new Error(`${name} should be a positive number`) 14 | } 15 | 16 | if (new Big(value).gte(MAX_INT64)) { 17 | throw new Error(`${name} should be less than 2^63`) 18 | } 19 | } 20 | 21 | /** 22 | * basic validation of coins 23 | * @param {Array} coins 24 | */ 25 | export const checkCoins = (coins: Coin[]) => { 26 | coins = coins || [] 27 | coins.forEach((coin) => { 28 | checkNumber(coin.amount) 29 | if (!coin.denom) { 30 | throw new Error("invalid denmon") 31 | } 32 | }) 33 | } 34 | 35 | export const validateSymbol = (symbol: string) => { 36 | if (!symbol) { 37 | throw new Error("suffixed token symbol cannot be empty") 38 | } 39 | 40 | const splitSymbols = symbol.split("-") 41 | 42 | //check length with .B suffix 43 | if (!/^[a-zA-z\d/.]{3,10}$/.test(splitSymbols[0])) { 44 | throw new Error("symbol length is limited to 3~10") 45 | } 46 | } 47 | 48 | export const validateTradingPair = (pair: string) => { 49 | const symbols = pair.split("_") 50 | if (symbols.length !== 2) { 51 | throw new Error('the pair should in format "symbol1_symbol2"') 52 | } 53 | 54 | validateSymbol(symbols[0]) 55 | validateSymbol(symbols[1]) 56 | } 57 | 58 | export const validateOffsetLimit = (offset: number, limit: number) => { 59 | if (offset < 0) { 60 | throw new Error("offset can't be less than 0") 61 | } 62 | 63 | if (limit < 0) { 64 | throw new Error("limit can't be less than 0") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/amino/encoder/varint.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js" 2 | 3 | function VarIntFunc(signed: boolean) { 4 | const encodingLength = (n: number) => { 5 | if (signed) n *= 2 6 | if (n < 0) { 7 | throw Error("varint value is out of bounds") 8 | } 9 | const bits = Math.log2(n + 1) 10 | return Math.ceil(bits / 7) || 1 11 | } 12 | 13 | const encode = (n: number, buffer?: Buffer | any, offset?: number) => { 14 | if (n < 0) { 15 | throw Error("varint value is out of bounds") 16 | } 17 | 18 | buffer = buffer || Buffer.alloc(encodingLength(n)) 19 | offset = offset || 0 20 | const nStr = n.toString() 21 | let bn = new BN(nStr, 10) 22 | const num255 = new BN(0xff) 23 | const num128 = new BN(0x80) 24 | 25 | // amino signed varint is multiplied by 2 26 | if (signed) { 27 | bn = bn.muln(2) 28 | } 29 | 30 | let i = 0 31 | while (bn.gten(0x80)) { 32 | buffer[offset + i] = bn.and(num255).or(num128).toNumber() 33 | bn = bn.shrn(7) 34 | i++ 35 | } 36 | 37 | buffer[offset + i] = bn.andln(0xff) 38 | 39 | // TODO 40 | // encode.bytes = i + 1 41 | 42 | return buffer 43 | } 44 | 45 | /** 46 | * https://github.com/golang/go/blob/master/src/encoding/binary/varint.go#L60 47 | */ 48 | const decode = (bytes: Buffer | any) => { 49 | let x = 0 50 | let s = 0 51 | for (let i = 0, len = bytes.length; i < len; i++) { 52 | const b = bytes[i] 53 | if (b < 0x80) { 54 | if (i > 9 || (i === 9 && b > 1)) { 55 | return 0 56 | } 57 | return x | (b << s) 58 | } 59 | x |= (b & 0x7f) << s 60 | s += 7 61 | } 62 | 63 | return 0 64 | } 65 | 66 | return { encode, decode, encodingLength } 67 | } 68 | 69 | export const UVarInt = VarIntFunc(false) 70 | export const VarInt = VarIntFunc(true) 71 | -------------------------------------------------------------------------------- /src/types/msg/token/timelock.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedTimeLockMsg extends SignMsg { 6 | from: string 7 | description: string 8 | amount: Coin[] 9 | lock_time: number 10 | } 11 | 12 | export interface TimeLockData extends Msg { 13 | from: Buffer 14 | description: string 15 | amount: Coin[] 16 | lock_time: number 17 | aminoPrefix: AminoPrefix 18 | } 19 | 20 | export class TimeLockMsg extends BaseMsg { 21 | private from: string 22 | private description: string 23 | private lock_time: number 24 | private amount: Coin[] 25 | 26 | constructor({ 27 | address, 28 | description, 29 | amount, 30 | lock_time, 31 | }: { 32 | address: string 33 | description: string 34 | amount: Coin[] 35 | lock_time: number 36 | }) { 37 | super() 38 | this.from = address 39 | this.description = description 40 | this.amount = amount 41 | this.lock_time = lock_time 42 | } 43 | 44 | getSignMsg() { 45 | const signMsg: SignedTimeLockMsg = { 46 | from: this.from, 47 | amount: this.amount, 48 | description: this.description, 49 | lock_time: this.lock_time, 50 | } 51 | 52 | return signMsg 53 | } 54 | 55 | getMsg() { 56 | const data: TimeLockData = { 57 | from: crypto.decodeAddress(this.from), 58 | description: this.description, 59 | amount: this.amount, 60 | lock_time: this.lock_time, 61 | aminoPrefix: AminoPrefix.TimeLockMsg, 62 | } 63 | 64 | return data 65 | } 66 | 67 | static defaultMsg() { 68 | return { 69 | from: Buffer.from(""), 70 | description: "", 71 | amount: [{ denom: "", amount: 0 }], 72 | lock_time: 0, 73 | aminoPrefix: AminoPrefix.TimeLockMsg, 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/types/msg/claim/transferOutMsg.ts: -------------------------------------------------------------------------------- 1 | import Big from "big.js" 2 | 3 | import { BaseMsg, Msg, SignMsg, Coin } from ".." 4 | import * as crypto from "../../../crypto" 5 | import { AminoPrefix } from "../../tx" 6 | 7 | export interface SignedTransferOutMsg extends SignMsg { 8 | from: string 9 | to: string 10 | amount: Coin 11 | expire_time: number 12 | } 13 | 14 | export interface TransferoutData extends Msg { 15 | from: Buffer 16 | to: Buffer 17 | amount: Coin 18 | expire_time: number 19 | aminoPrefix: AminoPrefix 20 | } 21 | 22 | export class TransferOutMsg extends BaseMsg { 23 | private from: string 24 | private to: string 25 | private amount: Coin 26 | private expire_time: number 27 | 28 | constructor({ 29 | from, 30 | to, 31 | amount, 32 | expire_time, 33 | }: { 34 | from: string 35 | to: string 36 | amount: Coin 37 | expire_time: number 38 | }) { 39 | super() 40 | this.from = from 41 | this.to = to 42 | this.amount = { 43 | ...amount, 44 | amount: Number(new Big(amount.amount).mul(Math.pow(10, 8)).toString()), 45 | } 46 | this.expire_time = expire_time 47 | } 48 | 49 | getSignMsg(): SignedTransferOutMsg { 50 | return { 51 | from: this.from, 52 | to: this.to, 53 | amount: this.amount, 54 | expire_time: this.expire_time, 55 | } 56 | } 57 | 58 | getMsg(): TransferoutData { 59 | return { 60 | from: crypto.decodeAddress(this.from), 61 | to: Buffer.from(this.to.slice(2), "hex"), 62 | amount: this.amount, 63 | expire_time: this.expire_time, 64 | aminoPrefix: AminoPrefix.TransferOutMsg, 65 | } 66 | } 67 | 68 | static defaultMsg(): TransferoutData { 69 | return { 70 | from: Buffer.from(""), 71 | to: Buffer.from(""), 72 | amount: { denom: "", amount: 0 }, 73 | expire_time: 0, 74 | aminoPrefix: AminoPrefix.TransferOutMsg, 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/types/msg/dex/listMiniMsg.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from "../../../crypto" 2 | import { AminoPrefix } from "../../tx" 3 | import { BaseMsg, Msg, SignMsg } from "../base" 4 | 5 | export interface SignedListMini extends SignMsg { 6 | from: string 7 | base_asset_symbol: string 8 | quote_asset_symbol: string 9 | init_price: number 10 | } 11 | 12 | export interface ListMiniData extends Msg { 13 | from: Buffer 14 | base_asset_symbol: string 15 | quote_asset_symbol: string 16 | init_price: number 17 | aminoPrefix: AminoPrefix 18 | } 19 | 20 | export class ListMiniMsg extends BaseMsg { 21 | private from: string 22 | private base_asset_symbol: string 23 | private quote_asset_symbol: string 24 | private init_price: number 25 | 26 | constructor({ 27 | from, 28 | base_asset_symbol, 29 | quote_asset_symbol, 30 | init_price, 31 | }: SignedListMini) { 32 | super() 33 | this.from = from 34 | this.base_asset_symbol = base_asset_symbol 35 | this.quote_asset_symbol = quote_asset_symbol 36 | this.init_price = init_price 37 | } 38 | 39 | getSignMsg() { 40 | const signMsg: SignedListMini = { 41 | from: this.from, 42 | base_asset_symbol: this.base_asset_symbol, 43 | quote_asset_symbol: this.quote_asset_symbol, 44 | init_price: this.init_price, 45 | } 46 | 47 | return signMsg 48 | } 49 | 50 | getMsg() { 51 | const data: ListMiniData = { 52 | from: crypto.decodeAddress(this.from), 53 | base_asset_symbol: this.base_asset_symbol, 54 | quote_asset_symbol: this.quote_asset_symbol, 55 | init_price: this.init_price, 56 | aminoPrefix: AminoPrefix.ListMiniMsg, 57 | } 58 | 59 | return data 60 | } 61 | 62 | static defaultMsg() { 63 | return { 64 | from: Buffer.from(""), 65 | base_asset_symbol: "", 66 | quote_asset_symbol: "", 67 | init_price: 0, 68 | aminoPrefix: AminoPrefix.ListMiniMsg, 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/ledger/index.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************** 2 | * BNB Beacon Chain Ledger App Interface 3 | * (c) 2018-2019 All BNB Chain Developers 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | ********************************************************************************/ 17 | /* eslint-disable*/ 18 | import ledgerApp from "./ledger-app" 19 | 20 | // intentionally ambiguous to confuse webpack (we don't need this in web builds) 21 | const LEDGER_NODE_HID_TRANSPORT_MODULE = "@ledgerhq/hw-transport-node-hid" 22 | 23 | const isBrowser = typeof window !== "undefined" 24 | // const Ledger = module.exports 25 | 26 | const Ledger: any = { 27 | app: ledgerApp, 28 | LedgerApp: ledgerApp, 29 | transports: { 30 | u2f: require("@ledgerhq/hw-transport-u2f").default, 31 | wble: require("@ledgerhq/hw-transport-web-ble").default, 32 | 33 | // requiring the node transport in the browser causes a bit of an issue with webpack! this is a conditional require 34 | node: 35 | !isBrowser && moduleExists(LEDGER_NODE_HID_TRANSPORT_MODULE) 36 | ? require(LEDGER_NODE_HID_TRANSPORT_MODULE).default 37 | : null, 38 | }, 39 | } 40 | 41 | module.exports = Ledger 42 | 43 | export default Ledger 44 | 45 | function moduleExists(name: string) { 46 | try { 47 | return require.resolve(name) 48 | } catch (e) { 49 | return false 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /__tests__/stake.test.ts: -------------------------------------------------------------------------------- 1 | import { getClient, address } from "./utils" 2 | 3 | describe("stake management", () => { 4 | beforeEach(() => { 5 | jest.setTimeout(50000) 6 | }) 7 | 8 | it("bsc delegate", async () => { 9 | const client = await getClient(true) 10 | const validatorAddress = "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2" 11 | 12 | try { 13 | const res = await client.stake.bscDelegate({ 14 | delegateAddress: address, 15 | validatorAddress, 16 | amount: 10, 17 | }) 18 | expect(res.status).toBe(200) 19 | } catch (err) { 20 | if (err.message.includes("insufficient fund")) { 21 | expect(1).toBeTruthy() 22 | } 23 | throw err 24 | } 25 | }) 26 | 27 | it("bsc undelegate", async () => { 28 | const client = await getClient(true) 29 | const validatorAddress = "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2" 30 | 31 | try { 32 | const res = await client.stake.bscUndelegate({ 33 | delegateAddress: address, 34 | validatorAddress, 35 | amount: 10, 36 | }) 37 | expect(res.status).toBe(200) 38 | } catch (err) { 39 | if (err.message.includes("insufficient fund")) { 40 | expect(1).toBeTruthy() 41 | } 42 | throw err 43 | } 44 | }) 45 | 46 | it("bsc redelegate", async () => { 47 | const client = await getClient(true) 48 | const validatorSrcAddress = "bva10npy5809y303f227g4leqw7vs3s6ep5ul26sq2" 49 | const validatorDstAddress = "bva1pcd6muhehuz6fy05wfhq9sd5fww6ggdap3adxg" 50 | try { 51 | const res = await client.stake.bscReDelegate({ 52 | delegateAddress: address, 53 | validatorSrcAddress, 54 | validatorDstAddress, 55 | amount: 10, 56 | }) 57 | expect(res.status).toBe(200) 58 | } catch (err) { 59 | if (err.message.includes("insufficient fund")) { 60 | expect(1).toBeTruthy() 61 | } 62 | throw err 63 | } 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /src/types/msg/claim/claimMsg.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | import { ClaimTypes } from "./claimTypes" 6 | 7 | export interface SignedClaimMsg extends SignMsg { 8 | claim_type: ClaimTypes 9 | sequence: number 10 | claim: string 11 | validator_address: string 12 | } 13 | 14 | export interface ClaimMsgData extends Msg { 15 | claim_type: ClaimTypes 16 | sequence: number 17 | claim: string 18 | validator_address: Buffer 19 | aminoPrefix: AminoPrefix 20 | } 21 | 22 | export class ClaimMsg extends BaseMsg { 23 | private claim_type: ClaimTypes 24 | private sequence: number 25 | private claim: string 26 | private validator_address: string 27 | 28 | constructor({ 29 | claim_type, 30 | sequence, 31 | claim, 32 | validator_address, 33 | }: { 34 | claim_type: ClaimTypes 35 | sequence: number 36 | claim: string 37 | validator_address: string 38 | }) { 39 | super() 40 | this.claim_type = claim_type 41 | this.sequence = sequence 42 | this.claim = claim 43 | this.validator_address = validator_address 44 | } 45 | 46 | getSignMsg(): SignedClaimMsg { 47 | return { 48 | claim_type: this.claim_type, 49 | sequence: this.sequence, 50 | claim: this.claim, 51 | validator_address: this.validator_address, 52 | } 53 | } 54 | 55 | getMsg(): ClaimMsgData { 56 | return { 57 | claim_type: this.claim_type, 58 | sequence: this.sequence, 59 | claim: this.claim, 60 | validator_address: crypto.decodeAddress(this.validator_address), 61 | aminoPrefix: AminoPrefix.ClaimMsg, 62 | } 63 | } 64 | 65 | static defaultMsg(): ClaimMsgData { 66 | return { 67 | claim_type: ClaimTypes.ClaimTypeSkipSequence, 68 | sequence: 0, 69 | claim: "", 70 | validator_address: Buffer.from(""), 71 | aminoPrefix: AminoPrefix.ClaimMsg, 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/types/msg/token/timeRelock.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedTimeReLockMsg extends SignMsg { 6 | from: string 7 | time_lock_id: number 8 | description: string 9 | amount: Coin[] 10 | lock_time: number 11 | } 12 | 13 | export interface TimeReLockData extends Msg { 14 | from: Buffer 15 | time_lock_id: number 16 | description: string 17 | amount: Coin[] 18 | lock_time: number 19 | aminoPrefix: AminoPrefix 20 | } 21 | 22 | export class TimeReLockMsg extends BaseMsg { 23 | private from: string 24 | private time_lock_id: number 25 | private description: string 26 | private lock_time: number 27 | private amount: Coin[] 28 | 29 | constructor({ 30 | address, 31 | time_lock_id, 32 | description, 33 | amount, 34 | lock_time, 35 | }: { 36 | address: string 37 | time_lock_id: number 38 | description: string 39 | amount: Coin[] 40 | lock_time: number 41 | }) { 42 | super() 43 | this.from = address 44 | this.description = description 45 | this.amount = amount 46 | this.lock_time = lock_time 47 | this.time_lock_id = time_lock_id 48 | } 49 | 50 | getSignMsg() { 51 | const signMsg: SignedTimeReLockMsg = { 52 | from: this.from, 53 | time_lock_id: this.time_lock_id, 54 | amount: this.amount, 55 | description: this.description, 56 | lock_time: this.lock_time, 57 | } 58 | 59 | return signMsg 60 | } 61 | 62 | getMsg() { 63 | const data: TimeReLockData = { 64 | from: crypto.decodeAddress(this.from), 65 | time_lock_id: this.time_lock_id, 66 | description: this.description, 67 | amount: this.amount, 68 | lock_time: this.lock_time, 69 | aminoPrefix: AminoPrefix.TimeRelockMsg, 70 | } 71 | 72 | return data 73 | } 74 | 75 | static defaultMsg() { 76 | return { 77 | from: Buffer.from(""), 78 | description: "", 79 | amount: 0, 80 | lock_time: 0, 81 | aminoPrefix: AminoPrefix.TimeRelockMsg, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/types/msg/dex/newOrder.ts: -------------------------------------------------------------------------------- 1 | import Big from "big.js" 2 | 3 | import { BaseMsg, Msg, SignMsg } from "../" 4 | import * as crypto from "../../../crypto" 5 | import { AminoPrefix } from "../../tx" 6 | 7 | export interface NewOrder { 8 | id: string 9 | symbol: string 10 | ordertype: number 11 | side: number 12 | price: number 13 | quantity: number 14 | timeinforce: number 15 | } 16 | 17 | export interface SignedNewOrder extends SignMsg, NewOrder { 18 | sender: string 19 | } 20 | 21 | export interface NewOrderData extends Msg, NewOrder { 22 | sender: Buffer 23 | aminoPrefix: AminoPrefix 24 | } 25 | 26 | const BASENUMBER = Math.pow(10, 8) 27 | 28 | export class NewOrderMsg extends BaseMsg { 29 | private newOrder: NewOrder 30 | private address: string 31 | public readonly aminoPrefix: AminoPrefix = AminoPrefix.NewOrderMsg 32 | 33 | constructor(data: NewOrder, address: string) { 34 | super() 35 | const bigPrice = new Big(data.price) 36 | const bigQuantity = new Big(data.quantity) 37 | 38 | this.newOrder = data 39 | this.newOrder.price = Number(bigPrice.mul(BASENUMBER).toString()) 40 | this.newOrder.quantity = Number(bigQuantity.mul(BASENUMBER).toString()) 41 | this.address = address 42 | } 43 | 44 | getSignMsg() { 45 | const signMsg: SignedNewOrder = { 46 | sender: this.address, 47 | ...this.newOrder, 48 | } 49 | return signMsg 50 | } 51 | 52 | getMsg() { 53 | const data: NewOrderData = { 54 | sender: crypto.decodeAddress(this.address), 55 | id: this.newOrder.id, 56 | symbol: this.newOrder.symbol, 57 | ordertype: this.newOrder.ordertype, 58 | side: this.newOrder.side, 59 | price: this.newOrder.price, 60 | quantity: this.newOrder.quantity, 61 | timeinforce: this.newOrder.timeinforce, 62 | aminoPrefix: this.aminoPrefix, 63 | } 64 | 65 | return data 66 | } 67 | 68 | static defaultMsg() { 69 | return { 70 | sender: Buffer.from(""), 71 | id: "", 72 | symbol: "", 73 | orderType: 0, 74 | side: 0, 75 | price: 0, 76 | quantity: 0, 77 | timeinforce: 0, 78 | aminoPrefix: AminoPrefix.NewOrderMsg, 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/types/msg/stake/bscUndelegateMsg.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from ".." 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedBscUndelegate extends SignMsg { 6 | delegator_addr: string 7 | validator_addr: string 8 | amount: Coin 9 | side_chain_id: string 10 | } 11 | 12 | export interface BscUndelegateData extends Msg { 13 | delegator_addr: Buffer 14 | validator_addr: Buffer 15 | amount: Coin 16 | side_chain_id: string 17 | aminoPrefix: AminoPrefix 18 | } 19 | 20 | export class BscUndelegateMsg extends BaseMsg { 21 | private delegator_addr: string 22 | private validator_addr: string 23 | private amount: Coin 24 | private side_chain_id: string 25 | 26 | constructor({ 27 | delegator_addr, 28 | validator_addr, 29 | amount, 30 | side_chain_id, 31 | }: { 32 | delegator_addr: string 33 | validator_addr: string 34 | amount: Coin 35 | side_chain_id: string 36 | }) { 37 | super() 38 | this.delegator_addr = delegator_addr 39 | this.validator_addr = validator_addr 40 | this.amount = amount 41 | this.side_chain_id = side_chain_id 42 | } 43 | 44 | getSignMsg() { 45 | const { denom, amount } = this.amount 46 | const signMsg: SignedBscUndelegate = { 47 | delegator_addr: this.delegator_addr, 48 | validator_addr: this.validator_addr, 49 | amount: { denom, amount: String(amount) }, 50 | side_chain_id: this.side_chain_id, 51 | } 52 | 53 | return { 54 | type: "cosmos-sdk/MsgSideChainUndelegate", 55 | value: signMsg, 56 | } 57 | } 58 | 59 | getMsg() { 60 | const data: BscUndelegateData = { 61 | delegator_addr: crypto.decodeAddress(this.delegator_addr), 62 | validator_addr: crypto.decodeAddress(this.validator_addr), 63 | amount: this.amount, 64 | side_chain_id: this.side_chain_id, 65 | aminoPrefix: AminoPrefix.MsgSideChainUndelegate, 66 | } 67 | 68 | return data 69 | } 70 | 71 | static defaultMsg() { 72 | return { 73 | delegator_addr: Buffer.from(""), 74 | validator_addr: Buffer.from(""), 75 | amount: [{ denom: "", amount: 0 }], 76 | side_chain_id: "", 77 | aminoPrefix: AminoPrefix.MsgSideChainUndelegate, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/types/tx/stdTx.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg } from "../msg" 2 | 3 | export interface StdSignMsg { 4 | chainId: string 5 | accountNumber: number 6 | sequence: number 7 | baseMsg?: BaseMsg 8 | msg?: Msg 9 | memo: string 10 | source: number 11 | data?: Buffer | null | string 12 | } 13 | 14 | export interface StdSignature { 15 | pub_key?: Buffer 16 | signature: Buffer 17 | account_number: number 18 | sequence: number 19 | } 20 | 21 | export interface StdTx { 22 | msg: Array 23 | signatures: Array 24 | memo: string 25 | source: number 26 | data?: Buffer | null | string 27 | aminoPrefix: AminoPrefix 28 | } 29 | 30 | export enum AminoPrefix { 31 | MsgSend = "2A2C87FA", 32 | NewOrderMsg = "CE6DC043", 33 | CancelOrderMsg = "166E681B", 34 | IssueMsg = "17EFAB80", 35 | BurnMsg = "7ED2D2A0", 36 | FreezeMsg = "E774B32D", 37 | UnfreezeMsg = "6515FF0D", 38 | MintMsg = "467E0829", 39 | ListMsg = "B41DE13F", 40 | StdTx = "F0625DEE", 41 | PubKeySecp256k1 = "EB5AE987", 42 | SignatureSecp256k1 = "7FC4A495", 43 | MsgSubmitProposal = "B42D614E", 44 | MsgDeposit = "A18A56E5", 45 | MsgVote = "A1CADD36", 46 | TimeLockMsg = "07921531", 47 | TimeUnlockMsg = "C4050C6C", 48 | TimeRelockMsg = "504711DA", 49 | HTLTMsg = "B33F9A24", 50 | DepositHTLTMsg = "63986496", 51 | ClaimHTLTMsg = "C1665300", 52 | RefundHTLTMsg = "3454A27C", 53 | SetAccountFlagsMsg = "BEA6E301", 54 | BnbchainAccount = "4BDC4C27", 55 | BnbchainToken = "140364E6", 56 | MsgCreateValidator = "EB361D01", 57 | MsgRemoveValidator = "C1AFE85F", 58 | MsgCreateValidatorProposal = "DB6A19FD", 59 | MsgEditValidator = "C2E8BCCD", 60 | MsgDelegate = "921D2E4E", 61 | MsgBeginUnbonding = "A3823C9A", 62 | MsgBeginRedelegate = "267996D2", 63 | MsgCreateSideChainValidator = "D17201E5", 64 | MsgEditSideChainValidator = "264CC57B", 65 | MsgSideChainDelegate = "E3A07FD2", 66 | MsgSideChainRedelegate = "E3CED364", 67 | MsgSideChainUndelegate = "514F7E0E", 68 | MsgSideChainStakeMigration = "38589196", 69 | Claim = "4E781C11", 70 | ClaimMsg = "175A0521", 71 | BindMsg = "B9AE640C", 72 | TransferOutMsg = "800819C0", 73 | IssueMiniMsg = "A3F16C41", 74 | IssueTinyMsg = "ED2832D4", 75 | SetURIMsg = "7B1D34E7", 76 | ListMiniMsg = "4C264019", 77 | MiniToken = "E0051C20", 78 | } 79 | -------------------------------------------------------------------------------- /src/types/msg/stake/bscDelegateMsg.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from ".." 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedBscDelegate extends SignMsg { 6 | delegator_addr: string 7 | validator_addr: string 8 | delegation: Coin 9 | side_chain_id: string 10 | } 11 | 12 | export interface BscDelegateData extends Msg { 13 | delegator_addr: Buffer 14 | validator_addr: Buffer 15 | delegation: Coin 16 | side_chain_id: string 17 | aminoPrefix: AminoPrefix 18 | } 19 | 20 | export class BscDelegateMsg extends BaseMsg { 21 | private delegator_addr: string 22 | private validator_addr: string 23 | private delegation: Coin 24 | private side_chain_id: string 25 | 26 | constructor({ 27 | delegator_addr, 28 | validator_addr, 29 | delegation, 30 | side_chain_id, 31 | }: { 32 | delegator_addr: string 33 | validator_addr: string 34 | delegation: Coin 35 | side_chain_id: string 36 | }) { 37 | super() 38 | this.delegator_addr = delegator_addr 39 | this.validator_addr = validator_addr 40 | this.delegation = delegation 41 | this.side_chain_id = side_chain_id 42 | } 43 | 44 | getSignMsg() { 45 | const { denom, amount } = this.delegation 46 | const signMsg: SignedBscDelegate = { 47 | delegator_addr: this.delegator_addr, 48 | validator_addr: this.validator_addr, 49 | delegation: { denom, amount: String(amount) }, 50 | side_chain_id: this.side_chain_id, 51 | } 52 | 53 | return { 54 | type: "cosmos-sdk/MsgSideChainDelegate", 55 | value: signMsg, 56 | } 57 | } 58 | 59 | getMsg() { 60 | const data: BscDelegateData = { 61 | delegator_addr: crypto.decodeAddress(this.delegator_addr), 62 | validator_addr: crypto.decodeAddress(this.validator_addr), 63 | delegation: this.delegation, 64 | side_chain_id: this.side_chain_id, 65 | aminoPrefix: AminoPrefix.MsgSideChainDelegate, 66 | } 67 | 68 | return data 69 | } 70 | 71 | static defaultMsg() { 72 | return { 73 | delegator_addr: Buffer.from(""), 74 | validator_addr: Buffer.from(""), 75 | delegation: [{ denom: "", amount: 0 }], 76 | side_chain_id: "", 77 | aminoPrefix: AminoPrefix.MsgSideChainDelegate, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/declarations.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | declare module "is-it-check" { 4 | export const boolean: (a: any) => a is boolean 5 | export const number: (a: any) => a is number 6 | export const integer: (a: any) => a is number 7 | export const string: (a: any) => a is string 8 | export const array: (a: any) => a is Array 9 | export const object: (a: any) => a is object 10 | } 11 | 12 | declare module "secure-random" { 13 | const csprng: (lenght: number) => ArrayBuffer 14 | export default csprng 15 | } 16 | 17 | declare module "crypto-browserify" { 18 | import * as _crypto from "crypto" 19 | const crypto: Pick< 20 | typeof _crypto, 21 | | "createHash" 22 | | "createHmac" 23 | | "pbkdf2" 24 | | "pbkdf2Sync" 25 | | "randomBytes" 26 | | "pseudoRandomBytes" 27 | | "createCipher" 28 | | "createDecipher" 29 | | "createDiffieHellman" 30 | | "createSign" 31 | | "createVerify" 32 | | "createECDH" 33 | | "publicEncrypt" 34 | | "privateDecrypt" 35 | | "privateEncrypt" 36 | | "publicDecrypt" 37 | | "createCipheriv" 38 | | "createDecipheriv" 39 | > 40 | export default crypto 41 | } 42 | 43 | declare module "protocol-buffers-encodings" { 44 | export namespace string { 45 | export const encode: (val: any, buffer: Buffer, offset?: number) => Buffer 46 | export const decode: (buf: Buffer | number[], offset?: number) => any 47 | export const encodingLength: (val: any) => number 48 | } 49 | 50 | export namespace bytes { 51 | export const encode: (val: any, buffer: Buffer, offset?: number) => Buffer 52 | export const decode: (buf: Buffer | number[], offset?: number) => any 53 | export const encodingLength: (val: any) => number 54 | } 55 | 56 | export namespace bool { 57 | export const encode: (val: any, buffer: Buffer, offset?: number) => Buffer 58 | export const decode: (buf: Buffer | number[], offset?: number) => any 59 | export const encodingLength: (val: any) => number 60 | } 61 | 62 | export namespace varint { 63 | export const encode: (val: any, buffer: Buffer, offset?: number) => Buffer 64 | export const decode: (buf: Buffer | number[], offset?: number) => any 65 | export const encodingLength: (val: any) => number 66 | } 67 | } 68 | 69 | declare module "ndjson" { 70 | import { Stream } from "stream" 71 | const wat: { stringify: () => Stream } 72 | export default wat 73 | } 74 | -------------------------------------------------------------------------------- /src/types/msg/claim/bindMsg.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedBindMsg extends SignMsg { 6 | from: string 7 | symbol: string 8 | amount: number 9 | contract_address: string 10 | contract_decimals: number 11 | expire_time: number 12 | } 13 | 14 | export interface BindMsgData extends Msg { 15 | from: Buffer 16 | symbol: string 17 | amount: number 18 | contract_address: Buffer 19 | contract_decimals: number 20 | expire_time: number 21 | aminoPrefix: AminoPrefix 22 | } 23 | 24 | export class BindMsg extends BaseMsg { 25 | private from: string 26 | private symbol: string 27 | private amount: number 28 | private contract_address: string 29 | private contract_decimals: number 30 | private expire_time: number 31 | 32 | constructor({ 33 | from, 34 | symbol, 35 | amount, 36 | contract_address, 37 | contract_decimals, 38 | expire_time, 39 | }: { 40 | from: string 41 | symbol: string 42 | amount: number 43 | contract_address: string 44 | contract_decimals: number 45 | expire_time: number 46 | }) { 47 | super() 48 | this.from = from 49 | this.symbol = symbol 50 | this.amount = amount 51 | this.contract_address = contract_address 52 | this.contract_decimals = contract_decimals 53 | this.expire_time = expire_time 54 | } 55 | 56 | getSignMsg(): SignedBindMsg { 57 | return { 58 | from: this.from, 59 | symbol: this.symbol, 60 | amount: this.amount, 61 | contract_address: this.contract_address, 62 | contract_decimals: this.contract_decimals, 63 | expire_time: this.expire_time, 64 | } 65 | } 66 | 67 | getMsg(): BindMsgData { 68 | return { 69 | from: crypto.decodeAddress(this.from), 70 | symbol: this.symbol, 71 | amount: this.amount, 72 | contract_address: Buffer.from(this.contract_address.slice(2), "hex"), 73 | contract_decimals: this.contract_decimals, 74 | expire_time: this.expire_time, 75 | aminoPrefix: AminoPrefix.BindMsg, 76 | } 77 | } 78 | 79 | static defaultMsg(): BindMsgData { 80 | return { 81 | from: Buffer.from(""), 82 | symbol: "", 83 | amount: 0, 84 | contract_address: Buffer.from(""), 85 | contract_decimals: 0, 86 | expire_time: 0, 87 | aminoPrefix: AminoPrefix.BindMsg, 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/types/msg/stake/bscRedelegateMsg.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from ".." 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedBscReDelegate extends SignMsg { 6 | delegator_addr: string 7 | validator_src_addr: string 8 | validator_dst_addr: string 9 | amount: Coin 10 | side_chain_id: string 11 | } 12 | 13 | export interface BscReDelegateData extends Msg { 14 | delegator_addr: Buffer 15 | validator_src_addr: Buffer 16 | validator_dst_addr: Buffer 17 | amount: Coin 18 | side_chain_id: string 19 | aminoPrefix: AminoPrefix 20 | } 21 | 22 | export class BscReDelegateMsg extends BaseMsg { 23 | private delegator_addr: string 24 | private validator_src_addr: string 25 | private validator_dst_addr: string 26 | private amount: Coin 27 | private side_chain_id: string 28 | 29 | constructor({ 30 | delegator_addr, 31 | validator_src_addr, 32 | validator_dst_addr, 33 | amount, 34 | side_chain_id, 35 | }: { 36 | delegator_addr: string 37 | validator_src_addr: string 38 | validator_dst_addr: string 39 | amount: Coin 40 | side_chain_id: string 41 | }) { 42 | super() 43 | this.delegator_addr = delegator_addr 44 | this.validator_src_addr = validator_src_addr 45 | this.validator_dst_addr = validator_dst_addr 46 | this.amount = amount 47 | this.side_chain_id = side_chain_id 48 | } 49 | 50 | getSignMsg() { 51 | const { denom, amount } = this.amount 52 | const signMsg: SignedBscReDelegate = { 53 | delegator_addr: this.delegator_addr, 54 | validator_src_addr: this.validator_src_addr, 55 | validator_dst_addr: this.validator_dst_addr, 56 | amount: { denom, amount: String(amount) }, 57 | side_chain_id: this.side_chain_id, 58 | } 59 | 60 | return { 61 | type: "cosmos-sdk/MsgSideChainRedelegate", 62 | value: signMsg, 63 | } 64 | } 65 | 66 | getMsg() { 67 | const data: BscReDelegateData = { 68 | delegator_addr: crypto.decodeAddress(this.delegator_addr), 69 | validator_src_addr: crypto.decodeAddress(this.validator_src_addr), 70 | validator_dst_addr: crypto.decodeAddress(this.validator_dst_addr), 71 | amount: this.amount, 72 | side_chain_id: this.side_chain_id, 73 | aminoPrefix: AminoPrefix.MsgSideChainRedelegate, 74 | } 75 | 76 | return data 77 | } 78 | 79 | static defaultMsg() { 80 | return { 81 | delegator_addr: Buffer.from(""), 82 | validator_src_addr: Buffer.from(""), 83 | validator_dst_addr: Buffer.from(""), 84 | amount: [{ denom: "", amount: 0 }], 85 | side_chain_id: "", 86 | aminoPrefix: AminoPrefix.MsgSideChainRedelegate, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /docs/api-docs/classes/transaction.md: -------------------------------------------------------------------------------- 1 | 2 | # Class: Transaction 3 | 4 | Creates a new transaction object. 5 | 6 | **`example`** 7 | var rawTx = { 8 | accountNumber: 1, 9 | chainId: 'bnbchain-1000', 10 | memo: '', 11 | msg: {}, 12 | type: 'NewOrderMsg', 13 | sequence: 29, 14 | source: 0 15 | }; 16 | var tx = new Transaction(rawTx); 17 | 18 | **`property`** {Buffer} raw The raw vstruct encoded transaction 19 | 20 | **`param`** account number 21 | 22 | **`param`** bnbChain Id 23 | 24 | **`param`** transaction memo 25 | 26 | **`param`** transaction type 27 | 28 | **`param`** object data of tx type 29 | 30 | **`param`** transaction counts 31 | 32 | **`param`** where does this transaction come from 33 | 34 | ## Hierarchy 35 | 36 | * **Transaction** 37 | 38 | ## Index 39 | 40 | ### Methods 41 | 42 | * [_serializePubKey](transaction.md#private-_serializepubkey) 43 | * [addSignature](transaction.md#addsignature) 44 | * [getSignBytes](transaction.md#getsignbytes) 45 | * [serialize](transaction.md#serialize) 46 | * [sign](transaction.md#sign) 47 | 48 | ## Methods 49 | 50 | ### `Private` _serializePubKey 51 | 52 | ▸ **_serializePubKey**(`unencodedPubKey`: BasePoint): *Buffer‹›* 53 | 54 | serializes a public key in a 33-byte compressed format. 55 | 56 | **Parameters:** 57 | 58 | Name | Type | 59 | ------ | ------ | 60 | `unencodedPubKey` | BasePoint | 61 | 62 | **Returns:** *Buffer‹›* 63 | 64 | ___ 65 | 66 | ### addSignature 67 | 68 | ▸ **addSignature**(`pubKey`: BasePoint, `signature`: Buffer): *[Transaction](transaction.md)* 69 | 70 | attaches a signature to the transaction 71 | 72 | **Parameters:** 73 | 74 | Name | Type | 75 | ------ | ------ | 76 | `pubKey` | BasePoint | 77 | `signature` | Buffer | 78 | 79 | **Returns:** *[Transaction](transaction.md)* 80 | 81 | ___ 82 | 83 | ### getSignBytes 84 | 85 | ▸ **getSignBytes**(`msg?`: SignMsg): *Buffer* 86 | 87 | generate the sign bytes for a transaction, given a msg 88 | 89 | **Parameters:** 90 | 91 | Name | Type | 92 | ------ | ------ | 93 | `msg?` | SignMsg | 94 | 95 | **Returns:** *Buffer* 96 | 97 | ___ 98 | 99 | ### serialize 100 | 101 | ▸ **serialize**(): *string* 102 | 103 | encode signed transaction to hex which is compatible with amino 104 | 105 | **Returns:** *string* 106 | 107 | ___ 108 | 109 | ### sign 110 | 111 | ▸ **sign**(`privateKey`: string, `msg?`: SignMsg): *[Transaction](transaction.md)* 112 | 113 | sign transaction with a given private key and msg 114 | 115 | **Parameters:** 116 | 117 | Name | Type | Description | 118 | ------ | ------ | ------ | 119 | `privateKey` | string | private key hex string | 120 | `msg?` | SignMsg | - | 121 | 122 | **Returns:** *[Transaction](transaction.md)* 123 | -------------------------------------------------------------------------------- /src/types/msg/stake/stakeMigrationMsg.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from ".." 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | export interface SignedStakeMigrationMsg extends SignMsg { 6 | delegator_addr: string 7 | validator_src_addr: string 8 | validator_dst_addr: string 9 | refund_addr: string 10 | amount: Coin 11 | } 12 | 13 | export interface StakeMigrationData extends Msg { 14 | validator_src_addr: Buffer 15 | validator_dst_addr: Buffer 16 | delegator_addr: Buffer 17 | refund_addr: Buffer 18 | amount: Coin 19 | aminoPrefix: AminoPrefix 20 | } 21 | 22 | export class StakeMigrationMsg extends BaseMsg { 23 | private delegator_addr: string 24 | private validator_src_addr: string 25 | private validator_dst_addr: string 26 | private refund_addr: string 27 | private amount: Coin 28 | 29 | constructor({ 30 | delegator_addr, 31 | validator_src_addr, 32 | validator_dst_addr, 33 | amount, 34 | refund_addr, 35 | }: { 36 | delegator_addr: string 37 | validator_src_addr: string 38 | validator_dst_addr: string 39 | amount: Coin 40 | refund_addr: string 41 | }) { 42 | super() 43 | this.delegator_addr = delegator_addr 44 | this.validator_src_addr = validator_src_addr 45 | this.validator_dst_addr = validator_dst_addr 46 | this.amount = amount 47 | this.refund_addr = refund_addr 48 | } 49 | 50 | getSignMsg() { 51 | const { denom, amount } = this.amount 52 | const signMsg: SignedStakeMigrationMsg = { 53 | validator_src_addr: this.validator_src_addr, 54 | validator_dst_addr: this.validator_dst_addr, 55 | delegator_addr: this.delegator_addr, 56 | refund_addr: this.refund_addr, 57 | amount: { denom, amount: String(amount) }, 58 | } 59 | 60 | return { 61 | type: "cosmos-sdk/MsgSideChainStakeMigration", 62 | value: signMsg, 63 | } 64 | } 65 | 66 | getMsg() { 67 | const data: StakeMigrationData = { 68 | validator_src_addr: crypto.decodeAddress(this.validator_src_addr), 69 | validator_dst_addr: Buffer.from(this.validator_dst_addr.slice(2), "hex"), 70 | delegator_addr: Buffer.from(this.delegator_addr.slice(2), "hex"), 71 | refund_addr: crypto.decodeAddress(this.refund_addr), 72 | amount: this.amount, 73 | aminoPrefix: AminoPrefix.MsgSideChainStakeMigration, 74 | } 75 | 76 | return data 77 | } 78 | 79 | static defaultMsg() { 80 | return { 81 | validator_src_addr: Buffer.from(""), 82 | validator_dst_addr: Buffer.from(""), 83 | delegator_addr: Buffer.from(""), 84 | refund_addr: Buffer.from(""), 85 | amount: [{ denom: "", amount: 0 }], 86 | aminoPrefix: AminoPrefix.MsgSideChainStakeMigration, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/types/msg/send.ts: -------------------------------------------------------------------------------- 1 | import Big, { BigSource } from "big.js" 2 | 3 | import * as crypto from "../../crypto" 4 | import { AminoPrefix } from "../tx" 5 | 6 | import { BaseMsg, Msg, SignMsg } from "./" 7 | 8 | export interface Coin { 9 | denom: string 10 | amount: BigSource 11 | } 12 | 13 | export interface SignInputOutput { 14 | address: string 15 | coins: Coin[] 16 | } 17 | 18 | interface InputOutput { 19 | address: Buffer 20 | coins: Coin[] 21 | } 22 | 23 | export interface SignedSend extends SignMsg { 24 | inputs: SignInputOutput[] 25 | outputs: SignInputOutput[] 26 | } 27 | 28 | export interface SendData extends Msg { 29 | inputs: InputOutput[] 30 | outputs: InputOutput[] 31 | aminoPrefix: AminoPrefix 32 | } 33 | 34 | /** 35 | * @ignore 36 | * Only support transfers of one-to-one, one-to-many 37 | */ 38 | export class SendMsg extends BaseMsg { 39 | private sender: string 40 | private outputs: SignInputOutput[] 41 | public readonly aminoPrefix: AminoPrefix = AminoPrefix.MsgSend 42 | constructor(sender: string, outputs: SignInputOutput[]) { 43 | super() 44 | this.sender = sender 45 | this.outputs = outputs 46 | } 47 | 48 | calInputCoins(inputsCoins: Coin[], coins: Coin[]) { 49 | coins.forEach((coin) => { 50 | const existCoin = inputsCoins.find((c) => c.denom === coin.denom) 51 | if (existCoin) { 52 | const existAmount = new Big(existCoin.amount) 53 | existCoin.amount = Number(existAmount.plus(coin.amount).toString()) 54 | } else { 55 | inputsCoins.push({ ...coin }) 56 | } 57 | }) 58 | } 59 | 60 | getSignMsg() { 61 | const signMsg: SignedSend = { 62 | inputs: [{ address: this.sender, coins: [] }], 63 | outputs: this.outputs, 64 | } 65 | 66 | this.outputs.forEach((item) => { 67 | this.calInputCoins(signMsg.inputs[0].coins, item.coins) 68 | }) 69 | 70 | return signMsg 71 | } 72 | 73 | getMsg() { 74 | const msg: SendData = { 75 | inputs: [{ address: crypto.decodeAddress(this.sender), coins: [] }], 76 | outputs: [], 77 | aminoPrefix: this.aminoPrefix, 78 | } 79 | 80 | this.outputs.forEach((item) => { 81 | this.calInputCoins(msg.inputs[0].coins, item.coins) 82 | 83 | const output: InputOutput = { 84 | address: crypto.decodeAddress(item.address), 85 | coins: item.coins, 86 | } 87 | 88 | msg.outputs.push(output) 89 | }) 90 | 91 | return msg 92 | } 93 | 94 | static defaultMsg() { 95 | return { 96 | inputs: [{ address: Buffer.from(""), coins: [{ denom: "", amount: 0 }] }], 97 | outputs: [ 98 | { address: Buffer.from(""), coins: [{ denom: "", amount: 0 }] }, 99 | ], 100 | aminoPrefix: AminoPrefix.MsgSend, 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The BNB Beacon Chain JavaScript SDK allows browsers and Node.js clients to interact 2 | with BNB Beacon Chain. It includes the following core components. 3 | 4 | - **crypto** - core cryptographic functions. 5 | - **amino** - 6 | [amino](https://github.com/binance-chain/docs-site/blob/master/docs/encoding.md) 7 | (protobuf-like) encoding and decoding of transactions. 8 | - **client** - implementations of BNB Beacon Chain transaction types, such as for 9 | transfers and trading. 10 | - **accounts** - management of "accounts" and wallets, including seed and 11 | encrypted mnemonic generation. 12 | - **ledger** - Ledger Nano S/X support via HID, U2F and Web BLE (Bluetooth). 13 | - **rpc** - Node RPC client. 14 | - **transaction** - Transaction Class, build and sign. 15 | 16 | You can find more detailed documentation and examples in our 17 | [Documentation](https://github.com/binance-chain/javascript-sdk/blob/master/docs/README.md) 18 | pages. 19 | 20 | ## Installation 21 | 22 | If you **do not** need Ledger support with Node.js: 23 | 24 | ```bash 25 | $ npm i @bnb-chain/javascript-sdk --no-optional 26 | # Or: 27 | $ yarn add @bnb-chain/javascript-sdk --no-optional 28 | ``` 29 | 30 | If you **need** Ledger support with Node.js: 31 | 32 | ```bash 33 | $ npm i @bnb-chain/javascript-sdk 34 | # Or: 35 | $ yarn add @bnb-chain/javascript-sdk 36 | ``` 37 | 38 | ### Prerequisites 39 | 40 | - **Windows users:** Please install 41 | [windows-build-tools](https://www.npmjs.com/package/windows-build-tools) 42 | first. 43 | 44 | - **Mac users:** Make sure XCode Command Line Tools are installed: 45 | `xcode-select --install`. 46 | 47 | - **Linux users:** Note that Ubuntu Xenial and newer distributions are 48 | recommended, especially when using Travis or other CI systems. You may need 49 | some dev packages to be installed on your system for USB support. On 50 | Debian-based distributions (like Ubuntu) you should install them with this 51 | command: 52 | 53 | ```bash 54 | $ sudo apt-get install libudev-dev libusb-dev usbutils 55 | ``` 56 | 57 | ### Use with Webpack 58 | 59 | We often see Webpack builds failing with the SDK due to the `usb` dependency, 60 | but adding this to your Webpack config should fix that: 61 | 62 | ```js 63 | module.exports = { 64 | plugins: [new webpack.IgnorePlugin(/^usb$/)], 65 | } 66 | ``` 67 | 68 | ## Testing 69 | 70 | All new code changes should be covered with unit tests. You can run the tests 71 | with the following command: 72 | 73 | ```bash 74 | $ yarn test 75 | ``` 76 | 77 | Tests for the Ledger hardware wallet integration have their own suite that runs 78 | in both node and in the browser: 79 | 80 | ```bash 81 | $ yarn test:ledger 82 | $ yarn test:ledger:browser 83 | ``` 84 | 85 | 86 | ## Contributing 87 | 88 | Contributions to the BNB Beacon Chain JavaScript SDK are welcome. Please ensure 89 | that you have tested the changes with a local client and have added unit test 90 | coverage for your code. 91 | -------------------------------------------------------------------------------- /docs/transaction-types.md: -------------------------------------------------------------------------------- 1 | # Supported Transaction Types 2 | 3 | Each transaction type has an encoded prefix, according to the 4 | [amino](https://github.com/tendermint/go-amino/blob/master/README.md) 5 | specification: 6 | 7 | Some supported type prefixes are as follows: 8 | 9 | | Type | Name | Prefix | Length | Notes | 10 | | -------------------------- | ------------------------------------- | ---------- | -------- | ----- | 11 | | PubKeyEd25519 | tendermint/PubKeyEd25519 | 0x1624DE64 | 0x20 | | 12 | | PubKeySecp256k1 | tendermint/PubKeySecp256k1 | 0xEB5AE987 | 0x21 | | 13 | | PubKeyMultisigThreshold | tendermint/PubKeyMultisigThreshold | 0x22C1F7E2 | variable | | 14 | | PrivKeyEd25519 | tendermint/PrivKeyEd25519 | 0xA3288910 | 0x40 | | 15 | | PrivKeySecp256k1 | tendermint/PrivKeySecp256k1 | 0xE1B0F79B | 0x20 | | 16 | | StdTx | auth/StdTx | 0xF0625DEE | variable | | 17 | | CreateOrderMsg | dex/NewOrder | 0xCE6DC043 | variable | | 18 | | CancelOrderMsg | dex/CancelOrder | 0x166E681B | variable | | 19 | | TokenIssueMsg | tokens/IssueMsg | 0x17EFAB80 | variable | | 20 | | TokenBurnMsg | tokens/BurnMsg | 0x7ED2D2A0 | variable | | 21 | | TimeLockMsg | tokens/TimeLockMsg | 0x07921531 | variable | | 22 | | TokenFreezeMsg | tokens/FreezeMsg | 0xE774B32D | variable | | 23 | | TokenUnfreezeMsg | tokens/UnfreezeMsg | 0x6515FF0D | variable | | 24 | | TimeUnlockMsg | tokens/TimeUnlockMsg | 0xC4050C6C | variable | | 25 | | TimeRelockMsg | tokens/TimeRelockMsg | 0x504711DA | variable | | 26 | | DexListMsg | dex/ListMsg | 0xB41DE13F | variable | | 27 | | MintMsg | tokens/MintMsg | 0x467E0829 | variable | | 28 | | SendMsg | cosmos-sdk/Send | 0x2A2C87FA | variable | | 29 | | SubmitProposalMsg | cosmos-sdk/MsgSubmitProposal | 0xB42D614E | variable | | 30 | | DepositMsg | cosmos-sdk/MsgDeposit | 0xA18A56E5 | variable | | 31 | | VoteMsg | cosmos-sdk/MsgVote | 0xA1CADD36 | variable | | 32 | | MsgCreateValidator | cosmos-sdk/MsgCreateValidator | 0xEB361D01 | variable | | 33 | | MsgRemoveValidator | cosmos-sdk/MsgRemoveValidator | 0xC1AFE85F | variable | | 34 | | MsgCreateValidatorProposal | cosmos-sdk/MsgCreateValidatorProposal | 0xDB6A19FD | variable | | 35 | -------------------------------------------------------------------------------- /src/types/msg/gov/submitProposal.ts: -------------------------------------------------------------------------------- 1 | import { BaseMsg, Msg, SignMsg, Coin } from "../" 2 | import * as crypto from "../../../crypto" 3 | import { AminoPrefix } from "../../tx" 4 | 5 | const proposalTypeMapping = { 6 | 0x04: "ListTradingPair", 7 | 0x00: "Nil", 8 | 0x01: "Text", 9 | 0x02: "ParameterChange", 10 | 0x03: "SoftwareUpgrade", 11 | 0x05: "FeeChange", 12 | 0x06: "CreateValidator", 13 | 0x07: "RemoveValidator", 14 | } as const 15 | 16 | export interface SignedSubmitProposal extends SignMsg { 17 | title: string 18 | description: string 19 | proposal_type: string 20 | proposer: string 21 | initial_deposit: { denom: string; amount: string }[] 22 | voting_period: string 23 | } 24 | 25 | export interface SubmitProposalData extends Msg { 26 | title: string 27 | description: string 28 | proposal_type: keyof typeof proposalTypeMapping 29 | proposer: Buffer 30 | initial_deposit: Coin[] 31 | voting_period: number 32 | aminoPrefix: AminoPrefix 33 | } 34 | 35 | export class SubmitProposalMsg extends BaseMsg { 36 | private title: string 37 | private description: string 38 | private proposal_type: keyof typeof proposalTypeMapping 39 | private address: string 40 | private initialDeposit: number 41 | private voting_period: number 42 | 43 | constructor({ 44 | address, 45 | title, 46 | proposal_type, 47 | initialDeposit, 48 | voting_period, 49 | description, 50 | }: { 51 | title: string 52 | description: string 53 | proposal_type: keyof typeof proposalTypeMapping 54 | address: string 55 | initialDeposit: number 56 | voting_period: number 57 | }) { 58 | super() 59 | this.address = address 60 | this.title = title 61 | this.proposal_type = proposal_type 62 | this.initialDeposit = initialDeposit 63 | this.voting_period = voting_period 64 | this.description = description 65 | } 66 | 67 | getSignMsg() { 68 | const signMsg: SignedSubmitProposal = { 69 | title: this.title, 70 | description: this.description, 71 | proposal_type: proposalTypeMapping[this.proposal_type], 72 | proposer: this.address, 73 | voting_period: this.voting_period.toString(), 74 | initial_deposit: [ 75 | { 76 | denom: "BNB", 77 | amount: new Big(this.initialDeposit).mul(Math.pow(10, 8)).toString(), 78 | }, 79 | ], 80 | } 81 | 82 | return signMsg 83 | } 84 | 85 | getMsg() { 86 | const data: SubmitProposalData = { 87 | title: this.title, 88 | description: this.description, 89 | proposal_type: this.proposal_type, 90 | proposer: crypto.decodeAddress(this.address), 91 | initial_deposit: [ 92 | { 93 | denom: "BNB", 94 | amount: +new Big(this.initialDeposit).mul(Math.pow(10, 8)).toString(), 95 | }, 96 | ], 97 | voting_period: this.voting_period, 98 | aminoPrefix: AminoPrefix.MsgSubmitProposal, 99 | } 100 | 101 | return data 102 | } 103 | 104 | static defaultMsg() { 105 | return { 106 | title: "", 107 | description: "", 108 | propsal_type: 0, 109 | proposer: Buffer.from(""), 110 | inital_deposit: [{ denom: "", amount: 0 }], 111 | voting_period: 0, 112 | aminoPrefix: AminoPrefix.MsgSubmitProposal, 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /docs/api-docs/classes/bridge.md: -------------------------------------------------------------------------------- 1 | 2 | # Class: Bridge 3 | 4 | Bridge 5 | 6 | ## Hierarchy 7 | 8 | * **Bridge** 9 | 10 | ## Index 11 | 12 | ### Constructors 13 | 14 | * [constructor](bridge.md#constructor) 15 | 16 | ### Methods 17 | 18 | * [bind](bridge.md#bind) 19 | * [transferFromBcToBsc](bridge.md#transferfrombctobsc) 20 | * [transferIn](bridge.md#transferin) 21 | * [transferOutRefund](bridge.md#transferoutrefund) 22 | * [upateBind](bridge.md#upatebind) 23 | 24 | ## Constructors 25 | 26 | ### constructor 27 | 28 | \+ **new Bridge**(`bncClient`: [BncClient](bncclient.md)): *[Bridge](bridge.md)* 29 | 30 | **Parameters:** 31 | 32 | Name | Type | Description | 33 | ------ | ------ | ------ | 34 | `bncClient` | [BncClient](bncclient.md) | | 35 | 36 | **Returns:** *[Bridge](bridge.md)* 37 | 38 | ## Methods 39 | 40 | ### bind 41 | 42 | ▸ **bind**(`__namedParameters`: object): *Promise‹object›* 43 | 44 | bind smart chain token to bep2 token 45 | 46 | **Parameters:** 47 | 48 | ▪ **__namedParameters**: *object* 49 | 50 | Name | Type | 51 | ------ | ------ | 52 | `amount` | number | 53 | `contractAddress` | string | 54 | `contractDecimal` | number | 55 | `expireTime` | number | 56 | `fromAddress` | string | 57 | `symbol` | string | 58 | 59 | **Returns:** *Promise‹object›* 60 | 61 | ___ 62 | 63 | ### transferFromBcToBsc 64 | 65 | ▸ **transferFromBcToBsc**(`__namedParameters`: object): *Promise‹object›* 66 | 67 | transfer token from BNB Beacon Chain to BNB Smart Chain 68 | 69 | **Parameters:** 70 | 71 | ▪ **__namedParameters**: *object* 72 | 73 | Name | Type | 74 | ------ | ------ | 75 | `amount` | number | 76 | `expireTime` | number | 77 | `fromAddress` | string | 78 | `symbol` | string | 79 | `toAddress` | string | 80 | 81 | **Returns:** *Promise‹object›* 82 | 83 | ___ 84 | 85 | ### transferIn 86 | 87 | ▸ **transferIn**(`__namedParameters`: object): *Promise‹object›* 88 | 89 | transfer smart chain token to BNB Beacon Chain receiver 90 | 91 | **Parameters:** 92 | 93 | ▪ **__namedParameters**: *object* 94 | 95 | Name | Type | 96 | ------ | ------ | 97 | `amounts` | number[] | 98 | `contract_address` | string | 99 | `expire_time` | number | 100 | `fromAddress` | string | 101 | `receiver_addresses` | string[] | 102 | `refund_addresses` | string[] | 103 | `relay_fee` | Coin | 104 | `sequence` | number | 105 | `symbol` | string | 106 | 107 | **Returns:** *Promise‹object›* 108 | 109 | ___ 110 | 111 | ### transferOutRefund 112 | 113 | ▸ **transferOutRefund**(`__namedParameters`: object): *Promise‹object›* 114 | 115 | refund tokens to sender if transfer to smart chain failed 116 | 117 | **Parameters:** 118 | 119 | ▪ **__namedParameters**: *object* 120 | 121 | Name | Type | 122 | ------ | ------ | 123 | `amount` | Coin | 124 | `fromAddress` | string | 125 | `refund_address` | string | 126 | `refund_reason` | RefundReason | 127 | `transfer_out_sequence` | number | 128 | 129 | **Returns:** *Promise‹object›* 130 | 131 | ___ 132 | 133 | ### upateBind 134 | 135 | ▸ **upateBind**(`__namedParameters`: object): *Promise‹object›* 136 | 137 | update bind request when events from smart chain received 138 | 139 | **Parameters:** 140 | 141 | ▪ **__namedParameters**: *object* 142 | 143 | Name | Type | 144 | ------ | ------ | 145 | `contract_address` | string | 146 | `fromAddress` | string | 147 | `sequence` | number | 148 | `status` | BindStatus | 149 | `symbol` | string | 150 | 151 | **Returns:** *Promise‹object›* 152 | -------------------------------------------------------------------------------- /__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import { BncClient } from "../src/client" 2 | import * as crypto from "../src/crypto" 3 | 4 | /* make sure the address from the mnemonic has balances, or the case will failed */ 5 | export const mnemonic = process.env.mnemonic 6 | 7 | export const keystores = { 8 | // keystore with sha3 mac 9 | new: { 10 | version: 1, 11 | id: "4cffb5da-2f64-48ea-b1c7-c4dd9fe53da8", 12 | crypto: { 13 | ciphertext: 14 | "721fadbb4d7a53aefb3895c089115da5203e36221bb8735f539e6c9d466b3567", 15 | cipherparams: { iv: "19e3ae15059bb8b80e91fdbb30d0d28b" }, 16 | cipher: "aes-256-ctr", 17 | kdf: "pbkdf2", 18 | kdfparams: { 19 | dklen: 32, 20 | salt: 21 | "04fc271dcd65e9e833ed2b6a1b4f90ce80b06fb57f9283c29d403cc269e4d8a7", 22 | c: 262144, 23 | prf: "hmac-sha256", 24 | }, 25 | mac: 26 | "32beae99a8cc2f9f2134f5aad1047b33182d3bd9996f9c7a88c51429da8942d7fbd44d6035934031aaee3af189cd54644c4655bb6c20c96f7c25ac906ca4786d", 27 | }, 28 | }, 29 | // keystore with sha256 mac 30 | legacy: { 31 | version: 1, 32 | id: "dfb09873-f16f-48c6-a6b8-bb5a705c47a7", 33 | address: "bnc1dxj068zgk007fchefj9n8tq06pcuce5ypqm5zk", 34 | crypto: { 35 | ciphertext: 36 | "33b7439a8d64d73357dc91f88a6b3a45e7303717664d17daf8e8dc1cc708fa4b", 37 | cipherparams: { iv: "88c726d70cd0437bfdb2312dc60103fc" }, 38 | cipher: "aes-256-ctr", 39 | kdf: "pbkdf2", 40 | kdfparams: { 41 | dklen: 32, 42 | salt: 43 | "ad10ef544417d4a25914dec3d908882686dd9d793b5c484b76fd5aa575cf54b9", 44 | c: 262144, 45 | prf: "hmac-sha256", 46 | }, 47 | mac: "f7cc301d18c97c71741492b8029544952ad5567a733971deb49fd3eb03ee696e", 48 | }, 49 | }, 50 | // keystore with bad mac 51 | badMac: { 52 | version: 1, 53 | id: "dfb09873-f16f-48c6-a6b8-bb5a705c47a7", 54 | address: "bnc1dxj068zgk007fchefj9n8tq06pcuce5ypqm5zk", 55 | crypto: { 56 | ciphertext: 57 | "33b7439a8d64d73357dc91f88a6b3a45e7303717664d17daf8e8dc1cc708fa4b", 58 | cipherparams: { iv: "88c726d70cd0437bfdb2312dc60103fc" }, 59 | cipher: "aes-256-ctr", 60 | kdf: "pbkdf2", 61 | kdfparams: { 62 | dklen: 32, 63 | salt: 64 | "ad10ef544417d4a25914dec3d908882686dd9d793b5c484b76fd5aa575cf54b9", 65 | c: 262144, 66 | prf: "hmac-sha256", 67 | }, 68 | mac: "x7cc301d18c97c71741492b8029544952ad5567a733971deb49fd3eb03ee696e", 69 | }, 70 | }, 71 | } 72 | 73 | export const targetAddress = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd" 74 | 75 | let client: BncClient 76 | 77 | export const getClient = async ( 78 | useAwaitSetPrivateKey = true, 79 | doNotSetPrivateKey = false, 80 | url = "https://testnet-dex-asiapacific.binance.org" 81 | ) => { 82 | if (client && client.chainId) { 83 | return client 84 | } 85 | 86 | client = new BncClient(url) 87 | await client.initChain() 88 | const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic) 89 | if (!doNotSetPrivateKey) { 90 | if (useAwaitSetPrivateKey) { 91 | await client.setPrivateKey(privateKey) 92 | } else { 93 | client.setPrivateKey(privateKey) // test without `await` 94 | } 95 | } 96 | 97 | // use default delegates (signing, broadcast) 98 | client.useDefaultSigningDelegate() 99 | client.useDefaultBroadcastDelegate() 100 | return client 101 | } 102 | 103 | export const wait = (ms) => { 104 | return new Promise(function (resolve) { 105 | setTimeout(function () { 106 | resolve() 107 | }, ms) 108 | }) 109 | } 110 | 111 | export const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic) 112 | 113 | export const address = crypto.getAddressFromPrivateKey(privateKey) 114 | -------------------------------------------------------------------------------- /docs/api-docs/classes/rpcclient.md: -------------------------------------------------------------------------------- 1 | 2 | # Class: RpcClient 3 | 4 | The BNB Beacon Chain Node rpc client 5 | 6 | ## Hierarchy 7 | 8 | * BaseRpc 9 | 10 | ↳ **RpcClient** 11 | 12 | ## Index 13 | 14 | ### Constructors 15 | 16 | * [constructor](rpcclient.md#constructor) 17 | 18 | ### Methods 19 | 20 | * [broadcastDelegate](rpcclient.md#broadcastdelegate) 21 | * [getAccount](rpcclient.md#getaccount) 22 | * [getBalance](rpcclient.md#getbalance) 23 | * [getBalances](rpcclient.md#getbalances) 24 | * [getDepth](rpcclient.md#getdepth) 25 | * [getOpenOrders](rpcclient.md#getopenorders) 26 | * [getTokenInfo](rpcclient.md#gettokeninfo) 27 | * [getTradingPairs](rpcclient.md#gettradingpairs) 28 | * [listAllTokens](rpcclient.md#listalltokens) 29 | 30 | ## Constructors 31 | 32 | ### constructor 33 | 34 | \+ **new RpcClient**(`uriString`: string, `netWork`: keyof typeof NETWORK_PREFIX_MAPPING): *[RpcClient](rpcclient.md)* 35 | 36 | **Parameters:** 37 | 38 | Name | Type | Default | Description | 39 | ------ | ------ | ------ | ------ | 40 | `uriString` | string | "localhost:27146" | dataseed address | 41 | `netWork` | keyof typeof NETWORK_PREFIX_MAPPING | - | BNB Beacon Chain network | 42 | 43 | **Returns:** *[RpcClient](rpcclient.md)* 44 | 45 | ## Methods 46 | 47 | ### broadcastDelegate 48 | 49 | ▸ **broadcastDelegate**(`signedTx`: [Transaction](transaction.md)): *Promise‹any›* 50 | 51 | The RPC broadcast delegate broadcasts a transaction via RPC. This is intended for optional use as BncClient's broadcast delegate. 52 | 53 | **Parameters:** 54 | 55 | Name | Type | Description | 56 | ------ | ------ | ------ | 57 | `signedTx` | [Transaction](transaction.md) | the signed transaction | 58 | 59 | **Returns:** *Promise‹any›* 60 | 61 | ___ 62 | 63 | ### getAccount 64 | 65 | ▸ **getAccount**(`address`: string): *Promise‹object›* 66 | 67 | **Parameters:** 68 | 69 | Name | Type | 70 | ------ | ------ | 71 | `address` | string | 72 | 73 | **Returns:** *Promise‹object›* 74 | 75 | Account info 76 | 77 | ___ 78 | 79 | ### getBalance 80 | 81 | ▸ **getBalance**(`address`: string, `symbol`: string): *Promise‹undefined | TokenBalance‹››* 82 | 83 | get balance by symbol and address 84 | 85 | **Parameters:** 86 | 87 | Name | Type | 88 | ------ | ------ | 89 | `address` | string | 90 | `symbol` | string | 91 | 92 | **Returns:** *Promise‹undefined | TokenBalance‹››* 93 | 94 | ___ 95 | 96 | ### getBalances 97 | 98 | ▸ **getBalances**(`address`: string): *Promise‹TokenBalance‹›[]›* 99 | 100 | **Parameters:** 101 | 102 | Name | Type | 103 | ------ | ------ | 104 | `address` | string | 105 | 106 | **Returns:** *Promise‹TokenBalance‹›[]›* 107 | 108 | ___ 109 | 110 | ### getDepth 111 | 112 | ▸ **getDepth**(`tradePair`: string): *Promise‹any›* 113 | 114 | **Parameters:** 115 | 116 | Name | Type | 117 | ------ | ------ | 118 | `tradePair` | string | 119 | 120 | **Returns:** *Promise‹any›* 121 | 122 | ___ 123 | 124 | ### getOpenOrders 125 | 126 | ▸ **getOpenOrders**(`address`: string, `symbol`: string): *Promise‹any›* 127 | 128 | **Parameters:** 129 | 130 | Name | Type | 131 | ------ | ------ | 132 | `address` | string | 133 | `symbol` | string | 134 | 135 | **Returns:** *Promise‹any›* 136 | 137 | ___ 138 | 139 | ### getTokenInfo 140 | 141 | ▸ **getTokenInfo**(`symbol`: string): *Promise‹object›* 142 | 143 | **Parameters:** 144 | 145 | Name | Type | Description | 146 | ------ | ------ | ------ | 147 | `symbol` | string | required | 148 | 149 | **Returns:** *Promise‹object›* 150 | 151 | token detail info 152 | 153 | ___ 154 | 155 | ### getTradingPairs 156 | 157 | ▸ **getTradingPairs**(`offset`: number, `limit`: number): *Promise‹any›* 158 | 159 | **Parameters:** 160 | 161 | Name | Type | 162 | ------ | ------ | 163 | `offset` | number | 164 | `limit` | number | 165 | 166 | **Returns:** *Promise‹any›* 167 | 168 | ___ 169 | 170 | ### listAllTokens 171 | 172 | ▸ **listAllTokens**(`offset`: number, `limit`: number): *Promise‹any›* 173 | 174 | get tokens by offset and limit 175 | 176 | **Parameters:** 177 | 178 | Name | Type | 179 | ------ | ------ | 180 | `offset` | number | 181 | `limit` | number | 182 | 183 | **Returns:** *Promise‹any›* 184 | 185 | token list 186 | -------------------------------------------------------------------------------- /__tests__/crypto.test.ts: -------------------------------------------------------------------------------- 1 | import * as bip39 from "bip39" 2 | 3 | import * as crypto from "../src/crypto" 4 | 5 | import { mnemonic } from "./utils" 6 | 7 | const privateKey = crypto.generatePrivateKey() 8 | const keyStore = crypto.generateKeyStore(privateKey, "1234567") 9 | 10 | describe("crypto", () => { 11 | it("generate a random address", () => { 12 | const privateKey = crypto.generatePrivateKey() 13 | const address = crypto.getAddressFromPrivateKey(privateKey) 14 | expect(address.length).toBe(43) 15 | }) 16 | 17 | it("generate private key from keyStore", () => { 18 | const pk = crypto.getPrivateKeyFromKeyStore(keyStore, "1234567") 19 | expect(pk).toBe(privateKey) 20 | }) 21 | 22 | it("generate private key from mnemonic", () => { 23 | const pk = crypto.getPrivateKeyFromMnemonic( 24 | "fragile duck lunch coyote cotton pole gym orange share muscle impulse mom pause isolate define oblige hungry sound stereo spider style river fun account" 25 | ) 26 | expect(pk.toString("hex")).toBe( 27 | "caf2009a04bd53d426fc0907383b3f1dfe13013aee694d0159f6befc3fdccd5f" 28 | ) 29 | }) 30 | 31 | it("generate mnemonic", () => { 32 | const mnemonic = crypto.generateMnemonic() 33 | expect(bip39.validateMnemonic(mnemonic)).toBe(true) 34 | }) 35 | 36 | it("decodeAddress", () => { 37 | const address = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd" 38 | const decod = crypto.decodeAddress(address) 39 | expect(decod.toString("hex")).toBe( 40 | "ba36f0fad74d8f41045463e4774f328f4af779e5" 41 | ) 42 | }) 43 | 44 | it("generate address from mnemonic", () => { 45 | const pk = crypto.getPrivateKeyFromMnemonic(mnemonic) 46 | const address = crypto.getAddressFromPrivateKey(pk) 47 | expect(address).toBe("tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd") 48 | }) 49 | 50 | it("generate address from mnemonic with index 1", () => { 51 | const pk = crypto.getPrivateKeyFromMnemonic(mnemonic, true, 1) 52 | const address = crypto.getAddressFromPrivateKey(pk) 53 | expect(address).toBe("tbnb1egswqkszzfc2uq78zjslc6u2uky4pw46gq25tu") 54 | }) 55 | 56 | it("generateSignature", () => { 57 | const privateKey = 58 | "30c5e838578a29e3e9273edddd753d6c9b38aca2446dd84bdfe2e5988b0da0a1" 59 | const msg = 60 | "7b226163636f756e745f6e756d626572223a2231222c22636861696e5f6964223a22626e62636861696e2d31303030222c226d656d6f223a22222c226d736773223a5b7b226964223a22423635363144434331303431333030353941374330384634384336343631304331463646393036342d3130222c226f7264657274797065223a322c227072696365223a3130303030303030302c227175616e74697479223a313230303030303030302c2273656e646572223a22626e63316b6574706d6e71736779637174786e7570723667636572707073306b6c797279687a36667a6c222c2273696465223a312c2273796d626f6c223a224254432d3543345f424e42222c2274696d65696e666f726365223a317d5d2c2273657175656e6365223a2239227d" 61 | const sig = crypto.generateSignature(msg, privateKey).toString("hex") 62 | expect(sig).toBe( 63 | "9c0421217ef92d556a14e3f442b07c85f6fc706dfcd8a72d6b58f05f96e95aa226b10f7cf62ccf7c9d5d953fa2c9ae80a1eacaf0c779d0253f1a34afd17eef34" 64 | ) 65 | }) 66 | 67 | it("verifySignature", () => { 68 | const publicKey = crypto.getPublicKeyFromPrivateKey(privateKey) 69 | const msg = 70 | "7b226163636f756e745f6e756d626572223a2231222c22636861696e5f6964223a22626e62636861696e2d31303030222c226d656d6f223a22222c226d736773223a5b7b226964223a22423635363144434331303431333030353941374330384634384336343631304331463646393036342d3130222c226f7264657274797065223a322c227072696365223a3130303030303030302c227175616e74697479223a313230303030303030302c2273656e646572223a22626e63316b6574706d6e71736779637174786e7570723667636572707073306b6c797279687a36667a6c222c2273696465223a312c2273796d626f6c223a224254432d3543345f424e42222c2274696d65696e666f726365223a317d5d2c2273657175656e6365223a2239227d" 71 | const sig = crypto.generateSignature(msg, privateKey).toString("hex") 72 | expect(crypto.verifySignature(sig, msg, publicKey)).toBeTruthy() 73 | }) 74 | 75 | it("generateSignature and verifySignature - utf8 memo", () => { 76 | const publicKey = crypto.getPublicKeyFromPrivateKey(privateKey) 77 | const msg = Buffer.from( 78 | '{"account_number":1,"data":"ABCD","chain_id":"bnbchain","memo":"smiley!☺","msgs":["msg"],"sequence":1,"source":1}' 79 | ).toString("hex") 80 | const sig = crypto.generateSignature(msg, privateKey).toString("hex") 81 | expect(crypto.verifySignature(sig, msg, publicKey)).toBeTruthy() 82 | }) 83 | }) 84 | -------------------------------------------------------------------------------- /src/client/stake/index.ts: -------------------------------------------------------------------------------- 1 | import Big from "big.js" 2 | 3 | import { BncClient } from "../" 4 | import * as crypto from "../../crypto" 5 | import { 6 | BscDelegateMsg, 7 | BaseMsg, 8 | BscUndelegateMsg, 9 | BscReDelegateMsg, 10 | } from "../../types" 11 | 12 | /** 13 | * Stake 14 | */ 15 | export class Stake { 16 | private _bncClient!: BncClient 17 | 18 | /** 19 | * @param {BncClient} bncClient 20 | */ 21 | constructor(bncClient: BncClient) { 22 | this._bncClient = bncClient 23 | } 24 | 25 | public async bscDelegate({ 26 | delegateAddress, 27 | validatorAddress, 28 | amount, 29 | symbol = "BNB", 30 | sideChainId = "chapel", //default value is ganges(testnet) 31 | }: { 32 | delegateAddress: string 33 | validatorAddress: string 34 | amount: number 35 | symbol?: string 36 | sideChainId?: string 37 | }) { 38 | if (!amount) { 39 | throw new Error("amount should not be empty") 40 | } 41 | 42 | if (!delegateAddress) { 43 | throw new Error("delegate address should not be null") 44 | } 45 | 46 | if (!crypto.checkAddress(validatorAddress, "bva")) { 47 | throw new Error("validator address is not valid") 48 | } 49 | 50 | amount = Number(new Big(amount).mul(Math.pow(10, 8)).toString()) 51 | 52 | const bscDelegateMsg = new BscDelegateMsg({ 53 | delegator_addr: delegateAddress, 54 | validator_addr: validatorAddress, 55 | delegation: { denom: symbol, amount }, 56 | side_chain_id: sideChainId, 57 | }) 58 | 59 | return await this.broadcast(bscDelegateMsg, delegateAddress) 60 | } 61 | 62 | public async bscUndelegate({ 63 | delegateAddress, 64 | validatorAddress, 65 | amount, 66 | symbol = "BNB", 67 | sideChainId = "chapel", //default value is ganges(testnet) 68 | }: { 69 | delegateAddress: string 70 | validatorAddress: string 71 | amount: number 72 | symbol?: string 73 | sideChainId?: string 74 | }) { 75 | if (!amount) { 76 | throw new Error("amount should not be empty") 77 | } 78 | 79 | if (!delegateAddress) { 80 | throw new Error("delegate address should not be null") 81 | } 82 | 83 | if (!crypto.checkAddress(validatorAddress, "bva")) { 84 | throw new Error("validator address is not valid") 85 | } 86 | 87 | amount = Number(new Big(amount).mul(Math.pow(10, 8)).toString()) 88 | 89 | const unDelegateMsg = new BscUndelegateMsg({ 90 | delegator_addr: delegateAddress, 91 | validator_addr: validatorAddress, 92 | amount: { denom: symbol, amount }, 93 | side_chain_id: sideChainId, 94 | }) 95 | 96 | return await this.broadcast(unDelegateMsg, delegateAddress) 97 | } 98 | 99 | public async bscReDelegate({ 100 | delegateAddress, 101 | validatorSrcAddress, 102 | validatorDstAddress, 103 | amount, 104 | symbol = "BNB", 105 | sideChainId = "chapel", //default value is ganges(testnet) 106 | }: { 107 | delegateAddress: string 108 | validatorSrcAddress: string 109 | validatorDstAddress: string 110 | amount: number 111 | symbol?: string 112 | sideChainId?: string 113 | }) { 114 | if (!amount) { 115 | throw new Error("amount should not be empty") 116 | } 117 | 118 | if (!delegateAddress) { 119 | throw new Error("delegate address should not be null") 120 | } 121 | 122 | if (!crypto.checkAddress(validatorSrcAddress, "bva")) { 123 | throw new Error("validator source address is not valid") 124 | } 125 | 126 | if (!crypto.checkAddress(validatorDstAddress, "bva")) { 127 | throw new Error("validator dest address is not valid") 128 | } 129 | 130 | amount = Number(new Big(amount).mul(Math.pow(10, 8)).toString()) 131 | 132 | const bscReDelegateMsg = new BscReDelegateMsg({ 133 | delegator_addr: delegateAddress, 134 | validator_src_addr: validatorSrcAddress, 135 | validator_dst_addr: validatorDstAddress, 136 | amount: { denom: symbol, amount }, 137 | side_chain_id: sideChainId, 138 | }) 139 | 140 | return await this.broadcast(bscReDelegateMsg, delegateAddress) 141 | } 142 | 143 | private async broadcast( 144 | msg: BaseMsg, 145 | fromAddress: string, 146 | sequence?: number 147 | ) { 148 | const signedTx = await this._bncClient._prepareTransaction( 149 | msg.getMsg(), 150 | msg.getSignMsg(), 151 | fromAddress, 152 | sequence 153 | ) 154 | return this._bncClient._broadcastDelegate(signedTx) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /__tests__/fixtures/cancelOrder.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "fromAddress": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", 4 | "symbol": "BCHSV.B-10F_BNB", 5 | "refid": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-29", 6 | "sequence": 33 7 | }, 8 | "signMsg": { 9 | "account_number": "34", 10 | "chain_id": "Binance-Chain-Ganges", 11 | "data": null, 12 | "memo": "", 13 | "msgs": [ 14 | { 15 | "refid": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-29", 16 | "sender": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", 17 | "symbol": "BCHSV.B-10F_BNB" 18 | } 19 | ], 20 | "sequence": "33", 21 | "source": "1" 22 | }, 23 | "signMsgBytes": "7b226163636f756e745f6e756d626572223a223334222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a22222c226d736773223a5b7b227265666964223a22424133364630464144373444384634313034353436334534373734463332384634414637373945352d3239222c2273656e646572223a2274626e623168676d3070376b68666b38357a707a3576306a38776e656a33613930773730397a7a6c666664222c2273796d626f6c223a2242434853562e422d3130465f424e42227d5d2c2273657175656e6365223a223333222c22736f75726365223a2231227d", 24 | "stdTx": { 25 | "msg": [ 26 | { 27 | "sender": [ 28 | 186, 29 | 54, 30 | 240, 31 | 250, 32 | 215, 33 | 77, 34 | 143, 35 | 65, 36 | 4, 37 | 84, 38 | 99, 39 | 228, 40 | 119, 41 | 79, 42 | 50, 43 | 143, 44 | 74, 45 | 247, 46 | 121, 47 | 229 48 | ], 49 | "symbol": "BCHSV.B-10F_BNB", 50 | "refid": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-29", 51 | "msgType": "CancelOrderMsg" 52 | } 53 | ], 54 | "signatures": [ 55 | { 56 | "pub_key": [ 57 | 235, 58 | 90, 59 | 233, 60 | 135, 61 | 33, 62 | 2, 63 | 151, 64 | 41, 65 | 165, 66 | 46, 67 | 78, 68 | 60, 69 | 43, 70 | 74, 71 | 78, 72 | 82, 73 | 170, 74 | 116, 75 | 3, 76 | 62, 77 | 237, 78 | 175, 79 | 139, 80 | 161, 81 | 223, 82 | 90, 83 | 182, 84 | 209, 85 | 245, 86 | 24, 87 | 253, 88 | 105, 89 | 230, 90 | 123, 91 | 189, 92 | 48, 93 | 155, 94 | 14 95 | ], //Buffer 96 | "signature": [ 97 | 217, 98 | 63, 99 | 176, 100 | 64, 101 | 43, 102 | 43, 103 | 48, 104 | 231, 105 | 234, 106 | 8, 107 | 225, 108 | 35, 109 | 187, 110 | 19, 111 | 154, 112 | 214, 113 | 139, 114 | 240, 115 | 161, 116 | 87, 117 | 127, 118 | 56, 119 | 89, 120 | 46, 121 | 178, 122 | 45, 123 | 17, 124 | 225, 125 | 39, 126 | 240, 127 | 155, 128 | 189, 129 | 51, 130 | 128, 131 | 242, 132 | 155, 133 | 75, 134 | 241, 135 | 91, 136 | 223, 137 | 169, 138 | 115, 139 | 69, 140 | 76, 141 | 92, 142 | 142, 143 | 212, 144 | 68, 145 | 242, 146 | 226, 147 | 86, 148 | 233, 149 | 86, 150 | 254, 151 | 152, 152 | 207, 153 | 210, 154 | 30, 155 | 136, 156 | 106, 157 | 148, 158 | 110, 159 | 33, 160 | 229 161 | ], //Buffer 162 | "account_number": 34, 163 | "sequence": 33 164 | } 165 | ], 166 | "memo": "", 167 | "source": 1, 168 | "data": "" 169 | }, 170 | "stdTxBytes": "d001f0625dee0a58166e681b0a14ba36f0fad74d8f41045463e4774f328f4af779e5120f42434853562e422d3130465f424e421a2b424133364630464144373444384634313034353436334534373734463332384634414637373945352d3239126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e1240d93fb0402b2b30e7ea08e123bb139ad68bf0a1577f38592eb22d11e127f09bbd3380f29b4bf15bdfa973454c5c8ed444f2e256e956fe98cfd21e886a946e21e5182220212001" 171 | } 172 | -------------------------------------------------------------------------------- /docs/api-docs/classes/ledgerapp.md: -------------------------------------------------------------------------------- 1 | 2 | # Class: LedgerApp 3 | 4 | Ledger app interface. 5 | 6 | **`static`** 7 | 8 | ## Hierarchy 9 | 10 | * **LedgerApp** 11 | 12 | ## Index 13 | 14 | ### Constructors 15 | 16 | * [constructor](ledgerapp.md#constructor) 17 | 18 | ### Methods 19 | 20 | * [getPublicKey](ledgerapp.md#getpublickey) 21 | * [getVersion](ledgerapp.md#getversion) 22 | * [publicKeySecp256k1](ledgerapp.md#publickeysecp256k1) 23 | * [showAddress](ledgerapp.md#showaddress) 24 | * [sign](ledgerapp.md#sign) 25 | * [signSecp256k1](ledgerapp.md#signsecp256k1) 26 | 27 | ## Constructors 28 | 29 | ### constructor 30 | 31 | \+ **new LedgerApp**(`transport`: Transport, `interactiveTimeout`: number, `nonInteractiveTimeout`: number): *[LedgerApp](ledgerapp.md)* 32 | 33 | Constructs a new LedgerApp. 34 | 35 | **Parameters:** 36 | 37 | Name | Type | Default | Description | 38 | ------ | ------ | ------ | ------ | 39 | `transport` | Transport | - | Ledger Transport, a subclass of ledgerjs Transport. | 40 | `interactiveTimeout` | number | DEFAULT_LEDGER_INTERACTIVE_TIMEOUT | The interactive (user input) timeout in ms. Default 45s. | 41 | `nonInteractiveTimeout` | number | DEFAULT_LEDGER_NONINTERACTIVE_TIMEOUT | The non-interactive timeout in ms. Default 3s. | 42 | 43 | **Returns:** *[LedgerApp](ledgerapp.md)* 44 | 45 | ## Methods 46 | 47 | ### getPublicKey 48 | 49 | ▸ **getPublicKey**(`hdPath`: number[]): *Promise‹PublicKey›* 50 | 51 | Gets the public key from the Ledger app that is currently open on the device. 52 | 53 | **`throws`** Will throw Error if a transport error occurs, or if the firmware app is not open. 54 | 55 | **Parameters:** 56 | 57 | Name | Type | Description | 58 | ------ | ------ | ------ | 59 | `hdPath` | number[] | The HD path to use to get the public key. Default is [44, 714, 0, 0, 0] | 60 | 61 | **Returns:** *Promise‹PublicKey›* 62 | 63 | ___ 64 | 65 | ### getVersion 66 | 67 | ▸ **getVersion**(): *Promise‹Version›* 68 | 69 | Gets the version of the Ledger app that is currently open on the device. 70 | 71 | **`throws`** Will throw Error if a transport error occurs, or if the firmware app is not open. 72 | 73 | **Returns:** *Promise‹Version›* 74 | 75 | ___ 76 | 77 | ### publicKeySecp256k1 78 | 79 | ▸ **publicKeySecp256k1**(`hdPath`: number[]): *Promise‹PublicKey›* 80 | 81 | Gets the public key from the Ledger app that is currently open on the device. 82 | 83 | **`throws`** Will throw Error if a transport error occurs, or if the firmware app is not open. 84 | 85 | **Parameters:** 86 | 87 | Name | Type | Default | Description | 88 | ------ | ------ | ------ | ------ | 89 | `hdPath` | number[] | [44, 714, 0, 0, 0] | The HD path to use to get the public key. Default is [44, 714, 0, 0, 0] | 90 | 91 | **Returns:** *Promise‹PublicKey›* 92 | 93 | ___ 94 | 95 | ### showAddress 96 | 97 | ▸ **showAddress**(`hrp`: string, `hdPath`: number[]): *Promise‹ReturnResponse›* 98 | 99 | Shows the user's address for the given HD path on the device display. 100 | 101 | **`throws`** Will throw Error if a transport error occurs, or if the firmware app is not open. 102 | 103 | **Parameters:** 104 | 105 | Name | Type | Default | Description | 106 | ------ | ------ | ------ | ------ | 107 | `hrp` | string | "bnb" | The bech32 human-readable prefix | 108 | `hdPath` | number[] | [44, 714, 0, 0, 0] | The HD path to use to get the public key. Default is [44, 714, 0, 0, 0] | 109 | 110 | **Returns:** *Promise‹ReturnResponse›* 111 | 112 | ___ 113 | 114 | ### sign 115 | 116 | ▸ **sign**(`signBytes`: Buffer, `hdPath`: number[]): *Promise‹SignedSignature›* 117 | 118 | Sends a transaction sign doc to the Ledger app to be signed. 119 | 120 | **`throws`** Will throw Error if a transport error occurs, or if the firmware app is not open. 121 | 122 | **Parameters:** 123 | 124 | Name | Type | Description | 125 | ------ | ------ | ------ | 126 | `signBytes` | Buffer | The TX sign doc bytes to sign | 127 | `hdPath` | number[] | The HD path to use to get the public key. Default is [44, 714, 0, 0, 0] | 128 | 129 | **Returns:** *Promise‹SignedSignature›* 130 | 131 | ___ 132 | 133 | ### signSecp256k1 134 | 135 | ▸ **signSecp256k1**(`signBytes`: Buffer, `hdPath`: number[]): *Promise‹SignedSignature›* 136 | 137 | Sends a transaction sign doc to the Ledger app to be signed. 138 | 139 | **`throws`** Will throw Error if a transport error occurs, or if the firmware app is not open. 140 | 141 | **Parameters:** 142 | 143 | Name | Type | Default | Description | 144 | ------ | ------ | ------ | ------ | 145 | `signBytes` | Buffer | - | The TX sign doc bytes to sign | 146 | `hdPath` | number[] | [44, 714, 0, 0, 0] | The HD path to use to get the public key. Default is [44, 714, 0, 0, 0] | 147 | 148 | **Returns:** *Promise‹SignedSignature›* 149 | -------------------------------------------------------------------------------- /__tests__/fixtures/placeOrder.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "address": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", 4 | "symbol": "ADA.B-B63_BNB", 5 | "side": 1, 6 | "price": 1, 7 | "quantity": 1, 8 | "sequence": 32 9 | }, 10 | "signMsg": { 11 | "account_number": "34", 12 | "chain_id": "Binance-Chain-Ganges", 13 | "data": null, 14 | "memo": "", 15 | "msgs": [ 16 | { 17 | "id": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-33", 18 | "ordertype": 2, 19 | "price": 100000000, 20 | "quantity": 100000000, 21 | "sender": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", 22 | "side": 1, 23 | "symbol": "ADA.B-B63_BNB", 24 | "timeinforce": 1 25 | } 26 | ], 27 | "sequence": "32", 28 | "source": "1" 29 | }, 30 | "signMsgBytes": "7b226163636f756e745f6e756d626572223a223334222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a22222c226d736773223a5b7b226964223a22424133364630464144373444384634313034353436334534373734463332384634414637373945352d3333222c226f7264657274797065223a322c227072696365223a3130303030303030302c227175616e74697479223a3130303030303030302c2273656e646572223a2274626e623168676d3070376b68666b38357a707a3576306a38776e656a33613930773730397a7a6c666664222c2273696465223a312c2273796d626f6c223a224144412e422d4236335f424e42222c2274696d65696e666f726365223a317d5d2c2273657175656e6365223a223332222c22736f75726365223a2231227d", 31 | "stdTx": { 32 | "msg": [ 33 | { 34 | "sender": [ 35 | 186, 36 | 54, 37 | 240, 38 | 250, 39 | 215, 40 | 77, 41 | 143, 42 | 65, 43 | 4, 44 | 84, 45 | 99, 46 | 228, 47 | 119, 48 | 79, 49 | 50, 50 | 143, 51 | 74, 52 | 247, 53 | 121, 54 | 229 55 | ], //Buffer 56 | "id": "BA36F0FAD74D8F41045463E4774F328F4AF779E5-33", 57 | "symbol": "ADA.B-B63_BNB", 58 | "ordertype": 2, 59 | "side": 1, 60 | "price": 100000000, 61 | "quantity": 100000000, 62 | "timeinforce": 1, 63 | "msgType": "NewOrderMsg" 64 | } 65 | ], 66 | "signatures": [ 67 | { 68 | "pub_key": [ 69 | 235, 70 | 90, 71 | 233, 72 | 135, 73 | 33, 74 | 2, 75 | 151, 76 | 41, 77 | 165, 78 | 46, 79 | 78, 80 | 60, 81 | 43, 82 | 74, 83 | 78, 84 | 82, 85 | 170, 86 | 116, 87 | 3, 88 | 62, 89 | 237, 90 | 175, 91 | 139, 92 | 161, 93 | 223, 94 | 90, 95 | 182, 96 | 209, 97 | 245, 98 | 24, 99 | 253, 100 | 105, 101 | 230, 102 | 123, 103 | 189, 104 | 48, 105 | 155, 106 | 14 107 | ], //Buffer 108 | "signature": [ 109 | 133, 110 | 31, 111 | 201, 112 | 84, 113 | 35, 114 | 66, 115 | 50, 116 | 26, 117 | 246, 118 | 62, 119 | 203, 120 | 186, 121 | 125, 122 | 62, 123 | 206, 124 | 84, 125 | 95, 126 | 42, 127 | 66, 128 | 186, 129 | 208, 130 | 27, 131 | 163, 132 | 44, 133 | 255, 134 | 85, 135 | 53, 136 | 177, 137 | 142, 138 | 84, 139 | 182, 140 | 211, 141 | 16, 142 | 110, 143 | 16, 144 | 182, 145 | 164, 146 | 82, 147 | 89, 148 | 147, 149 | 209, 150 | 133, 151 | 161, 152 | 68, 153 | 61, 154 | 154, 155 | 18, 156 | 81, 157 | 134, 158 | 150, 159 | 14, 160 | 2, 161 | 142, 162 | 171, 163 | 253, 164 | 216, 165 | 215, 166 | 108, 167 | 247, 168 | 10, 169 | 58, 170 | 126, 171 | 49, 172 | 0 173 | ], //Buffer 174 | "account_number": 34, 175 | "sequence": 32 176 | } 177 | ], 178 | "memo": "", 179 | "source": 1, 180 | "data": "" 181 | }, 182 | "stdTxBytes": "de01f0625dee0a66ce6dc0430a14ba36f0fad74d8f41045463e4774f328f4af779e5122b424133364630464144373444384634313034353436334534373734463332384634414637373945352d33331a0d4144412e422d4236335f424e42200428023080c2d72f3880c2d72f4002126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e1240851fc9542342321af63ecbba7d3ece545f2a42bad01ba32cff5535b18e54b6d3106e10b6a4525993d185a1443d9a125186960e028eabfdd8d76cf70a3a7e3100182220202001" 183 | } 184 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bnb-chain/javascript-sdk", 3 | "version": "4.4.3", 4 | "license": "Apache-2.0", 5 | "main": "lib/index.js", 6 | "typings": "lib/index.d.ts", 7 | "files": [ 8 | "lib/*" 9 | ], 10 | "scripts": { 11 | "test": "cross-env NODE_ENV=test jest", 12 | "test:ledger": "qunit --require babel-polyfiltypl tests-ledger/tests.js", 13 | "test:ledger:browser": "./tests-ledger/run-browser-tests.sh", 14 | "build": "rimraf lib && tsc --emitDeclarationOnly && babel --extensions '.ts' src -d lib", 15 | "build:docs": "rimraf docs/api-docs && typedoc --excludeExternals --excludeNotDocumented --readme none --out docs/api-docs --hideBreadcrumbs --disableSources", 16 | "prepublishOnly": "npm run build", 17 | "clean": "rimraf lib", 18 | "lint": "eslint '**/*.{ts,js}'", 19 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css,json,md,html,yml}\"" 20 | }, 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "dependencies": { 25 | "@babel/runtime": "^7.10.4", 26 | "@ledgerhq/hw-transport-u2f": "^5.9.0", 27 | "@ledgerhq/hw-transport-web-ble": "^5.9.0", 28 | "@types/bech32": "^1.1.2", 29 | "@types/big.js": "^4.0.5", 30 | "@types/bn.js": "^4.11.6", 31 | "@types/crypto-js": "^3.1.43", 32 | "@types/jest": "^25.1.4", 33 | "@types/ledgerhq__hw-transport": "^4.21.2", 34 | "@types/lodash": "^4.14.149", 35 | "@types/node": "^13.9.0", 36 | "@types/pumpify": "^1.4.1", 37 | "@types/tiny-secp256k1": "^1.0.0", 38 | "@types/uuid": "^7.0.0", 39 | "axios": "^0.24.0", 40 | "bech32": "^1.1.3", 41 | "big.js": "^5.2.2", 42 | "bip32": "^2.0.5", 43 | "bip39": "^3.0.2", 44 | "bn.js": "^4.11.8", 45 | "camelcase": "^5.3.1", 46 | "crypto-browserify": "^3.12.0", 47 | "crypto-js": "^4.2.0", 48 | "elliptic": "^6.6.1", 49 | "eslint-utils": "^1.4.2", 50 | "events": "^3.0.0", 51 | "is-it-check": "^1.0.12", 52 | "lodash": "^4.17.21", 53 | "minimist": "^1.2.5", 54 | "ndjson": "^1.5.0", 55 | "protocol-buffers-encodings": "^1.1.0", 56 | "pumpify": "^2.0.1", 57 | "secure-random": "^1.1.2", 58 | "tiny-secp256k1": "^1.1.3", 59 | "url": "^0.11.0", 60 | "uuid": "^3.3.2", 61 | "websocket-stream": "^5.5.0" 62 | }, 63 | "optionalDependencies": { 64 | "@ledgerhq/hw-transport-node-hid": "^5.10.0" 65 | }, 66 | "devDependencies": { 67 | "@babel/cli": "^7.10.4", 68 | "@babel/core": "^7.10.4", 69 | "@babel/plugin-proposal-class-properties": "^7.10.4", 70 | "@babel/plugin-transform-async-to-generator": "^7.10.4", 71 | "@babel/plugin-transform-runtime": "^7.10.4", 72 | "@babel/preset-env": "^7.10.4", 73 | "@babel/preset-typescript": "^7.10.4", 74 | "@types/bech32": "^1.1.2", 75 | "@types/big.js": "^4.0.5", 76 | "@types/bn.js": "^4.11.6", 77 | "@types/crypto-js": "^3.1.43", 78 | "@types/elliptic": "^6.4.18", 79 | "@types/jest": "^25.1.4", 80 | "@types/ledgerhq__hw-transport": "^4.21.2", 81 | "@types/lodash": "^4.14.149", 82 | "@types/node": "^13.9.0", 83 | "@types/pumpify": "^1.4.1", 84 | "@types/tiny-secp256k1": "^1.0.0", 85 | "@types/uuid": "^7.0.0", 86 | "@typescript-eslint/eslint-plugin": "^3.0.0", 87 | "@typescript-eslint/parser": "^3.1.0", 88 | "babel-core": "^7.0.0-0", 89 | "babel-eslint": "^10.0.2", 90 | "babel-jest": "^23.6.0", 91 | "babel-plugin-external-helpers": "^6.22.0", 92 | "babel-polyfill": "^6.26.0", 93 | "babel-preset-latest": "^6.24.1", 94 | "babel-preset-stage-2": "^6.24.1", 95 | "browserify": "^16.2.3", 96 | "buffer": "^5.2.1", 97 | "bufferutil": "^4.0.1", 98 | "builtin-modules": "^3.1.0", 99 | "cross-env": "^5.2.0", 100 | "eslint": "^7.1.0", 101 | "eslint-config-prettier": "^6.11.0", 102 | "eslint-plugin-import": "^2.20.2", 103 | "eslint-plugin-prettier": "^3.1.3", 104 | "http-server": "^0.11.1", 105 | "husky": "^4.2.5", 106 | "jest": "^27.4.5", 107 | "prettier": "^2.0.4", 108 | "qunit": "^2.9.2", 109 | "rimraf": "^3.0.2", 110 | "ts-jest": "^27.0.0", 111 | "typedoc": "^0.17.8", 112 | "typedoc-plugin-markdown": "^2.3.1", 113 | "typescript": "^3.9.3", 114 | "utf-8-validate": "^5.0.2" 115 | }, 116 | "resolutions": { 117 | "**/**/handlebars": "^4.1.2", 118 | "**/**/ecstatic": "^4.1.4", 119 | "**/**/mem": "^4.0.0", 120 | "**/**/minimist": "^1.2.6", 121 | "**/glob-parent": "^5.1.1", 122 | "**/qs": "^6.0.4", 123 | "**/braces": "^2.3.1", 124 | "**/ws": "^8.17.1", 125 | "**/node-notifier": "^8.0.1", 126 | "**/shell-quote": "^1.7.3", 127 | "**/decode-uri-component": "^0.2.2", 128 | "**/minimatch": "^3.0.5", 129 | "**/json5": "^1.0.2", 130 | "**/marked": "^4.0.10", 131 | "**/shelljs": "^0.8.5", 132 | "**/async": "^2.6.4", 133 | "**/cached-path-relative": "^1.1.0", 134 | "**/follow-redirects": "^1.14.7", 135 | "**/ajv": "^6.12.3", 136 | "**/semver-regex": "^3.1.4", 137 | "**/@babel/traverse": "^7.23.2", 138 | "**/browserify-sign": "^4.2.2", 139 | "**/elliptic": "^6.6.1" 140 | }, 141 | "husky": { 142 | "hooks": { 143 | "pre-commit": "yarn lint --quiet && yarn build:docs" 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/client/swap/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module Swap 3 | */ 4 | 5 | import { Buffer } from "buffer" 6 | 7 | import { BncClient } from ".." 8 | import * as crypto from "../../crypto" 9 | import { Coin, AminoPrefix } from "../../types" 10 | import { checkCoins } from "../../utils/validateHelper" 11 | 12 | class Swap { 13 | static instance: Swap 14 | private _bncClient!: BncClient 15 | 16 | /** 17 | * @param {Object} bncClient 18 | */ 19 | constructor(bncClient: BncClient) { 20 | if (!Swap.instance) { 21 | this._bncClient = bncClient 22 | Swap.instance = this 23 | } 24 | 25 | return Swap.instance 26 | } 27 | 28 | /** 29 | * HTLT(Hash timer locked transfer, create an atomic swap) 30 | * @param {String} from 31 | * @param {String} recipient 32 | * @param {String} recipientOtherChain 33 | * @param {String} senderOtherChain 34 | * @param {String} randomNumberHash 35 | * @param {Number} timestamp 36 | * @param {Array} amount 37 | * @param {String} expectedIncome 38 | * @param {Number} heightSpan 39 | * @param {boolean} crossChain 40 | * @returns {Promise} resolves with response (success or fail) 41 | */ 42 | async HTLT( 43 | from: string, 44 | recipient: string, 45 | recipientOtherChain: string, 46 | senderOtherChain: string, 47 | randomNumberHash: string, 48 | timestamp: number, 49 | amount: Coin[], 50 | expectedIncome: string, 51 | heightSpan: number, 52 | crossChain: boolean 53 | ) { 54 | checkCoins(amount) 55 | const htltMsg = { 56 | from: crypto.decodeAddress(from), 57 | to: crypto.decodeAddress(recipient), 58 | recipient_other_chain: recipientOtherChain, 59 | sender_other_chain: senderOtherChain, 60 | random_number_hash: Buffer.from(randomNumberHash, "hex"), 61 | timestamp: timestamp, 62 | amount: amount, 63 | expected_income: expectedIncome, 64 | height_span: heightSpan, 65 | cross_chain: crossChain, 66 | aminoPrefix: AminoPrefix.HTLTMsg, 67 | } 68 | 69 | const signHTLTMsg = { 70 | from: from, 71 | to: recipient, 72 | recipient_other_chain: recipientOtherChain, 73 | sender_other_chain: senderOtherChain, 74 | random_number_hash: randomNumberHash, 75 | timestamp: timestamp, 76 | amount: amount, 77 | expected_income: expectedIncome, 78 | height_span: heightSpan, 79 | cross_chain: crossChain, 80 | } 81 | 82 | const signedTx = await this._bncClient._prepareTransaction( 83 | htltMsg, 84 | signHTLTMsg, 85 | from 86 | ) 87 | return this._bncClient._broadcastDelegate(signedTx) 88 | } 89 | 90 | /** 91 | * depositHTLT(deposit assets to an existing swap) 92 | * @param {String} from 93 | * @param {String} swapID 94 | * @param {Array} amount 95 | * @returns {Promise} resolves with response (success or fail) 96 | */ 97 | async depositHTLT(from: string, swapID: string, amount: Coin[]) { 98 | checkCoins(amount) 99 | const depositHTLTMsg = { 100 | from: crypto.decodeAddress(from), 101 | amount: amount, 102 | swap_id: Buffer.from(swapID, "hex"), 103 | aminoPrefix: AminoPrefix.DepositHTLTMsg, 104 | } 105 | 106 | const signDepositHTLTMsg = { 107 | from: from, 108 | amount: amount, 109 | swap_id: swapID, 110 | } 111 | 112 | const signedTx = await this._bncClient._prepareTransaction( 113 | depositHTLTMsg, 114 | signDepositHTLTMsg, 115 | from 116 | ) 117 | return this._bncClient._broadcastDelegate(signedTx) 118 | } 119 | 120 | /** 121 | * claimHTLT(claim assets from an swap) 122 | * @param {String} from 123 | * @param {String} swapID 124 | * @param {String} randomNumber 125 | * @returns {Promise} resolves with response (success or fail) 126 | */ 127 | async claimHTLT(from: string, swapID: string, randomNumber: string) { 128 | const claimHTLTMsg = { 129 | from: crypto.decodeAddress(from), 130 | swap_id: Buffer.from(swapID, "hex"), 131 | random_number: Buffer.from(randomNumber, "hex"), 132 | aminoPrefix: AminoPrefix.ClaimHTLTMsg, 133 | } 134 | 135 | const signClaimHTLTMsg = { 136 | from: from, 137 | swap_id: swapID, 138 | random_number: randomNumber, 139 | } 140 | 141 | const signedTx = await this._bncClient._prepareTransaction( 142 | claimHTLTMsg, 143 | signClaimHTLTMsg, 144 | from 145 | ) 146 | return this._bncClient._broadcastDelegate(signedTx) 147 | } 148 | 149 | /** 150 | * refundHTLT(refund assets from an swap) 151 | * @param {String} from 152 | * @param {String} swapID 153 | * @returns {Promise} resolves with response (success or fail) 154 | */ 155 | async refundHTLT(from: string, swapID: string) { 156 | const refundHTLTMsg = { 157 | from: crypto.decodeAddress(from), 158 | swap_id: Buffer.from(swapID, "hex"), 159 | aminoPrefix: AminoPrefix.RefundHTLTMsg, 160 | } 161 | 162 | const signRefundHTLTMsg = { 163 | from: from, 164 | swap_id: swapID, 165 | } 166 | 167 | const signedTx = await this._bncClient._prepareTransaction( 168 | refundHTLTMsg, 169 | signRefundHTLTMsg, 170 | from 171 | ) 172 | return this._bncClient._broadcastDelegate(signedTx) 173 | } 174 | } 175 | 176 | export default Swap 177 | -------------------------------------------------------------------------------- /__tests__/dex.test.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from "../src/crypto" 2 | 3 | import { getClient, address, targetAddress, wait } from "./utils" 4 | 5 | describe("dex", () => { 6 | beforeEach(() => { 7 | jest.setTimeout(50000) 8 | }) 9 | 10 | it("transfer placeOrder cancelOrder only", async () => { 11 | const symbol = "BNB_USDT.B-B7C" 12 | const client = await getClient(true) 13 | const addr = address 14 | const accCode = crypto.decodeAddress(addr) 15 | const account = await client._httpClient.request( 16 | "get", 17 | `/api/v1/account/${addr}` 18 | ) 19 | 20 | const sequence = account.result && account.result.sequence 21 | const res = await client.transfer( 22 | addr, 23 | targetAddress, 24 | 0.00000001, 25 | "BNB", 26 | "hello world", 27 | sequence 28 | ) 29 | expect(res.status).toBe(200) 30 | 31 | await wait(3000) 32 | 33 | // acc needs .004 BNB to lock 34 | const res1 = await client.placeOrder(addr, symbol, 2, 40, 0.1, sequence + 1) 35 | expect(res1.status).toBe(200) 36 | 37 | await wait(5000) 38 | 39 | const orderId = `${accCode.toString("hex")}-${sequence + 2}`.toUpperCase() 40 | const res2 = await client.cancelOrder(addr, symbol, orderId, sequence + 2) 41 | 42 | expect(res2.status).toBe(200) 43 | }) 44 | 45 | it("transfer with presicion", async () => { 46 | jest.setTimeout(30000) 47 | 48 | const coin = "BNB" 49 | const amount = 2.00177011 50 | const client = await getClient(false) 51 | const addr = crypto.getAddressFromPrivateKey(client.privateKey) 52 | const account = await client._httpClient.request( 53 | "get", 54 | `/api/v1/account/${addr}` 55 | ) 56 | const sequence = account.result && account.result.sequence 57 | const res = await client.transfer( 58 | addr, 59 | targetAddress, 60 | amount, 61 | coin, 62 | "hello world", 63 | sequence 64 | ) 65 | expect(res.status).toBe(200) 66 | 67 | try { 68 | const hash = res.result[0].hash 69 | const res2 = await client._httpClient.get( 70 | `/api/v1/tx/${hash}?format=json` 71 | ) 72 | const sendAmount = 73 | res2.result.tx.value.msg[0].value.inputs[0].coins[0].amount 74 | expect(sendAmount).toBe(200177011) 75 | } catch (err) { 76 | // 77 | } 78 | }) 79 | 80 | it("transfer placeOrder cancelOrder (no await on set privkey)", async () => { 81 | jest.setTimeout(25000) 82 | const symbol = "BNB_USDT.B-B7C" 83 | const client = await getClient(false) 84 | const addr = crypto.getAddressFromPrivateKey(client.privateKey) 85 | 86 | // acc needs .004 BNB to lock 87 | // IOC - auto cancels 88 | const res1 = await client.placeOrder(addr, symbol, 2, 40, 0.01, null, 3) 89 | expect(res1.status).toBe(200) 90 | }) 91 | 92 | it("check number when transfer", async () => { 93 | const client = await getClient(true) 94 | const addr = crypto.getAddressFromPrivateKey(client.privateKey) 95 | 96 | const account = await client._httpClient.request( 97 | "get", 98 | `/api/v1/account/${addr}` 99 | ) 100 | const sequence = account.result && account.result.sequence 101 | 102 | try { 103 | await client.transfer( 104 | addr, 105 | targetAddress, 106 | -1, 107 | "BNB", 108 | "hello world", 109 | sequence 110 | ) 111 | } catch (err) { 112 | expect(err.message).toBe("amount should be a positive number") 113 | } 114 | 115 | try { 116 | await client.transfer( 117 | addr, 118 | targetAddress, 119 | Math.pow(2, 63), 120 | "BNB", 121 | "hello world", 122 | sequence 123 | ) 124 | } catch (err) { 125 | expect(err.message).toBe("amount should be less than 2^63") 126 | } 127 | }) 128 | 129 | it("check number when place order", async () => { 130 | const symbol = "BNB_USDT.B-B7C" 131 | const client = await getClient(true) 132 | const addr = crypto.getAddressFromPrivateKey(client.privateKey) 133 | 134 | try { 135 | await client.placeOrder(addr, symbol, 2, -40, 0.0001, 1) 136 | } catch (err) { 137 | expect(err.message).toBe("price should be a positive number") 138 | } 139 | 140 | try { 141 | await client.placeOrder(addr, symbol, 2, Math.pow(2, 63), 2, 1) 142 | } catch (err) { 143 | expect(err.message).toBe("price should be less than 2^63") 144 | } 145 | }) 146 | 147 | it("multiSend", async () => { 148 | const client = await getClient(true) 149 | const addr = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd" 150 | const transfers = [ 151 | { 152 | to: "tbnb1p4kpnj5qz5spsaf0d2555h6ctngse0me5q57qe", 153 | coins: [ 154 | { 155 | denom: "BNB", 156 | amount: 0.01, 157 | }, 158 | { 159 | denom: "USDT.B-B7C", 160 | amount: 0.01, 161 | }, 162 | ], 163 | }, 164 | { 165 | to: "tbnb1scjj8chhhp7lngdeflltzex22yaf9ep59ls4gk", 166 | coins: [ 167 | { 168 | denom: "USDT.B-B7C", 169 | amount: 0.02, 170 | }, 171 | { 172 | denom: "BNB", 173 | amount: 0.3, 174 | }, 175 | ], 176 | }, 177 | ] 178 | 179 | const { status } = await client.multiSend(addr, transfers) 180 | expect(status).toBe(200) 181 | }) 182 | }) 183 | -------------------------------------------------------------------------------- /src/tx/index.ts: -------------------------------------------------------------------------------- 1 | import { curve } from "elliptic" 2 | 3 | import { 4 | convertObjectToSignBytes, 5 | UVarInt, 6 | marshalBinary, 7 | encodeBinaryByteArray, 8 | } from "../amino" 9 | import * as crypto from "../crypto" 10 | import { 11 | BaseMsg, 12 | SignMsg, 13 | StdSignMsg, 14 | StdSignature, 15 | StdTx, 16 | AminoPrefix, 17 | } from "../types" 18 | 19 | /** 20 | * Creates a new transaction object. 21 | * @example 22 | * var rawTx = { 23 | * accountNumber: 1, 24 | * chainId: 'bnbchain-1000', 25 | * memo: '', 26 | * msg: {}, 27 | * type: 'NewOrderMsg', 28 | * sequence: 29, 29 | * source: 0 30 | * }; 31 | * var tx = new Transaction(rawTx); 32 | * @property {Buffer} raw The raw vstruct encoded transaction 33 | * @param {Number} data.account_number account number 34 | * @param {String} data.chain_id bnbChain Id 35 | * @param {String} data.memo transaction memo 36 | * @param {String} type transaction type 37 | * @param {Msg} data.msg object data of tx type 38 | * @param {Number} data.sequence transaction counts 39 | * @param {Number} data.source where does this transaction come from 40 | */ 41 | export default class Transaction { 42 | private sequence: NonNullable 43 | private accountNumber: NonNullable 44 | private chainId: StdSignMsg["chainId"] 45 | 46 | // DEPRECATED: Retained for backward compatibility, 47 | private msg?: any 48 | 49 | private baseMsg?: NonNullable 50 | private memo: StdSignMsg["memo"] 51 | private source: NonNullable 52 | private signatures: Array 53 | 54 | constructor(data: StdSignMsg) { 55 | data = data || {} 56 | if (!data.chainId) { 57 | throw new Error("chain id should not be null") 58 | } 59 | 60 | this.sequence = data.sequence || 0 61 | this.accountNumber = data.accountNumber || 0 62 | this.chainId = data.chainId 63 | this.msg = data.msg 64 | this.baseMsg = data.baseMsg 65 | this.memo = data.memo 66 | this.source = data.source || 0 // default value is 0 67 | this.signatures = [] 68 | } 69 | 70 | /** 71 | * generate the sign bytes for a transaction, given a msg 72 | * @param {SignMsg} concrete msg object 73 | * @return {Buffer} 74 | **/ 75 | getSignBytes(msg?: SignMsg): Buffer { 76 | msg = msg || (this.baseMsg && this.baseMsg.getSignMsg()) 77 | const signMsg = { 78 | account_number: this.accountNumber.toString(), 79 | chain_id: this.chainId, 80 | data: null, 81 | memo: this.memo, 82 | msgs: [msg], 83 | sequence: this.sequence.toString(), 84 | source: this.source.toString(), 85 | } 86 | return convertObjectToSignBytes(signMsg) 87 | } 88 | 89 | /** 90 | * attaches a signature to the transaction 91 | * @param {Elliptic.PublicKey} pubKey 92 | * @param {Buffer} signature 93 | * @return {Transaction} 94 | **/ 95 | addSignature(pubKey: curve.base.BasePoint, signature: Buffer): Transaction { 96 | const pubKeyBuf = this._serializePubKey(pubKey) // => Buffer 97 | this.signatures = [ 98 | { 99 | pub_key: pubKeyBuf, 100 | signature: signature, 101 | account_number: this.accountNumber, 102 | sequence: this.sequence, 103 | }, 104 | ] 105 | return this 106 | } 107 | 108 | /** 109 | * sign transaction with a given private key and msg 110 | * @param {string} privateKey private key hex string 111 | * @param {SignMsg} concrete msg object 112 | * @return {Transaction} 113 | **/ 114 | sign(privateKey: string, msg?: SignMsg): Transaction { 115 | if (!privateKey) { 116 | throw new Error("private key should not be null") 117 | } 118 | 119 | const signBytes = this.getSignBytes(msg) 120 | const privKeyBuf = Buffer.from(privateKey, "hex") 121 | const signature = crypto.generateSignature( 122 | signBytes.toString("hex"), 123 | privKeyBuf 124 | ) 125 | this.addSignature(crypto.generatePubKey(privKeyBuf), signature) 126 | return this 127 | } 128 | 129 | /** 130 | * encode signed transaction to hex which is compatible with amino 131 | */ 132 | serialize(): string { 133 | if (!this.signatures) { 134 | throw new Error("need signature") 135 | } 136 | const msg = this.msg || (this.baseMsg && this.baseMsg.getMsg()) 137 | const stdTx: StdTx = { 138 | msg: [msg], 139 | signatures: this.signatures, 140 | memo: this.memo, 141 | source: this.source, // sdk value is 0, web wallet value is 1 142 | data: "", 143 | aminoPrefix: AminoPrefix.StdTx, 144 | } 145 | const bytes = marshalBinary(stdTx) 146 | return bytes.toString("hex") 147 | } 148 | 149 | /** 150 | * serializes a public key in a 33-byte compressed format. 151 | * @param {Elliptic.PublicKey} unencodedPubKey 152 | * @return {Buffer} 153 | */ 154 | private _serializePubKey(unencodedPubKey: curve.base.BasePoint) { 155 | let format = 0x2 156 | const y = unencodedPubKey.getY() 157 | const x = unencodedPubKey.getX() 158 | if (y && y.isOdd()) { 159 | format |= 0x1 160 | } 161 | let pubBz = Buffer.concat([ 162 | UVarInt.encode(format), 163 | x.toArrayLike(Buffer, "be", 32), 164 | ]) 165 | 166 | // prefixed with length 167 | pubBz = encodeBinaryByteArray(pubBz) 168 | // add the amino prefix 169 | pubBz = Buffer.concat([Buffer.from("EB5AE987", "hex"), pubBz]) 170 | return pubBz 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Terms 2 | 3 | - **Account** refers to a private/public keypair with a corresponding address. 4 | The address is derived from a hardcoded derivation path of `44'/714'/0'/0/0` 5 | from the seed. 6 | - **Keystore** refers to an Ethereum-like encrypted keystore JSON blob 7 | containing the seed. 8 | - **Mnemonic** refers to a phrase of words that can be remembered and used to 9 | recover the seed. 10 | 11 | ## Creating a Client 12 | 13 | ```js 14 | //common 15 | const { BncClient } = require("@bnb-chain/javascript-sdk") 16 | 17 | //es6 18 | import { BncClient } from "@bnb-chain/javascript-sdk" 19 | 20 | const client = new BncClient("https://xxx.api.com/") 21 | client.initChain() 22 | ``` 23 | 24 | ## Creating an Account 25 | 26 | ```js 27 | client.createAccount() 28 | 29 | client.createAccountWithKeystore([password]) 30 | 31 | client.createAccountWithMnemonic() 32 | ``` 33 | 34 | ### Parameters 35 | 36 | - password - **String** 37 | 38 | ### Returns 39 | 40 | - Object - The account object with the following structure: 41 | 42 | - address - **String**: The account address. 43 | 44 | - privateKey - **String**: The accounts private key. This should never be 45 | shared or stored unencrypted in localstorage! Also make sure to null the 46 | memory after usage. 47 | 48 | - keystore - **Object**: The encrypted keystore JSON 49 | 50 | - mnemonic - **String**: mnemonic sentence -- a group of easy to remember 51 | words that can be used to recover the seed. 52 | 53 | ## Recover an Account from Keystore or Mnemonic 54 | 55 | ```js 56 | client.recoverAccountFromKeystore(keystore, password) 57 | 58 | client.recoverAccountFromMnemonic(mnemonic) 59 | 60 | client.recoverAccountFromPrivateKey(privateKey) 61 | ``` 62 | 63 | ### Parameters 64 | 65 | - keystore - **Object**: Keystore JSON object. 66 | 67 | - password - **String**: The password used for encryption 68 | 69 | - mnemonic - **String**: mnemonic sentence. 70 | 71 | ### Returns 72 | 73 | - Object - The account object with the following structure: 74 | 75 | - address - **String**: The account address. 76 | 77 | - privateKey - **String**: The accounts private key. This should never be 78 | shared or stored unencrypted in localStorage! Also make sure to null the 79 | memory after usage. 80 | 81 | ### Get Balances 82 | 83 | ```js 84 | client.getBalance(address) 85 | ``` 86 | 87 | ### Parameters 88 | 89 | - address - **String**: a valid BNB Beacon Chain address. 90 | 91 | ### Returns (example) 92 | 93 | ```js 94 | ;[ 95 | { 96 | symbol: "BNB", 97 | free: "3999.41000000", 98 | locked: "1000.00000000", 99 | frozen: "0.00000000", 100 | }, 101 | ] 102 | ``` 103 | 104 | ## Place an Order 105 | 106 | ```js 107 | client.placeOrder(address, symbol, side, price, quantity, sequence) 108 | ``` 109 | 110 | ### Parameters 111 | 112 | - address - **String**: a valid BNB Beacon Chain address 113 | 114 | - symbol - **String**: an active market pair 115 | 116 | - side - **String**: buy or sell, value can only be 1(buy) or 2(sell) 117 | 118 | - price - **Number**: the price of trade pair 119 | 120 | - quantity - **Number**: the amount of symbol 121 | 122 | - sequence - **Number**: sequence from account 123 | 124 | ### Returns (example) 125 | 126 | ```js 127 | { 128 | "result": { 129 | "code": 0, 130 | "data": "{type:dex/NewOrderResponse,value:{order_id:BA36F0FAD74D8F41045463E4774F328F4AF779E5-80}}", 131 | "hash": "641F4333F05B2374700E191EE6B6B03F9A543514", 132 | "log": "Msg 0: ", 133 | "ok": true 134 | }, 135 | "status": 200 136 | } 137 | 138 | ``` 139 | 140 | ## Transfer Tokens 141 | 142 | ```js 143 | client.transfer(fromAddress, toAddress, amount, asset, memo, sequence) 144 | ``` 145 | 146 | ### Parameters 147 | 148 | - fromAddress - **String**: a valid BNB Beacon Chain address. 149 | 150 | - toAddress - **String**: a valid BNB Beacon Chain address. 151 | 152 | - amount - **Number** 153 | 154 | - asset - **String** 155 | 156 | - memo - **String** 157 | 158 | - sequence - **Number**: sequence from account 159 | 160 | ### Returns (example) 161 | 162 | ```js 163 | { 164 | "result": [ 165 | { 166 | "code": 0, 167 | "hash": "1C1AC45E4E9D213606660264F906310458AEA449", 168 | "log": "Msg 0: ", 169 | "ok": true 170 | } 171 | ], 172 | "status": 200 173 | } 174 | ``` 175 | 176 | ### Cancel an Order 177 | 178 | ```js 179 | client.cancelOrder(fromAddress, symbols, orderIds, refids, sequence) 180 | ``` 181 | 182 | ### Parameters 183 | 184 | - fromAddress - **String**: a valid BNB Beacon Chain address. 185 | 186 | - symbols - **Array[String]** 187 | 188 | - orderIds - **Array[Number]** 189 | 190 | - refids - **Array[Number]** 191 | 192 | - sequence - **Number** 193 | 194 | ### Returns (example) 195 | 196 | ```js 197 | { 198 | "result": { 199 | "code": 0, 200 | "hash": "79D09209710E32D75935186830AF4309D2A2D9C5", 201 | "log": "Msg 0: ", 202 | "ok": true 203 | }, 204 | "status": 200 205 | } 206 | ``` 207 | 208 | ### Get an Account 209 | 210 | ```js 211 | client.getAccount(address) 212 | ``` 213 | 214 | ### Parameters 215 | 216 | - address - **String**: a valid BNB Beacon Chain address. 217 | 218 | ### Returns (example) 219 | 220 | ```js 221 | { 222 | "address": "bnc1hgm0p7khfk85zpz5v0j8wnej3a90w7098fpxyh", 223 | "public_key": [], 224 | "account_number": 9, 225 | "sequence": 81, 226 | "balances": [ 227 | { 228 | "symbol": "BNB", 229 | "free": "9834046.15286760", 230 | "locked": "145.00000000", 231 | "frozen": "0.00000000" 232 | } 233 | ] 234 | } 235 | ``` 236 | 237 | ## Other pages 238 | 239 | - [API Documentation](./api-docs/README.md) 240 | - [API Examples](./examples.md) 241 | - [Supported Transaction Types](./transaction-types.md) 242 | -------------------------------------------------------------------------------- /src/amino/decoder/index.ts: -------------------------------------------------------------------------------- 1 | import is from "is-it-check" 2 | import { 3 | string as varString, 4 | bool as varBool, 5 | bytes as varBytes, 6 | varint, 7 | } from "protocol-buffers-encodings" 8 | 9 | import typeToTyp3 from "../../utils/encoderHelper" 10 | 11 | const decoder = (bytes: Buffer, varType: any) => { 12 | const val = varType.decode(bytes, 0) 13 | const offset = varType.encodingLength(val) 14 | return { val, offset } 15 | } 16 | 17 | /** 18 | * @category amino 19 | * js amino UnmarshalBinaryLengthPrefixed 20 | * @param {Buffer} bytes 21 | * @param {Object} type 22 | * @returns {Object} 23 | * */ 24 | export const unMarshalBinaryLengthPrefixed = ( 25 | bytes: Buffer, 26 | type: any 27 | ): any => { 28 | if (bytes.length === 0) throw new TypeError("Cannot decode empty bytes") 29 | 30 | // read byte-length prefix 31 | const { offset: len } = decoder(bytes, varint) 32 | 33 | if (len < 0) 34 | throw new Error(`Error reading msg byte-length prefix: got code ${len}`) 35 | 36 | bytes = bytes.slice(len) 37 | 38 | return unMarshalBinaryBare(bytes, type) 39 | } 40 | 41 | /** 42 | * @category amino 43 | * js amino UnmarshalBinaryBare 44 | * @param {Buffer} bytes 45 | * @param {Object} type 46 | * @returns {Object} 47 | * */ 48 | export const unMarshalBinaryBare = (bytes: Buffer, type: any): any => { 49 | if (!is.object(type)) throw new TypeError("type should be object") 50 | 51 | if (!Buffer.isBuffer(bytes)) throw new TypeError("bytes must be buffer") 52 | 53 | if (is.array(type)) { 54 | if (!is.object(type[0])) throw new TypeError("type should be object") 55 | 56 | return decodeArrayBinary(bytes, type[0]) 57 | } 58 | 59 | return decodeBinary(bytes, type) 60 | } 61 | 62 | const decodeBinary = ( 63 | bytes: Buffer, 64 | type: any, 65 | isLengthPrefixed?: boolean 66 | ): any => { 67 | if (Buffer.isBuffer(type)) { 68 | return decoder(bytes, varBytes) 69 | } 70 | 71 | if (is.array(type)) { 72 | return decodeArrayBinary(bytes, type) 73 | } 74 | 75 | if (is.number(type)) { 76 | return decoder(bytes, varint) 77 | } 78 | 79 | if (is.boolean(type)) { 80 | return decoder(bytes, varBool) 81 | } 82 | 83 | if (is.string(type)) { 84 | return decoder(bytes, varString) 85 | } 86 | 87 | if (is.object(type)) { 88 | return decodeObjectBinary(bytes, type, isLengthPrefixed) 89 | } 90 | 91 | return 92 | } 93 | 94 | const setDefaultValue = (type: any, key: string) => { 95 | if (is.object(type[key])) type[key] = null 96 | 97 | if (is.number(type[key])) type[key] = 0 98 | 99 | if (is.boolean(type[key])) type[key] = false 100 | 101 | if (is.string(type[key])) type[key] = "" 102 | } 103 | 104 | const decodeObjectBinary = ( 105 | bytes: Buffer, 106 | type: any, 107 | isLengthPrefixed?: boolean 108 | ) => { 109 | let objectOffset = 0 110 | 111 | // read byte-length prefix 112 | if (isLengthPrefixed) { 113 | const { offset: len } = decoder(bytes, varint) 114 | bytes = bytes.slice(len) 115 | objectOffset += len 116 | } 117 | 118 | // If registered concrete, consume and verify prefix bytes. 119 | if (type.aminoPrefix) { 120 | bytes = bytes.slice(4) 121 | objectOffset += 4 122 | } 123 | 124 | let lastFieldNum = 0 125 | 126 | const keys = Object.keys(type).filter((key) => key !== "aminoPrefix") 127 | 128 | keys.forEach((key, index) => { 129 | if (is.array(type[key])) { 130 | const { offset, val } = decodeArrayBinary(bytes, type[key][0]) 131 | objectOffset += offset 132 | type[key] = val 133 | bytes = bytes.slice(offset) 134 | } else { 135 | const { fieldNum, typ } = decodeFieldNumberAndTyp3(bytes) 136 | 137 | //if this field is default value, continue 138 | if (index + 1 !== fieldNum || fieldNum < 0) { 139 | setDefaultValue(type, key) 140 | return 141 | } 142 | 143 | if (fieldNum <= lastFieldNum) { 144 | throw new Error( 145 | `encountered fieldNum: ${fieldNum}, but we have already seen fnum: ${lastFieldNum}` 146 | ) 147 | } 148 | 149 | lastFieldNum = fieldNum 150 | 151 | if (index + 1 !== fieldNum) { 152 | throw new Error("field number is not expected") 153 | } 154 | 155 | const typeWanted = typeToTyp3(type[key]) 156 | 157 | if (typ !== typeWanted) { 158 | throw new Error("field type is not expected") 159 | } 160 | 161 | //remove 1 byte of type 162 | bytes = bytes.slice(1) 163 | 164 | const { val, offset } = decodeBinary(bytes, type[key], true) 165 | type[key] = val 166 | 167 | //remove decoded bytes 168 | bytes = bytes.slice(offset) 169 | objectOffset += offset + 1 170 | } 171 | }) 172 | 173 | return { val: type, offset: objectOffset } 174 | } 175 | 176 | const decodeArrayBinary = (bytes: Buffer, type: any) => { 177 | const arr = [] 178 | let arrayOffset = 0 179 | let { fieldNum: fieldNumber } = decodeFieldNumberAndTyp3(bytes) 180 | 181 | while (true) { 182 | const { fieldNum } = decodeFieldNumberAndTyp3(bytes) 183 | 184 | if (fieldNum !== fieldNumber || fieldNum < 0) break 185 | 186 | //remove 1 byte of encoded field number and type 187 | bytes = bytes.slice(1) 188 | 189 | //is default value, skip and continue read bytes 190 | // if (bytes.length > 0 && bytes[0] === 0x00) continue 191 | if (bytes.length > 0 && bytes.readUInt8(0) === 0x00) continue 192 | 193 | const { offset, val } = decodeBinary(bytes, type, true) 194 | 195 | arr.push({ ...val }) 196 | bytes = bytes.slice(offset + 1) 197 | 198 | //add 1 byte of type 199 | arrayOffset += offset + 1 200 | fieldNumber = fieldNum 201 | } 202 | 203 | // console.log(arr) 204 | return { val: arr, offset: arrayOffset } 205 | } 206 | 207 | export const decodeFieldNumberAndTyp3 = (bytes: Buffer): any => { 208 | if (bytes.length < 2) { 209 | //default value 210 | return { fieldNum: -1 } 211 | } 212 | 213 | const { val } = decoder(bytes, varint) 214 | 215 | const typ = val & 7 216 | const fieldNum = val >> 3 217 | if (fieldNum > 1 << (29 - 1)) { 218 | throw new Error(`invalid field num ${fieldNum}`) 219 | } 220 | 221 | return { fieldNum, typ } 222 | } 223 | -------------------------------------------------------------------------------- /__tests__/fixtures/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "params": { 3 | "address": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", 4 | "to": "tbnb1ss57e8sa7xnwq030k2ctr775uac9gjzglqhvpy", 5 | "amount": 10, 6 | "asset": "BNB", 7 | "memo": "test" 8 | }, 9 | "signMsg": { 10 | "account_number": "34", 11 | "chain_id": "Binance-Chain-Ganges", 12 | "data": null, 13 | "memo": "test", 14 | "msgs": [ 15 | { 16 | "inputs": [ 17 | { 18 | "address": "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd", 19 | "coins": [ 20 | { 21 | "amount": 1000000000, 22 | "denom": "BNB" 23 | } 24 | ] 25 | } 26 | ], 27 | "outputs": [ 28 | { 29 | "address": "tbnb1ss57e8sa7xnwq030k2ctr775uac9gjzglqhvpy", 30 | "coins": [ 31 | { 32 | "amount": 1000000000, 33 | "denom": "BNB" 34 | } 35 | ] 36 | } 37 | ] 38 | } 39 | ], 40 | "sequence": "31", 41 | "source": "1" 42 | }, 43 | "signMsgBytes": "7b226163636f756e745f6e756d626572223a223334222c22636861696e5f6964223a2242696e616e63652d436861696e2d4e696c65222c2264617461223a6e756c6c2c226d656d6f223a2274657374222c226d736773223a5b7b22696e70757473223a5b7b2261646472657373223a2274626e623168676d3070376b68666b38357a707a3576306a38776e656a33613930773730397a7a6c666664222c22636f696e73223a5b7b22616d6f756e74223a2231303030303030303030222c2264656e6f6d223a22424e42227d5d7d5d2c226f757470757473223a5b7b2261646472657373223a2274626e6231737335376538736137786e77713033306b3263747237373575616339676a7a676c7168767079222c22636f696e73223a5b7b22616d6f756e74223a2231303030303030303030222c2264656e6f6d223a22424e42227d5d7d5d7d5d2c2273657175656e6365223a223331222c22736f75726365223a2231227d", 44 | "stdTx": { 45 | "msg": [ 46 | { 47 | "inputs": [ 48 | { 49 | "address": [ 50 | 186, 51 | 54, 52 | 240, 53 | 250, 54 | 215, 55 | 77, 56 | 143, 57 | 65, 58 | 4, 59 | 84, 60 | 99, 61 | 228, 62 | 119, 63 | 79, 64 | 50, 65 | 143, 66 | 74, 67 | 247, 68 | 121, 69 | 229 70 | ], 71 | "coins": [ 72 | { 73 | "denom": "BNB", 74 | "amount": 1000000000 75 | } 76 | ] 77 | } 78 | ], 79 | "outputs": [ 80 | { 81 | "address": [ 82 | 132, 83 | 41, 84 | 236, 85 | 158, 86 | 29, 87 | 241, 88 | 166, 89 | 224, 90 | 62, 91 | 47, 92 | 178, 93 | 176, 94 | 177, 95 | 251, 96 | 212, 97 | 231, 98 | 112, 99 | 84, 100 | 72, 101 | 72 102 | ], 103 | "coins": [ 104 | { 105 | "denom": "BNB", 106 | "amount": 1000000000 107 | } 108 | ] 109 | } 110 | ], 111 | "msgType": "MsgSend" 112 | } 113 | ], 114 | "signatures": [ 115 | { 116 | "pub_key": [ 117 | 235, 118 | 90, 119 | 233, 120 | 135, 121 | 33, 122 | 2, 123 | 151, 124 | 41, 125 | 165, 126 | 46, 127 | 78, 128 | 60, 129 | 43, 130 | 74, 131 | 78, 132 | 82, 133 | 170, 134 | 116, 135 | 3, 136 | 62, 137 | 237, 138 | 175, 139 | 139, 140 | 161, 141 | 223, 142 | 90, 143 | 182, 144 | 209, 145 | 245, 146 | 24, 147 | 253, 148 | 105, 149 | 230, 150 | 123, 151 | 189, 152 | 48, 153 | 155, 154 | 14 155 | ], 156 | "signature": [ 157 | 151, 158 | 180, 159 | 194, 160 | 228, 161 | 27, 162 | 13, 163 | 15, 164 | 97, 165 | 221, 166 | 207, 167 | 64, 168 | 32, 169 | 255, 170 | 240, 171 | 236, 172 | 178, 173 | 39, 174 | 214, 175 | 223, 176 | 105, 177 | 179, 178 | 221, 179 | 126, 180 | 101, 181 | 123, 182 | 52, 183 | 190, 184 | 14, 185 | 50, 186 | 185, 187 | 86, 188 | 226, 189 | 45, 190 | 12, 191 | 107, 192 | 229, 193 | 131, 194 | 45, 195 | 37, 196 | 53, 197 | 58, 198 | 226, 199 | 74, 200 | 240, 201 | 187, 202 | 34, 203 | 61, 204 | 74, 205 | 83, 206 | 55, 207 | 50, 208 | 5, 209 | 24, 210 | 196, 211 | 231, 212 | 112, 213 | 139, 214 | 132, 215 | 200, 216 | 224, 217 | 94, 218 | 182, 219 | 53, 220 | 107 221 | ], 222 | "account_number": 34, 223 | "sequence": 31 224 | } 225 | ], 226 | "memo": "test", 227 | "source": 1, 228 | "data": "", 229 | "msgType": "StdTx" 230 | }, 231 | "stdTxBytes": "cc01f0625dee0a4e2a2c87fa0a230a14ba36f0fad74d8f41045463e4774f328f4af779e5120b0a03424e42108094ebdc0312230a148429ec9e1df1a6e03e2fb2b0b1fbd4e770544848120b0a03424e42108094ebdc03126e0a26eb5ae98721029729a52e4e3c2b4a4e52aa74033eedaf8ba1df5ab6d1f518fd69e67bbd309b0e124097b4c2e41b0d0f61ddcf4020fff0ecb227d6df69b3dd7e657b34be0e32b956e22d0c6be5832d25353ae24af0bb223d4a5337320518c4e7708b84c8e05eb6356b1822201f1a04746573742001" 232 | } 233 | -------------------------------------------------------------------------------- /__tests__/encoder.test.ts: -------------------------------------------------------------------------------- 1 | import * as amino from "../src/amino" 2 | import { AminoPrefix } from "../src/types/" 3 | 4 | describe("encoder", () => { 5 | it("encode time", () => { 6 | const encodedTime = amino.encodeTime("1973-11-29T21:33:09.123456789Z") 7 | expect(encodedTime.toString("hex")).toBe("0915cd5b07000000001515cd5b07") 8 | }) 9 | 10 | it("encode number", () => { 11 | let encodedNumber = amino.encodeNumber(100000) 12 | encodedNumber = encodedNumber.toString("hex") 13 | expect(encodedNumber).toBe("a08d06") 14 | }) 15 | 16 | it("encode negtive number", () => { 17 | expect(() => { 18 | amino.encodeNumber(-100000) 19 | }).toThrow() 20 | }) 21 | 22 | it("encode big number", () => { 23 | let encodedNumber = amino.encodeNumber(Math.pow(10, 18)) 24 | encodedNumber = encodedNumber.toString("hex") 25 | expect(encodedNumber).toBe("808090bbbad6adf00d") 26 | }) 27 | 28 | it("UVarInt", () => { 29 | let encodedNumber = amino.UVarInt.encode(17) 30 | encodedNumber = encodedNumber.toString("hex") 31 | expect(encodedNumber).toBe("11") 32 | }) 33 | 34 | it("encode bool", () => { 35 | let encodedTrue = amino.encodeBool(true) 36 | encodedTrue = encodedTrue.toString("hex") 37 | expect(encodedTrue).toBe("01") 38 | 39 | let encodedFalse = amino.encodeBool(false) 40 | encodedFalse = encodedFalse.toString("hex") 41 | expect(encodedFalse).toBe("00") 42 | }) 43 | 44 | it("encode string", () => { 45 | const str = "You are beautiful" 46 | const encodedString = amino.encodeString(str) 47 | expect(encodedString.toString("hex")).toBe( 48 | "11596f75206172652062656175746966756c" 49 | ) 50 | }) 51 | 52 | it("convertObjectToSignBytes", () => { 53 | // unsorted, expect convertObjectToSignBytes to sort it 54 | const jsonObj = { 55 | sender: 2, 56 | symbol: 3, 57 | zlast: [ 58 | { z: "z", a: "z" }, 59 | { z: "a", a: "z" }, 60 | ], 61 | address: 1, 62 | } 63 | const str = amino.convertObjectToSignBytes(jsonObj) 64 | expect(str.toString()).toBe( 65 | '{"address":1,"sender":2,"symbol":3,"zlast":[{"a":"z","z":"z"},{"a":"z","z":"a"}]}' 66 | ) 67 | }) 68 | 69 | it("marshalBinary", () => { 70 | const stdTx = { 71 | msg: [ 72 | { 73 | sender: Buffer.from([ 74 | 182, 75 | 86, 76 | 29, 77 | 204, 78 | 16, 79 | 65, 80 | 48, 81 | 5, 82 | 154, 83 | 124, 84 | 8, 85 | 244, 86 | 140, 87 | 100, 88 | 97, 89 | 12, 90 | 31, 91 | 111, 92 | 144, 93 | 100, 94 | ]), 95 | id: "B6561DCC104130059A7C08F48C64610C1F6F9064-11", 96 | symbol: "BTC-5C4_BNB", 97 | ordertype: 2, 98 | side: 1, 99 | price: 100000000, 100 | quantity: 1200000000, 101 | timeinforce: 1, 102 | aminoPrefix: AminoPrefix.NewOrderMsg, 103 | }, 104 | ], 105 | signatures: [ 106 | { 107 | pub_key: Buffer.from([ 108 | 235, 109 | 90, 110 | 233, 111 | 135, 112 | 33, 113 | 3, 114 | 186, 115 | 245, 116 | 61, 117 | 20, 118 | 36, 119 | 248, 120 | 234, 121 | 131, 122 | 208, 123 | 58, 124 | 130, 125 | 246, 126 | 209, 127 | 87, 128 | 181, 129 | 64, 130 | 28, 131 | 78, 132 | 165, 133 | 127, 134 | 251, 135 | 131, 136 | 23, 137 | 135, 138 | 46, 139 | 21, 140 | 161, 141 | 159, 142 | 201, 143 | 183, 144 | 173, 145 | 123, 146 | ]), 147 | signature: Buffer.from([ 148 | 231, 149 | 154, 150 | 102, 151 | 6, 152 | 210, 153 | 140, 154 | 240, 155 | 123, 156 | 156, 157 | 198, 158 | 245, 159 | 102, 160 | 181, 161 | 36, 162 | 165, 163 | 40, 164 | 43, 165 | 19, 166 | 190, 167 | 204, 168 | 195, 169 | 22, 170 | 35, 171 | 118, 172 | 199, 173 | 159, 174 | 57, 175 | 38, 176 | 32, 177 | 201, 178 | 90, 179 | 68, 180 | 123, 181 | 25, 182 | 246, 183 | 78, 184 | 118, 185 | 30, 186 | 34, 187 | 167, 188 | 163, 189 | 188, 190 | 49, 191 | 26, 192 | 120, 193 | 14, 194 | 125, 195 | 159, 196 | 221, 197 | 82, 198 | 30, 199 | 47, 200 | 126, 201 | 222, 202 | 194, 203 | 83, 204 | 8, 205 | 197, 206 | 186, 207 | 198, 208 | 170, 209 | 28, 210 | 10, 211 | 49, 212 | ]), 213 | account_number: 1, 214 | sequence: 10, 215 | }, 216 | ], 217 | memo: "", 218 | aminoPrefix: AminoPrefix.StdTx, 219 | } 220 | 221 | const bytes = amino.marshalBinary(stdTx) 222 | expect(bytes).toBe( 223 | "db01f0625dee0a65ce6dc0430a14b6561dcc104130059a7c08f48c64610c1f6f9064122b423635363144434331303431333030353941374330384634384336343631304331463646393036342d31311a0b4254432d3543345f424e42200228013080c2d72f3880989abc044001126e0a26eb5ae9872103baf53d1424f8ea83d03a82f6d157b5401c4ea57ffb8317872e15a19fc9b7ad7b1240e79a6606d28cf07b9cc6f566b524a5282b13beccc3162376c79f392620c95a447b19f64e761e22a7a3bc311a780e7d9fdd521e2f7edec25308c5bac6aa1c0a311801200a" 224 | ) 225 | }) 226 | }) 227 | -------------------------------------------------------------------------------- /src/rpc/baseRpc.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * https://github.com/nomic-io/js-tendermint/blob/master/src/rpc.js 3 | */ 4 | 5 | import axios from "axios" 6 | import { EventEmitter } from "events" 7 | import is from "is-it-check" 8 | import ndjson from "ndjson" 9 | import Pumpify from "pumpify" 10 | import url from "url" 11 | import websocket from "websocket-stream" 12 | 13 | export type Args = { [k: string]: any } 14 | 15 | function convertHttpArgs(url: string, args: Args = {}) { 16 | const search = [] 17 | for (const k in args) { 18 | if (is.string(args[k])) { 19 | search.push(`${k}="${args[k]}"`) 20 | } else if (Buffer.isBuffer(args[k])) { 21 | search.push(`${k}=0x${args[k].toString("hex")}`) 22 | } else { 23 | search.push(`${k}=${args[k]}`) 24 | } 25 | } 26 | return `${url}?${search.join("&")}` 27 | } 28 | 29 | function convertWsArgs(args: Args = {}) { 30 | for (const k in args) { 31 | const v = args[k] 32 | if (typeof v === "number") { 33 | args[k] = String(v) 34 | } else if (Buffer.isBuffer(v)) { 35 | args[k] = "0x" + v.toString("hex") 36 | } else if (v instanceof Uint8Array) { 37 | args[k] = "0x" + Buffer.from(v).toString("hex") 38 | } 39 | } 40 | return args 41 | } 42 | 43 | const wsProtocols = ["ws:", "wss:"] 44 | const httpProtocols = ["http:", "https:"] 45 | const allProtocols = wsProtocols.concat(httpProtocols) 46 | 47 | export default class BaseRpc extends EventEmitter { 48 | private uri: string 49 | public call!: BaseRpc["callWs"] | BaseRpc["callHttp"] 50 | private closed = false 51 | private ws?: Pumpify 52 | 53 | constructor(uriString = "localhost:27146") { 54 | super() 55 | 56 | // parse full-node URI 57 | let { protocol, hostname, port } = url.parse(uriString) 58 | 59 | // default to http 60 | if (!protocol || !allProtocols.includes(protocol)) { 61 | const uri = url.parse(`http://${uriString}`) 62 | protocol = uri.protocol 63 | hostname = uri.hostname 64 | port = uri.port 65 | } 66 | 67 | this.uri = !port 68 | ? `${protocol}//${hostname}/` 69 | : `${protocol}//${hostname}:${port}/` 70 | 71 | if (protocol && wsProtocols.includes(protocol)) { 72 | this.uri = `${this.uri}websocket` 73 | this.call = this.callWs 74 | this.connectWs() 75 | } else if (protocol && httpProtocols.includes(protocol)) { 76 | this.call = this.callHttp 77 | } 78 | } 79 | 80 | connectWs() { 81 | this.ws = new Pumpify.obj(ndjson.stringify(), websocket(this.uri)) 82 | 83 | this.ws.on("error", (err) => this.emit("error", err)) 84 | this.ws.on("close", () => { 85 | if (this.closed) return 86 | this.emit("error", Error("websocket disconnected")) 87 | }) 88 | this.ws.on("data", (data) => { 89 | data = JSON.parse(data) 90 | if (!data.id) return 91 | this.emit(data.id, data.error, data.result) 92 | }) 93 | } 94 | 95 | callHttp(method: string, args?: Args) { 96 | let url = this.uri + method 97 | url = convertHttpArgs(url, args) 98 | return axios({ 99 | url: url, 100 | }).then( 101 | function ({ data }) { 102 | if (data.error) { 103 | const err = Error(data.error.message) 104 | Object.assign(err, data.error) 105 | throw err 106 | } 107 | return data.result 108 | }, 109 | function (err) { 110 | throw Error(err) 111 | } 112 | ) 113 | } 114 | 115 | callWs(method: string, args?: Args, listener?: (value: any) => void) { 116 | const self = this 117 | return new Promise((resolve, reject) => { 118 | const id = Math.random().toString(36) 119 | const params = convertWsArgs(args) 120 | if (method === "subscribe") { 121 | if (typeof listener !== "function") { 122 | throw Error("Must provide listener function") 123 | } 124 | 125 | // events get passed to listener 126 | this.on(id + "#event", (err, res) => { 127 | if (err) return self.emit("error", err) 128 | return listener(res.data.value) 129 | }) 130 | 131 | // promise resolves on successful subscription or error 132 | this.on(id, (err) => { 133 | if (err) return reject(err) 134 | resolve() 135 | }) 136 | } else { 137 | // response goes to promise 138 | this.once(id, (err, res) => { 139 | if (err) return reject(err) 140 | resolve(res) 141 | }) 142 | } 143 | 144 | this.ws?.write({ jsonrpc: "2.0", id, method, params }) 145 | }) 146 | } 147 | 148 | close() { 149 | this.closed = true 150 | if (!this.ws) return 151 | this.ws.destroy() 152 | } 153 | 154 | private createCallBasedMethod = (name: string) => ( 155 | args?: Args, 156 | listener?: Parameters[2] 157 | ): any => { 158 | return this.call(name, args, listener).then((res) => { 159 | return res 160 | }) 161 | } 162 | 163 | subscribe = this.createCallBasedMethod("subscribe") 164 | unsubscribe = this.createCallBasedMethod("unsubscribe") 165 | unsubscribeAll = this.createCallBasedMethod("unsubscribe_all") 166 | 167 | status = this.createCallBasedMethod("status") 168 | netInfo = this.createCallBasedMethod("net_info") 169 | blockchain = this.createCallBasedMethod("blockchain") 170 | genesis = this.createCallBasedMethod("genesis") 171 | health = this.createCallBasedMethod("health") 172 | block = this.createCallBasedMethod("block") 173 | blockResults = this.createCallBasedMethod("block_results") 174 | validators = this.createCallBasedMethod("validators") 175 | consensusState = this.createCallBasedMethod("consensus_state") 176 | dumpConsensusState = this.createCallBasedMethod("dump_consensus_state") 177 | broadcastTxCommit = this.createCallBasedMethod("broadcast_tx_commit") 178 | broadcastTxSync = this.createCallBasedMethod("broadcast_tx_sync") 179 | broadcastTxAsync = this.createCallBasedMethod("broadcast_tx_async") 180 | unconfirmedTxs = this.createCallBasedMethod("unconfirmed_txs") 181 | numUnconfirmedTxs = this.createCallBasedMethod("num_unconfirmed_txs") 182 | commit = this.createCallBasedMethod("commit") 183 | tx = this.createCallBasedMethod("tx") 184 | txSearch = this.createCallBasedMethod("tx_search") 185 | 186 | abciQuery = this.createCallBasedMethod("abci_query") 187 | abciInfo = this.createCallBasedMethod("abci_info") 188 | } 189 | -------------------------------------------------------------------------------- /docs/api-docs/classes/tokenmanagement.md: -------------------------------------------------------------------------------- 1 | 2 | # Class: TokenManagement 3 | 4 | issue or view tokens 5 | 6 | ## Hierarchy 7 | 8 | * **TokenManagement** 9 | 10 | ## Index 11 | 12 | ### Constructors 13 | 14 | * [constructor](tokenmanagement.md#constructor) 15 | 16 | ### Methods 17 | 18 | * [burn](tokenmanagement.md#burn) 19 | * [freeze](tokenmanagement.md#freeze) 20 | * [issue](tokenmanagement.md#issue) 21 | * [issueMiniToken](tokenmanagement.md#issueminitoken) 22 | * [issueTinyToken](tokenmanagement.md#issuetinytoken) 23 | * [mint](tokenmanagement.md#mint) 24 | * [setTokenUri](tokenmanagement.md#settokenuri) 25 | * [timeLock](tokenmanagement.md#timelock) 26 | * [timeRelock](tokenmanagement.md#timerelock) 27 | * [timeUnlock](tokenmanagement.md#timeunlock) 28 | * [unfreeze](tokenmanagement.md#unfreeze) 29 | 30 | ## Constructors 31 | 32 | ### constructor 33 | 34 | \+ **new TokenManagement**(`bncClient`: [BncClient](bncclient.md)): *[TokenManagement](tokenmanagement.md)* 35 | 36 | **Parameters:** 37 | 38 | Name | Type | Description | 39 | ------ | ------ | ------ | 40 | `bncClient` | [BncClient](bncclient.md) | | 41 | 42 | **Returns:** *[TokenManagement](tokenmanagement.md)* 43 | 44 | ## Methods 45 | 46 | ### burn 47 | 48 | ▸ **burn**(`fromAddress`: string, `symbol`: string, `amount`: BigSource): *Promise‹object›* 49 | 50 | burn some amount of token 51 | 52 | **Parameters:** 53 | 54 | Name | Type | 55 | ------ | ------ | 56 | `fromAddress` | string | 57 | `symbol` | string | 58 | `amount` | BigSource | 59 | 60 | **Returns:** *Promise‹object›* 61 | 62 | resolves with response (success or fail) 63 | 64 | ___ 65 | 66 | ### freeze 67 | 68 | ▸ **freeze**(`fromAddress`: string, `symbol`: string, `amount`: BigSource): *Promise‹object›* 69 | 70 | freeze some amount of token 71 | 72 | **Parameters:** 73 | 74 | Name | Type | 75 | ------ | ------ | 76 | `fromAddress` | string | 77 | `symbol` | string | 78 | `amount` | BigSource | 79 | 80 | **Returns:** *Promise‹object›* 81 | 82 | resolves with response (success or fail) 83 | 84 | ___ 85 | 86 | ### issue 87 | 88 | ▸ **issue**(`senderAddress`: string, `tokenName`: string, `symbol`: string, `totalSupply`: number, `mintable`: boolean): *Promise‹object›* 89 | 90 | create a new asset on BNB Beacon Chain 91 | 92 | **Parameters:** 93 | 94 | Name | Type | Default | 95 | ------ | ------ | ------ | 96 | `senderAddress` | string | - | 97 | `tokenName` | string | - | 98 | `symbol` | string | - | 99 | `totalSupply` | number | 0 | 100 | `mintable` | boolean | false | 101 | 102 | **Returns:** *Promise‹object›* 103 | 104 | resolves with response (success or fail) 105 | 106 | ___ 107 | 108 | ### issueMiniToken 109 | 110 | ▸ **issueMiniToken**(`senderAddress`: string, `tokenName`: string, `symbol`: string, `totalSupply`: number, `mintable`: boolean, `tokenUri?`: undefined | string): *Promise‹object›* 111 | 112 | issue a new mini-token, total supply should be less than 1M 113 | 114 | **Parameters:** 115 | 116 | Name | Type | Default | 117 | ------ | ------ | ------ | 118 | `senderAddress` | string | - | 119 | `tokenName` | string | - | 120 | `symbol` | string | - | 121 | `totalSupply` | number | 0 | 122 | `mintable` | boolean | false | 123 | `tokenUri?` | undefined | string | - | 124 | 125 | **Returns:** *Promise‹object›* 126 | 127 | resolves with response (success or fail) 128 | 129 | ___ 130 | 131 | ### issueTinyToken 132 | 133 | ▸ **issueTinyToken**(`senderAddress`: string, `tokenName`: string, `symbol`: string, `totalSupply`: number, `mintable`: boolean, `tokenUri?`: undefined | string): *Promise‹object›* 134 | 135 | issue a new tiny-token, total supply should be less than 10K 136 | 137 | **Parameters:** 138 | 139 | Name | Type | Default | 140 | ------ | ------ | ------ | 141 | `senderAddress` | string | - | 142 | `tokenName` | string | - | 143 | `symbol` | string | - | 144 | `totalSupply` | number | 0 | 145 | `mintable` | boolean | false | 146 | `tokenUri?` | undefined | string | - | 147 | 148 | **Returns:** *Promise‹object›* 149 | 150 | resolves with response (success or fail) 151 | 152 | ___ 153 | 154 | ### mint 155 | 156 | ▸ **mint**(`fromAddress`: string, `symbol`: string, `amount`: BigSource): *Promise‹object›* 157 | 158 | mint tokens for an existing token 159 | 160 | **Parameters:** 161 | 162 | Name | Type | 163 | ------ | ------ | 164 | `fromAddress` | string | 165 | `symbol` | string | 166 | `amount` | BigSource | 167 | 168 | **Returns:** *Promise‹object›* 169 | 170 | resolves with response (success or fail) 171 | 172 | ___ 173 | 174 | ### setTokenUri 175 | 176 | ▸ **setTokenUri**(`__namedParameters`: object): *Promise‹object›* 177 | 178 | set token URI of mini-token 179 | 180 | **Parameters:** 181 | 182 | ▪ **__namedParameters**: *object* 183 | 184 | Name | Type | 185 | ------ | ------ | 186 | `fromAddress` | string | 187 | `symbol` | string | 188 | `tokenUri` | string | 189 | 190 | **Returns:** *Promise‹object›* 191 | 192 | ___ 193 | 194 | ### timeLock 195 | 196 | ▸ **timeLock**(`fromAddress`: string, `description`: string, `amount`: Coin[], `lockTime`: number): *Promise‹object›* 197 | 198 | lock token for a while 199 | 200 | **Parameters:** 201 | 202 | Name | Type | 203 | ------ | ------ | 204 | `fromAddress` | string | 205 | `description` | string | 206 | `amount` | Coin[] | 207 | `lockTime` | number | 208 | 209 | **Returns:** *Promise‹object›* 210 | 211 | resolves with response (success or fail) 212 | 213 | ___ 214 | 215 | ### timeRelock 216 | 217 | ▸ **timeRelock**(`fromAddress`: string, `id`: number, `description`: string, `amount`: Coin[], `lockTime`: number): *Promise‹object›* 218 | 219 | lock more token or increase locked period 220 | 221 | **Parameters:** 222 | 223 | Name | Type | 224 | ------ | ------ | 225 | `fromAddress` | string | 226 | `id` | number | 227 | `description` | string | 228 | `amount` | Coin[] | 229 | `lockTime` | number | 230 | 231 | **Returns:** *Promise‹object›* 232 | 233 | resolves with response (success or fail) 234 | 235 | ___ 236 | 237 | ### timeUnlock 238 | 239 | ▸ **timeUnlock**(`fromAddress`: string, `id`: number): *Promise‹object›* 240 | 241 | unlock locked tokens 242 | 243 | **Parameters:** 244 | 245 | Name | Type | 246 | ------ | ------ | 247 | `fromAddress` | string | 248 | `id` | number | 249 | 250 | **Returns:** *Promise‹object›* 251 | 252 | resolves with response (success or fail) 253 | 254 | ___ 255 | 256 | ### unfreeze 257 | 258 | ▸ **unfreeze**(`fromAddress`: string, `symbol`: string, `amount`: BigSource): *Promise‹object›* 259 | 260 | unfreeze some amount of token 261 | 262 | **Parameters:** 263 | 264 | Name | Type | 265 | ------ | ------ | 266 | `fromAddress` | string | 267 | `symbol` | string | 268 | `amount` | BigSource | 269 | 270 | **Returns:** *Promise‹object›* 271 | 272 | resolves with response (success or fail) 273 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # API Examples 2 | 3 | ## client 4 | 5 | ### Example of a wallet-to-wallet transfer 6 | 7 | ```js 8 | const { BncClient } = require("@bnb-chain/javascript-sdk") 9 | const axios = require("axios") 10 | 11 | const asset = "BNB" // asset string 12 | const amount = 1.123 // amount float 13 | const addressTo = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd" // addressTo string 14 | const message = "A note to you" // memo string 15 | const api = "https://testnet-dex.binance.org/" /// api string 16 | 17 | let privKey = "DEADBEEF" // privkey hexstring (keep this safe) 18 | 19 | const bnbClient = new BncClient(api) 20 | const httpClient = axios.create({ baseURL: api }) 21 | 22 | bnbClient.chooseNetwork("testnet") // or this can be "mainnet" 23 | bnbClient.setPrivateKey(privKey) 24 | bnbClient.initChain() 25 | 26 | const addressFrom = bnbClient.getClientKeyAddress() // sender address string (e.g. bnb1...) 27 | const sequenceURL = `${api}api/v1/account/${addressFrom}/sequence` 28 | 29 | httpClient 30 | .get(sequenceURL) 31 | .then((res) => { 32 | const sequence = res.data.sequence || 0 33 | return bnbClient.transfer( 34 | addressFrom, 35 | addressTo, 36 | amount, 37 | asset, 38 | message, 39 | sequence 40 | ) 41 | }) 42 | .then((result) => { 43 | console.log(result) 44 | if (result.status === 200) { 45 | console.log("success", result.result[0].hash) 46 | } else { 47 | console.error("error", result) 48 | } 49 | }) 50 | .catch((error) => { 51 | console.error("error", error) 52 | }) 53 | ``` 54 | 55 | ### Example of token issuance 56 | 57 | ```js 58 | const { BncClient, crypto, utils } = require("@bnb-chain/javascript-sdk") 59 | 60 | // Token params 61 | const tokenName = "Your Token Name" 62 | const tokenSymb = "SYMBOL" 63 | const tokenSupp = 1000000 // one million tokens 64 | const tokenMint = false // mintable token? 65 | 66 | // Account params 67 | const tokenAddr = "** PUT THE ISSUER ADDRESS HERE **" 68 | const mnemonic = "** PUT THE ISSUER MNEMONIC HERE **" 69 | const privKey = crypto.getPrivateKeyFromMnemonic(mnemonic) 70 | 71 | const api = "https://testnet-dex.binance.org/" // api string; remove "testnet-" for mainnet 72 | const net = "testnet" // or this can be "mainnet" 73 | 74 | const bnbClient = new BncClient(api) 75 | bnbClient.chooseNetwork(net) 76 | 77 | async function main() { 78 | await bnbClient.setPrivateKey(privKey) 79 | await bnbClient.initChain() 80 | await bnbClient.tokens.issue( 81 | tokenAddr, 82 | tokenName, 83 | tokenSymb, 84 | tokenSupp, 85 | tokenMint 86 | ) 87 | } 88 | main() 89 | ``` 90 | 91 | ### RPC example (getAccount) 92 | 93 | ```js 94 | const { rpc } = require("@bnb-chain/javascript-sdk") 95 | new rpc("https://dataseed1.binance.org:443") 96 | .getAccount("bnb1qfmufc2q30cgw82ykjlpfeyauhcf5mad6p5y8t") 97 | .then((x) => console.log("", JSON.stringify(x))) 98 | // -> {"base":{"address":"bnb1qfmufc2q30cgw82ykjlpfeyauhcf5mad6p5y8t","coins":[{"denom":"BNB","amount":2843667357},{"denom":"MTV-4C6","amount":7711000000000}],"public_key":{"type":"Buffer","data":[235,90,233,135,33,3,21,36,100,69,241,5,162,40,77,204,210,190,159,234,66,242,232,59,133,159,82,159,122,185,65,28,191,55,53,132,61,135]},"account_number":221399,"sequence":7383},"name":"","locked":[{"denom":"BNB","amount":2473269620},{"denom":"MTV-4C6","amount":6645000000000}],"frozen":[]} 99 | ``` 100 | 101 | ## crypto 102 | 103 | Generate privatekey, address, keystore and mnemonics: 104 | 105 | ```js 106 | // keystore 107 | const keyStore = crypto.generateKeyStore(privateKey, password) 108 | 109 | // generate key entropy 110 | const privateKey = crypto.generatePrivateKey() 111 | 112 | // addresses 113 | const address = crypto.getAddressFromPublicKey(publicKey) 114 | // or 115 | const address = crypto.getAddressFromPrivateKey(privateKey) 116 | 117 | // mnemonic 118 | const mnemonic = crypto.generateMnemonic() // => 24 words 119 | console.log("valid mnemonic?", crypto.validateMnemonic(mnemonic)) 120 | ``` 121 | 122 | Recover private keys, addresses from keystore and mnemonics: 123 | 124 | ```js 125 | // recover from keystore 126 | const privateKey = crypto.getPrivateKeyFromKeyStore(keystore, password) 127 | 128 | // recover from mnemonic 129 | const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic) 130 | 131 | // get an address 132 | const address = crypto.getAddressFromPrivateKey(privateKey) 133 | ``` 134 | 135 | ## amino (js-amino) 136 | 137 | [go-amino on GitHub](https://github.com/tendermint/go-amino) 138 | 139 | Serialize an object to hex string compatible with go-amino: 140 | 141 | ```js 142 | amino.marshalBinary(data) 143 | 144 | amino.marshalBinaryBare(data) 145 | ``` 146 | 147 | ## Ledger 148 | 149 | The following code is an example of what you can do with the Ledger support: 150 | 151 | ```js 152 | const { ledger } = require("@bnb-chain/javascript-sdk") 153 | ledger(async () => { 154 | // check environment (web, node) 155 | console.log("node?", await ledger.transports.node.isSupported()) // => true if node 156 | console.log("web?", await ledger.transports.u2f.isSupported()) // => true if web 157 | console.log("bluetooth?", await ledger.transports.wble.isSupported()) // => true if web 158 | 159 | // use the node transport 160 | const timeout = 5000 161 | const transport = await ledger.transports.node.create(timeout) 162 | const app = new ledger.app(transport) 163 | 164 | // do some things! (firmware app must be open now) 165 | 166 | // get version 167 | try { 168 | const version = await app.getVersion() 169 | console.log("version", version) 170 | } catch ({ message, statusCode }) { 171 | console.error("version error", message, statusCode) 172 | } 173 | 174 | // we can provide the hd path (app checks first two parts are same as below) 175 | const hdPath = [44, 714, 0, 0, 0] 176 | 177 | // get public key 178 | let pk 179 | try { 180 | pk = (await app.getPublicKey(hdPath)).pk 181 | console.log("public key", pk) 182 | } catch ({ message, statusCode }) { 183 | console.error("pk error", message, statusCode) 184 | } 185 | 186 | // get address from pubkey 187 | const address = sdk.crypto.getAddressFromPublicKey(pk) 188 | console.log("address", address) 189 | 190 | // txMsg can be a string, as it is passed to Buffer.from(txMsg) - default encoding is `utf8` 191 | const txMsg = `{"account_number":1,"chain_id":"bnbchain","data":null,"memo":"memo","msgs":["msg"],"sequence":1,"source":1}` 192 | 193 | // sign a tx 194 | try { 195 | const address = await app.showAddress() 196 | const signature = (await app.sign(txMsg, hdPath)).signature 197 | console.log("signature", signature) 198 | } catch ({ message, statusCode }) { 199 | console.error("sign error", message, statusCode) 200 | } 201 | })() 202 | ``` 203 | -------------------------------------------------------------------------------- /src/amino/encoder/index.ts: -------------------------------------------------------------------------------- 1 | import is from "is-it-check" 2 | import { string as VarString } from "protocol-buffers-encodings" 3 | 4 | import typeToTyp3 from "../../utils/encoderHelper" 5 | 6 | import { UVarInt } from "./varint" 7 | 8 | const sortObject = (obj: any): any => { 9 | if (obj === null) return null 10 | if (typeof obj !== "object") return obj 11 | // arrays have typeof "object" in js! 12 | if (Array.isArray(obj)) return obj.map(sortObject) 13 | const sortedKeys = Object.keys(obj).sort() 14 | const result: any = {} 15 | sortedKeys.forEach((key) => { 16 | result[key] = sortObject(obj[key]) 17 | }) 18 | return result 19 | } 20 | 21 | /** 22 | * encode number 23 | * @category amino 24 | * @param num 25 | */ 26 | export const encodeNumber = (num: number) => UVarInt.encode(num) 27 | 28 | /** 29 | * encode bool 30 | * @category amino 31 | * @param b 32 | */ 33 | export const encodeBool = (b: boolean) => 34 | b ? UVarInt.encode(1) : UVarInt.encode(0) 35 | 36 | /** 37 | * encode string 38 | * @category amino 39 | * @param str 40 | */ 41 | export const encodeString = (str: string) => { 42 | const buf = Buffer.alloc(VarString.encodingLength(str)) 43 | return VarString.encode(str, buf, 0) 44 | } 45 | 46 | /** 47 | * encode time 48 | * @category amino 49 | * @param value 50 | */ 51 | export const encodeTime = (value: string | Date) => { 52 | const millis = new Date(value).getTime() 53 | const seconds = Math.floor(millis / 1000) 54 | const nanos = Number(seconds.toString().padEnd(9, "0")) 55 | 56 | const buffer = Buffer.alloc(14) 57 | 58 | // buffer[0] = (1 << 3) | 1 // field 1, typ3 1 59 | buffer.writeInt32LE((1 << 3) | 1, 0) 60 | buffer.writeUInt32LE(seconds, 1) 61 | 62 | // buffer[9] = (2 << 3) | 5 // field 2, typ3 5 63 | buffer.writeInt32LE((2 << 3) | 5, 9) 64 | buffer.writeUInt32LE(nanos, 10) 65 | 66 | return buffer 67 | } 68 | 69 | /** 70 | * @category amino 71 | * @param obj -- {object} 72 | * @return bytes {Buffer} 73 | */ 74 | export const convertObjectToSignBytes = (obj: any) => 75 | Buffer.from(JSON.stringify(sortObject(obj))) 76 | 77 | /** 78 | * js amino MarshalBinary 79 | * @category amino 80 | * @param {Object} obj 81 | * */ 82 | export const marshalBinary = (obj: any) => { 83 | if (!is.object(obj)) throw new TypeError("data must be an object") 84 | 85 | return encodeBinary(obj, -1, true).toString("hex") 86 | } 87 | 88 | /** 89 | * js amino MarshalBinaryBare 90 | * @category amino 91 | * @param {Object} obj 92 | * */ 93 | export const marshalBinaryBare = (obj: any) => { 94 | if (!is.object(obj)) throw new TypeError("data must be an object") 95 | 96 | return encodeBinary(obj).toString("hex") 97 | } 98 | 99 | /** 100 | * This is the main entrypoint for encoding all types in binary form. 101 | * @category amino 102 | * @param {*} js data type (not null, not undefined) 103 | * @param {Number} field index of object 104 | * @param {Boolean} isByteLenPrefix 105 | * @return {Buffer} binary of object. 106 | */ 107 | export const encodeBinary = ( 108 | val: any, 109 | fieldNum?: number, 110 | isByteLenPrefix?: boolean 111 | ) => { 112 | if (val === null || val === undefined) throw new TypeError("unsupported type") 113 | 114 | if (Buffer.isBuffer(val)) { 115 | if (isByteLenPrefix) { 116 | return Buffer.concat([UVarInt.encode(val.length), val]) 117 | } 118 | return val 119 | } 120 | 121 | if (is.array(val)) { 122 | return encodeArrayBinary(fieldNum, val, isByteLenPrefix) 123 | } 124 | 125 | if (is.number(val)) { 126 | return encodeNumber(val) 127 | } 128 | 129 | if (is.boolean(val)) { 130 | return encodeBool(val) 131 | } 132 | 133 | if (is.string(val)) { 134 | return encodeString(val) 135 | } 136 | 137 | if (is.object(val)) { 138 | return encodeObjectBinary(val, isByteLenPrefix) 139 | } 140 | 141 | return 142 | } 143 | 144 | /** 145 | * prefixed with bytes length 146 | * @category amino 147 | * @param {Buffer} bytes 148 | * @return {Buffer} with bytes length prefixed 149 | */ 150 | export const encodeBinaryByteArray = (bytes: Buffer) => { 151 | const lenPrefix = bytes.length 152 | return Buffer.concat([UVarInt.encode(lenPrefix), bytes]) 153 | } 154 | 155 | /** 156 | * @category amino 157 | * @param {Object} obj 158 | * @return {Buffer} with bytes length prefixed 159 | */ 160 | export const encodeObjectBinary = (obj: any, isByteLenPrefix?: boolean) => { 161 | const bufferArr: any[] = [] 162 | 163 | Object.keys(obj).forEach((key, index) => { 164 | if (key === "aminoPrefix" || key === "version") return 165 | 166 | if (isDefaultValue(obj[key])) return 167 | 168 | if (is.array(obj[key]) && obj[key].length > 0) { 169 | bufferArr.push(encodeArrayBinary(index, obj[key])) 170 | } else { 171 | bufferArr.push(encodeTypeAndField(index, obj[key])) 172 | bufferArr.push(encodeBinary(obj[key], index, true)) 173 | } 174 | }) 175 | 176 | let bytes = Buffer.concat(bufferArr) 177 | 178 | // add prefix 179 | if (obj.aminoPrefix) { 180 | const prefix = Buffer.from(obj.aminoPrefix, "hex") 181 | bytes = Buffer.concat([prefix, bytes]) 182 | } 183 | 184 | // Write byte-length prefixed. 185 | if (isByteLenPrefix) { 186 | const lenBytes = UVarInt.encode(bytes.length) 187 | bytes = Buffer.concat([lenBytes, bytes]) 188 | } 189 | 190 | return bytes 191 | } 192 | 193 | /** 194 | * @category amino 195 | * @param {Number} fieldNum object field index 196 | * @param {Array} arr 197 | * @param {Boolean} isByteLenPrefix 198 | * @return {Buffer} bytes of array 199 | */ 200 | export const encodeArrayBinary = ( 201 | fieldNum: number | undefined, 202 | arr: any[], 203 | isByteLenPrefix?: boolean 204 | ) => { 205 | const result: any[] = [] 206 | 207 | arr.forEach((item) => { 208 | result.push(encodeTypeAndField(fieldNum, item)) 209 | 210 | if (isDefaultValue(item)) { 211 | result.push(Buffer.from("00", "hex")) 212 | return 213 | } 214 | 215 | result.push(encodeBinary(item, fieldNum, true)) 216 | }) 217 | 218 | //encode length 219 | if (isByteLenPrefix) { 220 | const length = result.reduce((prev, item) => prev + item.length, 0) 221 | result.unshift(UVarInt.encode(length)) 222 | } 223 | 224 | return Buffer.concat(result) 225 | } 226 | 227 | // Write field key. 228 | const encodeTypeAndField = (index: number | undefined, field: any) => { 229 | index = Number(index) 230 | const value = ((index + 1) << 3) | typeToTyp3(field) 231 | return UVarInt.encode(value) 232 | } 233 | 234 | const isDefaultValue = (obj: any) => { 235 | if (obj === null) return false 236 | 237 | return ( 238 | (is.number(obj) && obj === 0) || 239 | (is.string(obj) && obj === "") || 240 | (is.array(obj) && obj.length === 0) || 241 | (is.boolean(obj) && !obj) 242 | ) 243 | } 244 | 245 | export * from "./varint" 246 | -------------------------------------------------------------------------------- /__tests__/rpc.test.ts: -------------------------------------------------------------------------------- 1 | import rpcClient from "../src/rpc" 2 | 3 | const NETWORK = "testnet" 4 | const getClient = (type) => { 5 | let uri = "https://data-seed-pre-0-s3.binance.org" 6 | if (type === "wss") { 7 | uri = "wss://data-seed-pre-0-s3.binance.org" 8 | } 9 | return new rpcClient(uri, NETWORK) 10 | } 11 | 12 | const address = "tbnb1cyl8v7mzh9s9gx5q9e5q0jpq7njlfpy53f2nrn" 13 | const symbol = "BNB" 14 | const tradePair = "BNB_USDT.B-B7C" 15 | 16 | describe("rpc", () => { 17 | beforeEach(() => { 18 | jest.setTimeout(50000) 19 | }) 20 | 21 | it("rest status", async () => { 22 | const client = getClient("https") 23 | const res = await client.status() 24 | expect(res).toBeTruthy() 25 | expect(res.node_info.network).toBe("Binance-Chain-Ganges") 26 | }) 27 | 28 | it("wss status", async () => { 29 | const client = getClient("wss") 30 | const res = await client.status() 31 | expect(res).toBeTruthy() 32 | expect(res.node_info.network).toBe("Binance-Chain-Ganges") 33 | client.close() 34 | }) 35 | 36 | it("rest net_info", async () => { 37 | const client = getClient("https") 38 | const res = await client.netInfo() 39 | expect(res).toBeTruthy() 40 | expect(res.peers.length).toBeGreaterThanOrEqual(0) 41 | }) 42 | 43 | it("wss net_info", async () => { 44 | const client = getClient("wss") 45 | const res = await client.netInfo() 46 | expect(res).toBeTruthy() 47 | expect(res.peers.length).toBeGreaterThanOrEqual(0) 48 | client.close() 49 | }) 50 | 51 | it("rest getTokenInfo", async () => { 52 | const client = getClient("https") 53 | const symbol = "MINT-04F" // mint is true 54 | const res = await client.getTokenInfo(symbol) 55 | expect(res.symbol).toBe(symbol) 56 | }) 57 | 58 | it("wss getTokenInfo", async () => { 59 | const client = getClient("wss") 60 | const symbol = "BNB" // mint is false 61 | const res = await client.getTokenInfo(symbol) 62 | expect(res.symbol).toBe(symbol) 63 | client.close() 64 | }) 65 | 66 | it("rest listAllTokens", async () => { 67 | const client = getClient("https") 68 | const res = await client.listAllTokens(0, 15) 69 | expect(res).toBeTruthy() 70 | expect(res.length).toBe(15) 71 | }) 72 | 73 | it("wss listAllTokens", async () => { 74 | const client = getClient("https") 75 | const res = await client.listAllTokens(2, 2) 76 | expect(res).toBeTruthy() 77 | expect(res.length).toBe(2) 78 | client.close() 79 | }) 80 | 81 | it("rest txSearch", async () => { 82 | const client = getClient("https") 83 | const params = { 84 | query: "tx.height=8669273", 85 | prove: true, 86 | page: 1, 87 | perPage: 10, 88 | } 89 | 90 | const result = await client.txSearch(params) 91 | expect(result.txs).toBeTruthy() 92 | }) 93 | 94 | it("wss txSearch", async () => { 95 | const client = getClient("https") 96 | const params = { 97 | query: "tx.height=8669273", 98 | prove: true, 99 | page: 1, 100 | perPage: 10, 101 | } 102 | 103 | const result = await client.txSearch(params) 104 | expect(result.txs).toBeTruthy() 105 | client.close() 106 | }) 107 | 108 | it("https txHash", async () => { 109 | const client = getClient("https") 110 | const params = { 111 | hash: Buffer.from( 112 | "41EB40A5E21D4946BECD922426EDE4789A07384D446A90C499F93344B3B2659B", 113 | "hex" 114 | ), 115 | prove: true, 116 | } 117 | const result = await client.tx(params) 118 | expect(result.tx_result).toBeTruthy() 119 | client.close() 120 | }) 121 | 122 | it("rest getAccount", async () => { 123 | const client = getClient("https") 124 | const result = await client.getAccount(address) 125 | expect(result.base.address).toBe(address) 126 | }) 127 | 128 | it("wss getAccount", async () => { 129 | const client = getClient("https") 130 | const result = await client.getAccount(address) 131 | expect(result.base.address).toBe(address) 132 | client.close() 133 | }) 134 | 135 | it("rest getBalances", async () => { 136 | const client = getClient("https") 137 | const result = await client.getBalances(address) 138 | expect(result).toBeTruthy() 139 | expect(result.length).toBeGreaterThanOrEqual(0) 140 | }) 141 | 142 | it("only rest getBalance", async () => { 143 | const client = getClient("https") 144 | const result = await client.getBalance(address, symbol) 145 | if (result) { 146 | expect( 147 | result.free + result.locked + result.frozen 148 | ).toBeGreaterThanOrEqual(0) 149 | } 150 | }) 151 | 152 | it("rest getOpenOrders", async () => { 153 | const client = getClient("https") 154 | const result = await client.getOpenOrders(address, tradePair) 155 | expect(result).toBeTruthy() 156 | expect(result.length).toBeGreaterThanOrEqual(0) 157 | }) 158 | 159 | it("wss getOpenOrders", async () => { 160 | const client = getClient("https") 161 | const result = await client.getOpenOrders(address, tradePair) 162 | expect(result).toBeTruthy() 163 | expect(result.length).toBeGreaterThanOrEqual(0) 164 | client.close() 165 | }) 166 | 167 | it("rest getTradingPairs", async () => { 168 | const client = getClient("https") 169 | const result = await client.getTradingPairs(0, 2) 170 | expect(result).toBeTruthy() 171 | expect(result.length).toBe(2) 172 | }) 173 | 174 | it("wss getTradingPairs", async () => { 175 | const client = getClient("https") 176 | const result = await client.getTradingPairs(1, 2) 177 | expect(result).toBeTruthy() 178 | expect(result.length).toBe(2) 179 | client.close() 180 | }) 181 | 182 | it("rest getDepth", async () => { 183 | const client = getClient("https") 184 | const result = await client.getDepth(tradePair) 185 | expect(result).toBeTruthy() 186 | expect(result.height).toBeTruthy() 187 | expect(result.levels.length).toBeGreaterThanOrEqual(0) 188 | }) 189 | 190 | it("wss getDepth", async () => { 191 | const client = getClient("https") 192 | const result = await client.getDepth(tradePair) 193 | expect(result).toBeTruthy() 194 | expect(result.height).toBeTruthy() 195 | expect(result.levels.length).toBeGreaterThanOrEqual(0) 196 | client.close() 197 | }) 198 | 199 | it("subscribe", async () => { 200 | const client = getClient("wss") 201 | await new Promise((resolve) => { 202 | client.subscribe({ query: "tm.event = 'CompleteProposal'" }, (events) => { 203 | resolve(events) 204 | expect(events).toBeTruthy() 205 | expect(events.step).toBe("RoundStepPropose") 206 | }) 207 | }) 208 | }) 209 | 210 | it("getTxByHash", async () => { 211 | const client = getClient("https") 212 | const hashStr = 213 | "5E0C54841CF0261EE111EB6B024762DB200F3FA59397967AB4BC641154C0B789" 214 | const result = await client.getTxByHash(hashStr, true) 215 | // console.log(JSON.stringify(result)) 216 | expect(result).toBeTruthy() 217 | expect(result.height).toBeTruthy() 218 | expect(result.hash).toBe(hashStr) 219 | }) 220 | }) 221 | -------------------------------------------------------------------------------- /src/types/rpc/index.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from "../msg" 2 | import { AminoPrefix } from "../tx" 3 | 4 | export class Token { 5 | public aminoPrefix = AminoPrefix.BnbchainToken 6 | 7 | public name: string 8 | public symbol: string 9 | public original_symbol: string 10 | public total_supply: number 11 | public owner: Buffer 12 | public mintable: boolean 13 | 14 | constructor( 15 | opts: Partial<{ 16 | name: string 17 | symbol: string 18 | original_symbol: string 19 | total_supply: number 20 | owner: Buffer 21 | mintable: boolean 22 | }> = {} 23 | ) { 24 | this.name = opts.name || "" 25 | this.symbol = opts.symbol || "" 26 | this.original_symbol = opts.original_symbol || "" 27 | this.total_supply = opts.total_supply || 0 28 | this.owner = opts.owner || Buffer.alloc(0) 29 | this.mintable = opts.mintable || false 30 | } 31 | } 32 | 33 | export class TokenOfList { 34 | public name: string 35 | public symbol: string 36 | public original_symbol: string 37 | public total_supply: number 38 | public owner: Buffer 39 | public mintable: boolean 40 | 41 | constructor( 42 | opts: Partial<{ 43 | name: string 44 | symbol: string 45 | original_symbol: string 46 | total_supply: number 47 | owner: Buffer 48 | mintable: boolean 49 | }> = {} 50 | ) { 51 | this.name = opts.name || "" 52 | this.symbol = opts.symbol || "" 53 | this.original_symbol = opts.original_symbol || "" 54 | this.total_supply = opts.total_supply || 0 55 | this.owner = opts.owner || Buffer.alloc(0) 56 | this.mintable = opts.mintable || false 57 | } 58 | } 59 | 60 | export class BaseAccount { 61 | public address: Buffer 62 | public coins: Coin[] 63 | public public_key: Buffer 64 | public account_number: number 65 | public sequence: number 66 | 67 | constructor( 68 | opts: Partial<{ 69 | address: Buffer 70 | coins: Coin[] 71 | public_key: Buffer 72 | account_number: number 73 | sequence: number 74 | }> = {} 75 | ) { 76 | this.address = opts.address || Buffer.alloc(0) 77 | this.coins = opts.coins || [{ denom: "", amount: 0 }] 78 | this.public_key = opts.public_key || Buffer.alloc(0) 79 | this.account_number = opts.account_number || 0 80 | this.sequence = opts.sequence || 0 81 | } 82 | } 83 | 84 | export class AppAccount { 85 | public aminoPrefix = AminoPrefix.BnbchainAccount 86 | 87 | public base: BaseAccount 88 | public name: string 89 | public locked: Coin[] 90 | public frozen: Coin[] 91 | 92 | constructor( 93 | opts: Partial<{ 94 | base: BaseAccount 95 | name: string 96 | locked: Coin[] 97 | frozen: Coin[] 98 | }> = {} 99 | ) { 100 | this.base = opts.base || new BaseAccount() 101 | this.name = opts.name || "" 102 | this.locked = opts.locked || [{ denom: "", amount: 0 }] 103 | this.frozen = opts.frozen || [{ denom: "", amount: 0 }] 104 | } 105 | } 106 | 107 | export class TokenBalance { 108 | public symbol: string 109 | public free: number 110 | public locked: number 111 | public frozen: number 112 | 113 | constructor( 114 | opts: Partial<{ 115 | symbol: string 116 | free: number 117 | locked: number 118 | frozen: number 119 | }> = {} 120 | ) { 121 | this.symbol = opts.symbol || "" 122 | this.free = opts.free || 0 123 | this.locked = opts.locked || 0 124 | this.frozen = opts.frozen || 0 125 | } 126 | } 127 | 128 | export class OpenOrder { 129 | public id: string 130 | public symbol: string 131 | public price: number 132 | public quantity: number 133 | public cumQty: number 134 | public createdHeight: number 135 | public createdTimestamp: number 136 | public lastUpdatedHeight: number 137 | public lastUpdatedTimestamp: number 138 | 139 | constructor( 140 | opts: Partial<{ 141 | id: string 142 | symbol: string 143 | price: number 144 | quantity: number 145 | cumQty: number 146 | createdHeight: number 147 | createdTimestamp: number 148 | lastUpdatedHeight: number 149 | lastUpdatedTimestamp: number 150 | }> = {} 151 | ) { 152 | this.id = opts.id || "" 153 | this.symbol = opts.symbol || "" 154 | this.price = opts.price || 0 155 | this.quantity = opts.quantity || 0 156 | this.cumQty = opts.cumQty || 0 157 | this.createdHeight = opts.createdHeight || 0 158 | this.createdTimestamp = opts.createdTimestamp || 0 159 | this.lastUpdatedHeight = opts.lastUpdatedHeight || 0 160 | this.lastUpdatedTimestamp = opts.lastUpdatedTimestamp || 0 161 | } 162 | } 163 | 164 | export class TradingPair { 165 | public base_asset_symbol: string 166 | public quote_asset_symbol: string 167 | public list_price: number 168 | public tick_size: number 169 | public lot_size: number 170 | 171 | constructor( 172 | opts: Partial<{ 173 | base_asset_symbol: string 174 | quote_asset_symbol: string 175 | list_price: number 176 | tick_size: number 177 | lot_size: number 178 | }> = {} 179 | ) { 180 | this.base_asset_symbol = opts.base_asset_symbol || "" 181 | this.quote_asset_symbol = opts.quote_asset_symbol || "" 182 | this.list_price = opts.list_price || 0 183 | this.tick_size = opts.tick_size || 0 184 | this.lot_size = opts.lot_size || 0 185 | } 186 | } 187 | 188 | export class OrderBookLevel { 189 | public buyQty: number 190 | public buyPrice: number 191 | public sellQty: number 192 | public sellPrice: number 193 | 194 | constructor( 195 | opts: Partial<{ 196 | buyQty: number 197 | buyPrice: number 198 | sellQty: number 199 | sellPrice: number 200 | }> = {} 201 | ) { 202 | this.buyQty = opts.buyQty || 0 203 | this.buyPrice = opts.buyPrice || 0 204 | this.sellQty = opts.sellQty || 0 205 | this.sellPrice = opts.sellPrice || 0 206 | } 207 | } 208 | 209 | export class OrderBook { 210 | public height: number 211 | public levels: OrderBookLevel[] 212 | 213 | constructor( 214 | opts: Partial<{ height: number; levels: OrderBookLevel[] }> = {} 215 | ) { 216 | this.height = opts.height || 0 217 | this.levels = opts.levels || [new OrderBookLevel()] 218 | } 219 | } 220 | 221 | export class SubmitProposalMsg { 222 | public aminoPrefix = AminoPrefix.MsgSubmitProposal 223 | 224 | public title: string 225 | public description: string 226 | public proposal_type: number 227 | public proposer: Buffer 228 | public initial_deposit: number[] 229 | public voting_period: number 230 | 231 | constructor( 232 | opts: Partial<{ 233 | title: string 234 | description: string 235 | proposal_type: number 236 | proposer: Buffer 237 | initial_deposit: number[] 238 | voting_period: number 239 | }> = {} 240 | ) { 241 | opts = opts || {} 242 | this.title = opts.title || "" 243 | this.description = opts.description || "" 244 | this.proposal_type = opts.proposal_type || 0 245 | this.proposer = opts.proposer || Buffer.alloc(0) 246 | this.initial_deposit = opts.initial_deposit || [] 247 | this.voting_period = opts.voting_period || 0 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/client/gov/index.ts: -------------------------------------------------------------------------------- 1 | import Big, { BigSource } from "big.js" 2 | 3 | import { BncClient } from ".." // This is a circular dependecy; should be changed to `import type` asap 4 | import * as crypto from "../../crypto" 5 | import { Coin, AminoPrefix } from "../../types" 6 | import { checkCoins } from "../../utils/validateHelper" 7 | 8 | import proposalType from "./proposalType" 9 | 10 | const BASENUMBER = Math.pow(10, 8) 11 | 12 | const proposalTypeMapping = { 13 | 0x04: "ListTradingPair", 14 | 0x00: "Nil", 15 | 0x01: "Text", 16 | 0x02: "ParameterChange", 17 | 0x03: "SoftwareUpgrade", 18 | 0x05: "FeeChange", 19 | 0x06: "CreateValidator", 20 | 0x07: "RemoveValidator", 21 | } as const 22 | 23 | /** 24 | * VoteOption 25 | * @ignore 26 | * @example 27 | * OptionEmpty - 0x00 28 | * OptionYes - 0x01 29 | * OptionAbstain - 0x02 30 | * OptionNo - 0x03 31 | * OptionNoWithVeto - 0x04 32 | */ 33 | export const voteOption = { 34 | OptionEmpty: 0x00, 35 | OptionYes: 0x01, 36 | OptionAbstain: 0x02, 37 | OptionNo: 0x03, 38 | OptionNoWithVeto: 0x04, 39 | } as const 40 | 41 | const voteOptionMapping = { 42 | 0x00: "Empty", 43 | 0x01: "Yes", 44 | 0x02: "Abstain", 45 | 0x03: "No", 46 | 0x04: "NoWithVeto", 47 | } 48 | 49 | class Gov { 50 | static instance: Gov 51 | private _bncClient!: BncClient 52 | 53 | /** 54 | * @param {Object} bncClient 55 | */ 56 | constructor(bncClient: BncClient) { 57 | if (!Gov.instance) { 58 | this._bncClient = bncClient 59 | Gov.instance = this 60 | } 61 | 62 | return Gov.instance 63 | } 64 | 65 | /** 66 | * Submit a list proposal along with an initial deposit 67 | * @param {Object} listParams 68 | * @example 69 | * var listParams = { 70 | * title: 'New trading pair', 71 | * description: '', 72 | * baseAsset: 'BTC', 73 | * quoteAsset: 'BNB', 74 | * initPrice: 1, 75 | * address: '', 76 | * initialDeposit: 2000, 77 | * expireTime: 1570665600, 78 | * votingPeriod: 604800 79 | * } 80 | */ 81 | async submitListProposal(listParams: { 82 | baseAsset: string 83 | quoteAsset: string 84 | initPrice: BigSource 85 | description: string 86 | expireTime: string 87 | address: string 88 | title: string 89 | initialDeposit: BigSource 90 | votingPeriod: BigSource 91 | }) { 92 | const listTradingPairObj = { 93 | base_asset_symbol: listParams.baseAsset, 94 | quote_asset_symbol: listParams.quoteAsset, 95 | init_price: +new Big(listParams.initPrice).mul(BASENUMBER).toString(), 96 | description: listParams.description, 97 | expire_time: new Date(listParams.expireTime).toISOString(), 98 | } 99 | 100 | const description = JSON.stringify(listTradingPairObj) 101 | const { address, title, initialDeposit, votingPeriod } = listParams 102 | return await this.submitProposal( 103 | address, 104 | title, 105 | description, 106 | proposalType.ProposalTypeListTradingPair, 107 | initialDeposit, 108 | votingPeriod 109 | ) 110 | } 111 | 112 | /** 113 | * Submit a proposal along with an initial deposit. 114 | * Proposal title, description, type and deposit can 115 | * be given directly or through a proposal JSON file. 116 | * @param {String} address 117 | * @param {String} title 118 | * @param {String} description 119 | * @param {Number} proposalType 120 | * @param {Number} initialDeposit 121 | * @param {String} votingPeriod 122 | * @return {Promise} resolves with response (success or fail) 123 | */ 124 | async submitProposal( 125 | address: string, 126 | title: string, 127 | description: string, 128 | proposalType: keyof typeof proposalTypeMapping, 129 | initialDeposit: BigSource, 130 | votingPeriod: BigSource 131 | ) { 132 | const accAddress = crypto.decodeAddress(address) 133 | const coins = [ 134 | { 135 | denom: "BNB", 136 | amount: new Big(initialDeposit).mul(BASENUMBER).toString(), 137 | }, 138 | ] 139 | 140 | votingPeriod = +new Big(votingPeriod).mul(10 ** 9).toString() 141 | 142 | const proposalMsg = { 143 | title, 144 | description, 145 | proposal_type: proposalType, 146 | proposer: accAddress, 147 | initial_deposit: [ 148 | { 149 | denom: "BNB", 150 | amount: +new Big(initialDeposit).mul(BASENUMBER).toString(), 151 | }, 152 | ], 153 | voting_period: votingPeriod, 154 | aminoPrefix: AminoPrefix.MsgSubmitProposal, 155 | } 156 | 157 | const signMsg = { 158 | description, 159 | initial_deposit: coins, 160 | proposal_type: proposalTypeMapping[proposalType], 161 | proposer: address, 162 | title, 163 | voting_period: votingPeriod.toString(), 164 | } 165 | 166 | const signedTx = await this._bncClient._prepareTransaction( 167 | proposalMsg, 168 | signMsg, 169 | address 170 | ) 171 | return this._bncClient._broadcastDelegate(signedTx) 172 | } 173 | 174 | /** 175 | * Deposit tokens for activing proposal 176 | * @param {Number} proposalId 177 | * @param {String} address 178 | * @param {Array} coins 179 | * @example 180 | * var coins = [{ 181 | * "denom": "BNB", 182 | * "amount": 10 183 | * }] 184 | */ 185 | async deposit(proposalId: number, address: string, coins: Coin[]) { 186 | const accAddress = crypto.decodeAddress(address) 187 | 188 | checkCoins(coins) 189 | 190 | const amount: Coin[] = [] 191 | coins.forEach((coin) => { 192 | amount.push({ 193 | denom: coin.denom, 194 | amount: +new Big(coin.amount).mul(BASENUMBER).toString(), 195 | }) 196 | }) 197 | 198 | const depositMsg = { 199 | proposal_id: proposalId, 200 | depositer: accAddress, 201 | amount, 202 | aminoPrefix: AminoPrefix.MsgDeposit, 203 | } 204 | 205 | const signMsg = { 206 | amount: amount.map((coin) => ({ 207 | denom: coin.denom, 208 | amount: String(coin.amount), 209 | })), 210 | depositer: address, 211 | proposal_id: String(proposalId), 212 | } 213 | 214 | const signedTx = await this._bncClient._prepareTransaction( 215 | depositMsg, 216 | signMsg, 217 | address 218 | ) 219 | return this._bncClient._broadcastDelegate(signedTx) 220 | } 221 | 222 | /** 223 | * 224 | * @param {Number} proposalId 225 | * @param {String} voter 226 | * @param {VoteOption} option 227 | */ 228 | async vote( 229 | proposalId: number, 230 | voter: string, 231 | option: keyof typeof voteOptionMapping 232 | ) { 233 | const accAddress = crypto.decodeAddress(voter) 234 | 235 | const voteMsg = { 236 | proposal_id: proposalId, 237 | voter: accAddress, 238 | option, 239 | aminoPrefix: AminoPrefix.MsgVote, 240 | } 241 | 242 | const signMsg = { 243 | option: voteOptionMapping[option], 244 | proposal_id: String(proposalId), 245 | voter, 246 | } 247 | 248 | const signedTx = await this._bncClient._prepareTransaction( 249 | voteMsg, 250 | signMsg, 251 | voter 252 | ) 253 | return this._bncClient._broadcastDelegate(signedTx) 254 | } 255 | } 256 | 257 | export default Gov 258 | --------------------------------------------------------------------------------