@mysten/suins
Version:
205 lines (203 loc) • 9.02 kB
JavaScript
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