@iota/iota-names-sdk
Version:
IOTA-Names SDK
590 lines (589 loc) • 21.9 kB
JavaScript
"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