diff --git a/lib/src/coin/cronosCoin.ts b/lib/src/coin/cronosCoin.ts new file mode 100644 index 00000000..4a873aa6 --- /dev/null +++ b/lib/src/coin/cronosCoin.ts @@ -0,0 +1,144 @@ +import Big from 'big.js'; +import ow from 'ow'; + +import { owCoin } from './ow.types'; +import { InitConfigurations } from '../core/cro'; +import { Network } from '../network/network'; +import { Coin as CosmosCoin, coin as cosmosCoin, coins as cosmosCoins } from '../cosmos/coins'; +import { ICoin } from './coin'; + +export const cronosCoin = function (config: InitConfigurations) { + return class CronosCoin implements ICoin { + /** + * Total supply in base unit represented as string + * @type {string} + * @static + * @memberof Coin + */ + public static TOTAL_SUPPLY_STRING = '100000000000000000000000000000'; + + public static TOTAL_SUPPLY = new CronosCoin(CronosCoin.TOTAL_SUPPLY_STRING); + + /** + * Coin value stored in basic unit as Big + */ + public readonly baseAmount: Big; + + public readonly network: Network; + + /** + * Constructor to create a Coin + * @param {string} amount coins amount represented as string + * @throws {Error} amount or unit is invalid + * @returns {Coin} + */ + constructor(amount: string) { + ow(amount, 'amount', ow.string); + + let coins: Big; + try { + coins = new Big(amount); + } catch (err) { + throw new TypeError(`Expected amount to be a base10 number represented as string, got \`${amount}\``); + } + this.network = config.network; + this.baseAmount = CronosCoin.parseBaseAmount(coins); + } + + getNetwork(): Network { + return this.network; + } + + /** + * Parse and validate a amount in base unit represented as Big + * @param {Big} baseAmount amount in base unit + * @returns {Big} the parsed coins in base unit + * @throws {TypeError} coins amount is invalid + */ + static parseBaseAmount(baseAmount: Big): Big { + if (baseAmount.cmp(baseAmount.toFixed(0)) !== 0) { + throw new TypeError(`Expected base amount to be an integer, got \`${baseAmount}\``); + } + if (baseAmount.lt(0)) { + throw new TypeError(`Expected base amount to be positive, got \`${baseAmount}\``); + } + + if (baseAmount.gt(CronosCoin.TOTAL_SUPPLY_STRING)) { + throw new TypeError(`Expected base amount to be within total supply, got \`${baseAmount}\``); + } + + return baseAmount; + } + + /** + * Add two coins together and returns a new Coin + * @param {Coin} anotherCoin coins to add + * @returns {Coin} + * @throws {Error} adding two coins would exceed total supply + * @memberof Coin + */ + public add(anotherCoin: CronosCoin): CronosCoin { + ow(anotherCoin, owCoin()); + + const newAmount = this.baseAmount.add(anotherCoin.toBig()); + if (newAmount.gt(CronosCoin.TOTAL_SUPPLY_STRING)) { + throw new Error('Adding two Coin together exceed total supply'); + } + return new CronosCoin(newAmount.toString()); + } + + /** + * Subtract another Coin and returns a new Coin + * @param {Coin} anotherCoin coins to subtract + * @returns {Coin} + * @throws {Error} subtracting two coins would become negative + * @memberof Coin + */ + public sub(anotherCoin: CronosCoin): CronosCoin { + ow(anotherCoin, owCoin()); + + const newAmount = this.baseAmount.sub(anotherCoin.toBig()); + if (newAmount.lt(0)) { + throw new Error('Subtracting the Coin results in negation Coin'); + } + return new CronosCoin(newAmount.toString()); + } + + /** + * Returns the Big representation of the Coin in base unit + * @returns {Big} + * @memberof Coin + */ + public toBig(): Big { + return this.baseAmount; + } + + /** + * Returns the Cosmos-compatible Coin object representation + * @returns {CosmosCoin} + * @memberof Coin + * */ + public toCosmosCoin(): CosmosCoin { + return cosmosCoin(this.toString(), config.network.coin.baseDenom); + } + + /** + * Returns the Cosmos-compatible Coin object representation + * @returns {CosmosCoin[]} + * @memberof Coin + * */ + public toCosmosCoins(): CosmosCoin[] { + return cosmosCoins(this.toString(), config.network.coin.baseDenom); + } + + /** + * Returns a string representation of the Coin. + * @returns {string} + * @throws {Error} unit is invalid + * @memberof Coin + */ + public toString(): string { + return this.baseAmount.toString(); + } + }; +}; diff --git a/lib/src/coin/ow.types.ts b/lib/src/coin/ow.types.ts index 70463ee5..43bc1fc6 100644 --- a/lib/src/coin/ow.types.ts +++ b/lib/src/coin/ow.types.ts @@ -1,6 +1,6 @@ import ow from 'ow'; import { owOptionalStrictObject, owStrictObject } from '../ow.types'; -import { isCoin, Units } from './coin'; +import { isCoin, Units } from './v2.coin/v2.coin'; export const owCoinUnit = ow.string.validate((val) => ({ validator: Object.values(Units).includes(val as any), diff --git a/lib/src/coin/v2.coin/v2.coin.ts b/lib/src/coin/v2.coin/v2.coin.ts index 409419b9..8799f212 100644 --- a/lib/src/coin/v2.coin/v2.coin.ts +++ b/lib/src/coin/v2.coin/v2.coin.ts @@ -9,6 +9,7 @@ import { Coin as CosmosCoin, coin as cosmosCoin, coins as cosmosCoins } from '.. export enum Units { BASE = 'base', CRO = 'cro', + CUSTOM = 'custom', } // Duck type check due to limitations of non exportable type for proper instance of checks @@ -111,6 +112,8 @@ export const coinv2 = function (config: InitConfigurations) { } else if (!['cro', 'tcro'].includes(denom.toLowerCase())) { throw new Error('Provided Units and Denom do not belong to the same network.'); } + } else if (unit === Units.CUSTOM) { + this.baseAmount = CoinV2.parseCustomAmount(coins); } this.denom = denom || this.network.coin.baseDenom; this.receivedAmount = coins; @@ -122,7 +125,7 @@ export const coinv2 = function (config: InitConfigurations) { * @param {string} denom chain compatible denom value */ public static fromCustomAmountDenom = (amount: string, denom: string): CoinV2 => { - return new CoinV2(amount, Units.BASE, denom); + return new CoinV2(amount, Units.CUSTOM, denom); }; getNetwork(): Network { @@ -173,6 +176,23 @@ export const coinv2 = function (config: InitConfigurations) { return baseAmount; } + /** + * Parse and validate a amount in base unit represented as Big + * @param {Big} customAmount amount in base unit + * @returns {Big} the parsed coins in base unit + * @throws {TypeError} coins amount is invalid + */ + static parseCustomAmount(customAmount: Big): Big { + if (customAmount.cmp(customAmount.toFixed(0)) !== 0) { + throw new TypeError(`Expected base amount to be an integer, got \`${customAmount}\``); + } + if (customAmount.lt(0)) { + throw new TypeError(`Expected base amount to be positive, got \`${customAmount}\``); + } + + return customAmount; + } + /** * Create a Coin from the base unit * @param {string} baseValue coins value in base unit diff --git a/lib/src/core/cronos.ts b/lib/src/core/cronos.ts new file mode 100644 index 00000000..b2a52e8d --- /dev/null +++ b/lib/src/core/cronos.ts @@ -0,0 +1,144 @@ +import ow from 'ow'; + +import { croClient } from '../client/client'; +import { Network } from '../network/network'; +import { cronosCoin } from '../coin/cronosCoin'; +import { owCroSDKInitParams } from './ow.types'; +import { rawTransaction } from '../transaction/raw'; +import { msgSend } from '../transaction/msg/bank/msgsend'; +import { userAddress } from '../address/address'; +import { msgDeposit } from '../transaction/msg/gov/MsgDeposit'; +import { msgVote } from '../transaction/msg/gov/MsgVote'; +import { msgSubmitProposal } from '../transaction/msg/gov/MsgSubmitProposal'; +import { communityPoolSpendProposal } from '../transaction/msg/gov/proposal/CommunityPoolSpendProposal'; +import { paramChangeProposal } from '../transaction/msg/gov/proposal/ParamChangeProposal'; +import { cancelSoftwareUpgradeProposal } from '../transaction/msg/gov/proposal/CancelSoftwareUpgradeProposal'; +import { softwareUpgradeProposal } from '../transaction/msg/gov/proposal/SoftwareUpgradeProposal'; +import { textProposal } from '../transaction/msg/gov/proposal/TextProposal'; +import { msgTransferIBC } from '../transaction/msg/ibc/applications/MsgTransfer'; +import { msgCreateClientIBC } from '../transaction/msg/ibc/core/MsgCreateClient'; +import { msgSendV2 } from '../transaction/msg/v2/bank/v2.msgsend'; +import { msgDepositV2 } from '../transaction/msg/v2/gov/v2.MsgDeposit'; +import { communityPoolSpendProposalV2 } from '../transaction/msg/v2/gov/proposal/v2.CommunityPoolSpendProposal'; +import { msgSubmitProposalV2 } from '../transaction/msg/v2/gov/v2.MsgSubmitProposal'; +import { msgUpdateClientIBC } from '../transaction/msg/ibc/core/MsgUpdateClient'; +import { msgUpgradeClientIBC } from '../transaction/msg/ibc/core/MsgUpgradeClient'; +import { msgSubmitMisbehaviourIBC } from '../transaction/msg/ibc/core/MsgSubmitMisbehaviour'; +import { rawTransactionV2 } from '../transaction/v2.raw'; +import { msgClientState } from '../transaction/msg/ibc/lightclients/ClientState'; +import { msgConsensusState } from '../transaction/msg/ibc/lightclients/ConsensusState'; +import { msgHeader } from '../transaction/msg/ibc/lightclients/Header'; +import { MsgConnectionOpenConfirmIBC } from '../transaction/msg/ibc/core/connection/MsgConnectionOpenConfirm'; +import { MsgConnectionOpenTryIBC } from '../transaction/msg/ibc/core/connection/MsgConnectionOpenTry'; +import { InitConfigurations } from './cro'; +import { coinv2 } from '../coin/v2.coin/v2.coin'; + +export const CronosSDK = function (configs: InitConfigurations) { + ow(configs, 'configs', owCroSDKInitParams); + + return { + CroClient: croClient(configs), + CronosCoin: cronosCoin(configs), + RawTransaction: rawTransaction(configs), + Address: userAddress(configs), + gov: { + MsgDeposit: msgDeposit(configs), + MsgVote: msgVote(configs), + MsgSubmitProposal: msgSubmitProposal(configs), + proposal: { + CommunityPoolSpendProposal: communityPoolSpendProposal(configs), + ParamChangeProposal: paramChangeProposal(), + CancelSoftwareUpgradeProposal: cancelSoftwareUpgradeProposal(), + SoftwareUpgradeProposal: softwareUpgradeProposal(), + TextProposal: textProposal(), + }, + }, + bank: { + MsgSend: msgSend(configs), + }, + ibc: { + MsgTransfer: msgTransferIBC(configs), + MsgCreateClient: msgCreateClientIBC(configs), + MsgUpdateClient: msgUpdateClientIBC(configs), + MsgUpgradeClient: msgUpgradeClientIBC(configs), + MsgSubmitMisbehaviour: msgSubmitMisbehaviourIBC(configs), + lightclient: { + ClientState: msgClientState(), + ConsensusState: msgConsensusState(), + Header: msgHeader(), + }, + connection: { + MsgConnectionOpenConfirm: MsgConnectionOpenConfirmIBC(configs), + MsgConnectionOpenTry: MsgConnectionOpenTryIBC(configs), + }, + }, + v2: { + bank: { + MsgSendV2: msgSendV2(configs), + }, + gov: { + MsgDepositV2: msgDepositV2(configs), + MsgSubmitProposalV2: msgSubmitProposalV2(configs), + proposal: { + CommunityPoolSpendProposalV2: communityPoolSpendProposalV2(configs), + }, + }, + RawTransactionV2: rawTransactionV2(configs), + CoinV2: coinv2(configs), + }, + Options: configs, + }; +}; + +export class CronosNetwork { + public static Mainnet: Network = { + defaultNodeUrl: 'https://rpc.cronos.org', + chainId: 'cronosmainnet_25-1', + addressPrefix: 'crc', + validatorAddressPrefix: 'crccncl', + validatorPubKeyPrefix: 'crccnclconspub', + coin: { + baseDenom: 'basecro', + croDenom: '', + }, + bip44Path: { + coinType: 1, + account: 0, + }, + rpcUrl: 'https://rpc.cronos.org', + }; + + public static Testnet: Network = { + defaultNodeUrl: 'https://rpc-t3.cronos.org', + chainId: 'cronostestnet_338-3', + addressPrefix: 'tcro', + validatorAddressPrefix: 'tcrocncl', + validatorPubKeyPrefix: 'tcrocnclconspub', + coin: { + baseDenom: 'basetcro', + croDenom: '', + }, + bip44Path: { + coinType: 1, + account: 0, + }, + rpcUrl: 'https://rpc-t3.cronos.org', + }; + + public static TestnetP11: Network = { + defaultNodeUrl: 'https://rpc-p11.cronos.org', + chainId: 'pioneereleventestnet_340-1', + addressPrefix: 'tcrc', + validatorAddressPrefix: 'tcrccncl', + validatorPubKeyPrefix: 'tcrccnclconspub', + coin: { + baseDenom: 'basetcro', + croDenom: '', + }, + bip44Path: { + coinType: 1, + account: 0, + }, + rpcUrl: 'https://rpc-p11.cronos.org:443', + }; +} diff --git a/lib/src/index.ts b/lib/src/index.ts index 21584458..a9f8174a 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -3,7 +3,8 @@ import { HDKey } from './hdkey/hdkey'; import { Secp256k1KeyPair } from './keypair/secp256k1'; import utils from './utils'; import { CroNetwork, CroSDK } from './core/cro'; -import { Units } from './coin/coin'; +import { CronosNetwork, CronosSDK } from './core/cronos'; +import { Units } from './coin/v2.coin/v2.coin'; import { TxDecoder } from './utils/txDecoder'; // The maximum number of decimal places of the results of operations involving division @@ -15,7 +16,9 @@ const _ = { HDKey, Secp256k1KeyPair, CroSDK, + CronosSDK, CroNetwork, + CronosNetwork, Units, TxDecoder, };