UNPKG

@iota/iota-names-sdk

Version:
590 lines (589 loc) 21.9 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var iota_names_client_exports = {}; __export(iota_names_client_exports, { IotaNamesClient: () => IotaNamesClient }); module.exports = __toCommonJS(iota_names_client_exports); var import_bcs = require("@iota/iota-sdk/bcs"); var import__ = require("@iota/iota-sdk/graphql/schemas/2025.2"); var import_utils = require("@iota/iota-sdk/utils"); var import_blake2b = require("@noble/hashes/blake2b"); var import_utils2 = require("@noble/hashes/utils"); var import_bcs2 = require("./bcs.js"); var import_constants = require("./constants.js"); var import_coupons = require("./coupons.js"); var import_helpers = require("./helpers.js"); var import_utils3 = require("./utils.js"); class IotaNamesClient { constructor(config) { this.graphQlClient = config.graphQlClient; if ("network" in config) { this.config = import_constants.packages[config.network]; } else { this.config = config.packageInfo; } } async isMethodSupported(packageId, module2, functionName) { const methodData = await this.graphQlClient.query({ query: `query getMethod($package: IotaAddress!, $module: String!, $function: String!) { package(address: $package) { module(name: $module) { function(name: $function) { name } } } }`, variables: { package: packageId, module: module2, function: functionName } }); return methodData.data?.package?.module?.function != null; } /** * Get the corresponding address for the given package. * Sometimes new versions might contain new types, * so its important to use the package they were created on when reading them in e.g GraphQL. * * - If the package does not have any versioning then the value is returned directly. * - If there are multiple versions available then the version passed as argument will be used to retrieve the package. * - If none of above returned a version then the latest will be picked. * * @argument {string} key - Package key. * @argument {string} [version=v1] - Optionally specify your desired version. */ getPackage(key, version = "latest") { const pkg = this.config[key]; if (typeof pkg === "string") return pkg; if (version !== "latest" && version in pkg && typeof pkg[version] === "string") { return pkg[version]; } const fallback = Object.values(pkg).toReversed()[0]; if (!fallback) throw new Error(`Package ${key} is not set`); return fallback; } /** * Returns the core config of IOTA Names. */ async getCoreConfig() { const iotaNamesObjectId = this.getPackage("iotaNamesObjectId", "v1"); const packageId = this.getPackage("packageId", "v1"); const coreConfigBcsB64 = (0, import_utils.toB64)( import_bcs2.DummyFieldBcs.serialize({ dummy_field: false }).toBytes() ); const coreConfigResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getCoreConfig($parentId: IotaAddress!, $name: DynamicFieldName!) { owner(address: $parentId) { address dynamicField(name: $name) { value { ... on MoveValue { json } } } } } `), variables: { parentId: iotaNamesObjectId, name: { type: (0, import_helpers.getConfigType)(packageId, (0, import_helpers.getCoreConfigType)(packageId)), bcs: coreConfigBcsB64 } } }); const coreConfig = coreConfigResponse?.data?.owner?.dynamicField?.value?.json; if (!coreConfig) { throw new Error("Core config not found or is invalid"); } return coreConfig; } /** * Returns the subnames config of IOTA Names. */ async getSubnamesConfig() { const iotaNamesObjectId = this.getPackage("iotaNamesObjectId", "v1"); const packageId = this.getPackage("packageId", "v1"); const subnamesPackageId = this.getPackage("subnamesPackageId", "v1"); const subnamesConfigBcsB64 = (0, import_utils.toB64)( import_bcs2.DummyFieldBcs.serialize({ dummy_field: false }).toBytes() ); const subnamesConfigResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getSubnameConfig($parentId: IotaAddress!, $name: DynamicFieldName!) { owner(address: $parentId) { address dynamicField(name: $name) { value { ... on MoveValue { json } } } } } `), variables: { parentId: iotaNamesObjectId, name: { type: (0, import_helpers.getConfigType)(packageId, (0, import_helpers.getSubnamesConfigType)(subnamesPackageId)), bcs: subnamesConfigBcsB64 } } }); const subnamesConfig = subnamesConfigResponse?.data?.owner?.dynamicField?.value?.json; if (!subnamesConfig) { throw new Error("Subnames config not found or is invalid"); } return subnamesConfig; } /** * Returns the price list for IOTA names in the base asset. */ // Format: // { // [ 3, 3 ] => 500000000, // [ 4, 4 ] => 100000000, // [ 5, 63 ] => 20000000 // } async getPriceList() { const iotaNamesObjectId = this.getPackage("iotaNamesObjectId", "v1"); const packageId = this.getPackage("packageId", "v1"); const pricingConfigBcsB64 = (0, import_utils.toB64)( import_bcs2.DummyFieldBcs.serialize({ dummy_field: false }).toBytes() ); const priceListResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getPriceList($parentId: IotaAddress!, $name: DynamicFieldName!) { owner(address: $parentId) { address dynamicField(name: $name) { value { ... on MoveValue { json } } } } } `), variables: { parentId: iotaNamesObjectId, name: { type: (0, import_helpers.getConfigType)(packageId, (0, import_helpers.getPricelistConfigType)(packageId)), bcs: pricingConfigBcsB64 } } }); const priceList = priceListResponse?.data?.owner?.dynamicField?.value?.json?.pricing; const contents = priceList?.contents; if (!contents) { throw new Error("Price list not found or content is invalid"); } const priceMap = /* @__PURE__ */ new Map(); for (const entry of contents) { const { pos0, pos1 } = entry.key; const key = [Number(pos0), Number(pos1)]; const value = Number(entry.value); priceMap.set(key, value); } return priceMap; } /** * Returns the renewal price list for IOTA names in the base asset. */ // Format: // { // [ 3, 3 ] => 500000000000, // [ 4, 4 ] => 250000000000, // [ 5, 63 ] => 50000000000 // } async getRenewalPriceList() { const iotaNamesObjectId = this.getPackage("iotaNamesObjectId", "v1"); const packageId = this.getPackage("packageId", "v1"); const pricingConfigBcsB64 = (0, import_utils.toB64)( import_bcs2.DummyFieldBcs.serialize({ dummy_field: false }).toBytes() ); const priceListResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getRenewalPriceList($parentId: IotaAddress!, $name: DynamicFieldName!) { owner(address: $parentId) { address dynamicField(name: $name) { value { ... on MoveValue { json } } } } } `), variables: { parentId: iotaNamesObjectId, name: { type: (0, import_helpers.getConfigType)(packageId, (0, import_helpers.getRenewalPricelistConfigType)(packageId)), bcs: pricingConfigBcsB64 } } }); const priceList = priceListResponse?.data?.owner?.dynamicField?.value?.json?.config?.pricing; const contents = priceList?.contents; if (!contents) { throw new Error("Price list not found or content is invalid"); } const priceMap = /* @__PURE__ */ new Map(); for (const entry of contents) { const { pos0, pos1 } = entry.key; const key = [Number(pos0), Number(pos1)]; const value = Number(entry.value); priceMap.set(key, value); } return priceMap; } async getPublicName(address) { const response = await this.graphQlClient.query({ query: (0, import__.graphql)(` query resolveNameServiceName($address: IotaAddress!, $nameFormat: NameFormat) { address(address: $address) { publicName: iotaNamesDefaultName(format: $nameFormat) } } `), variables: { address } }); const publicName = response?.data?.address?.publicName ?? null; return publicName; } async getDenyListTableIds() { const iotaNamesObjectId = this.getPackage("iotaNamesObjectId", "v1"); const packageId = this.getPackage("packageId", "v1"); const denyListBcsB64 = (0, import_utils.toB64)( import_bcs2.DummyFieldBcs.serialize({ dummy_field: false }).toBytes() ); const response = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getDenyList($address: IotaAddress!, $type: String!, $bcs: Base64!) { owner(address: $address) { dynamicField(name: { type: $type, bcs: $bcs }) { name { type { repr } json } value { ... on MoveValue { type { repr } json } } } } } `), variables: { address: iotaNamesObjectId, type: (0, import_helpers.getRegistryKeyType)(packageId, (0, import_helpers.getDenyListType)(packageId)), bcs: denyListBcsB64 } }); const denyList = response?.data?.owner?.dynamicField?.value?.json; return { reservedTableId: denyList?.reserved?.id ?? null, blockedTableId: denyList?.blocked?.id ?? null }; } /** * Gets the full reserved/blocked list */ async getRestrictedList(type) { const { reservedTableId, blockedTableId } = await this.getDenyListTableIds(); if (type === "reserved" && !reservedTableId) return []; if (type === "blocked" && !blockedTableId) return []; const tableId = type === "reserved" ? reservedTableId : blockedTableId; const results = []; let cursor = null; const pageSize = 50; let hasMorePages = true; while (hasMorePages) { const response = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getReservedListFromTable( $address: IotaAddress! $first: Int! $after: String ) { owner(address: $address) { dynamicFields(first: $first, after: $after) { pageInfo { hasNextPage endCursor } nodes { name { json } } } } } `), variables: { address: tableId, first: pageSize, after: cursor } }); const df = response?.data?.owner?.dynamicFields; const nodes = df?.nodes ?? []; for (const n of nodes) { const json = n?.name?.json; if (typeof json === "string" && json.length > 0) { results.push(json); continue; } } if (!df?.pageInfo?.hasNextPage) { hasMorePages = false; } else { cursor = df.pageInfo.endCursor; } cursor = df.pageInfo.endCursor; } return results; } async getNameRecord(name) { if (!(0, import_utils3.isValidIotaName)(name)) throw new Error("Invalid IOTA name"); const registryTableId = this.getPackage("registryTableId", "v1"); const packageId = this.getPackage("packageId", "v1"); const nameBcsB64 = (0, import_utils.toB64)( import_bcs2.NameBcs.serialize({ labels: (0, import_utils3.normalizeIotaName)(name, "dot").split(".").reverse() }).toBytes() ); const nameRecordResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getNameRecord($parentId: IotaAddress!, $name: DynamicFieldName!) { owner(address: $parentId) { address dynamicField(name: $name) { value { ... on MoveValue { json } } } } } `), variables: { parentId: registryTableId, name: { type: (0, import_helpers.getNameType)(packageId), bcs: nameBcsB64 } } }); const nameRecord = nameRecordResponse.data?.owner?.dynamicField?.value?.json; if (!nameRecord) return null; const nameRecordData = nameRecord.data?.contents; if (nameRecord.error || !nameRecordData) throw new Error("Name record not found. This name is not registered."); const data = {}; if (nameRecordData) { nameRecordData.forEach((field) => { if (field.key) { data[field.key] = field.value; } }); } return { name, nftId: nameRecord?.nft_id, targetAddress: nameRecord?.target_address, expirationDate: new Date(Number(nameRecord?.expiration_timestamp_ms)), data, avatar: data[import_constants.ALLOWED_METADATA.avatar] }; } async getCouponHouse() { const iotaNamesObjectId = this.getPackage("iotaNamesObjectId", "v1"); const packageId = this.getPackage("packageId", "v1"); const couponsPackageId = this.getPackage("couponsPackageId", "v1"); const DummyFieldB64 = import_bcs2.DummyFieldBcs.serialize({ dummy_field: false }).toBase64(); const couponHouseResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getIotaNamesCouponHouseRegistryKey( $parentId: IotaAddress! $name: DynamicFieldName! ) { owner(address: $parentId) { dynamicField(name: $name) { value { ... on MoveValue { bcs } } } } } `), variables: { parentId: iotaNamesObjectId, name: { type: `${packageId}::iota_names::RegistryKey<${couponsPackageId}::coupon_house::CouponHouse>`, bcs: DummyFieldB64 } } }); const couponsHouseDynamicFieldBcsValue = couponHouseResponse?.data?.owner?.dynamicField?.value?.bcs; if (!couponsHouseDynamicFieldBcsValue) { throw new Error("Coupon house not found or is invalid"); } return import_bcs2.CouponHouseBcs.parse((0, import_utils.fromB64)(couponsHouseDynamicFieldBcsValue)); } async resolveCoupon(couponCode) { const couponHouse = await this.getCouponHouse(); const couponsTableId = couponHouse?.coupons?.coupons?.id.id.bytes; if (!couponsTableId) { throw new Error("Coupons table ID not found in the coupon house"); } const couponCodeHash = (0, import_utils2.bytesToHex)((0, import_blake2b.blake2b)(couponCode, { dkLen: 32 })); const couponCodeBytes = (0, import_utils2.hexToBytes)(couponCodeHash); const couponCodeB64 = import_bcs.bcs.vector(import_bcs.bcs.u8()).serialize(couponCodeBytes).toBase64(); const couponResponse = await this.graphQlClient.query({ query: (0, import__.graphql)(` query getCouponBcs($parentId: IotaAddress!, $name: DynamicFieldName!) { owner(address: $parentId) { dynamicField(name: $name) { value { ... on MoveValue { bcs } } } } } `), variables: { parentId: couponsTableId, name: { type: "vector<u8>", bcs: couponCodeB64 } } }); const couponBcsBase64 = couponResponse?.data?.owner?.dynamicField?.value?.bcs; if (!couponBcsBase64) { return null; } const couponData = import_bcs2.CouponBcs.parse((0, import_utils.fromB64)(couponBcsBase64)); return { ...couponData, couponCode }; } /** * Calculates the registration or renewal price for an SLN (Second Level Name). * It expects a name, the number of years and a `IotaNamesPriceList` object, * as returned from `iotaNamesClient.getPriceList()` function, or `iotaNames.getRenewalPriceList()` function. * * It throws an error: * 1. if the name is a subname * 2. if the name is not a valid IOTA name * 3. if the years are not between 1 and 5 */ async calculatePrice({ name, years, isRegistration = true }) { if (!(0, import_utils3.isValidIotaName)(name)) { throw new Error("Invalid IOTA names"); } (0, import_helpers.validateYears)(years); if ((0, import_helpers.isSubname)(name)) { throw new Error("Subnames do not have a registration fee"); } const length = (0, import_utils3.normalizeIotaName)(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 calculateDiscountedPrice({ coupons, name, years, isRegistration = true, address }) { if (coupons.every((coupon) => typeof coupon === "string")) { const couponPromises = coupons.map(async (couponCode) => { const coupon = await this.resolveCoupon(couponCode); if (!coupon) { throw new Error(`Coupon not found: ${couponCode}`); } return coupon; }); coupons = await Promise.all(couponPromises); } const normalizedName = (0, import_utils3.normalizeIotaName)(name, "dot"); (0, import_utils3.validateIotaName)(normalizedName); const nameParts = normalizedName.split("."); const firstNamePart = nameParts[0]; (0, import_coupons.validateCoupons)(coupons, years, firstNamePart.length, address); const standardPrice = await this.calculatePrice({ name, years, isRegistration }); return (0, import_coupons.applyCouponsToPrice)(coupons, standardPrice); } } //# sourceMappingURL=iota-names-client.js.map