UNPKG

@mysten/suins

Version:
205 lines (203 loc) 9.02 kB
import { mainPackage } from "./constants.mjs"; import { isSubName, validateYears } from "./helpers.mjs"; import { SuiPriceServiceConnection, SuiPythClient } from "./pyth/pyth.mjs"; import { ConfigKey } from "./contracts/suins/suins.mjs"; import { Domain } from "./contracts/suins/domain.mjs"; import { NameRecord } from "./contracts/suins/name_record.mjs"; import { PricingConfig, RenewalConfig } from "./contracts/suins/pricing_config.mjs"; import { PaymentsConfig } from "./contracts/suins_payments/payments.mjs"; import { isValidSuiNSName, normalizeSuiNSName } from "@mysten/sui/utils"; //#region src/suins-client.ts /** * Creates a SuiNS client extension that can be used with `client.$extend()`. * * @example * ```ts * import { SuiJsonRpcClient, getJsonRpcFullnodeUrl } from '@mysten/sui/jsonRpc'; * import { suins } from '@mysten/suins'; * * const client = new SuiJsonRpcClient({ * url: getJsonRpcFullnodeUrl('mainnet'), * network: 'mainnet', * }).$extend(suins()); * * const nameRecord = await client.suins.getNameRecord('example.sui'); * ``` */ function suins({ name = "suins", packageInfo } = {}) { return { name, register: (client) => { return new SuinsClient({ client, network: client.network, packageInfo }); } }; } var SuinsClient = class { constructor(config) { this.client = config.client; this.network = config.network || "mainnet"; if (config.packageInfo) this.config = config.packageInfo; else 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. */ async getPriceList() { var _result$dynamicField; 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 configType = `${this.config.packageIdV1}::suins::ConfigKey<${this.config.packageIdPricing}::pricing_config::PricingConfig>`; const nameBytes = ConfigKey.serialize({ dummy_field: false }).toBytes(); const result = await this.client.core.getDynamicField({ parentId: this.config.suins, name: { type: configType, bcs: nameBytes } }); if (!((_result$dynamicField = result.dynamicField) === null || _result$dynamicField === void 0 || (_result$dynamicField = _result$dynamicField.value) === null || _result$dynamicField === void 0 ? void 0 : _result$dynamicField.bcs)) throw new Error("Price list not found or content is invalid"); const pricingConfig = PricingConfig.parse(result.dynamicField.value.bcs); const priceMap = /* @__PURE__ */ new Map(); for (const entry of pricingConfig.pricing.contents) { const key = [Number(entry.key[0]), Number(entry.key[1])]; const value = Number(entry.value); priceMap.set(key, value); } return priceMap; } /** * Returns the renewal price list for SuiNS names in the base asset. */ async getRenewalPriceList() { var _result$dynamicField2; 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 configType = `${this.config.packageIdV1}::suins::ConfigKey<${this.config.packageIdPricing}::pricing_config::RenewalConfig>`; const nameBytes = ConfigKey.serialize({ dummy_field: false }).toBytes(); const result = await this.client.core.getDynamicField({ parentId: this.config.suins, name: { type: configType, bcs: nameBytes } }); if (!((_result$dynamicField2 = result.dynamicField) === null || _result$dynamicField2 === void 0 || (_result$dynamicField2 = _result$dynamicField2.value) === null || _result$dynamicField2 === void 0 ? void 0 : _result$dynamicField2.bcs)) throw new Error("Price list not found or content structure is invalid"); const renewalConfig = RenewalConfig.parse(result.dynamicField.value.bcs); const priceMap = /* @__PURE__ */ new Map(); for (const entry of renewalConfig.config.pricing.contents) { const key = [Number(entry.key[0]), Number(entry.key[1])]; const value = Number(entry.value); priceMap.set(key, value); } return priceMap; } /** * Returns the coin discount list for SuiNS names. */ async getCoinTypeDiscount() { var _result$dynamicField3; 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 configType = `${this.config.packageIdV1}::suins::ConfigKey<${this.config.payments.packageId}::payments::PaymentsConfig>`; const result = await this.client.core.getDynamicField({ parentId: this.config.suins, name: { type: configType, bcs: ConfigKey.serialize({ dummy_field: false }).toBytes() } }); if (!((_result$dynamicField3 = result.dynamicField) === null || _result$dynamicField3 === void 0 || (_result$dynamicField3 = _result$dynamicField3.value) === null || _result$dynamicField3 === void 0 ? void 0 : _result$dynamicField3.bcs)) throw new Error("Payments config not found or content structure is invalid"); const paymentsConfig = PaymentsConfig.parse(result.dynamicField.value.bcs); const discountMap = /* @__PURE__ */ new Map(); for (const entry of paymentsConfig.currencies.contents) { const key = entry.key.name; const value = Number(entry.value.discount_percentage); discountMap.set(key, value); } return discountMap; } async getNameRecord(name) { var _result$dynamicField$; if (!isValidSuiNSName(name)) throw new Error("Invalid SuiNS name"); if (!this.config.registryTableId) throw new Error("Suins package ID is not set"); const labels = normalizeSuiNSName(name, "dot").split(".").reverse(); const result = await this.client.core.getDynamicField({ parentId: this.config.registryTableId, name: { type: `${this.config.packageIdV1}::domain::Domain`, bcs: Domain.serialize({ labels }).toBytes() } }); if (!result.dynamicField) return null; if (!((_result$dynamicField$ = result.dynamicField.value) === null || _result$dynamicField$ === void 0 ? void 0 : _result$dynamicField$.bcs)) throw new Error("Name record not found. This domain is not registered."); const record = NameRecord.parse(result.dynamicField.value.bcs); const data = {}; for (const entry of record.data.contents) data[entry.key] = entry.value; return { name, nftId: record.nft_id, targetAddress: record.target_address ?? "", expirationTimestampMs: Number(record.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, feeCoin) { const connection = new SuiPriceServiceConnection(this.network === "testnet" ? "https://hermes-beta.pyth.network" : "https://hermes.pyth.network"); const priceIDs = [feed]; const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs); return new SuiPythClient(this.client, this.config.pyth.pythStateId, this.config.pyth.wormholeStateId).updatePriceFeeds(tx, priceUpdateData, priceIDs, feeCoin); } async getPythBaseUpdateFee() { return new SuiPythClient(this.client, this.config.pyth.pythStateId, this.config.pyth.wormholeStateId).getBaseUpdateFee(); } async getObjectType(objectId) { var _result$object; const result = await this.client.core.getObject({ objectId }); if ((_result$object = result.object) === null || _result$object === void 0 ? void 0 : _result$object.type) return result.object.type; throw new Error(`Type information not found for object ID: ${objectId}`); } }; //#endregion export { SuinsClient, suins }; //# sourceMappingURL=suins-client.mjs.map