From 8048e0ab189cbaea21232c032e364cd7374b0def Mon Sep 17 00:00:00 2001 From: Hitansh Madan Date: Wed, 11 Oct 2023 11:57:22 +0530 Subject: [PATCH] feat(sdk-coin-islm): add Islamic Coin Ticket: WIN-490 --- Dockerfile | 3 + modules/account-lib/package.json | 1 + modules/account-lib/src/index.ts | 5 + modules/account-lib/tsconfig.json | 3 + modules/bitgo/package.json | 3 +- modules/bitgo/src/v2/coinFactory.ts | 6 +- modules/bitgo/src/v2/coins/index.ts | 2 + modules/bitgo/tsconfig.json | 3 + modules/sdk-coin-islm/.eslintignore | 5 + modules/sdk-coin-islm/.gitignore | 3 + modules/sdk-coin-islm/.mocharc.yml | 8 + modules/sdk-coin-islm/.npmignore | 14 ++ modules/sdk-coin-islm/.prettierignore | 2 + modules/sdk-coin-islm/.prettierrc.yml | 3 + modules/sdk-coin-islm/README.md | 30 +++ modules/sdk-coin-islm/package.json | 60 ++++++ modules/sdk-coin-islm/resources/README.md | 15 ++ .../crypto/v1/ethsecp256k1/ethSecp256k1.proto | 23 +++ .../resources/proto/gogoproto/gogo.proto | 145 ++++++++++++++ .../resources/types/ethSecp256k1.ts | 183 ++++++++++++++++++ modules/sdk-coin-islm/src/index.ts | 4 + modules/sdk-coin-islm/src/islm.ts | 72 +++++++ modules/sdk-coin-islm/src/lib/constants.ts | 6 + modules/sdk-coin-islm/src/lib/index.ts | 10 + modules/sdk-coin-islm/src/lib/keyPair.ts | 19 ++ .../src/lib/transactionBuilderFactory.ts | 78 ++++++++ modules/sdk-coin-islm/src/lib/utils.ts | 104 ++++++++++ modules/sdk-coin-islm/src/register.ts | 8 + modules/sdk-coin-islm/src/tislm.ts | 25 +++ modules/sdk-coin-islm/tsconfig.json | 26 +++ modules/sdk-core/src/bitgo/environments.ts | 3 + tsconfig.packages.json | 3 + 32 files changed, 873 insertions(+), 2 deletions(-) create mode 100644 modules/sdk-coin-islm/.eslintignore create mode 100644 modules/sdk-coin-islm/.gitignore create mode 100644 modules/sdk-coin-islm/.mocharc.yml create mode 100644 modules/sdk-coin-islm/.npmignore create mode 100644 modules/sdk-coin-islm/.prettierignore create mode 100644 modules/sdk-coin-islm/.prettierrc.yml create mode 100644 modules/sdk-coin-islm/README.md create mode 100644 modules/sdk-coin-islm/package.json create mode 100644 modules/sdk-coin-islm/resources/README.md create mode 100644 modules/sdk-coin-islm/resources/proto/ethermint/crypto/v1/ethsecp256k1/ethSecp256k1.proto create mode 100644 modules/sdk-coin-islm/resources/proto/gogoproto/gogo.proto create mode 100644 modules/sdk-coin-islm/resources/types/ethSecp256k1.ts create mode 100644 modules/sdk-coin-islm/src/index.ts create mode 100644 modules/sdk-coin-islm/src/islm.ts create mode 100644 modules/sdk-coin-islm/src/lib/constants.ts create mode 100644 modules/sdk-coin-islm/src/lib/index.ts create mode 100644 modules/sdk-coin-islm/src/lib/keyPair.ts create mode 100644 modules/sdk-coin-islm/src/lib/transactionBuilderFactory.ts create mode 100644 modules/sdk-coin-islm/src/lib/utils.ts create mode 100644 modules/sdk-coin-islm/src/register.ts create mode 100644 modules/sdk-coin-islm/src/tislm.ts create mode 100644 modules/sdk-coin-islm/tsconfig.json diff --git a/Dockerfile b/Dockerfile index 693d80e9e5..0f11085a92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,6 +70,7 @@ COPY --from=builder /tmp/bitgo/modules/sdk-coin-eth2 /var/modules/sdk-coin-eth2/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-hash /var/modules/sdk-coin-hash/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-hbar /var/modules/sdk-coin-hbar/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-injective /var/modules/sdk-coin-injective/ +COPY --from=builder /tmp/bitgo/modules/sdk-coin-islm /var/modules/sdk-coin-islm/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-near /var/modules/sdk-coin-near/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-osmo /var/modules/sdk-coin-osmo/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-polygon /var/modules/sdk-coin-polygon/ @@ -131,6 +132,7 @@ cd /var/modules/sdk-coin-eth2 && yarn link && \ cd /var/modules/sdk-coin-hash && yarn link && \ cd /var/modules/sdk-coin-hbar && yarn link && \ cd /var/modules/sdk-coin-injective && yarn link && \ +cd /var/modules/sdk-coin-islm && yarn link && \ cd /var/modules/sdk-coin-near && yarn link && \ cd /var/modules/sdk-coin-osmo && yarn link && \ cd /var/modules/sdk-coin-polygon && yarn link && \ @@ -195,6 +197,7 @@ RUN cd /var/bitgo-express && \ yarn link @bitgo/sdk-coin-hash && \ yarn link @bitgo/sdk-coin-hbar && \ yarn link @bitgo/sdk-coin-injective && \ + yarn link @bitgo/sdk-coin-islm && \ yarn link @bitgo/sdk-coin-near && \ yarn link @bitgo/sdk-coin-osmo && \ yarn link @bitgo/sdk-coin-polygon && \ diff --git a/modules/account-lib/package.json b/modules/account-lib/package.json index 0c6f556f6e..396943114f 100644 --- a/modules/account-lib/package.json +++ b/modules/account-lib/package.json @@ -43,6 +43,7 @@ "@bitgo/sdk-coin-hash": "^1.4.11", "@bitgo/sdk-coin-hbar": "^1.5.11", "@bitgo/sdk-coin-injective": "^1.4.11", + "@bitgo/sdk-coin-islm": "^1.0.0", "@bitgo/sdk-coin-near": "^1.6.11", "@bitgo/sdk-coin-osmo": "^1.6.11", "@bitgo/sdk-coin-polygon": "^1.8.0", diff --git a/modules/account-lib/src/index.ts b/modules/account-lib/src/index.ts index 1482e21103..3ead0b6831 100644 --- a/modules/account-lib/src/index.ts +++ b/modules/account-lib/src/index.ts @@ -86,6 +86,9 @@ export { Sei }; import * as Injective from '@bitgo/sdk-coin-injective'; export { Injective }; +import * as Islm from '@bitgo/sdk-coin-islm'; +export { Islm }; + import * as Zeta from '@bitgo/sdk-coin-zeta'; export { Zeta }; @@ -165,6 +168,8 @@ const coinBuilderMap = { tzeta: Zeta.TransactionBuilderFactory, core: Core.TransactionBuilderFactory, tcore: Core.TransactionBuilderFactory, + islm: Islm.TransactionBuilderFactory, + tislm: Islm.TransactionBuilderFactory, }; /** diff --git a/modules/account-lib/tsconfig.json b/modules/account-lib/tsconfig.json index 49df6f6589..8862a24c21 100644 --- a/modules/account-lib/tsconfig.json +++ b/modules/account-lib/tsconfig.json @@ -55,6 +55,9 @@ { "path": "../sdk-coin-injective" }, + { + "path": "../sdk-coin-islm" + }, { "path": "../sdk-coin-near" }, diff --git a/modules/bitgo/package.json b/modules/bitgo/package.json index 6b9a8c7c5c..eec52eaa8e 100644 --- a/modules/bitgo/package.json +++ b/modules/bitgo/package.json @@ -61,6 +61,7 @@ "@bitgo/sdk-coin-btc": "^1.7.11", "@bitgo/sdk-coin-btg": "^1.5.11", "@bitgo/sdk-coin-celo": "^1.5.11", + "@bitgo/sdk-coin-core": "^1.0.0", "@bitgo/sdk-coin-cspr": "^1.5.0", "@bitgo/sdk-coin-dash": "^1.5.11", "@bitgo/sdk-coin-doge": "^1.12.11", @@ -73,7 +74,7 @@ "@bitgo/sdk-coin-hash": "^1.4.11", "@bitgo/sdk-coin-hbar": "^1.5.11", "@bitgo/sdk-coin-injective": "^1.4.11", - "@bitgo/sdk-coin-core": "^1.0.0", + "@bitgo/sdk-coin-islm": "^1.0.0", "@bitgo/sdk-coin-ltc": "^2.2.11", "@bitgo/sdk-coin-near": "^1.6.11", "@bitgo/sdk-coin-osmo": "^1.6.11", diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index e55ea146f1..a860b559d8 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -45,6 +45,7 @@ import { Hash, Hbar, Injective, + Islm, Ltc, Ofc, OfcToken, @@ -70,6 +71,7 @@ import { Tbsv, Tbtc, Tcelo, + Tcore, Tcspr, Tdash, Tdoge, @@ -85,7 +87,7 @@ import { Thbar, Tia, Tinjective, - Tcore, + Tislm, Tltc, Tosmo, Tpolygon, @@ -143,6 +145,7 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void { globalCoinFactory.register('hbar', Hbar.createInstance); globalCoinFactory.register('ltc', Ltc.createInstance); globalCoinFactory.register('injective', Injective.createInstance); + globalCoinFactory.register('islm', Islm.createInstance); globalCoinFactory.register('near', Near.createInstance); globalCoinFactory.register('ofc', Ofc.createInstance); globalCoinFactory.register('osmo', Osmo.createInstance); @@ -182,6 +185,7 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void { globalCoinFactory.register('thash', Thash.createInstance); globalCoinFactory.register('thbar', Thbar.createInstance); globalCoinFactory.register('tinjective', Tinjective.createInstance); + globalCoinFactory.register('tislm', Tislm.createInstance); globalCoinFactory.register('tltc', Tltc.createInstance); globalCoinFactory.register('tnear', TNear.createInstance); globalCoinFactory.register('tosmo', Tosmo.createInstance); diff --git a/modules/bitgo/src/v2/coins/index.ts b/modules/bitgo/src/v2/coins/index.ts index e28460697e..60e1427d06 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -25,6 +25,7 @@ import { Ethw } from '@bitgo/sdk-coin-ethw'; import { Hash, Thash } from '@bitgo/sdk-coin-hash'; import { Hbar, Thbar } from '@bitgo/sdk-coin-hbar'; import { Injective, Tinjective } from '@bitgo/sdk-coin-injective'; +import { Islm, Tislm } from '@bitgo/sdk-coin-islm'; import { Ltc, Tltc } from '@bitgo/sdk-coin-ltc'; import { Osmo, Tosmo } from '@bitgo/sdk-coin-osmo'; import { Polygon, PolygonToken, Tpolygon } from '@bitgo/sdk-coin-polygon'; @@ -78,6 +79,7 @@ export { Ton, Tton }; export { Bld, Tbld }; export { Sei, Tsei }; export { Injective, Tinjective }; +export { Islm, Tislm }; export { Trx, Ttrx }; export { StellarToken, Txlm, Xlm }; export { Txrp, Xrp }; diff --git a/modules/bitgo/tsconfig.json b/modules/bitgo/tsconfig.json index 014e122315..a0fc655028 100644 --- a/modules/bitgo/tsconfig.json +++ b/modules/bitgo/tsconfig.json @@ -113,6 +113,9 @@ { "path": "../sdk-coin-injective" }, + { + "path": "../sdk-coin-islm" + }, { "path": "../sdk-coin-ltc" }, diff --git a/modules/sdk-coin-islm/.eslintignore b/modules/sdk-coin-islm/.eslintignore new file mode 100644 index 0000000000..190f83e0df --- /dev/null +++ b/modules/sdk-coin-islm/.eslintignore @@ -0,0 +1,5 @@ +node_modules +.idea +public +dist + diff --git a/modules/sdk-coin-islm/.gitignore b/modules/sdk-coin-islm/.gitignore new file mode 100644 index 0000000000..67ccce4c64 --- /dev/null +++ b/modules/sdk-coin-islm/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.idea/ +dist/ diff --git a/modules/sdk-coin-islm/.mocharc.yml b/modules/sdk-coin-islm/.mocharc.yml new file mode 100644 index 0000000000..95814796d1 --- /dev/null +++ b/modules/sdk-coin-islm/.mocharc.yml @@ -0,0 +1,8 @@ +require: 'ts-node/register' +timeout: '60000' +reporter: 'min' +reporter-option: + - 'cdn=true' + - 'json=false' +exit: true +spec: ['test/unit/**/*.ts'] diff --git a/modules/sdk-coin-islm/.npmignore b/modules/sdk-coin-islm/.npmignore new file mode 100644 index 0000000000..d5fb3a098c --- /dev/null +++ b/modules/sdk-coin-islm/.npmignore @@ -0,0 +1,14 @@ +!dist/ +dist/test/ +dist/tsconfig.tsbuildinfo +.idea/ +.prettierrc.yml +tsconfig.json +src/ +test/ +scripts/ +.nyc_output +CODEOWNERS +node_modules/ +.prettierignore +.mocharc.js diff --git a/modules/sdk-coin-islm/.prettierignore b/modules/sdk-coin-islm/.prettierignore new file mode 100644 index 0000000000..3a11d6af29 --- /dev/null +++ b/modules/sdk-coin-islm/.prettierignore @@ -0,0 +1,2 @@ +.nyc_output/ +dist/ diff --git a/modules/sdk-coin-islm/.prettierrc.yml b/modules/sdk-coin-islm/.prettierrc.yml new file mode 100644 index 0000000000..7c3d8dd32a --- /dev/null +++ b/modules/sdk-coin-islm/.prettierrc.yml @@ -0,0 +1,3 @@ +printWidth: 120 +singleQuote: true +trailingComma: 'es5' diff --git a/modules/sdk-coin-islm/README.md b/modules/sdk-coin-islm/README.md new file mode 100644 index 0000000000..45d88d7605 --- /dev/null +++ b/modules/sdk-coin-islm/README.md @@ -0,0 +1,30 @@ +# BitGo sdk-coin-islm + +SDK coins provide a modular approach to a monolithic architecture. This and all BitGoJS SDK coins allow developers to use only the coins needed for a given project. + +## Installation + +All coins are loaded traditionally through the `bitgo` package. If you are using coins individually, you will be accessing the coin via the `@bitgo/sdk-api` package. + +In your project install both `@bitgo/sdk-api` and `@bitgo/sdk-coin-islm`. + +```shell +npm i @bitgo/sdk-api @bitgo/sdk-coin-islm +``` + +Next, you will be able to initialize an instance of "bitgo" through `@bitgo/sdk-api` instead of `bitgo`. + +```javascript +import { BitGoAPI } from '@bitgo/sdk-api'; +import { Islm } from '@bitgo/sdk-coin-islm'; + +const sdk = new BitGoAPI(); + +sdk.register('islm', Islm.createInstance); +``` + +## Development + +Most of the coin implementations are derived from `@bitgo/sdk-core`, `@bitgo/statics`, and coin specific packages. These implementations are used to interact with the BitGo API and BitGo platform services. + +You will notice that the basic version of common class extensions have been provided to you and must be resolved before the package build will succeed. Upon initiation of a given SDK coin, you will need to verify that your coin has been included in the root `tsconfig.packages.json` and that the linting, formatting, and testing succeeds when run both within the coin and from the root of BitGoJS. diff --git a/modules/sdk-coin-islm/package.json b/modules/sdk-coin-islm/package.json new file mode 100644 index 0000000000..021bb96af7 --- /dev/null +++ b/modules/sdk-coin-islm/package.json @@ -0,0 +1,60 @@ +{ + "name": "@bitgo/sdk-coin-islm", + "version": "1.0.0", + "description": "BitGo SDK coin library for Islm", + "main": "./dist/src/index.js", + "types": "./dist/src/index.d.ts", + "scripts": { + "build": "yarn tsc --build --incremental --verbose .", + "fmt": "prettier --write .", + "check-fmt": "prettier --check .", + "clean": "rm -r ./dist", + "lint": "eslint --quiet .", + "prepare": "npm run build", + "test": "npm run coverage", + "coverage": "nyc -- npm run unit-test", + "unit-test": "mocha" + }, + "author": "BitGo SDK Team ", + "license": "MIT", + "engines": { + "node": ">=16 <19" + }, + "repository": { + "type": "git", + "url": "https://github.com/BitGo/BitGoJS.git", + "directory": "modules/sdk-coin-islm" + }, + "lint-staged": { + "*.{js,ts}": [ + "yarn prettier --write", + "yarn eslint --fix" + ] + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "extension": [ + ".ts" + ] + }, + "dependencies": { + "@bitgo/abstract-cosmos": "^1.6.11", + "@bitgo/sdk-core": "^8.24.0", + "@bitgo/statics": "^27.0.0", + "@cosmjs/amino": "^0.29.5", + "@cosmjs/encoding": "^0.29.5", + "@cosmjs/proto-signing": "^0.29.5", + "@cosmjs/stargate": "^0.29.5", + "bignumber.js": "^9.1.1", + "cosmjs-types": "^0.6.1", + "ethers": "^5.7.2", + "keccak": "3.0.3", + "protobufjs": "^7.2.4" + }, + "devDependencies": { + "@bitgo/sdk-api": "^1.22.0", + "@bitgo/sdk-test": "^1.2.41" + } +} diff --git a/modules/sdk-coin-islm/resources/README.md b/modules/sdk-coin-islm/resources/README.md new file mode 100644 index 0000000000..579760c7b3 --- /dev/null +++ b/modules/sdk-coin-islm/resources/README.md @@ -0,0 +1,15 @@ +# Resources + +This directory contains external logic required to handle signing/validation/address derivation for +a specific coin. + +Typically, this manifests as a smaller snippet of code from a larger codebase, of which, this +library only requires a small portion of. + +--- + +## ISLM Resources + +Haqq Network (Islamic Coin) doesn't utilize the cosmos default `secp256k1` PubKey type & utilizes as different PubKey type `ethSecp256k1` exposed by ethermint. The protobuf files for `ethSecp256k1` are defined here : https://github.com/evmos/ethermint/blob/main/proto/ethermint/crypto/v1/ethsecp256k1/keys.proto + +This directory contains the generated TypeScript types required, and also contains the `.proto` files used to generate them. diff --git a/modules/sdk-coin-islm/resources/proto/ethermint/crypto/v1/ethsecp256k1/ethSecp256k1.proto b/modules/sdk-coin-islm/resources/proto/ethermint/crypto/v1/ethsecp256k1/ethSecp256k1.proto new file mode 100644 index 0000000000..7f4e46469e --- /dev/null +++ b/modules/sdk-coin-islm/resources/proto/ethermint/crypto/v1/ethsecp256k1/ethSecp256k1.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +package ethermint.crypto.v1.ethsecp256k1; + +import "gogoproto/gogo.proto"; + +option go_package = "github.com/evmos/ethermint/crypto/ethsecp256k1"; + +// PubKey defines a type alias for an ecdsa.PublicKey that implements +// Tendermint's PubKey interface. It represents the 33-byte compressed public +// key format. +message PubKey { + option (gogoproto.goproto_stringer) = false; + + // key is the public key in byte form + bytes key = 1; +} + +// PrivKey defines a type alias for an ecdsa.PrivateKey that implements +// Tendermint's PrivateKey interface. +message PrivKey { + // key is the private key in byte form + bytes key = 1; +} diff --git a/modules/sdk-coin-islm/resources/proto/gogoproto/gogo.proto b/modules/sdk-coin-islm/resources/proto/gogoproto/gogo.proto new file mode 100644 index 0000000000..974b36a7cc --- /dev/null +++ b/modules/sdk-coin-islm/resources/proto/gogoproto/gogo.proto @@ -0,0 +1,145 @@ +// Protocol Buffers for Go with Gadgets +// +// Copyright (c) 2013, The GoGo Authors. All rights reserved. +// http://github.com/cosmos/gogoproto +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto2"; +package gogoproto; + +import "google/protobuf/descriptor.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "GoGoProtos"; +option go_package = "github.com/cosmos/gogoproto/gogoproto"; + +extend google.protobuf.EnumOptions { + optional bool goproto_enum_prefix = 62001; + optional bool goproto_enum_stringer = 62021; + optional bool enum_stringer = 62022; + optional string enum_customname = 62023; + optional bool enumdecl = 62024; +} + +extend google.protobuf.EnumValueOptions { + optional string enumvalue_customname = 66001; +} + +extend google.protobuf.FileOptions { + optional bool goproto_getters_all = 63001; + optional bool goproto_enum_prefix_all = 63002; + optional bool goproto_stringer_all = 63003; + optional bool verbose_equal_all = 63004; + optional bool face_all = 63005; + optional bool gostring_all = 63006; + optional bool populate_all = 63007; + optional bool stringer_all = 63008; + optional bool onlyone_all = 63009; + + optional bool equal_all = 63013; + optional bool description_all = 63014; + optional bool testgen_all = 63015; + optional bool benchgen_all = 63016; + optional bool marshaler_all = 63017; + optional bool unmarshaler_all = 63018; + optional bool stable_marshaler_all = 63019; + + optional bool sizer_all = 63020; + + optional bool goproto_enum_stringer_all = 63021; + optional bool enum_stringer_all = 63022; + + optional bool unsafe_marshaler_all = 63023; + optional bool unsafe_unmarshaler_all = 63024; + + optional bool goproto_extensions_map_all = 63025; + optional bool goproto_unrecognized_all = 63026; + optional bool gogoproto_import = 63027; + optional bool protosizer_all = 63028; + optional bool compare_all = 63029; + optional bool typedecl_all = 63030; + optional bool enumdecl_all = 63031; + + optional bool goproto_registration = 63032; + optional bool messagename_all = 63033; + + optional bool goproto_sizecache_all = 63034; + optional bool goproto_unkeyed_all = 63035; +} + +extend google.protobuf.MessageOptions { + optional bool goproto_getters = 64001; + optional bool goproto_stringer = 64003; + optional bool verbose_equal = 64004; + optional bool face = 64005; + optional bool gostring = 64006; + optional bool populate = 64007; + optional bool stringer = 67008; + optional bool onlyone = 64009; + + optional bool equal = 64013; + optional bool description = 64014; + optional bool testgen = 64015; + optional bool benchgen = 64016; + optional bool marshaler = 64017; + optional bool unmarshaler = 64018; + optional bool stable_marshaler = 64019; + + optional bool sizer = 64020; + + optional bool unsafe_marshaler = 64023; + optional bool unsafe_unmarshaler = 64024; + + optional bool goproto_extensions_map = 64025; + optional bool goproto_unrecognized = 64026; + + optional bool protosizer = 64028; + optional bool compare = 64029; + + optional bool typedecl = 64030; + + optional bool messagename = 64033; + + optional bool goproto_sizecache = 64034; + optional bool goproto_unkeyed = 64035; +} + +extend google.protobuf.FieldOptions { + optional bool nullable = 65001; + optional bool embed = 65002; + optional string customtype = 65003; + optional string customname = 65004; + optional string jsontag = 65005; + optional string moretags = 65006; + optional string casttype = 65007; + optional string castkey = 65008; + optional string castvalue = 65009; + + optional bool stdtime = 65010; + optional bool stdduration = 65011; + optional bool wktpointer = 65012; + + optional string castrepeated = 65013; +} diff --git a/modules/sdk-coin-islm/resources/types/ethSecp256k1.ts b/modules/sdk-coin-islm/resources/types/ethSecp256k1.ts new file mode 100644 index 0000000000..3d24d5775a --- /dev/null +++ b/modules/sdk-coin-islm/resources/types/ethSecp256k1.ts @@ -0,0 +1,183 @@ +/* eslint-disable */ +import * as _m0 from 'protobufjs/minimal'; + +export const protobufPackage = 'ethermint.crypto.v1.ethsecp256k1'; + +/** + * PubKey defines a type alias for an ecdsa.PublicKey that implements + * Tendermint's PubKey interface. It represents the 33-byte compressed public + * key format. + */ +export interface PubKey { + /** key is the public key in byte form */ + key: Uint8Array; +} + +/** + * PrivKey defines a type alias for an ecdsa.PrivateKey that implements + * Tendermint's PrivateKey interface. + */ +export interface PrivKey { + /** key is the private key in byte form */ + key: Uint8Array; +} + +function createBasePubKey(): PubKey { + return { key: new Uint8Array(0) }; +} + +export const PubKey = { + encode(message: PubKey, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key.length !== 0) { + writer.uint32(10).bytes(message.key); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): PubKey { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePubKey(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.key = reader.bytes(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): PubKey { + return { key: isSet(object.key) ? bytesFromBase64(object.key) : new Uint8Array(0) }; + }, + + toJSON(message: PubKey): unknown { + const obj: any = {}; + if (message.key.length !== 0) { + obj.key = base64FromBytes(message.key); + } + return obj; + }, + + create, I>>(base?: I): PubKey { + return PubKey.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): PubKey { + const message = createBasePubKey(); + message.key = object.key ?? new Uint8Array(0); + return message; + }, +}; + +function createBasePrivKey(): PrivKey { + return { key: new Uint8Array(0) }; +} + +export const PrivKey = { + encode(message: PrivKey, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.key.length !== 0) { + writer.uint32(10).bytes(message.key); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): PrivKey { + const reader = input instanceof _m0.Reader ? input : _m0.Reader.create(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBasePrivKey(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + if (tag !== 10) { + break; + } + + message.key = reader.bytes(); + continue; + } + if ((tag & 7) === 4 || tag === 0) { + break; + } + reader.skipType(tag & 7); + } + return message; + }, + + fromJSON(object: any): PrivKey { + return { key: isSet(object.key) ? bytesFromBase64(object.key) : new Uint8Array(0) }; + }, + + toJSON(message: PrivKey): unknown { + const obj: any = {}; + if (message.key.length !== 0) { + obj.key = base64FromBytes(message.key); + } + return obj; + }, + + create, I>>(base?: I): PrivKey { + return PrivKey.fromPartial(base ?? ({} as any)); + }, + fromPartial, I>>(object: I): PrivKey { + const message = createBasePrivKey(); + message.key = object.key ?? new Uint8Array(0); + return message; + }, +}; + +function bytesFromBase64(b64: string): Uint8Array { + if (globalThis.Buffer) { + return Uint8Array.from(globalThis.Buffer.from(b64, 'base64')); + } else { + const bin = globalThis.atob(b64); + const arr = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; ++i) { + arr[i] = bin.charCodeAt(i); + } + return arr; + } +} + +function base64FromBytes(arr: Uint8Array): string { + if (globalThis.Buffer) { + return globalThis.Buffer.from(arr).toString('base64'); + } else { + const bin: string[] = []; + arr.forEach((byte) => { + bin.push(globalThis.String.fromCharCode(byte)); + }); + return globalThis.btoa(bin.join('')); + } +} + +type Builtin = Date | Function | Uint8Array | string | number | boolean | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends globalThis.Array + ? globalThis.Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { [K in Exclude>]: never }; + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/modules/sdk-coin-islm/src/index.ts b/modules/sdk-coin-islm/src/index.ts new file mode 100644 index 0000000000..1bd07fdb77 --- /dev/null +++ b/modules/sdk-coin-islm/src/index.ts @@ -0,0 +1,4 @@ +export * from './islm'; +export * from './lib'; +export * from './register'; +export * from './tislm'; diff --git a/modules/sdk-coin-islm/src/islm.ts b/modules/sdk-coin-islm/src/islm.ts new file mode 100644 index 0000000000..2d7583a7b5 --- /dev/null +++ b/modules/sdk-coin-islm/src/islm.ts @@ -0,0 +1,72 @@ +import { CosmosCoin, CosmosKeyPair, GasAmountDetails } from '@bitgo/abstract-cosmos'; +import { BaseCoin, BitGoBase, Environments } from '@bitgo/sdk-core'; +import { BaseCoin as StaticsBaseCoin, BaseUnit, coins } from '@bitgo/statics'; +import { KeyPair, TransactionBuilderFactory } from './lib'; +import { GAS_AMOUNT, GAS_LIMIT } from './lib/constants'; +import utils from './lib/utils'; +import { Hash } from 'crypto'; + +export class Islm extends CosmosCoin { + protected readonly _staticsCoin: Readonly; + protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly) { + super(bitgo, staticsCoin); + + if (!staticsCoin) { + throw new Error('missing required constructor parameter staticsCoin'); + } + + this._staticsCoin = staticsCoin; + } + + static createInstance(bitgo: BitGoBase, staticsCoin?: Readonly): BaseCoin { + return new Islm(bitgo, staticsCoin); + } + + /** @inheritDoc **/ + getBuilder(): TransactionBuilderFactory { + return new TransactionBuilderFactory(coins.get(this.getChain())); + } + + /** @inheritDoc **/ + getBaseFactor(): string | number { + return 1e18; + } + + /** @inheritDoc **/ + isValidAddress(address: string): boolean { + return utils.isValidAddress(address) || utils.isValidValidatorAddress(address); + } + + /** @inheritDoc **/ + protected getPublicNodeUrl(): string { + return Environments[this.bitgo.getEnv()].islmNodeUrl; + } + + /** @inheritDoc **/ + getDenomination(): string { + return BaseUnit.ISLM; + } + + /** @inheritDoc **/ + getGasAmountDetails(): GasAmountDetails { + return { + gasAmount: GAS_AMOUNT, + gasLimit: GAS_LIMIT, + }; + } + + /** @inheritDoc **/ + getKeyPair(publicKey: string): CosmosKeyPair { + return new KeyPair({ pub: publicKey }); + } + + /** @inheritDoc **/ + getAddressFromPublicKey(publicKey: string): string { + return new KeyPair({ pub: publicKey }).getAddress(); + } + + /** @inheritDoc **/ + getHashFunction(): Hash { + return utils.getHashFunction(); + } +} diff --git a/modules/sdk-coin-islm/src/lib/constants.ts b/modules/sdk-coin-islm/src/lib/constants.ts new file mode 100644 index 0000000000..b4dc517bf1 --- /dev/null +++ b/modules/sdk-coin-islm/src/lib/constants.ts @@ -0,0 +1,6 @@ +export const validDenoms = ['aISLM']; +export const accountAddressRegex = /^(haqq)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; +export const validatorAddressRegex = /^(haqqvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l']{38})$/; +export const GAS_LIMIT = 200000; +export const GAS_AMOUNT = '4000000000000000'; +export const ADDRESS_PREFIX = 'haqq'; diff --git a/modules/sdk-coin-islm/src/lib/index.ts b/modules/sdk-coin-islm/src/lib/index.ts new file mode 100644 index 0000000000..87cc346db5 --- /dev/null +++ b/modules/sdk-coin-islm/src/lib/index.ts @@ -0,0 +1,10 @@ +import * as Constants from './constants'; +import * as Utils from './utils'; + +export { + CosmosTransaction as Transaction, + CosmosTransactionBuilder as TransactionBuilder, +} from '@bitgo/abstract-cosmos'; +export { KeyPair } from './keyPair'; +export { TransactionBuilderFactory } from './transactionBuilderFactory'; +export { Constants, Utils }; diff --git a/modules/sdk-coin-islm/src/lib/keyPair.ts b/modules/sdk-coin-islm/src/lib/keyPair.ts new file mode 100644 index 0000000000..b2ee4b7921 --- /dev/null +++ b/modules/sdk-coin-islm/src/lib/keyPair.ts @@ -0,0 +1,19 @@ +import { KeyPairOptions } from '@bitgo/sdk-core'; +import { CosmosKeyPair } from '@bitgo/abstract-cosmos'; +import { ADDRESS_PREFIX } from './constants'; +import { toBech32, fromHex } from '@cosmjs/encoding'; +import { computeAddress } from 'ethers/lib/utils'; + +/** + * Islm keys and address management. + */ +export class KeyPair extends CosmosKeyPair { + constructor(source?: KeyPairOptions) { + super(source); + } + + /** @inheritdoc */ + getAddress(): string { + return toBech32(ADDRESS_PREFIX, fromHex(computeAddress('0x' + this.getKeys().pub).slice(2))); + } +} diff --git a/modules/sdk-coin-islm/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-islm/src/lib/transactionBuilderFactory.ts new file mode 100644 index 0000000000..4f2c2ac166 --- /dev/null +++ b/modules/sdk-coin-islm/src/lib/transactionBuilderFactory.ts @@ -0,0 +1,78 @@ +import { + CosmosTransaction, + CosmosTransactionBuilder, + CosmosTransferBuilder, + StakingActivateBuilder, + StakingDeactivateBuilder, + StakingWithdrawRewardsBuilder, +} from '@bitgo/abstract-cosmos'; +import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import islmUtils from './utils'; + +export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { + constructor(_coinConfig: Readonly) { + super(_coinConfig); + } + + /** @inheritdoc */ + from(raw: string): CosmosTransactionBuilder { + const tx = new CosmosTransaction(this._coinConfig, islmUtils); + tx.enrichTransactionDetailsFromRawTransaction(raw); + try { + switch (tx.type) { + case TransactionType.Send: + return this.getTransferBuilder(tx); + case TransactionType.StakingActivate: + return this.getStakingActivateBuilder(tx); + case TransactionType.StakingDeactivate: + return this.getStakingDeactivateBuilder(tx); + case TransactionType.StakingWithdraw: + return this.getStakingWithdrawRewardsBuilder(tx); + default: + throw new InvalidTransactionError('Invalid transaction'); + } + } catch (e) { + throw new InvalidTransactionError('Invalid transaction: ' + e.message); + } + } + + /** @inheritdoc */ + getTransferBuilder(tx?: CosmosTransaction): CosmosTransferBuilder { + return this.initializeBuilder(tx, new CosmosTransferBuilder(this._coinConfig, islmUtils)); + } + + /** @inheritdoc */ + getStakingActivateBuilder(tx?: CosmosTransaction): StakingActivateBuilder { + return this.initializeBuilder(tx, new StakingActivateBuilder(this._coinConfig, islmUtils)); + } + + /** @inheritdoc */ + getStakingDeactivateBuilder(tx?: CosmosTransaction): StakingDeactivateBuilder { + return this.initializeBuilder(tx, new StakingDeactivateBuilder(this._coinConfig, islmUtils)); + } + + /** @inheritdoc */ + getStakingWithdrawRewardsBuilder(tx?: CosmosTransaction): StakingWithdrawRewardsBuilder { + return this.initializeBuilder(tx, new StakingWithdrawRewardsBuilder(this._coinConfig, islmUtils)); + } + + /** @inheritdoc */ + getWalletInitializationBuilder(): void { + throw new Error('Method not implemented.'); + } + + /** + * Initialize the builder with the given transaction + * + * @param {CosmosTransaction | undefined} tx - the transaction used to initialize the builder + * @param {CosmosTransactionBuilder} builder - the builder to be initialized + * @returns {CosmosTransactionBuilder} the builder initialized + */ + protected initializeBuilder(tx: CosmosTransaction | undefined, builder: T): T { + if (tx) { + builder.initBuilder(tx); + } + return builder; + } +} diff --git a/modules/sdk-coin-islm/src/lib/utils.ts b/modules/sdk-coin-islm/src/lib/utils.ts new file mode 100644 index 0000000000..14313ac049 --- /dev/null +++ b/modules/sdk-coin-islm/src/lib/utils.ts @@ -0,0 +1,104 @@ +import { InvalidTransactionError } from '@bitgo/sdk-core'; +import { Coin } from '@cosmjs/stargate'; +import BigNumber from 'bignumber.js'; + +import { CosmosUtils, PubKeyType, PubKeyTypeUrl } from '@bitgo/abstract-cosmos'; +import * as constants from './constants'; +import { DecodedTxRaw } from '@cosmjs/proto-signing'; +import { fromBase64, toBase64, toHex, fromHex } from '@cosmjs/encoding'; +import { Pubkey } from '@cosmjs/amino'; +import { Any } from 'cosmjs-types/google/protobuf/any'; +import { PubKey } from '../../resources/types/ethSecp256k1'; +import { Hash } from 'crypto'; +import Keccak from 'keccak'; + +export class IslmUtils extends CosmosUtils { + /** @inheritdoc */ + isValidAddress(address: string): boolean { + return constants.accountAddressRegex.test(address); + } + + /** @inheritdoc */ + isValidValidatorAddress(address: string): boolean { + return constants.validatorAddressRegex.test(address); + } + + /** @inheritdoc */ + validateAmount(amount: Coin): void { + const amountBig = BigNumber(amount.amount); + if (amountBig.isLessThanOrEqualTo(0)) { + throw new InvalidTransactionError('transactionBuilder: validateAmount: Invalid amount: ' + amount.amount); + } + if (!constants.validDenoms.find((denom) => denom === amount.denom)) { + throw new InvalidTransactionError('transactionBuilder: validateAmount: Invalid denom: ' + amount.denom); + } + } + + /** @inheritdoc */ + getPublicKeyFromDecodedTx(decodedTx: DecodedTxRaw): string | undefined { + const publicKeyUInt8Array = decodedTx.authInfo.signerInfos?.[0].publicKey?.value; + if (publicKeyUInt8Array) { + return toHex(fromBase64(this.decodePubkey(decodedTx.authInfo.signerInfos?.[0].publicKey)?.value)); + } + return undefined; + } + + /** + * Decodes a single pubkey from ptotobuf `Any` into `Pubkey`. + * @param {Any} pubkey + * @returns {Pubkey} the Amino JSON representation (type/value wrapper) of the pubkey + */ + decodePubkey(pubkey?: Any | null): Pubkey | null { + if (!pubkey || !pubkey.value) { + return null; + } + const { key } = PubKey.decode(pubkey.value); + return this.encodeEthSecp256k1Pubkey(key); + } + + /** @inheritdoc */ + getEncodedPubkey(pubkey: string): Any { + return this.encodePubkey(this.encodeEthSecp256k1Pubkey(fromHex(pubkey))); + } + + /** + * Takes a pubkey in the Amino JSON object style (type/value wrapper) + * and convertes it into a protobuf `Any`. + * @param {Pubkey} pubkey Amino JSON object style pubkey + * @returns {Any} pubkey encoded as protobuf `Any` + */ + encodePubkey(pubkey: Pubkey): Any { + const pubkeyProto = PubKey.fromPartial({ + key: fromBase64(pubkey.value), + }); + return Any.fromPartial({ + typeUrl: PubKeyTypeUrl.ethSecp256k1, + value: Uint8Array.from(PubKey.encode(pubkeyProto).finish()), + }); + } + + /** + * Takes a public key as raw bytes and returns the Amino JSON + * representation of it (type/value wrapper). + * @param {Uint8Array} pubkey public key as raw bytes + * @returns {Any} Amino JSON style pubkey + */ + encodeEthSecp256k1Pubkey(pubkey: Uint8Array): Pubkey { + if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { + throw new Error('Public key must be compressed ethSecp256k1, i.e. 33 bytes starting with 0x02 or 0x03'); + } + return { + type: PubKeyType.ethSecp256k1, + value: toBase64(pubkey), + }; + } + + /** @inheritdoc */ + getHashFunction(): Hash { + return Keccak('keccak256'); + } +} + +const islmUtils: CosmosUtils = new IslmUtils(); + +export default islmUtils; diff --git a/modules/sdk-coin-islm/src/register.ts b/modules/sdk-coin-islm/src/register.ts new file mode 100644 index 0000000000..6ee34147e7 --- /dev/null +++ b/modules/sdk-coin-islm/src/register.ts @@ -0,0 +1,8 @@ +import { BitGoBase } from '@bitgo/sdk-core'; +import { Islm } from './islm'; +import { Tislm } from './tislm'; + +export const register = (sdk: BitGoBase): void => { + sdk.register('islm', Islm.createInstance); + sdk.register('tislm', Tislm.createInstance); +}; diff --git a/modules/sdk-coin-islm/src/tislm.ts b/modules/sdk-coin-islm/src/tislm.ts new file mode 100644 index 0000000000..2be3296540 --- /dev/null +++ b/modules/sdk-coin-islm/src/tislm.ts @@ -0,0 +1,25 @@ +/** + * Testnet Islm + * + * @format + */ +import { BaseCoin, BitGoBase } from '@bitgo/sdk-core'; +import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; +import { Islm } from './islm'; + +export class Tislm extends Islm { + protected readonly _staticsCoin: Readonly; + protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly) { + super(bitgo, staticsCoin); + + if (!staticsCoin) { + throw new Error('missing required constructor parameter staticsCoin'); + } + + this._staticsCoin = staticsCoin; + } + + static createInstance(bitgo: BitGoBase, staticsCoin?: Readonly): BaseCoin { + return new Tislm(bitgo, staticsCoin); + } +} diff --git a/modules/sdk-coin-islm/tsconfig.json b/modules/sdk-coin-islm/tsconfig.json new file mode 100644 index 0000000000..bcc240224e --- /dev/null +++ b/modules/sdk-coin-islm/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./", + "strictPropertyInitialization": false, + "esModuleInterop": true, + "typeRoots": ["../../types", "./node_modules/@types", "../../node_modules/@types"] + }, + "include": ["src/**/*", "test/**/*", "resources/**/*"], + "exclude": ["node_modules"], + "references": [ + { + "path": "../sdk-api" + }, + { + "path": "../sdk-core" + }, + { + "path": "../sdk-test" + }, + { + "path": "../abstract-cosmos" + } + ] +} diff --git a/modules/sdk-core/src/bitgo/environments.ts b/modules/sdk-core/src/bitgo/environments.ts index fe96895159..0b4fb4512d 100644 --- a/modules/sdk-core/src/bitgo/environments.ts +++ b/modules/sdk-core/src/bitgo/environments.ts @@ -32,6 +32,7 @@ interface EnvironmentTemplate { bldNodeUrl: string; zetaNodeUrl: string; coreNodeUrl: string; + islmNodeUrl: string; dotNodeUrls: string[]; tronNodes: { full: string; @@ -113,6 +114,7 @@ const mainnetBase: EnvironmentTemplate = { bldNodeUrl: 'https://agoric-api.polkachu.com', zetaNodeUrl: 'https://zetachain-athens.blockpi.network/lcd/v1/public', // TODO(WIN-142): update to mainnet url when it's live coreNodeUrl: 'https://full-node.mainnet-1.coreum.dev:26657', + islmNodeUrl: 'https://rest.cosmos.haqq.network', dotNodeUrls: ['wss://rpc.polkadot.io'], tronNodes: { full: 'https://api.trongrid.io', @@ -151,6 +153,7 @@ const testnetBase: EnvironmentTemplate = { bldNodeUrl: 'https://devnet.api.agoric.net', zetaNodeUrl: 'https://rpc.ankr.com/http/zetachain_athens_testnet', coreNodeUrl: 'https://full-node.testnet-1.coreum.dev:26657', + islmNodeUrl: 'https://rest.cosmos.testedge2.haqq.network ', dotNodeUrls: ['wss://westend-rpc.polkadot.io'], tronNodes: { full: 'https://api.shasta.trongrid.io', diff --git a/tsconfig.packages.json b/tsconfig.packages.json index cc05038d37..e39a7f0a01 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -106,6 +106,9 @@ { "path": "./modules/sdk-coin-injective" }, + { + "path": "./modules/sdk-coin-islm" + }, { "path": "./modules/sdk-coin-ltc" },