import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519' import { Secp256k1Keypair } from '@mysten/sui/keypairs/secp256k1' import { fromB64, fromBase64, fromHex, fromHEX, normalizeSuiObjectId } from '@mysten/sui/utils' import type { SuiAddressType, SuiStructTag } from '../type/sui' import { CoinAssist, GAS_TYPE_ARG, GAS_TYPE_ARG_LONG } from './coinAssist' import { removeHexPrefix } from './hex' const EQUAL = 0 const LESS_THAN = 1 const GREATER_THAN = 2 function cmp(a: number, b: number) { if (a === b) { return EQUAL } if (a < b) { return LESS_THAN } return GREATER_THAN } function compare(symbolX: string, symbolY: string) { let i = 0 const len = symbolX.length <= symbolY.length ? symbolX.length : symbolY.length const lenCmp = cmp(symbolX.length, symbolY.length) while (i < len) { const elemCmp = cmp(symbolX.charCodeAt(i), symbolY.charCodeAt(i)) i += 1 if (elemCmp !== 0) { return elemCmp } } return lenCmp } export function isSortedSymbols(symbolX: string, symbolY: string) { return compare(symbolX, symbolY) === LESS_THAN } export function composeType(address: string, generics: SuiAddressType[]): SuiAddressType export function composeType(address: string, struct: string, generics?: SuiAddressType[]): SuiAddressType export function composeType(address: string, module: string, struct: string, generics?: SuiAddressType[]): SuiAddressType export function composeType(address: string, ...args: unknown[]): SuiAddressType { const generics: string[] = Array.isArray(args[args.length - 1]) ? (args.pop() as string[]) : [] const chains = [address, ...args].filter(Boolean) let result: string = chains.join('::') if (generics && generics.length) { result += `<${generics.join(', ')}>` } return result } export function extractAddressFromType(type: string) { return type.split('::')[0] } export function extractStructTagFromType(type: string): SuiStructTag { try { let _type = type.replace(/\s/g, '') const genericsString = _type.match(/(<.+>)$/) const generics = genericsString?.[0]?.match(/(\w+::\w+::\w+)(?:<.*?>(?!>))?/g) if (generics) { _type = _type.slice(0, _type.indexOf('<')) const tag = extractStructTagFromType(_type) const structTag: SuiStructTag = { ...tag, type_arguments: generics.map((item) => extractStructTagFromType(item).source_address), } structTag.type_arguments = structTag.type_arguments.map((item) => { return CoinAssist.isSuiCoin(item) ? item : extractStructTagFromType(item).source_address }) structTag.source_address = composeType(structTag.full_address, structTag.type_arguments) return structTag } const parts = _type.split('::') const isSuiCoin = _type === GAS_TYPE_ARG || _type === GAS_TYPE_ARG_LONG const structTag: SuiStructTag = { full_address: _type, address: isSuiCoin ? '0x2' : normalizeSuiObjectId(parts[0]), module: parts[1], name: parts[2], type_arguments: [], source_address: '', } structTag.full_address = `${structTag.address}::${structTag.module}::${structTag.name}` structTag.source_address = composeType(structTag.full_address, structTag.type_arguments) return structTag } catch (error) { return { full_address: type, address: '', module: '', name: '', type_arguments: [], source_address: type, } } } export function normalizeCoinType(coinType: string): string { return extractStructTagFromType(coinType).source_address } export function fixSuiObjectId(value: string): string { if (value.toLowerCase().startsWith('0x')) { return normalizeSuiObjectId(value) } return value } /** * Fixes and normalizes a coin type by removing or keeping the prefix. * * @param {string} coinType - The coin type to be fixed. * @param {boolean} removePrefix - Whether to remove the prefix or not (default: true). * @returns {string} - The fixed and normalized coin type. */ export const fixCoinType = (coinType: string, removePrefix = true) => { const arr = coinType.split('::') const address = arr.shift() as string let normalizeAddress = normalizeSuiObjectId(address) if (removePrefix) { normalizeAddress = removeHexPrefix(normalizeAddress) } return `${normalizeAddress}::${arr.join('::')}` } /** * Recursively traverses the given data object and patches any string values that represent Sui object IDs. * * @param {any} data - The data object to be patched. */ export function patchFixSuiObjectId(data: any) { for (const key in data) { const type = typeof data[key] if (type === 'object') { patchFixSuiObjectId(data[key]) } else if (type === 'string') { const value = data[key] if (value && !value.includes('::')) { data[key] = fixSuiObjectId(value) } } } } /** * Converts a secret key in string or Uint8Array format to an Ed25519 key pair. * @param {string|Uint8Array} secretKey - The secret key to convert. * @param {string} ecode - The encoding of the secret key ('hex' or 'base64'). Defaults to 'hex'. * @returns {Ed25519Keypair} - Returns the Ed25519 key pair. */ export function secretKeyToEd25519Keypair(secretKey: string | Uint8Array, ecode: 'hex' | 'base64' = 'hex'): Ed25519Keypair { if (secretKey instanceof Uint8Array) { const key = Buffer.from(secretKey) return Ed25519Keypair.fromSecretKey(new Uint8Array(key)) } const hexKey = ecode === 'hex' ? fromHex(secretKey) : fromBase64(secretKey) return Ed25519Keypair.fromSecretKey(hexKey) } /** * Converts a secret key in string or Uint8Array format to a Secp256k1 key pair. * @param {string|Uint8Array} secretKey - The secret key to convert. * @param {string} ecode - The encoding of the secret key ('hex' or 'base64'). Defaults to 'hex'. * @returns {Ed25519Keypair} - Returns the Secp256k1 key pair. */ export function secretKeyToSecp256k1Keypair(secretKey: string | Uint8Array, ecode: 'hex' | 'base64' = 'hex'): Secp256k1Keypair { if (secretKey instanceof Uint8Array) { const key = Buffer.from(secretKey) return Secp256k1Keypair.fromSecretKey(new Uint8Array(key)) } const hexKey = ecode === 'hex' ? fromHEX(secretKey) : fromB64(secretKey) return Secp256k1Keypair.fromSecretKey(hexKey) }