diff --git a/CODEOWNERS b/CODEOWNERS index be1087656b..87d4094101 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -53,6 +53,7 @@ /modules/sdk-coin-atom/ @BitGo/ethalt-team /modules/sdk-coin-avaxc/ @BitGo/ethalt-team /modules/sdk-coin-avaxp/ @BitGo/ethalt-team +/modules/sdk-coin-baby/ @BitGo/ethalt-team /modules/sdk-coin-bera/ @BitGo/ethalt-team /modules/sdk-coin-bsc/ @BitGo/ethalt-team /modules/sdk-coin-coredao/ @BitGo/ethalt-team diff --git a/Dockerfile b/Dockerfile index bf8ae3c399..c17eadce12 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,6 +63,7 @@ COPY --from=builder /tmp/bitgo/modules/sdk-coin-atom /var/modules/sdk-coin-atom/ COPY --from=builder /tmp/bitgo/modules/abstract-cosmos /var/modules/abstract-cosmos/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-avaxc /var/modules/sdk-coin-avaxc/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-avaxp /var/modules/sdk-coin-avaxp/ +COPY --from=builder /tmp/bitgo/modules/sdk-coin-baby /var/modules/sdk-coin-baby/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-eth /var/modules/sdk-coin-eth/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-bera /var/modules/sdk-coin-bera/ COPY --from=builder /tmp/bitgo/modules/sdk-coin-bld /var/modules/sdk-coin-bld/ @@ -141,6 +142,7 @@ cd /var/modules/sdk-coin-atom && yarn link && \ cd /var/modules/abstract-cosmos && yarn link && \ cd /var/modules/sdk-coin-avaxc && yarn link && \ cd /var/modules/sdk-coin-avaxp && yarn link && \ +cd /var/modules/sdk-coin-baby && yarn link && \ cd /var/modules/sdk-coin-eth && yarn link && \ cd /var/modules/sdk-coin-bera && yarn link && \ cd /var/modules/sdk-coin-bld && yarn link && \ @@ -222,6 +224,7 @@ RUN cd /var/bitgo-express && \ yarn link @bitgo/abstract-cosmos && \ yarn link @bitgo/sdk-coin-avaxc && \ yarn link @bitgo/sdk-coin-avaxp && \ + yarn link @bitgo/sdk-coin-baby && \ yarn link @bitgo/sdk-coin-eth && \ yarn link @bitgo/sdk-coin-bera && \ yarn link @bitgo/sdk-coin-bld && \ diff --git a/modules/account-lib/package.json b/modules/account-lib/package.json index 74268f9496..2818d09ce0 100644 --- a/modules/account-lib/package.json +++ b/modules/account-lib/package.json @@ -33,6 +33,7 @@ "@bitgo/sdk-coin-atom": "^13.1.16", "@bitgo/sdk-coin-avaxc": "^5.2.10", "@bitgo/sdk-coin-avaxp": "^5.0.59", + "@bitgo/sdk-coin-baby": "^1.0.0", "@bitgo/sdk-coin-bera": "^2.3.4", "@bitgo/sdk-coin-bld": "^3.0.29", "@bitgo/sdk-coin-bsc": "^22.2.13", diff --git a/modules/account-lib/src/index.ts b/modules/account-lib/src/index.ts index 22f86229b6..bdf5a23d00 100644 --- a/modules/account-lib/src/index.ts +++ b/modules/account-lib/src/index.ts @@ -104,6 +104,9 @@ export { Coreum }; import * as Rune from '@bitgo/sdk-coin-rune'; export { Rune }; +import * as Baby from '@bitgo/sdk-coin-baby'; +export { Baby }; + import * as Sol from '@bitgo/sdk-coin-sol'; export { Sol }; @@ -224,6 +227,8 @@ const coinBuilderMap = { tapt: Apt.TransactionBuilder, icp: Icp.TransactionBuilder, ticp: Icp.TransactionBuilder, + baby: Baby.TransactionBuilder, + tbaby: Baby.TransactionBuilder, }; /** diff --git a/modules/bitgo/package.json b/modules/bitgo/package.json index 0c79ca3fd7..49564ed4cd 100644 --- a/modules/bitgo/package.json +++ b/modules/bitgo/package.json @@ -56,6 +56,7 @@ "@bitgo/sdk-coin-atom": "^13.1.16", "@bitgo/sdk-coin-avaxc": "^5.2.10", "@bitgo/sdk-coin-avaxp": "^5.0.59", + "@bitgo/sdk-coin-baby": "^1.0.0", "@bitgo/sdk-coin-bch": "^2.1.10", "@bitgo/sdk-coin-bcha": "^2.2.10", "@bitgo/sdk-coin-bera": "^2.3.4", diff --git a/modules/bitgo/src/v2/coinFactory.ts b/modules/bitgo/src/v2/coinFactory.ts index dfe7da2148..eef4ba2fae 100644 --- a/modules/bitgo/src/v2/coinFactory.ts +++ b/modules/bitgo/src/v2/coinFactory.ts @@ -20,6 +20,7 @@ import { AvaxC, AvaxCToken, AvaxP, + Baby, Bch, Bera, BeraToken, @@ -84,6 +85,7 @@ import { Tatom, TavaxC, TavaxP, + Tbaby, Tbch, Tbera, Tbld, @@ -155,6 +157,7 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void { globalCoinFactory.register('atom', Atom.createInstance); globalCoinFactory.register('avaxc', AvaxC.createInstance); globalCoinFactory.register('avaxp', AvaxP.createInstance); + globalCoinFactory.register('baby', Baby.createInstance); globalCoinFactory.register('bch', Bch.createInstance); globalCoinFactory.register('bcha', Bcha.createInstance); globalCoinFactory.register('bera', Bera.createInstance); @@ -212,6 +215,7 @@ function registerCoinConstructors(globalCoinFactory: CoinFactory): void { globalCoinFactory.register('tatom', Tatom.createInstance); globalCoinFactory.register('tavaxc', TavaxC.createInstance); globalCoinFactory.register('tavaxp', TavaxP.createInstance); + globalCoinFactory.register('tbaby', Tbaby.createInstance); globalCoinFactory.register('tbch', Tbch.createInstance); globalCoinFactory.register('tbcha', Tbcha.createInstance); globalCoinFactory.register('tbera', Tbera.createInstance); diff --git a/modules/bitgo/src/v2/coins/index.ts b/modules/bitgo/src/v2/coins/index.ts index 15ee419899..4809c14dda 100644 --- a/modules/bitgo/src/v2/coins/index.ts +++ b/modules/bitgo/src/v2/coins/index.ts @@ -7,6 +7,7 @@ import { Arbeth, Tarbeth, ArbethToken } from '@bitgo/sdk-coin-arbeth'; import { Atom, Tatom } from '@bitgo/sdk-coin-atom'; import { AvaxC, AvaxCToken, TavaxC } from '@bitgo/sdk-coin-avaxc'; import { AvaxP, TavaxP } from '@bitgo/sdk-coin-avaxp'; +import { Baby, Tbaby } from '@bitgo/sdk-coin-baby'; import { Bch, Tbch } from '@bitgo/sdk-coin-bch'; import { Bcha, Tbcha } from '@bitgo/sdk-coin-bcha'; import { Bera, Tbera, BeraToken } from '@bitgo/sdk-coin-bera'; @@ -65,6 +66,7 @@ export { Ada, Tada }; export { Atom, Tatom }; export { AvaxC, AvaxCToken, TavaxC }; export { AvaxP, TavaxP }; +export { Baby, Tbaby }; export { Bch, Tbch }; export { Bera, Tbera, BeraToken }; export { Bsc, BscToken, Tbsc }; diff --git a/modules/bitgo/tsconfig.json b/modules/bitgo/tsconfig.json index 39f8776e39..a88650cff6 100644 --- a/modules/bitgo/tsconfig.json +++ b/modules/bitgo/tsconfig.json @@ -60,9 +60,6 @@ { "path": "../sdk-api" }, - { - "path": "../sdk-hmac" - }, { "path": "../sdk-coin-ada" }, @@ -84,6 +81,9 @@ { "path": "../sdk-coin-avaxp" }, + { + "path": "../sdk-coin-baby" + }, { "path": "../sdk-coin-bch" }, @@ -234,6 +234,9 @@ { "path": "../sdk-core" }, + { + "path": "../sdk-hmac" + }, { "path": "../sdk-test" }, diff --git a/modules/sdk-coin-baby/.eslintignore b/modules/sdk-coin-baby/.eslintignore new file mode 100644 index 0000000000..190f83e0df --- /dev/null +++ b/modules/sdk-coin-baby/.eslintignore @@ -0,0 +1,5 @@ +node_modules +.idea +public +dist + diff --git a/modules/sdk-coin-baby/.gitignore b/modules/sdk-coin-baby/.gitignore new file mode 100644 index 0000000000..67ccce4c64 --- /dev/null +++ b/modules/sdk-coin-baby/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.idea/ +dist/ diff --git a/modules/sdk-coin-baby/.mocharc.yml b/modules/sdk-coin-baby/.mocharc.yml new file mode 100644 index 0000000000..95814796d1 --- /dev/null +++ b/modules/sdk-coin-baby/.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-baby/.npmignore b/modules/sdk-coin-baby/.npmignore new file mode 100644 index 0000000000..d5fb3a098c --- /dev/null +++ b/modules/sdk-coin-baby/.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-baby/.prettierignore b/modules/sdk-coin-baby/.prettierignore new file mode 100644 index 0000000000..3a11d6af29 --- /dev/null +++ b/modules/sdk-coin-baby/.prettierignore @@ -0,0 +1,2 @@ +.nyc_output/ +dist/ diff --git a/modules/sdk-coin-baby/.prettierrc.yml b/modules/sdk-coin-baby/.prettierrc.yml new file mode 100644 index 0000000000..7c3d8dd32a --- /dev/null +++ b/modules/sdk-coin-baby/.prettierrc.yml @@ -0,0 +1,3 @@ +printWidth: 120 +singleQuote: true +trailingComma: 'es5' diff --git a/modules/sdk-coin-baby/README.md b/modules/sdk-coin-baby/README.md new file mode 100644 index 0000000000..43f4ad2c23 --- /dev/null +++ b/modules/sdk-coin-baby/README.md @@ -0,0 +1,30 @@ +# BitGo sdk-coin-baby + +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-baby`. + +```shell +npm i @bitgo/sdk-api @bitgo/sdk-coin-baby +``` + +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 { Baby } from '@bitgo/sdk-coin-baby'; + +const sdk = new BitGoAPI(); + +sdk.register('baby', Baby.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-baby/package.json b/modules/sdk-coin-baby/package.json new file mode 100644 index 0000000000..8a054a1d28 --- /dev/null +++ b/modules/sdk-coin-baby/package.json @@ -0,0 +1,55 @@ +{ + "name": "@bitgo/sdk-coin-baby", + "version": "1.0.0", + "description": "BitGo SDK coin library for Babylon", + "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": ">=18 <21" + }, + "repository": { + "type": "git", + "url": "https://github.com/BitGo/BitGoJS.git", + "directory": "modules/sdk-coin-baby" + }, + "lint-staged": { + "*.{js,ts}": [ + "yarn prettier --write", + "yarn eslint --fix" + ] + }, + "publishConfig": { + "access": "public" + }, + "nyc": { + "extension": [ + ".ts" + ] + }, + "dependencies": { + "@bitgo/abstract-cosmos": "^11.2.16", + "@bitgo/sdk-core": "^28.20.0", + "@bitgo/statics": "^50.20.0", + "@cosmjs/amino": "^0.29.5", + "@cosmjs/encoding": "^0.29.5", + "@cosmjs/stargate": "^0.29.5", + "bignumber.js": "^9.1.1" + }, + "devDependencies": { + "@bitgo/sdk-api": "^1.58.2", + "@bitgo/sdk-test": "^8.0.65" + } +} diff --git a/modules/sdk-coin-baby/src/baby.ts b/modules/sdk-coin-baby/src/baby.ts new file mode 100644 index 0000000000..493e9aab86 --- /dev/null +++ b/modules/sdk-coin-baby/src/baby.ts @@ -0,0 +1,73 @@ +import { CosmosCoin, CosmosKeyPair, GasAmountDetails } from '@bitgo/abstract-cosmos'; +import { BaseCoin, BitGoBase, Environments } from '@bitgo/sdk-core'; +import { BaseUnit, BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics'; +import { KeyPair, TransactionBuilderFactory } from './lib'; +import { GAS_AMOUNT, GAS_LIMIT } from './lib/constants'; +import utils from './lib/utils'; + +/** + * + * Full Name: Babylon + * Website: https://babylonlabs.io/ + * Docs: https://docs.babylonlabs.io/ + * GitHub : https://github.com/babylonlabs-io/networks + */ +export class Baby 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 Baby(bitgo, staticsCoin); + } + + /** @inheritDoc **/ + getBaseFactor(): string | number { + return 1e6; + } + + getBuilder(): TransactionBuilderFactory { + return new TransactionBuilderFactory(coins.get(this.getChain())); + } + + /** @inheritDoc **/ + isValidAddress(address: string): boolean { + return utils.isValidAddress(address) || utils.isValidValidatorAddress(address); + } + + /** @inheritDoc **/ + getDenomination(): string { + return BaseUnit.BABY; + } + + /** @inheritDoc **/ + getGasAmountDetails(): GasAmountDetails { + return { + gasAmount: GAS_AMOUNT, + gasLimit: GAS_LIMIT, + }; + } + + /** @inheritDoc **/ + getKeyPair(publicKey: string): CosmosKeyPair { + return new KeyPair({ pub: publicKey }); + } + + /** + * Get the public node url from the Environments constant we have defined + */ + protected getPublicNodeUrl(): string { + return Environments[this.bitgo.getEnv()].babyNodeUrl; + } + + getAddressFromPublicKey(pubKey: string): string { + return new KeyPair({ pub: pubKey }).getAddress(); + } +} diff --git a/modules/sdk-coin-baby/src/index.ts b/modules/sdk-coin-baby/src/index.ts new file mode 100644 index 0000000000..f7ba55145c --- /dev/null +++ b/modules/sdk-coin-baby/src/index.ts @@ -0,0 +1,4 @@ +export * from './lib'; +export * from './baby'; +export * from './tbaby'; +export * from './register'; diff --git a/modules/sdk-coin-baby/src/lib/constants.ts b/modules/sdk-coin-baby/src/lib/constants.ts new file mode 100644 index 0000000000..7f4f7bbfb7 --- /dev/null +++ b/modules/sdk-coin-baby/src/lib/constants.ts @@ -0,0 +1,7 @@ +export const validDenoms = ['baby', 'tbaby', 'ubbn']; +export const accountAddressRegex = /^(bbn)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38})$/; +export const validatorAddressRegex = /^(bbnvaloper)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l]{38})$/; +export const contractAddressRegex = /^(bbn)1(['qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)$/; +export const ADDRESS_PREFIX = 'bbn'; +export const GAS_AMOUNT = '7000'; +export const GAS_LIMIT = 200000; diff --git a/modules/sdk-coin-baby/src/lib/index.ts b/modules/sdk-coin-baby/src/lib/index.ts new file mode 100644 index 0000000000..87cc346db5 --- /dev/null +++ b/modules/sdk-coin-baby/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-baby/src/lib/keyPair.ts b/modules/sdk-coin-baby/src/lib/keyPair.ts new file mode 100644 index 0000000000..10c081cb5f --- /dev/null +++ b/modules/sdk-coin-baby/src/lib/keyPair.ts @@ -0,0 +1,26 @@ +import { KeyPairOptions } from '@bitgo/sdk-core'; +import { pubkeyToAddress } from '@cosmjs/amino'; + +import { CosmosKeyPair } from '@bitgo/abstract-cosmos'; +import { ADDRESS_PREFIX } from './constants'; + +/** + * Babylon keys and address management. + */ +export class KeyPair extends CosmosKeyPair { + constructor(source?: KeyPairOptions) { + super(source); + } + + /** @inheritdoc */ + getAddress(): string { + const base64String = Buffer.from(this.getKeys().pub.slice(0, 66), 'hex').toString('base64'); + return pubkeyToAddress( + { + type: 'tendermint/PubKeySecp256k1', + value: base64String, + }, + ADDRESS_PREFIX + ); + } +} diff --git a/modules/sdk-coin-baby/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-baby/src/lib/transactionBuilderFactory.ts new file mode 100644 index 0000000000..0a4f612b92 --- /dev/null +++ b/modules/sdk-coin-baby/src/lib/transactionBuilderFactory.ts @@ -0,0 +1,89 @@ +import { + CosmosTransaction, + CosmosTransactionBuilder, + CosmosTransferBuilder, + StakingActivateBuilder, + StakingDeactivateBuilder, + StakingWithdrawRewardsBuilder, + ContractCallBuilder, + StakingRedelegateBuilder, +} from '@bitgo/abstract-cosmos'; +import { BaseTransactionBuilderFactory, InvalidTransactionError, TransactionType } from '@bitgo/sdk-core'; +import { BaseCoin as CoinConfig } from '@bitgo/statics'; +import utils from './utils'; + +export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { + constructor(_coinConfig: Readonly) { + super(_coinConfig); + } + + /** @inheritdoc */ + from(raw: string): CosmosTransactionBuilder { + const tx = new CosmosTransaction(this._coinConfig, utils); + 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); + case TransactionType.ContractCall: + return this.getContractCallBuilder(tx); + case TransactionType.StakingRedelegate: + return this.getStakingRedelegateBuilder(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, utils)); + } + /** @inheritdoc */ + getStakingActivateBuilder(tx?: CosmosTransaction): StakingActivateBuilder { + return this.initializeBuilder(tx, new StakingActivateBuilder(this._coinConfig, utils)); + } + /** @inheritdoc */ + getStakingDeactivateBuilder(tx?: CosmosTransaction): StakingDeactivateBuilder { + return this.initializeBuilder(tx, new StakingDeactivateBuilder(this._coinConfig, utils)); + } + /** @inheritdoc */ + getStakingWithdrawRewardsBuilder(tx?: CosmosTransaction): StakingWithdrawRewardsBuilder { + return this.initializeBuilder(tx, new StakingWithdrawRewardsBuilder(this._coinConfig, utils)); + } + + getContractCallBuilder(tx?: CosmosTransaction): ContractCallBuilder { + return this.initializeBuilder(tx, new ContractCallBuilder(this._coinConfig, utils)); + } + + getStakingRedelegateBuilder(tx?: CosmosTransaction): StakingRedelegateBuilder { + return this.initializeBuilder(tx, new StakingRedelegateBuilder(this._coinConfig, utils)); + } + + /** @inheritdoc */ + getWalletInitializationBuilder(): void { + throw new Error('Method not implemented.'); + } + + /** + * Initialize the builder with the given transaction + * + * @param {Transaction | undefined} tx - the transaction used to initialize the builder + * @param {TransactionBuilder} builder - the builder to be initialized + * @returns {TransactionBuilder} the builder initialized + */ + private initializeBuilder(tx: CosmosTransaction | undefined, builder: T): T { + if (tx) { + builder.initBuilder(tx); + } + return builder; + } +} diff --git a/modules/sdk-coin-baby/src/lib/utils.ts b/modules/sdk-coin-baby/src/lib/utils.ts new file mode 100644 index 0000000000..7f24764e04 --- /dev/null +++ b/modules/sdk-coin-baby/src/lib/utils.ts @@ -0,0 +1,37 @@ +import { CosmosUtils } from '@bitgo/abstract-cosmos'; +import { InvalidTransactionError } from '@bitgo/sdk-core'; +import { Coin } from '@cosmjs/stargate'; +import BigNumber from 'bignumber.js'; +import * as constants from './constants'; + +export class Utils extends CosmosUtils { + /** @inheritdoc */ + isValidAddress(address: string): boolean { + return this.isValidCosmosLikeAddressWithMemoId(address, constants.accountAddressRegex); + } + + /** @inheritdoc */ + isValidValidatorAddress(address: string): boolean { + return this.isValidBech32AddressMatchingRegex(address, constants.validatorAddressRegex); + } + + /** @inheritdoc */ + isValidContractAddress(address: string): boolean { + return this.isValidBech32AddressMatchingRegex(address, constants.contractAddressRegex); + } + + /** @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); + } + } +} + +const utils = new Utils(); + +export default utils; diff --git a/modules/sdk-coin-baby/src/register.ts b/modules/sdk-coin-baby/src/register.ts new file mode 100644 index 0000000000..e5cdbdaa64 --- /dev/null +++ b/modules/sdk-coin-baby/src/register.ts @@ -0,0 +1,8 @@ +import { BitGoBase } from '@bitgo/sdk-core'; +import { Baby } from './baby'; +import { Tbaby } from './tbaby'; + +export const register = (sdk: BitGoBase): void => { + sdk.register('baby', Baby.createInstance); + sdk.register('tbaby', Tbaby.createInstance); +}; diff --git a/modules/sdk-coin-baby/src/tbaby.ts b/modules/sdk-coin-baby/src/tbaby.ts new file mode 100644 index 0000000000..4cc59bed1d --- /dev/null +++ b/modules/sdk-coin-baby/src/tbaby.ts @@ -0,0 +1,25 @@ +/** + * Testnet Babylon + * + * @format + */ +import { BaseCoin, BitGoBase } from '@bitgo/sdk-core'; +import { Baby } from './baby'; +import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics'; + +export class Tbaby extends Baby { + 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 Tbaby(bitgo, staticsCoin); + } +} diff --git a/modules/sdk-coin-baby/test/resources/baby.ts b/modules/sdk-coin-baby/test/resources/baby.ts new file mode 100644 index 0000000000..f4f995f8f5 --- /dev/null +++ b/modules/sdk-coin-baby/test/resources/baby.ts @@ -0,0 +1,194 @@ +export const TEST_ACCOUNT = { + pubAddress: 'bbn1897xa4swxx9dr7z0zut0mfs7efplx80q86kd8q', + compressedPublicKey: '02bcdbd054a73aa6097c1926c87eb7d66142d2ea710584c4b3e9844e1dab1538f0', + compressedPublicKeyTwo: '02001fda4568760a99e58ee295b4a51edcc6a689297a71f7d1571cf4e1253abcde', + uncompressedPublicKey: + '04bcdbd054a73aa6097c1926c87eb7d66142d2ea710584c4b3e9844e1dab1538f0a088291e83cb3438431deb5e251439e338d8edc4389ea2004f442a73cc97afc8', + privateKey: '3f020639e98e5c4953abb23d01a7c892e83b57593a7f46f37298b58cbf1cacd5', + extendedPrv: + 'xprv9s21ZrQH143K3n6kDURwkfkBxB58Fxoz7cFCbpQeFws4K5iXaSUcpq18cCqJQ74MnqNrnLBHfE7YvUgKpnckmpsBLExGSRK55Ud5uuxGrxL', + extendedPub: + 'xpub661MyMwAqRbcGGBDKVxx7ogvWCucfRXqUqAoQCpFpHQ3Bt3g7ynsNdKcTWvGGYrpq6VYPPgxjMfKaszXMhKmmCZEnhg9RpqcGeP9uCnZsD9', +}; + +export const TEST_SEND_TX = { + hash: 'F91AD3FF898461BCE40A97C3D4A1CD6993D64599450A1C08185FA7DA446D4632', + signature: 'GxwY0ko3vlfPsU6gYZ2iqt3Acg+GBBrsSPUHTnP5Xv8GHgbQH2OfCdI/61hqEPx3LMqRTl9zEcKfBkHtB7cg4Q==', + pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', + privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', + signedTxBase64: + 'CooBCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwEmUKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQME+MaK7sQPg5Csp69Sx+wjOtEcEey6eSJ8EOSEmjdR8BIECgIIARgGEhEKCwoEdWJibhIDMjAwEKCNBhpAGxwY0ko3vlfPsU6gYZ2iqt3Acg+GBBrsSPUHTnP5Xv8GHgbQH2OfCdI/61hqEPx3LMqRTl9zEcKfBkHtB7cg4Q==', + sender: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + recipient: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + chainId: 'bbn-test-5', + accountNumber: 6, + sequence: 6, + sendAmount: '10000', + feeAmount: '200', + sendMessage: { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + amount: [ + { + denom: 'ubbn', + amount: '10000', + }, + ], + toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + }, + }, + gasBudget: { + amount: [{ denom: 'ubbn', amount: '200' }], + gasLimit: 100000, + }, +}; + +export const TEST_SEND_TX2 = { + hash: 'F3F54170A03449385C5E1CD14CD8F0D23931186618C76A3491C4B4C5D03B4DD8', + signature: '9tD0slY4loMya9rocQROMVQAw0eWfiTlMZFr9VQ8RAg+q4YFgDhteHvFZvKQPn6Y9SCSlUkBr0I+ZlIj8v7oug==', + pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', + privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', + signedTxBase64: + 'CooBCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwEmUKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQME+MaK7sQPg5Csp69Sx+wjOtEcEey6eSJ8EOSEmjdR8BIECgIIARgJEhEKCwoEdWJibhIDMjAwEKCNBhpA9tD0slY4loMya9rocQROMVQAw0eWfiTlMZFr9VQ8RAg+q4YFgDhteHvFZvKQPn6Y9SCSlUkBr0I+ZlIj8v7oug==', + sender: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + recipient: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + chainId: 'bbn-test-5', + accountNumber: 9, + sequence: 9, + sendAmount: '10000', + feeAmount: '200', + sendMessage: { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + amount: [ + { + denom: 'ubbn', + amount: '10000', + }, + ], + toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + }, + }, + gasBudget: { + amount: [{ denom: 'ubbn', amount: '200' }], + gasLimit: 100000, + }, +}; + +export const TEST_SEND_MANY_TX = { + hash: 'CEA0A14B0BE20834B0A193B9327FCAAB21A539BEE44B89299848280ED371A2DC', + signature: '5AHfov+3gmMBS/cLiM0kRBuyBhqpEOqyFPg3ANhzAdNB2Yfp7eR2BFep+xQaB/5r+3ISLrIdUv0V97uU8ENtwQ==', + pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', + privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', + signedTxBase64: + 'CpQCCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMWM5bnB0MHhod2Z2bXRuaHlxM2pxZmhxMDg4OWw4dzA0cWF5M2RzGg0KBHViYm4SBTEwMDAwEmUKUApGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQME+MaK7sQPg5Csp69Sx+wjOtEcEey6eSJ8EOSEmjdR8BIECgIIARgIEhEKCwoEdWJibhIDMjAwEKCNBhpA5AHfov+3gmMBS/cLiM0kRBuyBhqpEOqyFPg3ANhzAdNB2Yfp7eR2BFep+xQaB/5r+3ISLrIdUv0V97uU8ENtwQ==', + sender: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + chainId: 'bbn-test-5', + accountNumber: 8, + sequence: 8, + memo: '', + sendMessages: [ + { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + amount: [ + { + denom: 'ubbn', + amount: '10000', + }, + ], + toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + }, + }, + { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + amount: [ + { + denom: 'ubbn', + amount: '10000', + }, + ], + toAddress: 'bbn1c9npt0xhwfvmtnhyq3jqfhq0889l8w04qay3ds', + fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + }, + }, + ], + gasBudget: { + amount: [{ denom: 'ubbn', amount: '200' }], + gasLimit: 100000, + }, +}; + +export const TEST_TX_WITH_MEMO = { + hash: '75CD78D6C4C413750B43D91553B0BED47CB779F23B9D0CE8F94C47F82F97F069', + signature: 'YQNm391A/NqX5RoJFSwqfv9VX0GrWxk6rJMDxGjtCaJrUtsc6JnlM66cw1miDajaQlZu5LBi6JgHmrN7Nq39JA==', + pubKey: 'AwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1Hw', + privateKey: 'QAzeAkPWRGyRT8/TvJcRC7VSzQHV9QhH6YTmGZbnvmk=', + signedTxBase64: + 'Co8BCocBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmcKKmJibjEyNzRlcDhwbnJsZWo1dmdtdHdwcHlzeW56Y2Q0Zmh4YzNrdTB0MxIqYmJuMTlzeWhxdzVxZXVhczM2NWN3OXB2ZGFkemV4N2R1cmZtN2hsZm44Gg0KBHViYm4SBTEwMDAwEgMyNDESZQpQCkYKHy9jb3Ntb3MuY3J5cHRvLnNlY3AyNTZrMS5QdWJLZXkSIwohAwT4xoruxA+DkKynr1LH7CM60RwR7Lp5InwQ5ISaN1HwEgQKAggBGAcSEQoLCgR1YmJuEgMyMDAQoI0GGkBhA2bf3UD82pflGgkVLCp+/1VfQatbGTqskwPEaO0JomtS2xzomeUzrpzDWaINqNpCVm7ksGLomAeas3s2rf0k', + from: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + to: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + chainId: 'bbn-test-5', + accountNumber: 7, + sequence: 7, + feeAmount: '200', + sendAmount: '10000', + sendMessage: { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + amount: [ + { + denom: 'ubbn', + amount: '10000', + }, + ], + fromAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + toAddress: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + }, + }, + memo: '241', + gasBudget: { + amount: [{ denom: 'ubbn', amount: '200' }], + gasLimit: 100000, + }, +}; + +export const address = { + address1: 'bbn1897xa4swxx9dr7z0zut0mfs7efplx80q86kd8q', + address2: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + address3: 'bbn19syhqw5qeuas365cw9pvdadzax7durfm7hlfn8', + address4: 'bbn1ukzj0eme6srq8rtghy6zatk3gshw0ysr3pmjy9', + address5: 'bbn1pa3s79kuppz7d0aq3rh9c439k4pwtfhxaea969', + address6: 'bbn1c9npt0xhwfvmtnhyq3jqfhq0889l8w04qay3ds', + validatorAddress1: 'bbnvaloper109x4ruspxarwt62puwcenhclw36l9v7j92f0ex', + validatorAddress2: 'bbnvaloper1fa0c7df8v25mv926wey4m9kunhhm7svnp6tezt', + validatorAddress3: 'bbnvaloper15rrsv9439j60o7wwv8l6zkv8tp5lrqy7quzx87', + validatorAddress4: 'bbnvaloper1rlrjlm6rec6h2nvzakyj39d5fv7p7vgs3kq472', + noMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3', + validMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=2', + invalidMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=xyz', + multipleMemoIdAddress: 'bbn1274ep8pnrlej5vgmtwppysynzcd4fhxc3ku0t3?memoId=3&memoId=12', +}; + +export const blockHash = { + hash1: '4CF6A4005A3DEFB73B8AAC90D2FDF31E941E4F58D3D183E5F959E6CBC0AB4103', + hash2: 'B3A416BA5A8C916279858861AEC61A79546D06FD0A541621E8054AA498311066', +}; + +export const txIds = { + hash1: 'F91AD3FF898461BCE40A97C3D4A1CD6993D64599450A1C08185FA7DA446D4632', + hash2: 'A7FB4EB5000BCFDB8A481E85027907A54BD05EFF9ED83CEBC5F53B1C77812896', + hash3: '61E0C20DAD6C6077F3B07519CF03133FE77C98780E299DA5DDBED7FE523ED9E2', +}; + +export const coinAmounts = { + amount1: { amount: '100000', denom: 'ubbn' }, + amount2: { amount: '1000000', denom: 'ubbn' }, + amount3: { amount: '10000000', denom: 'ubbn' }, + amount4: { amount: '-1', denom: 'ubbn' }, + amount5: { amount: '1000000000', denom: 'mbbn' }, +}; diff --git a/modules/sdk-coin-baby/test/unit/baby.ts b/modules/sdk-coin-baby/test/unit/baby.ts new file mode 100644 index 0000000000..cccb4290b0 --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/baby.ts @@ -0,0 +1,264 @@ +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import BigNumber from 'bignumber.js'; +import sinon from 'sinon'; +import { Baby, Tbaby } from '../../src'; +import utils from '../../src/lib/utils'; +import { TEST_SEND_MANY_TX, TEST_SEND_TX, TEST_TX_WITH_MEMO, address } from '../resources/baby'; +import should = require('should'); + +describe('Babylon', function () { + let bitgo: TestBitGoAPI; + let basecoin; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('baby', Baby.createInstance); + bitgo.safeRegister('tbaby', Tbaby.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tbaby'); + }); + + it('should return the right info', function () { + const baby = bitgo.coin('baby'); + const tbaby = bitgo.coin('tbaby'); + + baby.getChain().should.equal('baby'); + baby.getFamily().should.equal('baby'); + baby.getFullName().should.equal('Babylon'); + baby.getBaseFactor().should.equal(1e6); + + tbaby.getChain().should.equal('tbaby'); + tbaby.getFamily().should.equal('baby'); + tbaby.getFullName().should.equal('Testnet Babylon'); + tbaby.getBaseFactor().should.equal(1e6); + }); + + describe('Address Validation', () => { + it('should get address details without memoId', function () { + const addressDetails = basecoin.getAddressDetails(address.noMemoIdAddress); + addressDetails.address.should.equal(address.noMemoIdAddress); + should.not.exist(addressDetails.memoId); + }); + + it('should get address details with memoId', function () { + const addressDetails = basecoin.getAddressDetails(address.validMemoIdAddress); + addressDetails.address.should.equal(address.validMemoIdAddress.split('?')[0]); + addressDetails.memoId.should.equal('2'); + }); + + it('should throw on invalid memo id address', () => { + (() => { + basecoin.getAddressDetails(address.invalidMemoIdAddress); + }).should.throw(); + }); + + it('should throw on multiple memo id address', () => { + (() => { + basecoin.getAddressDetails(address.multipleMemoIdAddress); + }).should.throw(); + }); + + it('should validate wallet receive address', async function () { + const receiveAddress = { + address: 'bbn1897xa4swxx9dr7z0zut0mfs7efplx80q86kd8q?memoId=7', + coinSpecific: { + rootAddress: 'bbn1897xa4swxx9dr7z0zut0mfs7efplx80q86kd8q', + memoID: '7', + }, + }; + const isValid = await basecoin.isWalletAddress(receiveAddress); + isValid.should.equal(true); + }); + + it('should validate account addresses correctly', () => { + should.equal(utils.isValidAddress(address.address1), true); + should.equal(utils.isValidAddress(address.address2), true); + should.equal(utils.isValidAddress(address.address3), false); + should.equal(utils.isValidAddress(address.address4), false); + should.equal(utils.isValidAddress('dfjk35y'), false); + should.equal(utils.isValidAddress(undefined as unknown as string), false); + should.equal(utils.isValidAddress(''), false); + should.equal(utils.isValidAddress(address.validMemoIdAddress), true); + should.equal(utils.isValidAddress(address.invalidMemoIdAddress), false); + should.equal(utils.isValidAddress(address.multipleMemoIdAddress), false); + }); + it('should validate validator addresses correctly', () => { + should.equal(utils.isValidValidatorAddress(address.validatorAddress1), true); + should.equal(utils.isValidValidatorAddress(address.validatorAddress2), true); + should.equal(utils.isValidValidatorAddress(address.validatorAddress3), false); + should.equal(utils.isValidValidatorAddress(address.validatorAddress4), false); + should.equal(utils.isValidValidatorAddress('dfjk35y'), false); + should.equal(utils.isValidValidatorAddress(undefined as unknown as string), false); + should.equal(utils.isValidValidatorAddress(''), false); + }); + }); + + describe('Verify transaction: ', () => { + it('should succeed to verify transaction', async function () { + const txPrebuild = { + txHex: TEST_SEND_TX.signedTxBase64, + txInfo: {}, + }; + const txParams = { + recipients: [ + { + address: TEST_SEND_TX.recipient, + amount: TEST_SEND_TX.sendAmount, + }, + ], + }; + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should succeed to verify sendMany transaction', async function () { + const txPrebuild = { + txHex: TEST_SEND_MANY_TX.signedTxBase64, + txInfo: {}, + }; + const txParams = { + recipients: [ + { + address: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + amount: '10000', + }, + { + address: 'bbn1c9npt0xhwfvmtnhyq3jqfhq0889l8w04qay3ds', + amount: '10000', + }, + ], + }; + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should fail to verify transaction with invalid param', async function () { + const txPrebuild = {}; + const txParams = { recipients: undefined }; + await basecoin + .verifyTransaction({ + txParams, + txPrebuild, + }) + .should.rejectedWith('missing required tx prebuild property txHex'); + }); + }); + + describe('Explain Transaction: ', () => { + it('should explain a transfer transaction', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_SEND_TX.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_SEND_TX.hash, + outputs: [ + { + address: TEST_SEND_TX.recipient, + amount: TEST_SEND_TX.sendAmount, + }, + ], + outputAmount: TEST_SEND_TX.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_SEND_TX.gasBudget.amount[0].amount }, + type: 0, + }); + }); + + it('should explain sendMany transfer transaction', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_SEND_MANY_TX.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_SEND_MANY_TX.hash, + outputs: [ + { + address: 'bbn19syhqw5qeuas365cw9pvdadzex7durfm7hlfn8', + amount: '10000', + }, + { + address: 'bbn1c9npt0xhwfvmtnhyq3jqfhq0889l8w04qay3ds', + amount: '10000', + }, + ], + outputAmount: '20000', + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_SEND_MANY_TX.gasBudget.amount[0].amount }, + type: 0, + }); + }); + + it('should explain a transfer transaction with memo', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_TX_WITH_MEMO.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_TX_WITH_MEMO.hash, + outputs: [ + { + address: TEST_TX_WITH_MEMO.to, + amount: TEST_TX_WITH_MEMO.sendAmount, + memo: TEST_TX_WITH_MEMO.memo, + }, + ], + outputAmount: TEST_TX_WITH_MEMO.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_TX_WITH_MEMO.gasBudget.amount[0].amount }, + type: 0, + }); + }); + + it('should fail to explain transaction with missing params', async function () { + try { + await basecoin.explainTransaction({}); + } catch (error) { + should.equal(error.message, 'missing required txHex parameter'); + } + }); + + it('should fail to explain transaction with invalid params', async function () { + try { + await basecoin.explainTransaction({ txHex: 'randomString' }); + } catch (error) { + should.equal(error.message.startsWith('Invalid transaction:'), true); + } + }); + }); + + describe('Parse Transactions: ', () => { + const transferInputsResponse = { + address: TEST_SEND_TX.recipient, + amount: new BigNumber(TEST_SEND_TX.sendAmount).plus(TEST_SEND_TX.gasBudget.amount[0].amount).toFixed(), + }; + + const transferOutputsResponse = { + address: TEST_SEND_TX.recipient, + amount: TEST_SEND_TX.sendAmount, + }; + + it('should parse a transfer transaction', async function () { + const parsedTransaction = await basecoin.parseTransaction({ txHex: TEST_SEND_TX.signedTxBase64 }); + + parsedTransaction.should.deepEqual({ + inputs: [transferInputsResponse], + outputs: [transferOutputsResponse], + }); + }); + + it('should fail to parse a transfer transaction when explainTransaction response is undefined', async function () { + const stub = sinon.stub(Baby.prototype, 'explainTransaction'); + stub.resolves(undefined); + await basecoin + .parseTransaction({ txHex: TEST_SEND_TX.signedTxBase64 }) + .should.be.rejectedWith('Invalid transaction'); + stub.restore(); + }); + }); +}); diff --git a/modules/sdk-coin-baby/test/unit/getBuilderFactory.ts b/modules/sdk-coin-baby/test/unit/getBuilderFactory.ts new file mode 100644 index 0000000000..082badb593 --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/getBuilderFactory.ts @@ -0,0 +1,6 @@ +import { TransactionBuilderFactory } from '../../src'; +import { coins } from '@bitgo/statics'; + +export const getBuilderFactory = (coin: string): TransactionBuilderFactory => { + return new TransactionBuilderFactory(coins.get(coin)); +}; diff --git a/modules/sdk-coin-baby/test/unit/keyPair.ts b/modules/sdk-coin-baby/test/unit/keyPair.ts new file mode 100644 index 0000000000..065885751c --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/keyPair.ts @@ -0,0 +1,119 @@ +import { fromBase64, toHex } from '@cosmjs/encoding'; +import assert from 'assert'; +import should from 'should'; + +import { KeyPair } from '../../src'; +import { TEST_ACCOUNT, TEST_SEND_TX } from '../resources/baby'; + +describe('Babylon Key Pair', () => { + describe('should create a valid KeyPair', () => { + it('from an empty value', () => { + const keyPairObj = new KeyPair(); + const keys = keyPairObj.getKeys(); + should.exists(keys.prv); + should.exists(keys.pub); + should.equal(keys.prv!.length, 64); + should.equal(keys.pub.length, 66); + + const extendedKeys = keyPairObj.getExtendedKeys(); + should.exists(extendedKeys.xprv); + should.exists(extendedKeys.xpub); + }); + + it('from a private key', () => { + const privateKey = TEST_SEND_TX.privateKey; + const keyPairObj = new KeyPair({ prv: toHex(fromBase64(privateKey)) }); + const keys = keyPairObj.getKeys(); + should.exists(keys.prv); + should.exists(keys.pub); + should.equal(keys.prv, toHex(fromBase64(TEST_SEND_TX.privateKey))); + should.equal(keys.pub, toHex(fromBase64(TEST_SEND_TX.pubKey))); + should.equal(keyPairObj.getAddress(), TEST_SEND_TX.sender); + + assert.throws(() => keyPairObj.getExtendedKeys()); + }); + + it('from a compressed public key', () => { + const publicKey = TEST_ACCOUNT.compressedPublicKey; + const keyPairObj = new KeyPair({ pub: publicKey }); + const keys = keyPairObj.getKeys(); + should.not.exist(keys.prv); + should.exists(keys.pub); + should.equal(keys.pub, TEST_ACCOUNT.compressedPublicKey); + + assert.throws(() => keyPairObj.getExtendedKeys()); + }); + + it('from an uncompressed public key', () => { + // Input is uncompressed, but we output the compressed key to keep + // parity with Cosmos network expectations. + const publicKey = TEST_ACCOUNT.uncompressedPublicKey; + const keyPairObj = new KeyPair({ pub: publicKey }); + const keys = keyPairObj.getKeys(); + should.not.exist(keys.prv); + should.exists(keys.pub); + should.notEqual(keys.pub, publicKey); + should.equal(keys.pub, TEST_ACCOUNT.compressedPublicKey); + + assert.throws(() => keyPairObj.getExtendedKeys()); + }); + }); + + describe('should fail to create a KeyPair', () => { + it('from an invalid privateKey', () => { + assert.throws( + () => new KeyPair({ prv: '' }), + (e: any) => e.message === 'Unsupported private key' + ); + }); + + it('from an invalid publicKey', () => { + assert.throws( + () => new KeyPair({ pub: '' }), + (e: any) => e.message.startsWith('Unsupported public key') + ); + }); + + it('from an undefined seed', () => { + const undefinedBuffer = undefined as unknown as Buffer; + assert.throws( + () => new KeyPair({ seed: undefinedBuffer }), + (e: any) => e.message.startsWith('Invalid key pair options') + ); + }); + + it('from an undefined private key', () => { + const undefinedStr: string = undefined as unknown as string; + assert.throws( + () => new KeyPair({ prv: undefinedStr }), + (e: any) => e.message.startsWith('Invalid key pair options') + ); + }); + + it('from an undefined public key', () => { + const undefinedStr: string = undefined as unknown as string; + assert.throws( + () => new KeyPair({ pub: undefinedStr }), + (e: any) => e.message.startsWith('Invalid key pair options') + ); + }); + }); + + describe('get unique address ', () => { + it('from a private key', () => { + const keyPair = new KeyPair({ prv: toHex(fromBase64(TEST_SEND_TX.privateKey)) }); + should.equal(keyPair.getAddress(), TEST_SEND_TX.sender); + }); + + it('from a compressed public key', () => { + const keyPair = new KeyPair({ pub: toHex(fromBase64(TEST_SEND_TX.pubKey)) }); + should.equal(keyPair.getAddress(), TEST_SEND_TX.sender); + }); + + it('should be different for different public keys', () => { + const keyPairOne = new KeyPair({ pub: TEST_ACCOUNT.compressedPublicKey }); + const keyPairTwo = new KeyPair({ pub: TEST_ACCOUNT.compressedPublicKeyTwo }); + should.notEqual(keyPairOne.getAddress(), keyPairTwo.getAddress()); + }); + }); +}); diff --git a/modules/sdk-coin-baby/test/unit/transaction.ts b/modules/sdk-coin-baby/test/unit/transaction.ts new file mode 100644 index 0000000000..f8255981d0 --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/transaction.ts @@ -0,0 +1,127 @@ +import { CosmosTransaction, SendMessage } from '@bitgo/abstract-cosmos'; +import { toHex, TransactionType } from '@bitgo/sdk-core'; +import { coins } from '@bitgo/statics'; +import { fromBase64 } from '@cosmjs/encoding'; +import should from 'should'; +import { Transaction } from '../../src'; +import utils from '../../src/lib/utils'; +import * as testData from '../resources/baby'; + +describe('Babylon Transaction', () => { + let tx: Transaction; + const config = coins.get('tbaby'); + + beforeEach(() => { + tx = new CosmosTransaction(config, utils); + }); + + describe('Empty transaction', () => { + it('should throw empty transaction', function () { + should.throws(() => tx.toJson(), 'Empty transaction'); + should.throws(() => tx.toBroadcastFormat(), 'Empty transaction'); + }); + }); + + describe('From raw transaction', () => { + it('should build a transfer from raw signed base64', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_SEND_TX.signedTxBase64); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_SEND_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_SEND_TX.gasBudget); + should.equal(json.publicKey, toHex(fromBase64(testData.TEST_SEND_TX.pubKey))); + should.equal( + (json.sendMessages[0].value as SendMessage).toAddress, + testData.TEST_SEND_TX.sendMessage.value.toAddress + ); + should.deepEqual( + (json.sendMessages[0].value as SendMessage).amount, + testData.TEST_SEND_TX.sendMessage.value.amount + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_SEND_TX.signature); + should.equal(tx.type, TransactionType.Send); + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + }); + + it('should build a transfer from raw signed hex', function () { + tx.enrichTransactionDetailsFromRawTransaction(toHex(fromBase64(testData.TEST_SEND_TX.signedTxBase64))); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_SEND_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_SEND_TX.gasBudget); + should.equal(json.publicKey, toHex(fromBase64(testData.TEST_SEND_TX.pubKey))); + should.equal( + (json.sendMessages[0].value as SendMessage).toAddress, + testData.TEST_SEND_TX.sendMessage.value.toAddress + ); + should.deepEqual( + (json.sendMessages[0].value as SendMessage).amount, + testData.TEST_SEND_TX.sendMessage.value.amount + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_SEND_TX.signature); + should.equal(tx.type, TransactionType.Send); + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + }); + + it('should fail to build a transfer from incorrect raw hex', function () { + should.throws( + () => tx.enrichTransactionDetailsFromRawTransaction('random' + testData.TEST_SEND_TX.signedTxBase64), + 'incorrect raw data' + ); + }); + it('should fail to explain transaction with invalid raw hex', function () { + should.throws(() => tx.enrichTransactionDetailsFromRawTransaction('randomString'), 'Invalid transaction'); + }); + }); + + describe('Explain transaction', () => { + it('should explain a transfer pay transaction', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_SEND_TX.signedTxBase64); + const explainedTransaction = tx.explainTransaction(); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: testData.TEST_SEND_TX.hash, + outputs: [ + { + address: testData.TEST_SEND_TX.recipient, + amount: testData.TEST_SEND_TX.sendAmount, + }, + ], + outputAmount: testData.TEST_SEND_TX.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: testData.TEST_SEND_TX.feeAmount }, + type: 0, + }); + }); + it('should fail to explain transaction with invalid raw base64 string', function () { + should.throws(() => tx.enrichTransactionDetailsFromRawTransaction('randomString'), 'Invalid transaction'); + }); + }); +}); diff --git a/modules/sdk-coin-baby/test/unit/transactionBuilder/transactionBuilder.ts b/modules/sdk-coin-baby/test/unit/transactionBuilder/transactionBuilder.ts new file mode 100644 index 0000000000..934d95945d --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/transactionBuilder/transactionBuilder.ts @@ -0,0 +1,55 @@ +import { TransactionType } from '@bitgo/sdk-core'; +import should from 'should'; + +import * as testData from '../../resources/baby'; +import { getBuilderFactory } from '../getBuilderFactory'; + +describe('Babylon Transaction Builder', async () => { + const factory = getBuilderFactory('tbaby'); + const testTxData = testData.TEST_SEND_TX; + let data; + + beforeEach(() => { + data = [ + { + type: TransactionType.Send, + testTx: testData.TEST_SEND_TX, + builder: factory.getTransferBuilder(), + }, + ]; + }); + + it('should build a signed tx from signed tx data', async function () { + const txBuilder = factory.from(testTxData.signedTxBase64); + const tx = await txBuilder.build(); + should.equal(tx.type, TransactionType.Send); + // Should recreate the same raw tx data when re-build and turned to broadcast format + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTxData.signedTxBase64); + }); + + describe('gasBudget tests', async () => { + it('should succeed for valid gasBudget', function () { + for (const { builder } of data) { + should.doesNotThrow(() => builder.gasBudget(testTxData.gasBudget)); + } + }); + + it('should throw for invalid gasBudget', function () { + const invalidGasBudget = 0; + for (const { builder } of data) { + should(() => builder.gasBudget({ gasLimit: invalidGasBudget })).throw('Invalid gas limit ' + invalidGasBudget); + } + }); + }); + + it('validateAddress', function () { + const invalidAddress = { address: 'randomString' }; + for (const { builder } of data) { + should.doesNotThrow(() => builder.validateAddress({ address: testTxData.sender })); + should(() => builder.validateAddress(invalidAddress)).throwError( + 'transactionBuilder: address isValidAddress check failed: ' + invalidAddress.address + ); + } + }); +}); diff --git a/modules/sdk-coin-baby/test/unit/transactionBuilder/transferBuilder.ts b/modules/sdk-coin-baby/test/unit/transactionBuilder/transferBuilder.ts new file mode 100644 index 0000000000..c78442de3d --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/transactionBuilder/transferBuilder.ts @@ -0,0 +1,193 @@ +import { TransactionType } from '@bitgo/sdk-core'; +import { fromBase64, toHex } from '@cosmjs/encoding'; +import should from 'should'; +import * as testData from '../../resources/baby'; +import { getBuilderFactory } from '../getBuilderFactory'; + +describe('Baby Transfer Builder', () => { + const factory = getBuilderFactory('tbaby'); + const testTx = testData.TEST_SEND_TX; + const testTx2 = testData.TEST_SEND_TX2; + const testTxWithMemo = testData.TEST_TX_WITH_MEMO; + it('should build a Transfer tx with signature', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + txBuilder.addSignature({ pub: toHex(fromBase64(testTx.pubKey)) }, Buffer.from(testTx.signature, 'base64')); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + }); + + it('should build a Transfer tx with signature and memo', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTxWithMemo.sequence); + txBuilder.gasBudget(testTxWithMemo.gasBudget); + txBuilder.messages([testTxWithMemo.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTxWithMemo.pubKey))); + txBuilder.memo(testTxWithMemo.memo); + txBuilder.addSignature( + { pub: toHex(fromBase64(testTxWithMemo.pubKey)) }, + Buffer.from(testTxWithMemo.signature, 'base64') + ); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTxWithMemo.gasBudget); + should.deepEqual(json.sendMessages, [testTxWithMemo.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTxWithMemo.pubKey))); + should.deepEqual(json.sequence, testTxWithMemo.sequence); + should.equal(json.memo, testTxWithMemo.memo); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTxWithMemo.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testTxWithMemo.sendMessage.value.fromAddress, + value: testTxWithMemo.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTxWithMemo.sendMessage.value.toAddress, + value: testTxWithMemo.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + }); + + it('should build a Transfer tx without signature', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + tx.toBroadcastFormat(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + }); + + it('should sign a Transfer tx', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTx2.sequence); + txBuilder.gasBudget(testTx2.gasBudget); + txBuilder.messages([testTx2.sendMessage.value]); + txBuilder.accountNumber(testTx2.accountNumber); + txBuilder.chainId(testTx2.chainId); + txBuilder.sign({ key: toHex(fromBase64(testTx2.privateKey)) }); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTx2.gasBudget); + should.deepEqual(json.sendMessages, [testTx2.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx2.pubKey))); + should.deepEqual(json.sequence, testTx2.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(tx.signature[0], toHex(fromBase64(testTx2.signature))); + should.equal(rawTx, testTx2.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX2.sender, + value: testData.TEST_SEND_TX2.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX2.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX2.sendMessage.value.amount[0].amount, + coin: 'tbaby', + }, + ]); + }); + + it('should build a sendMany Transfer tx', async function () { + const testSendManyTx = testData.TEST_SEND_MANY_TX; + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testSendManyTx.sequence); + txBuilder.gasBudget(testSendManyTx.gasBudget); + txBuilder.messages(testSendManyTx.sendMessages.map((msg) => msg.value)); + txBuilder.publicKey(toHex(fromBase64(testSendManyTx.pubKey))); + txBuilder.chainId(testSendManyTx.chainId); + txBuilder.accountNumber(testSendManyTx.accountNumber); + txBuilder.memo(testSendManyTx.memo); + txBuilder.addSignature( + { pub: toHex(fromBase64(testSendManyTx.pubKey)) }, + Buffer.from(testSendManyTx.signature, 'base64') + ); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testSendManyTx.gasBudget); + should.deepEqual(json.sendMessages, testSendManyTx.sendMessages); + should.deepEqual(json.publicKey, toHex(fromBase64(testSendManyTx.pubKey))); + should.deepEqual(json.sequence, testSendManyTx.sequence); + should.deepEqual( + tx.inputs, + testSendManyTx.sendMessages.map((msg) => { + return { + address: msg.value.fromAddress, + value: msg.value.amount[0].amount, + coin: 'tbaby', + }; + }) + ); + should.deepEqual( + tx.outputs, + testSendManyTx.sendMessages.map((msg) => { + return { + address: msg.value.toAddress, + value: msg.value.amount[0].amount, + coin: 'tbaby', + }; + }) + ); + should.equal(tx.id, testSendManyTx.hash); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testSendManyTx.signedTxBase64); + }); +}); diff --git a/modules/sdk-coin-baby/test/unit/utils.ts b/modules/sdk-coin-baby/test/unit/utils.ts new file mode 100644 index 0000000000..0beb7d2803 --- /dev/null +++ b/modules/sdk-coin-baby/test/unit/utils.ts @@ -0,0 +1,55 @@ +import should from 'should'; +import utils from '../../src/lib/utils'; +import { address, blockHash, txIds } from '../resources/baby'; +import * as testData from '../resources/baby'; + +describe('utils', () => { + it('should validate addresses correctly', () => { + should.equal(utils.isValidAddress(address.address1), true); + should.equal(utils.isValidAddress(address.address2), true); + should.equal(utils.isValidAddress(address.address3), false); + should.equal(utils.isValidAddress(address.address4), false); + should.equal(utils.isValidAddress('dfjk35y'), false); + should.equal(utils.isValidAddress(undefined as unknown as string), false); + should.equal(utils.isValidAddress(''), false); + }); + + it('should validate block hash correctly', () => { + should.equal(utils.isValidBlockId(blockHash.hash1), true); + should.equal(utils.isValidBlockId(blockHash.hash2), true); + should.equal(utils.isValidBlockId(undefined as unknown as string), false); + should.equal(utils.isValidBlockId(''), false); + }); + + it('should validate invalid block hash correctly', () => { + should.equal(utils.isValidBlockId(''), false); + should.equal(utils.isValidBlockId('0xade35465gfvdcsxsz24300'), false); + should.equal(utils.isValidBlockId(blockHash.hash2 + 'ff'), false); + should.equal(utils.isValidBlockId('latest'), false); + }); + + it('should validate transaction id correctly', () => { + should.equal(utils.isValidTransactionId(txIds.hash1), true); + should.equal(utils.isValidTransactionId(txIds.hash2), true); + should.equal(utils.isValidTransactionId(txIds.hash3), true); + }); + + it('should validate invalid transaction id correctly', () => { + should.equal(utils.isValidTransactionId(''), false); + should.equal(utils.isValidTransactionId(txIds.hash1.slice(3)), false); + should.equal(utils.isValidTransactionId(txIds.hash3 + '00'), false); + should.equal(utils.isValidTransactionId('dalij43ta0ga2dadda02'), false); + }); + + it('validateAmount', function () { + should.doesNotThrow(() => utils.validateAmountData([testData.coinAmounts.amount1])); + should.doesNotThrow(() => utils.validateAmountData([testData.coinAmounts.amount2])); + should.doesNotThrow(() => utils.validateAmountData([testData.coinAmounts.amount3])); + should(() => utils.validateAmountData([testData.coinAmounts.amount4])).throwError( + 'transactionBuilder: validateAmount: Invalid amount: ' + testData.coinAmounts.amount4.amount + ); + should(() => utils.validateAmountData([testData.coinAmounts.amount5])).throwError( + 'transactionBuilder: validateAmount: Invalid denom: ' + testData.coinAmounts.amount5.denom + ); + }); +}); diff --git a/modules/sdk-coin-baby/tsconfig.json b/modules/sdk-coin-baby/tsconfig.json new file mode 100644 index 0000000000..339ace24a2 --- /dev/null +++ b/modules/sdk-coin-baby/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./", + "strictPropertyInitialization": false, + "esModuleInterop": true, + "typeRoots": ["../../types", "./node_modules/@types", "../../node_modules/@types"] + }, + "include": ["src/**/*", "test/**/*"], + "exclude": ["node_modules"], + "references": [ + { + "path": "../sdk-api" + }, + { + "path": "../sdk-core" + }, + { + "path": "../sdk-test" + } + ] +} diff --git a/modules/sdk-core/src/bitgo/environments.ts b/modules/sdk-core/src/bitgo/environments.ts index 1eec781efd..1967342c79 100644 --- a/modules/sdk-core/src/bitgo/environments.ts +++ b/modules/sdk-core/src/bitgo/environments.ts @@ -47,6 +47,7 @@ interface EnvironmentTemplate { zetaNodeUrl: string; coreumNodeUrl: string; runeNodeUrl: string; + babyNodeUrl: string; islmNodeUrl: string; dotNodeUrls: string[]; tronNodes: { @@ -144,6 +145,7 @@ const mainnetBase: EnvironmentTemplate = { zetaNodeUrl: 'https://zetachain.blockpi.network', // reference https://www.zetachain.com/docs/reference/api/ coreumNodeUrl: 'https://full-node.mainnet-1.coreum.dev:1317', runeNodeUrl: 'https://thornode.ninerealms.com', + babyNodeUrl: 'https://babylon.explorers.guru/', islmNodeUrl: 'https://rest.cosmos.haqq.network', dotNodeUrls: ['wss://rpc.polkadot.io'], tronNodes: { @@ -198,6 +200,7 @@ const testnetBase: EnvironmentTemplate = { zetaNodeUrl: 'https://rest.nodejumper.io/zetachaintestnet', // reference : https://www.zetachain.com/docs/reference/api/ coreumNodeUrl: 'https://full-node.testnet-1.coreum.dev:1317', runeNodeUrl: 'https://stagenet-thornode.ninerealms.com', + babyNodeUrl: 'https://testnet.babylon.explorers.guru/', islmNodeUrl: 'https://rest.cosmos.testedge2.haqq.network ', dotNodeUrls: ['wss://westend-rpc.polkadot.io'], tronNodes: { diff --git a/tsconfig.packages.json b/tsconfig.packages.json index 4419a7c178..9677c72442 100644 --- a/tsconfig.packages.json +++ b/tsconfig.packages.json @@ -4,15 +4,15 @@ { "path": "./modules/abstract-cosmos" }, - { - "path": "./modules/abstract-substrate" - }, { "path": "./modules/abstract-eth" }, { "path": "./modules/abstract-lightning" }, + { + "path": "./modules/abstract-substrate" + }, { "path": "./modules/abstract-utxo" }, @@ -58,6 +58,9 @@ { "path": "./modules/sdk-coin-avaxp" }, + { + "path": "./modules/sdk-coin-baby" + }, { "path": "./modules/sdk-coin-bch" },