aftermath-ts-sdk
Version:
Aftermath TypeScript SDK
695 lines (694 loc) • 28.2 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Helpers = void 0;
const dynamicFieldsApiHelpers_1 = require("../apiHelpers/dynamicFieldsApiHelpers");
const eventsApiHelpers_1 = require("../apiHelpers/eventsApiHelpers");
const inspectionsApiHelpers_1 = require("../apiHelpers/inspectionsApiHelpers");
const objectsApiHelpers_1 = require("../apiHelpers/objectsApiHelpers");
const transactionsApiHelpers_1 = require("../apiHelpers/transactionsApiHelpers");
const casting_1 = require("./casting");
const utils_1 = require("@mysten/sui/utils");
const cryptography_1 = require("@mysten/sui/cryptography");
const ed25519_1 = require("@mysten/sui/keypairs/ed25519");
const secp256k1_1 = require("@mysten/sui/keypairs/secp256k1");
const secp256r1_1 = require("@mysten/sui/keypairs/secp256r1");
/**
* A utility class containing various helper functions for general use across
* the Aftermath TS ecosystem. This includes numeric operations, object field
* extraction, array transformations, slippage adjustments, and Move error parsing.
*/
class Helpers {
/**
* Combines two arrays into a single array of pairs. The result length is the
* minimum of the two input arrays' lengths.
*
* @param firstCollection - The first array.
* @param lastCollection - The second array.
* @returns An array of `[firstCollection[i], lastCollection[i]]` pairs.
*/
static zip(firstCollection, lastCollection) {
const length = Math.min(firstCollection.length, lastCollection.length);
const zipped = [];
for (let index = 0; index < length; index++) {
zipped.push([firstCollection[index], lastCollection[index]]);
}
return zipped;
}
/**
* Removes circular references from an object or array, returning a JSON-safe structure.
* Any cyclic references are replaced with `undefined`.
*
* @param obj - The object or array to remove circular references from.
* @param seen - Internal usage to track references that have already been visited.
* @returns A structure that can be safely JSON-stringified.
*/
static removeCircularReferences(obj, seen = new WeakSet()) {
if (obj && typeof obj === "object") {
if (seen.has(obj)) {
return undefined;
}
seen.add(obj);
if (Array.isArray(obj)) {
return obj.map((item) => this.removeCircularReferences(item, seen));
}
else {
const entries = Object.entries(obj).map(([key, value]) => [
key,
this.removeCircularReferences(value, seen),
]);
return Object.fromEntries(entries);
}
}
return obj;
}
// =========================================================================
// Type Checking
// =========================================================================
/**
* Checks if an unknown value is an array of strings.
*
* @param value - The value to check.
* @returns `true` if `value` is a string array, otherwise `false`.
*/
static isArrayOfStrings(value) {
return (Array.isArray(value) &&
value.every((item) => typeof item === "string"));
}
// =========================================================================
// Sui Object Parsing
// =========================================================================
/**
* Extracts the fully qualified type (e.g., "0x2::coin::Coin<...>") from a `SuiObjectResponse`,
* normalizing it with leading zeroes if necessary.
*
* @param data - The object response from Sui.
* @returns The normalized object type string.
* @throws If the type is not found.
*/
static getObjectType(data) {
var _b, _c;
const objectType = (_b = data.data) === null || _b === void 0 ? void 0 : _b.type;
if (objectType)
return _a.addLeadingZeroesToType(objectType);
throw new Error("no object type found on " + ((_c = data.data) === null || _c === void 0 ? void 0 : _c.objectId));
}
/**
* Extracts the object ID from a `SuiObjectResponse`, normalizing it with leading zeroes.
*
* @param data - The object response from Sui.
* @returns A zero-padded `ObjectId`.
* @throws If the objectId is not found.
*/
static getObjectId(data) {
var _b, _c;
const objectId = (_b = data.data) === null || _b === void 0 ? void 0 : _b.objectId;
if (objectId)
return _a.addLeadingZeroesToType(objectId);
throw new Error("no object id found on " + ((_c = data.data) === null || _c === void 0 ? void 0 : _c.type));
}
/**
* Retrieves the fields of a Move object from a `SuiObjectResponse`.
*
* @param data - The Sui object response containing a Move object.
* @returns A record of fields for that object.
* @throws If no fields are found.
*/
static getObjectFields(data) {
var _b, _c;
try {
const content = (_b = data.data) === null || _b === void 0 ? void 0 : _b.content;
return content.fields;
}
catch (e) {
throw new Error("no object fields found on " + ((_c = data.data) === null || _c === void 0 ? void 0 : _c.objectId));
}
}
/**
* Retrieves display metadata from a Sui object response, if present.
*
* @param data - The Sui object response.
* @returns The display fields for that object.
* @throws If display fields are not found.
*/
static getObjectDisplay(data) {
var _b, _c;
const display = (_b = data.data) === null || _b === void 0 ? void 0 : _b.display;
if (display)
return display;
throw new Error("no object display found on " + ((_c = data.data) === null || _c === void 0 ? void 0 : _c.objectId));
}
// =========================================================================
// Error Parsing
// =========================================================================
/**
* Parses a MoveAbort error message from Sui into a possible `(errorCode, packageId, module)`,
* if the message follows a known pattern. Otherwise returns undefined.
*
* @param inputs - The object containing the raw `errorMessage` from Sui.
* @returns A partial structure of the error details or undefined.
*/
static parseMoveErrorMessage(inputs) {
const { errorMessage } = inputs;
if (!errorMessage.toLowerCase().includes("moveabort"))
return undefined;
const moveErrorCode = (inputs) => {
const { errorMessage } = inputs;
const startIndex = errorMessage.lastIndexOf(",");
const endIndex = errorMessage.lastIndexOf(")");
if (startIndex <= 0 || endIndex <= 0 || startIndex >= endIndex)
return undefined;
try {
const errorCode = parseInt(errorMessage.slice(startIndex + 1, endIndex));
if (Number.isNaN(errorCode))
return undefined;
return errorCode;
}
catch (_b) {
return undefined;
}
};
const moveErrorPackageId = (inputs) => {
const { errorMessage } = inputs;
const startIndex = errorMessage.toLowerCase().indexOf("address:");
const endIndex = errorMessage.indexOf(", name:");
if (startIndex <= 0 || endIndex <= 0 || startIndex >= endIndex)
return undefined;
try {
const pkgStr = errorMessage
.slice(startIndex + 8, endIndex)
.trim()
.replaceAll("0x", "");
const packageId = _a.addLeadingZeroesToType("0x" + pkgStr);
if (!this.isValidHex(packageId))
return undefined;
return packageId;
}
catch (_b) {
return undefined;
}
};
const moveErrorModule = (inputs) => {
const { errorMessage } = inputs;
const startIndex = errorMessage
.toLowerCase()
.indexOf('identifier("');
const endIndex = errorMessage.indexOf('")');
if (startIndex <= 0 || endIndex <= 0 || startIndex >= endIndex)
return undefined;
try {
return errorMessage.slice(startIndex + 12, endIndex).trim();
}
catch (_b) {
return undefined;
}
};
const errorCode = moveErrorCode({ errorMessage });
const packageId = moveErrorPackageId({ errorMessage });
const module = moveErrorModule({ errorMessage });
if (errorCode === undefined || !packageId || !module)
return undefined;
return { errorCode, packageId, module };
}
/**
* Translates a Move abort error message into a known error string if it matches
* entries in a given `moveErrors` table. This is used to map on-chain error codes
* to user-friendly messages.
*
* @param inputs - Includes the raw `errorMessage` and a `moveErrors` object keyed by package, module, and code.
* @returns A structure with `errorCode`, `packageId`, `module`, and a human-readable `error` string, or `undefined`.
*/
static translateMoveErrorMessage(inputs) {
const { errorMessage, moveErrors } = inputs;
const parsed = this.parseMoveErrorMessage({ errorMessage });
if (!parsed ||
!(parsed.packageId in moveErrors) ||
!(parsed.module in moveErrors[parsed.packageId]) ||
!(parsed.errorCode in moveErrors[parsed.packageId][parsed.module]))
return undefined;
return Object.assign(Object.assign({}, parsed), { error: moveErrors[parsed.packageId][parsed.module][parsed.errorCode] });
}
}
exports.Helpers = Helpers;
_a = Helpers;
// =========================================================================
// Api Helpers (Static References)
// =========================================================================
/**
* Static reference to the `DynamicFieldsApiHelpers`, providing utility methods
* for working with dynamic fields in Sui objects.
*/
Helpers.dynamicFields = dynamicFieldsApiHelpers_1.DynamicFieldsApiHelpers;
/**
* Static reference to the `EventsApiHelpers`, providing methods for
* querying and filtering Sui events.
*/
Helpers.events = eventsApiHelpers_1.EventsApiHelpers;
/**
* Static reference to the `InspectionsApiHelpers`, used for reading
* Summaries or inspection data from objects.
*/
Helpers.inspections = inspectionsApiHelpers_1.InspectionsApiHelpers;
/**
* Static reference to the `ObjectsApiHelpers`, providing direct
* retrieval or manipulation of on-chain Sui objects.
*/
Helpers.objects = objectsApiHelpers_1.ObjectsApiHelpers;
/**
* Static reference to the `TransactionsApiHelpers`, enabling easier
* queries for transaction data by digest or other criteria.
*/
Helpers.transactions = transactionsApiHelpers_1.TransactionsApiHelpers;
// =========================================================================
// Type Manipulation
// =========================================================================
/**
* Removes all leading zeroes (after the '0x') from a string that represents
* a Sui address or object type. For instance, "0x0000123" => "0x123".
*
* @param type - The hex string to process, potentially including "::" module syntax.
* @returns The same string with unnecessary leading zeroes stripped out.
*/
Helpers.stripLeadingZeroesFromType = (type) => type.replaceAll(/x0+/g, "x");
/**
* Ensures the given Sui address or object type is zero-padded to 64 hex digits
* after the "0x". If a "::" suffix is present, only the address portion is padded.
*
* @param type - The "0x..." string or extended type (0x..::module).
* @returns A new string normalized to a 64-hex-digit address or object ID.
* @throws If the address portion is already longer than 64 hex digits.
*/
Helpers.addLeadingZeroesToType = (type) => {
const EXPECTED_TYPE_LENGTH = 64;
let strippedType = type.replace("0x", "");
let typeSuffix = "";
if (strippedType.includes("::")) {
const splitType = strippedType.replace("0x", "").split("::");
typeSuffix = splitType
.slice(1)
.reduce((acc, str) => acc + "::" + str, "");
strippedType = splitType[0];
}
const typeLength = strippedType.length;
if (typeLength > EXPECTED_TYPE_LENGTH)
throw new Error("invalid type length");
const zerosNeeded = EXPECTED_TYPE_LENGTH - typeLength;
const zeroString = Array(zerosNeeded).fill("0").join("");
const newType = "0x" + zeroString + strippedType;
return newType + typeSuffix;
};
/**
* Splits a non-SUI coin type string that may be prefixed by a chain ID for external usage,
* returning the chain and the coin type. If no chain is recognized, defaults to `"sui"`.
*
* @param coin - The coin string, which may look like `"bsc:0x<...>"` or just `"0x<...>"`.
* @returns An object with the `chain` (e.g. "bsc") and the `coinType`.
*/
Helpers.splitNonSuiCoinType = (coin) => {
const [uncastChain, coinType] = coin.split(":");
if (!uncastChain || !coinType)
return { coinType: coin, chain: "sui" };
const chain = uncastChain;
return { chain, coinType };
};
// =========================================================================
// Numbers
// =========================================================================
/**
* Checks if a given string represents a valid number (integer or decimal).
*
* @param str - The string to test.
* @returns `true` if it's a valid numeric string, otherwise `false`.
*/
Helpers.isNumber = (str) => /^\d*\.?\d*$/g.test(str);
/**
* Sums an array of floating-point numbers, returning the numeric total.
*
* @param arr - The array of numbers to sum.
* @returns The total as a float.
*/
Helpers.sum = (arr) => arr.reduce((prev, cur) => prev + cur, 0);
/**
* Sums an array of bigints, returning the total as a bigint.
*
* @param arr - The array of bigints to sum.
* @returns The resulting total as a bigint.
*/
Helpers.sumBigInt = (arr) => arr.reduce((prev, cur) => prev + cur, BigInt(0));
/**
* Determines if two numbers are close within a given tolerance factor,
* i.e., `|a - b| <= tolerance * max(a, b)`.
*
* @param a - The first number.
* @param b - The second number.
* @param tolerance - A fraction representing the max allowed difference relative to max(a, b).
* @returns `true` if within tolerance, otherwise `false`.
*/
Helpers.closeEnough = (a, b, tolerance) => Math.abs(a - b) <= tolerance * Math.max(a, b);
/**
* Determines if two bigints are close within a given tolerance factor,
* by casting them to numbers internally.
*
* @param a - First bigint.
* @param b - Second bigint.
* @param tolerance - A fraction representing the max allowed difference relative to max(a, b).
* @returns `true` if within tolerance, otherwise `false`.
*/
Helpers.closeEnoughBigInt = (a, b, tolerance) => _a.closeEnough(Number(a), Number(b), tolerance);
/**
* Checks whether the integer divisions of `a` and `b` (by `fixedOne`) differ
* by at most 1. Typically used in fixed math scenarios to see if two scaled
* values are "very close."
*
* @param a - First number (scaled).
* @param b - Second number (scaled).
* @param fixedOne - The scaling factor representing 1.0 in the same scale as `a` and `b`.
* @returns `true` if the integer parts differ by <= 1, otherwise `false`.
*/
Helpers.veryCloseInt = (a, b, fixedOne) => Math.abs(Math.floor(a / fixedOne) - Math.floor(b / fixedOne)) <= 1;
/**
* A small object containing "blended" math operations that handle
* mixed numeric types (number vs. bigint). This is primarily for
* internal usage in advanced math scenarios.
*/
Helpers.blendedOperations = {
/**
* Multiply two floating-point numbers.
*/
mulNNN: (a, b) => a * b,
/**
* Multiply a float and a bigint, returning a bigint (floor).
*/
mulNNB: (a, b) => BigInt(Math.floor(a * b)),
/**
* Multiply a float and a bigint, returning a float.
*/
mulNBN: (a, b) => a * Number(b),
/**
* Multiply a float and a bigint, returning a bigint (floor).
*/
mulNBB: (a, b) => BigInt(Math.floor(a * Number(b))),
/**
* Multiply two bigints, returning a float.
*/
mulBBN: (a, b) => Number(a * b),
/**
* Multiply two bigints, returning a bigint.
*/
mulBBB: (a, b) => a * b,
};
/**
* Returns the maximum of multiple bigints.
*
* @param args - The bigints to compare.
* @returns The largest bigint.
*/
Helpers.maxBigInt = (...args) => args.reduce((m, e) => (e > m ? e : m));
/**
* Returns the minimum of multiple bigints.
*
* @param args - The bigints to compare.
* @returns The smallest bigint.
*/
Helpers.minBigInt = (...args) => args.reduce((m, e) => (e < m ? e : m));
/**
* Returns the absolute value of a bigint.
*
* @param num - The input bigint.
* @returns A bigint representing the absolute value of `num`.
*/
Helpers.absBigInt = (num) => (num < BigInt(0) ? -num : num);
// =========================================================================
// Display
// =========================================================================
/**
* Capitalizes only the first letter of a string, making the rest lowercase.
* E.g., "HELLO" => "Hello".
*
* @param str - The input string to transform.
* @returns The resulting string with the first character in uppercase and the rest in lowercase.
*/
Helpers.capitalizeOnlyFirstLetter = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
// =========================================================================
// JSON
// =========================================================================
/**
* Parses a JSON string containing potential BigInt values.
* By default, it only converts strings ending in 'n' (like `"123n"`) to BigInts.
*
* @param json - The JSON string to parse.
* @param unsafeStringNumberConversion - If `true`, all numeric strings (e.g., "123") will also become BigInts.
* @returns The parsed JSON object with BigInt conversions where applicable.
*/
Helpers.parseJsonWithBigint = (json, unsafeStringNumberConversion = false) => JSON.parse(json, (key, value) => {
// handles bigint casting
if (typeof value === "string" && /^-?\d+n$/.test(value)) {
return BigInt(value.slice(0, -1));
}
if (unsafeStringNumberConversion &&
typeof value === "string" &&
_a.isNumber(value)) {
return BigInt(value);
}
return value;
});
// =========================================================================
// General
// =========================================================================
/**
* Creates a deep copy of the given target, handling nested arrays and objects.
* Dates are cloned by their timestamp.
*
* @param target - The data to clone deeply.
* @returns A new object/array/date structure mirroring `target`.
*/
Helpers.deepCopy = (target) => {
if (target === null) {
return target;
}
if (target instanceof Date) {
return new Date(target.getTime());
}
if (Array.isArray(target)) {
const cp = [];
target.forEach((v) => {
cp.push(v);
});
return cp.map((n) => _a.deepCopy(n));
}
if (typeof target === "object") {
const cp = Object.assign({}, target);
Object.keys(cp).forEach((k) => {
cp[k] = _a.deepCopy(cp[k]);
});
return cp;
}
return target;
};
/**
* Finds the index of the maximum value in an array. Returns -1 if the array is empty.
*
* @param arr - The input array.
* @returns The index of the maximum value, or -1 if the array is empty.
*/
Helpers.indexOfMax = (arr) => {
if (arr.length === 0)
return -1;
let max = arr[0];
let maxIndex = 0;
for (let i = 1; i < arr.length; i++) {
if (arr[i] > max) {
maxIndex = i;
max = arr[i];
}
}
return maxIndex;
};
/**
* Returns a new array with unique elements from the input array,
* preserving the order of first occurrences.
*
* @param arr - The original array.
* @returns An array of unique items.
*/
Helpers.uniqueArray = (arr) => [...new Set(arr)];
/**
* Returns a Promise that resolves after a specified number of milliseconds.
*
* @param ms - The delay time in milliseconds.
* @returns A promise that resolves after `ms` milliseconds.
*/
Helpers.sleep = (ms) => new Promise((r) => setTimeout(r, ms));
/**
* Creates a unique ID-like string by combining the current timestamp and random base36 digits.
*
* @returns A short random string (base36) that can serve as a unique identifier.
*/
Helpers.createUid = () => Date.now().toString(36) + Math.random().toString(36).substring(2);
/**
* Splits an array into two groups: those for which `func` returns `true` (or truthy),
* and those for which it returns `false`. The result is returned as a tuple `[trues, falses]`.
*
* @param array - The array to filter.
* @param func - The function used to test each element.
* @returns A tuple containing two arrays: `[elements that pass, elements that fail]`.
*/
Helpers.bifilter = (array, func) => {
const trues = [];
const falses = [];
for (let index = 0; index < array.length; index++) {
const item = array[index];
if (func(item, index, array))
trues[trues.length] = item;
else
falses[falses.length] = item;
}
return [trues, falses];
};
/**
* An async version of `bifilter`, returning a tuple of `[trues, falses]`.
* Each element is tested asynchronously in parallel via `func`.
*
* @param array - The array to filter.
* @param func - An async function returning `true` or `false`.
* @returns A tuple `[trues, falses]` after asynchronous evaluation.
*/
Helpers.bifilterAsync = (array, func) => __awaiter(void 0, void 0, void 0, function* () {
const predicates = yield Promise.all(array.map(func));
return _a.bifilter(array, (_, index) => predicates[index]);
});
/**
* Filters the entries of an object based on a predicate function,
* returning a new object with only those entries for which `predicate`
* returns `true`.
*
* @param obj - The original object to filter.
* @param predicate - A function taking `(key, value)` and returning a boolean.
* @returns A new object with only the entries that pass the predicate.
*/
Helpers.filterObject = (obj, predicate) => Object.keys(obj).reduce((acc, key) => {
const val = obj[key];
if (!predicate(key, val))
return acc;
return Object.assign(Object.assign({}, acc), { [key]: val });
}, {});
/**
* Applies downward slippage to a bigint amount by subtracting `slippage * amount`.
* For instance, for 1% slippage, we reduce the amount by 1%.
*
* @param amount - The original bigint amount.
* @param slippage - An integer percent (e.g., 1 => 1%).
* @returns The adjusted bigint after subtracting the slippage portion.
*/
Helpers.applySlippageBigInt = (amount, slippage) => {
return (amount -
BigInt(Math.floor(casting_1.Casting.normalizeSlippageTolerance(slippage) *
Number(amount))));
};
/**
* Applies downward slippage to a floating-point amount. E.g., for 1% slippage,
* reduce by 1% of `amount`.
*
* @param amount - The original float value.
* @param slippage - An integer percent (e.g., 1 => 1%).
* @returns The float after applying slippage.
*/
Helpers.applySlippage = (amount, slippage) => {
return amount - casting_1.Casting.normalizeSlippageTolerance(slippage) * amount;
};
/**
* Roughly checks if a string is a valid Sui type (e.g., "0x2::sui::SUI").
* This is not guaranteed to be perfect, but covers common cases.
*
* @param str - The string to validate.
* @returns `true` if it meets the minimum structure, otherwise `false`.
*/
Helpers.isValidType = (str) => {
// TODO: use regex
const trimmedStr = str.trim();
return (trimmedStr.startsWith("0x") &&
trimmedStr.length >= 9 &&
trimmedStr.indexOf("::") >= 3 &&
trimmedStr.lastIndexOf("::") >= 6 &&
!trimmedStr.endsWith(":"));
};
/**
* Checks if a string is a valid hex representation, optionally prefixed with "0x".
*
* @param hexString - The string to check.
* @returns `true` if `hexString` is a valid hex, otherwise `false`.
*/
Helpers.isValidHex = (hexString) => {
const hexPattern = /^(0x)?[0-9A-F]+$/i;
return hexPattern.test(hexString);
};
// =========================================================================
// Tx Command Input Construction
// =========================================================================
/**
* Utility for building transaction commands with either a string-based
* `ObjectId` or an existing transaction object argument. If it's a string,
* it's converted via `tx.object(...)`; if already a `TransactionObjectArgument`,
* it's returned as-is.
*
* @param tx - The current `Transaction` block to add the object to.
* @param object - Either an `ObjectId` or a `TransactionObjectArgument`.
* @returns A `TransactionObjectArgument` referencing the provided object.
*/
Helpers.addTxObject = (tx, object) => {
return typeof object === "string" ? tx.object(object) : object;
};
// =========================================================================
// Sui Address / Key Checking
// =========================================================================
/**
* Checks if a given string is a valid Sui address by normalizing it to a
* 64-hex-digit form and calling `isValidSuiAddress`.
*
* @param address - The Sui address to validate.
* @returns `true` if valid, `false` otherwise.
*/
Helpers.isValidSuiAddress = (address) => (0, utils_1.isValidSuiAddress)((() => {
if (!address.startsWith("0x") || address.length < 3)
return "";
try {
return _a.addLeadingZeroesToType(address);
}
catch (e) {
return "";
}
})());
// =========================================================================
// Keypair
// =========================================================================
/**
* Constructs a `Keypair` instance from a private key string. The `privateKey`
* may indicate the signing scheme (ED25519, Secp256k1, or Secp256r1) via prefix,
* as recognized by `decodeSuiPrivateKey`.
*
* @param privateKey - The full private key string (e.g., "0x<64_hex_chars>").
* @returns A new `Keypair` instance for signing transactions.
* @throws If the schema is unsupported.
*/
Helpers.keypairFromPrivateKey = (privateKey) => {
const parsedKeypair = (0, cryptography_1.decodeSuiPrivateKey)(privateKey);
switch (parsedKeypair.schema) {
case "ED25519":
return ed25519_1.Ed25519Keypair.fromSecretKey(parsedKeypair.secretKey);
case "Secp256k1":
return secp256k1_1.Secp256k1Keypair.fromSecretKey(parsedKeypair.secretKey);
case "Secp256r1":
return secp256r1_1.Secp256r1Keypair.fromSecretKey(parsedKeypair.secretKey);
default:
throw new Error(`unsupported schema \`${parsedKeypair.schema}\``);
}
};