UNPKG

@mysten/suins

Version:
244 lines (243 loc) 9.55 kB
import { isValidSuiNSName, normalizeSuiNSName } from "@mysten/sui/utils"; import { mainPackage } from "./constants.js"; import { getCoinDiscountConfigType, getConfigType, getDomainType, getPricelistConfigType, getRenewalPricelistConfigType, isSubName, validateYears } from "./helpers.js"; import { SuiPriceServiceConnection, SuiPythClient } from "./pyth/pyth.js"; class SuinsClient { constructor(config) { this.client = config.client; this.network = config.network || "mainnet"; if (this.network === "mainnet") { this.config = mainPackage.mainnet; } else if (this.network === "testnet") { this.config = mainPackage.testnet; } else { throw new Error("Invalid network"); } } /** * Returns the price list for SuiNS names in the base asset. */ // Format: // { // [ 3, 3 ] => 500000000, // [ 4, 4 ] => 100000000, // [ 5, 63 ] => 20000000 // } async getPriceList() { if (!this.config.suins) throw new Error("Suins object ID is not set"); if (!this.config.packageId) throw new Error("Price list config not found"); const priceList = await this.client.getDynamicFieldObject({ parentId: this.config.suins, name: { type: getConfigType( this.config.packageIdV1, getPricelistConfigType(this.config.packageIdPricing) ), value: { dummy_field: false } } }); if (!priceList?.data?.content || priceList.data.content.dataType !== "moveObject" || !("fields" in priceList.data.content)) { throw new Error("Price list not found or content is invalid"); } const fields = priceList.data.content.fields; if (!fields.value || !fields.value.fields || !fields.value.fields.pricing) { throw new Error("Pricing fields not found in the price list"); } const contentArray = fields.value.fields.pricing.fields.contents; const priceMap = /* @__PURE__ */ new Map(); for (const entry of contentArray) { const keyFields = entry.fields.key.fields; const key = [Number(keyFields.pos0), Number(keyFields.pos1)]; const value = Number(entry.fields.value); priceMap.set(key, value); } return priceMap; } /** * Returns the renewal price list for SuiNS names in the base asset. */ // Format: // { // [ 3, 3 ] => 500000000, // [ 4, 4 ] => 100000000, // [ 5, 63 ] => 20000000 // } async getRenewalPriceList() { if (!this.config.suins) throw new Error("Suins object ID is not set"); if (!this.config.packageId) throw new Error("Price list config not found"); const priceList = await this.client.getDynamicFieldObject({ parentId: this.config.suins, name: { type: getConfigType( this.config.packageIdV1, getRenewalPricelistConfigType(this.config.packageIdPricing) ), value: { dummy_field: false } } }); if (!priceList || !priceList.data || !priceList.data.content || priceList.data.content.dataType !== "moveObject" || !("fields" in priceList.data.content)) { throw new Error("Price list not found or content structure is invalid"); } const fields = priceList.data.content.fields; if (!fields.value || !fields.value.fields || !fields.value.fields.config || !fields.value.fields.config.fields.pricing || !fields.value.fields.config.fields.pricing.fields.contents) { throw new Error("Pricing fields not found in the price list"); } const contentArray = fields.value.fields.config.fields.pricing.fields.contents; const priceMap = /* @__PURE__ */ new Map(); for (const entry of contentArray) { const keyFields = entry.fields.key.fields; const key = [Number(keyFields.pos0), Number(keyFields.pos1)]; const value = Number(entry.fields.value); priceMap.set(key, value); } return priceMap; } /** * Returns the coin discount list for SuiNS names. */ // Format: // { // 'b48aac3f53bab328e1eb4c5b3c34f55e760f2fb3f2305ee1a474878d80f650f0::TESTUSDC::TESTUSDC' => 0, // '0000000000000000000000000000000000000000000000000000000000000002::sui::SUI' => 0, // 'b48aac3f53bab328e1eb4c5b3c34f55e760f2fb3f2305ee1a474878d80f650f0::TESTNS::TESTNS' => 25 // } async getCoinTypeDiscount() { if (!this.config.suins) throw new Error("Suins object ID is not set"); if (!this.config.packageId) throw new Error("Price list config not found"); const dfValue = await this.client.getDynamicFieldObject({ parentId: this.config.suins, name: { type: getConfigType( this.config.packageIdV1, getCoinDiscountConfigType(this.config.payments.packageId) ), value: { dummy_field: false } } }); if (!dfValue || !dfValue.data || !dfValue.data.content || dfValue.data.content.dataType !== "moveObject" || !("fields" in dfValue.data.content)) { throw new Error("dfValue not found or content structure is invalid"); } const fields = dfValue.data.content.fields; if (!fields.value || !fields.value.fields || !fields.value.fields.base_currency || !fields.value.fields.base_currency.fields || !fields.value.fields.base_currency.fields.name || !fields.value.fields.currencies || !fields.value.fields.currencies.fields || !fields.value.fields.currencies.fields.contents) { throw new Error("Required fields are missing in dfValue"); } const content = fields.value.fields; const currencyDiscounts = content.currencies.fields.contents; const discountMap = /* @__PURE__ */ new Map(); for (const entry of currencyDiscounts) { const key = entry.fields.key.fields.name; const value = Number(entry.fields.value.fields.discount_percentage); discountMap.set(key, value); } return discountMap; } async getNameRecord(name) { if (!isValidSuiNSName(name)) throw new Error("Invalid SuiNS name"); if (!this.config.registryTableId) throw new Error("Suins package ID is not set"); const nameRecord = await this.client.getDynamicFieldObject({ parentId: this.config.registryTableId, name: { type: getDomainType(this.config.packageIdV1), value: normalizeSuiNSName(name, "dot").split(".").reverse() } }); const fields = nameRecord.data?.content; if (nameRecord.error?.code === "dynamicFieldNotFound") return null; if (nameRecord.error || !fields || fields.dataType !== "moveObject") throw new Error("Name record not found. This domain is not registered."); const content = fields.fields; const data = {}; content.value.fields.data.fields.contents.forEach((item) => { data[item.fields.key] = item.fields.value; }); return { name, nftId: content.value.fields?.nft_id, targetAddress: content.value.fields?.target_address, expirationTimestampMs: content.value.fields?.expiration_timestamp_ms, data, avatar: data.avatar, contentHash: data.content_hash, walrusSiteId: data.walrus_site_id }; } /** * Calculates the registration or renewal price for an SLD (Second Level Domain). * It expects a domain name, the number of years and a `SuinsPriceList` object, * as returned from `suinsClient.getPriceList()` function, or `suins.getRenewalPriceList()` function. * * It throws an error: * 1. if the name is a subdomain * 2. if the name is not a valid SuiNS name * 3. if the years are not between 1 and 5 */ async calculatePrice({ name, years, isRegistration = true }) { if (!isValidSuiNSName(name)) { throw new Error("Invalid SuiNS name"); } validateYears(years); if (isSubName(name)) { throw new Error("Subdomains do not have a registration fee"); } const length = normalizeSuiNSName(name, "dot").split(".")[0].length; const priceList = await this.getPriceList(); const renewalPriceList = await this.getRenewalPriceList(); let yearsRemain = years; let price = 0; if (isRegistration) { for (const [[minLength, maxLength], pricePerYear] of priceList.entries()) { if (length >= minLength && length <= maxLength) { price += pricePerYear; yearsRemain -= 1; break; } } } for (const [[minLength, maxLength], pricePerYear] of renewalPriceList.entries()) { if (length >= minLength && length <= maxLength) { price += yearsRemain * pricePerYear; break; } } return price; } async getPriceInfoObject(tx, feed) { const endpoint = this.network === "testnet" ? "https://hermes-beta.pyth.network" : "https://hermes.pyth.network"; const connection = new SuiPriceServiceConnection(endpoint); const priceIDs = [ feed // ASSET/USD price ID ]; const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs); const wormholeStateId = this.config.pyth.wormholeStateId; const pythStateId = this.config.pyth.pythStateId; const client = new SuiPythClient(this.client, pythStateId, wormholeStateId); return await client.updatePriceFeeds(tx, priceUpdateData, priceIDs); } async getObjectType(objectId) { const objectResponse = await this.client.getObject({ id: objectId, options: { showType: true } }); if (objectResponse && objectResponse.data && objectResponse.data.type) { return objectResponse.data.type; } throw new Error(`Type information not found for object ID: ${objectId}`); } } export { SuinsClient }; //# sourceMappingURL=suins-client.js.map