Skip to content

Commit

Permalink
Merge pull request #61 from aldrin-labs/feature/create-coin-params-va…
Browse files Browse the repository at this point in the history
…lidation

Feature/create coin params validation
  • Loading branch information
avernikoz authored Apr 22, 2024
2 parents f52cba0 + 11e3dd4 commit 602b3b0
Show file tree
Hide file tree
Showing 4 changed files with 336 additions and 16 deletions.
2 changes: 1 addition & 1 deletion examples/create-coin/create-coin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ createCoin({
description: "test-token-321",
fixedSupply: true,
mintAmount: "9000000",
name: "test-token-321",
name: "test token 321",
signerAddress: user,
symbol: "TEST_TOKEN_THR",
url: "",
Expand Down
145 changes: 145 additions & 0 deletions src/errors/create-coin/invalid-param-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Custom error class representing an error when a coin name provided for the coin creation is invalid.
* @class
* @extends Error
*/
export class InvalidCoinNameError extends Error {
/**
* Creates an instance of InvalidCoinNameError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin symbol provided for the coin creation is invalid.
* @class
* @extends Error
*/
export class InvalidCoinSymbolError extends Error {
/**
* Creates an instance of InvalidCoinSymbolError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin decimals provided for the coin creation are invalid.
* @class
* @extends Error
*/
export class InvalidCoinDecimalsError extends Error {
/**
* Creates an instance of InvalidCoinDecimalsError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin total supply provided for the coin creation is invalid.
* @class
* @extends Error
*/
export class InvalidCoinTotalSupplyError extends Error {
/**
* Creates an instance of InvalidCoinTotalSupplyError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin description provided for the coin creation is invalid.
* @class
* @extends Error
*/
export class InvalidCoinDescriptionError extends Error {
/**
* Creates an instance of InvalidCoinDescriptionError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin image provided for the coin creation is invalid.
* @class
* @extends Error
*/
export class InvalidCoinImageError extends Error {
/**
* Creates an instance of InvalidCoinImageError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a signer address provided for the coin creation is invalid.
* @class
* @extends Error
*/
export class InvalidSignerAddressError extends Error {
/**
* Creates an instance of InvalidSignerAddressError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin name and a coin description provided for the coin creation
* are equal.
* @class
* @extends Error
*/
export class NameEqualsToDescriptionError extends Error {
/**
* Creates an instance of NameEqualsToDescriptionError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}

/**
* Custom error class representing an error when a coin symbol and a coin description provided for the coin creation
* are equal.
* @class
* @extends Error
*/
export class SymbolEqualsToDescriptionError extends Error {
/**
* Creates an instance of SymbolEqualsToDescriptionError.
* @constructor
* @param {string} msg - The error message.
*/
constructor(msg: string) {
super(msg);
}
}
98 changes: 83 additions & 15 deletions src/managers/coin/CoinManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,29 @@
import { CoinMetadata, SuiClient } from "@mysten/sui.js/client";
import { TransactionBlock } from "@mysten/sui.js/transactions";
import { normalizeSuiAddress } from "@mysten/sui.js/utils";
import { isValidSuiAddress, normalizeSuiAddress } from "@mysten/sui.js/utils";
import {
InvalidCoinDecimalsError,
InvalidCoinDescriptionError,
InvalidCoinImageError,
InvalidCoinNameError,
InvalidCoinSymbolError,
InvalidCoinTotalSupplyError,
InvalidSignerAddressError,
NameEqualsToDescriptionError,
SymbolEqualsToDescriptionError,
} from "../../errors/create-coin/invalid-param-errors";
import { LONG_SUI_COIN_TYPE, SHORT_SUI_COIN_TYPE } from "../../providers/common";
import { CommonCoinData, CreateCoinTransactionParams, ICoinManager, Providers, UpdatedCoinsCache } from "../types";
import initMoveByteCodeTemplate from "./create-coin/utils/move-bytecode-template";
import { getBytecode } from "./create-coin/utils/template";
import {
validateCoinDecimals,
validateCoinDescription,
validateCoinImage,
validateCoinName,
validateCoinSymbol,
validateTotalSupply,
} from "./create-coin/utils/validation";

/**
* @class CoinManagerSingleton
Expand Down Expand Up @@ -219,22 +238,15 @@ export class CoinManagerSingleton implements ICoinManager {
* @param {string} params.signerAddress - The address of the signer.
* @param {TransactionBlock} [params.transaction] - The optional transaction block.
* @return {Promise<TransactionBlock>} - A promise resolving to the created transaction block.
* @throws {Error} If the request to create the coin fails.
* @throws {Error} If the request to create the coin or params validation fails.
*/
public static async getCreateCoinTransaction({
name,
symbol,
decimals,
fixedSupply,
mintAmount,
url,
description,
signerAddress,
transaction,
}: CreateCoinTransactionParams): Promise<TransactionBlock> {
const tx = transaction ?? new TransactionBlock();

public static async getCreateCoinTransaction(params: CreateCoinTransactionParams): Promise<TransactionBlock> {
try {
CoinManagerSingleton.validateCreateCoinParams(params);

const { name, symbol, decimals, fixedSupply, mintAmount, url, description, signerAddress, transaction } = params;
const tx = transaction ?? new TransactionBlock();

await initMoveByteCodeTemplate(CoinManagerSingleton.COIN_CREATION_BYTECODE_TEMPLATE_URL);

const [upgradeCap] = tx.publish({
Expand Down Expand Up @@ -263,4 +275,60 @@ export class CoinManagerSingleton implements ICoinManager {
throw error;
}
}

/**
* Validates parameters for creating the coin.
*
* @param {CreateCoinTransactionParams} params - Parameters for creating the coin.
* @throws {Error} If the validation fails.
*/
public static validateCreateCoinParams({
name,
symbol,
decimals,
mintAmount,
url,
description,
signerAddress,
}: CreateCoinTransactionParams): void {
if (!validateCoinName(name)) {
throw new InvalidCoinNameError(`[validateCreateCoinParams] Coin name ${name} is invalid`);
}

if (!validateCoinSymbol(symbol)) {
throw new InvalidCoinSymbolError(`[validateCreateCoinParams] Coin symbol ${symbol} is invalid`);
}

if (!validateCoinDecimals(decimals)) {
throw new InvalidCoinDecimalsError(`[validateCreateCoinParams] Coin decimals ${decimals} are invalid`);
}

if (!validateTotalSupply(mintAmount, decimals)) {
throw new InvalidCoinTotalSupplyError(`[validateCreateCoinParams] Total supply ${mintAmount} is invalid`);
}

if (!validateCoinDescription(description)) {
throw new InvalidCoinDescriptionError(`[validateCreateCoinParams] Coin description ${description} is invalid`);
}

if (!validateCoinImage(url)) {
throw new InvalidCoinImageError(`[validateCreateCoinParams] Coin image ${url} is invalid`);
}

if (!isValidSuiAddress(signerAddress)) {
throw new InvalidSignerAddressError(`[validateCreateCoinParams] Signer address ${signerAddress} is invalid`);
}

if (name.trim() === description.trim()) {
throw new NameEqualsToDescriptionError(
`[validateCreateCoinParams] Coin name ${name} and coin description ${description} are equal`,
);
}

if (symbol.trim() === description.trim()) {
throw new SymbolEqualsToDescriptionError(
`[validateCreateCoinParams] Coin symbol ${symbol} and coin description ${description} are equal`,
);
}
}
}
107 changes: 107 additions & 0 deletions src/managers/coin/create-coin/utils/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import BigNumber from "bignumber.js";

/**
* Validates the coin name to be a non-empty string.
*
* @param {string} coinName - The coin name to validate.
* @return {boolean} - Returns true if the coin name is valid, otherwise false.
*/
export function validateCoinName(coinName: string): boolean {
const regex = /^[a-zA-Z0-9\s]+$/;
return typeof coinName === "string" && coinName.trim() !== "" && regex.test(coinName);
}

/**
* Validates the coin symbol based on the specified pattern.
*
* @param {string} coinSymbol - The coin symbol to validate.
* @return {boolean} - Returns true if the coin symbol is valid, otherwise false.
*/
export function validateCoinSymbol(coinSymbol: string): boolean {
const regex = /^[a-zA-Z_]+$/;
const isCoinSymbolIsValid = typeof coinSymbol === "string" && regex.test(coinSymbol);

return isCoinSymbolIsValid;
}

/**
* Validates the coin description to be a string.
*
* @param {string} coinDescription - The coin description to validate.
* @return {boolean} - Returns true if the coin description is a string, otherwise false.
*/
export function validateCoinDescription(coinDescription: string): boolean {
return typeof coinDescription === "string";
}

/**
* Validates the coin decimals based on the specified pattern.
*
* @param {string} coinDecimals - The coin decimals to validate (as a string).
* @return {boolean} - Returns true if the coin decimals are a valid integer, otherwise false.
*/
export function validateCoinDecimals(coinDecimals: string): boolean {
// Convert the string to an integer
const decimalsAsInt = parseInt(coinDecimals, 10);

// Check if the conversion is successful and perform the validations
return (
typeof decimalsAsInt === "number" &&
!isNaN(decimalsAsInt) &&
decimalsAsInt >= 0 &&
decimalsAsInt <= 11 &&
decimalsAsInt === Math.floor(decimalsAsInt)
);
}

/**
* Calculate the maximum total supply based on decimals.
*
* @param {number} decimals - The number of decimals for the token.
* @return {BigNumber} The maximum total supply.
*/
export function calculateMaxTotalSupply(decimals: string): BigNumber {
const pow = 19 - parseInt(decimals);
const output = new BigNumber(10).pow(pow).minus(1);

return output;
}

/**
* Validates the total supply to be a string containing only numbers and not exceeding the specified maxTotalSupply.
*
* @param {string} totalSupply - The total supply to validate.
* @param {number} decimals - The number of decimals for the token.
* @return {boolean} - Returns true if the total supply is a string containing only numbers and
* does not exceed or equal maxTotalSupply, otherwise false.
*/
export function validateTotalSupply(totalSupply: string, decimals: string): boolean {
if (typeof totalSupply !== "string" || !/^\d+$/.test(totalSupply)) {
return false; // Return false if totalSupply is not a string containing only numbers
}

const totalSupplyBigNumber = new BigNumber(totalSupply);
const maxTotalSupply = calculateMaxTotalSupply(decimals);
const isTotalSupplyIsValid = totalSupplyBigNumber.isLessThanOrEqualTo(maxTotalSupply);

return isTotalSupplyIsValid;
}

/**
* Validates a parameter intended for use as an image of a coin.
*
* @param {string} coinImage - The value to be validated as a coin image.
* @return {boolean} Returns true if the coinImage is valid, otherwise false.
*
* @description
* This function validates the `coinImage` parameter to ensure it meets the criteria for an acceptable coin image.
* The validation process includes:
* - Checking if the `coinImage` parameter is an empty string, which is allowed.
* - Verifying if the `coinImage` parameter matches either a base64-encoded string or a valid URL format.
*/
export function validateCoinImage(coinImage: string): boolean {
const base64ImageRegex = /^data:image\/(png|jpeg|jpg|gif);base64,([A-Za-z0-9+/]+={0,2})$/;
const urlRegex = /^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$/i;

return coinImage === "" || base64ImageRegex.test(coinImage) || urlRegex.test(coinImage);
}

0 comments on commit 602b3b0

Please sign in to comment.