UNPKG

@mysten/suins

Version:
1 lines 14.9 kB
{"version":3,"file":"suins-client.mjs","names":["NameRecordBcs"],"sources":["../src/suins-client.ts"],"sourcesContent":["// Copyright (c) Mysten Labs, Inc.\n// SPDX-License-Identifier: Apache-2.0\nimport type { ClientWithCoreApi, SuiClientTypes } from '@mysten/sui/client';\nimport type { Transaction, TransactionObjectArgument } from '@mysten/sui/transactions';\nimport { isValidSuiNSName, normalizeSuiNSName } from '@mysten/sui/utils';\n\nimport { mainPackage } from './constants.js';\nimport { isSubName, validateYears } from './helpers.js';\nimport { SuiPriceServiceConnection, SuiPythClient } from './pyth/pyth.js';\nimport type {\n\tCoinTypeDiscount,\n\tNameRecord,\n\tPackageInfo,\n\tSuinsClientConfig,\n\tSuinsPriceList,\n} from './types.js';\n\nimport { ConfigKey } from './contracts/suins/suins.js';\nimport { Domain } from './contracts/suins/domain.js';\nimport { NameRecord as NameRecordBcs } from './contracts/suins/name_record.js';\nimport { PricingConfig, RenewalConfig } from './contracts/suins/pricing_config.js';\nimport { PaymentsConfig } from './contracts/suins_payments/payments.js';\n\nexport type SuinsExtensionOptions<Name extends string = 'suins'> = {\n\tname?: Name;\n\tpackageInfo?: PackageInfo;\n};\n\n/**\n * Creates a SuiNS client extension that can be used with `client.$extend()`.\n *\n * @example\n * ```ts\n * import { SuiJsonRpcClient, getJsonRpcFullnodeUrl } from '@mysten/sui/jsonRpc';\n * import { suins } from '@mysten/suins';\n *\n * const client = new SuiJsonRpcClient({\n * url: getJsonRpcFullnodeUrl('mainnet'),\n * network: 'mainnet',\n * }).$extend(suins());\n *\n * const nameRecord = await client.suins.getNameRecord('example.sui');\n * ```\n */\nexport function suins<const Name extends string = 'suins'>({\n\tname = 'suins' as Name,\n\tpackageInfo,\n}: SuinsExtensionOptions<Name> = {}) {\n\treturn {\n\t\tname,\n\t\tregister: (client: ClientWithCoreApi) => {\n\t\t\treturn new SuinsClient({\n\t\t\t\tclient,\n\t\t\t\tnetwork: client.network,\n\t\t\t\tpackageInfo,\n\t\t\t});\n\t\t},\n\t};\n}\n\nexport class SuinsClient {\n\tclient: ClientWithCoreApi;\n\tnetwork: SuiClientTypes.Network;\n\tconfig: PackageInfo;\n\n\tconstructor(config: SuinsClientConfig) {\n\t\tthis.client = config.client;\n\t\tthis.network = config.network || 'mainnet';\n\n\t\tif (config.packageInfo) {\n\t\t\tthis.config = config.packageInfo;\n\t\t} else if (this.network === 'mainnet') {\n\t\t\tthis.config = mainPackage.mainnet;\n\t\t} else if (this.network === 'testnet') {\n\t\t\tthis.config = mainPackage.testnet;\n\t\t} else {\n\t\t\tthrow new Error('Invalid network');\n\t\t}\n\t}\n\n\t/**\n\t * Returns the price list for SuiNS names in the base asset.\n\t */\n\n\t// Format:\n\t// {\n\t// \t[ 3, 3 ] => 500000000,\n\t// \t[ 4, 4 ] => 100000000,\n\t// \t[ 5, 63 ] => 20000000\n\t// }\n\tasync getPriceList(): Promise<SuinsPriceList> {\n\t\tif (!this.config.suins) throw new Error('Suins object ID is not set');\n\t\tif (!this.config.packageId) throw new Error('Price list config not found');\n\n\t\tconst configType = `${this.config.packageIdV1}::suins::ConfigKey<${this.config.packageIdPricing}::pricing_config::PricingConfig>`;\n\t\tconst nameBytes = ConfigKey.serialize({ dummy_field: false }).toBytes();\n\n\t\tconst result = await this.client.core.getDynamicField({\n\t\t\tparentId: this.config.suins,\n\t\t\tname: { type: configType, bcs: nameBytes },\n\t\t});\n\n\t\tif (!result.dynamicField?.value?.bcs) {\n\t\t\tthrow new Error('Price list not found or content is invalid');\n\t\t}\n\n\t\tconst pricingConfig = PricingConfig.parse(result.dynamicField.value.bcs);\n\t\tconst priceMap = new Map();\n\n\t\tfor (const entry of pricingConfig.pricing.contents) {\n\t\t\tconst key = [Number(entry.key[0]), Number(entry.key[1])];\n\t\t\tconst value = Number(entry.value);\n\t\t\tpriceMap.set(key, value);\n\t\t}\n\n\t\treturn priceMap;\n\t}\n\n\t/**\n\t * Returns the renewal price list for SuiNS names in the base asset.\n\t */\n\n\t// Format:\n\t// {\n\t// \t[ 3, 3 ] => 500000000,\n\t// \t[ 4, 4 ] => 100000000,\n\t// \t[ 5, 63 ] => 20000000\n\t// }\n\tasync getRenewalPriceList(): Promise<SuinsPriceList> {\n\t\tif (!this.config.suins) throw new Error('Suins object ID is not set');\n\t\tif (!this.config.packageId) throw new Error('Price list config not found');\n\n\t\tconst configType = `${this.config.packageIdV1}::suins::ConfigKey<${this.config.packageIdPricing}::pricing_config::RenewalConfig>`;\n\t\tconst nameBytes = ConfigKey.serialize({ dummy_field: false }).toBytes();\n\n\t\tconst result = await this.client.core.getDynamicField({\n\t\t\tparentId: this.config.suins,\n\t\t\tname: { type: configType, bcs: nameBytes },\n\t\t});\n\n\t\tif (!result.dynamicField?.value?.bcs) {\n\t\t\tthrow new Error('Price list not found or content structure is invalid');\n\t\t}\n\n\t\tconst renewalConfig = RenewalConfig.parse(result.dynamicField.value.bcs);\n\t\tconst priceMap = new Map();\n\n\t\tfor (const entry of renewalConfig.config.pricing.contents) {\n\t\t\tconst key = [Number(entry.key[0]), Number(entry.key[1])];\n\t\t\tconst value = Number(entry.value);\n\t\t\tpriceMap.set(key, value);\n\t\t}\n\n\t\treturn priceMap;\n\t}\n\n\t/**\n\t * Returns the coin discount list for SuiNS names.\n\t */\n\n\t// Format:\n\t// {\n\t// \t'b48aac3f53bab328e1eb4c5b3c34f55e760f2fb3f2305ee1a474878d80f650f0::TESTUSDC::TESTUSDC' => 0,\n\t// \t'0000000000000000000000000000000000000000000000000000000000000002::sui::SUI' => 0,\n\t// \t'b48aac3f53bab328e1eb4c5b3c34f55e760f2fb3f2305ee1a474878d80f650f0::TESTNS::TESTNS' => 25\n\t// }\n\tasync getCoinTypeDiscount(): Promise<CoinTypeDiscount> {\n\t\tif (!this.config.suins) throw new Error('Suins object ID is not set');\n\t\tif (!this.config.packageId) throw new Error('Price list config not found');\n\n\t\tconst configType = `${this.config.packageIdV1}::suins::ConfigKey<${this.config.payments.packageId}::payments::PaymentsConfig>`;\n\n\t\tconst result = await this.client.core.getDynamicField({\n\t\t\tparentId: this.config.suins,\n\t\t\tname: { type: configType, bcs: ConfigKey.serialize({ dummy_field: false }).toBytes() },\n\t\t});\n\n\t\tif (!result.dynamicField?.value?.bcs) {\n\t\t\tthrow new Error('Payments config not found or content structure is invalid');\n\t\t}\n\n\t\tconst paymentsConfig = PaymentsConfig.parse(result.dynamicField.value.bcs);\n\t\tconst discountMap = new Map();\n\n\t\tfor (const entry of paymentsConfig.currencies.contents) {\n\t\t\tconst key = entry.key.name;\n\t\t\tconst value = Number(entry.value.discount_percentage);\n\t\t\tdiscountMap.set(key, value);\n\t\t}\n\n\t\treturn discountMap;\n\t}\n\n\tasync getNameRecord(name: string): Promise<NameRecord | null> {\n\t\tif (!isValidSuiNSName(name)) throw new Error('Invalid SuiNS name');\n\t\tif (!this.config.registryTableId) throw new Error('Suins package ID is not set');\n\n\t\tconst labels = normalizeSuiNSName(name, 'dot').split('.').reverse();\n\n\t\tconst result = await this.client.core.getDynamicField({\n\t\t\tparentId: this.config.registryTableId,\n\t\t\tname: {\n\t\t\t\ttype: `${this.config.packageIdV1}::domain::Domain`,\n\t\t\t\tbcs: Domain.serialize({ labels }).toBytes(),\n\t\t\t},\n\t\t});\n\n\t\tif (!result.dynamicField) return null;\n\n\t\tif (!result.dynamicField.value?.bcs) {\n\t\t\tthrow new Error('Name record not found. This domain is not registered.');\n\t\t}\n\n\t\tconst record = NameRecordBcs.parse(result.dynamicField.value.bcs);\n\n\t\tconst data: Record<string, string> = {};\n\t\tfor (const entry of record.data.contents) {\n\t\t\tdata[entry.key] = entry.value;\n\t\t}\n\n\t\treturn {\n\t\t\tname,\n\t\t\tnftId: record.nft_id,\n\t\t\ttargetAddress: record.target_address ?? '',\n\t\t\texpirationTimestampMs: Number(record.expiration_timestamp_ms),\n\t\t\tdata,\n\t\t\tavatar: data.avatar,\n\t\t\tcontentHash: data.content_hash,\n\t\t\twalrusSiteId: data.walrus_site_id,\n\t\t};\n\t}\n\n\t/**\n\t * Calculates the registration or renewal price for an SLD (Second Level Domain).\n\t * It expects a domain name, the number of years and a `SuinsPriceList` object,\n\t * as returned from `suinsClient.getPriceList()` function, or `suins.getRenewalPriceList()` function.\n\t *\n\t * It throws an error:\n\t * 1. if the name is a subdomain\n\t * 2. if the name is not a valid SuiNS name\n\t * 3. if the years are not between 1 and 5\n\t */\n\tasync calculatePrice({\n\t\tname,\n\t\tyears,\n\t\tisRegistration = true,\n\t}: {\n\t\tname: string;\n\t\tyears: number;\n\t\tisRegistration?: boolean;\n\t}) {\n\t\tif (!isValidSuiNSName(name)) {\n\t\t\tthrow new Error('Invalid SuiNS name');\n\t\t}\n\t\tvalidateYears(years);\n\n\t\tif (isSubName(name)) {\n\t\t\tthrow new Error('Subdomains do not have a registration fee');\n\t\t}\n\n\t\tconst length = normalizeSuiNSName(name, 'dot').split('.')[0].length;\n\t\tconst priceList = await this.getPriceList();\n\t\tconst renewalPriceList = await this.getRenewalPriceList();\n\t\tlet yearsRemain = years;\n\t\tlet price = 0;\n\n\t\tif (isRegistration) {\n\t\t\tfor (const [[minLength, maxLength], pricePerYear] of priceList.entries()) {\n\t\t\t\tif (length >= minLength && length <= maxLength) {\n\t\t\t\t\tprice += pricePerYear; // Registration is always 1 year\n\t\t\t\t\tyearsRemain -= 1;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfor (const [[minLength, maxLength], pricePerYear] of renewalPriceList.entries()) {\n\t\t\tif (length >= minLength && length <= maxLength) {\n\t\t\t\tprice += yearsRemain * pricePerYear;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn price;\n\t}\n\n\tasync getPriceInfoObject(tx: Transaction, feed: string, feeCoin?: TransactionObjectArgument) {\n\t\tconst endpoint =\n\t\t\tthis.network === 'testnet'\n\t\t\t\t? 'https://hermes-beta.pyth.network'\n\t\t\t\t: 'https://hermes.pyth.network';\n\t\tconst connection = new SuiPriceServiceConnection(endpoint);\n\t\tconst priceIDs = [feed];\n\t\tconst priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs);\n\n\t\tconst client = new SuiPythClient(\n\t\t\tthis.client,\n\t\t\tthis.config.pyth.pythStateId,\n\t\t\tthis.config.pyth.wormholeStateId,\n\t\t);\n\n\t\treturn client.updatePriceFeeds(tx, priceUpdateData, priceIDs, feeCoin);\n\t}\n\n\tasync getPythBaseUpdateFee(): Promise<number> {\n\t\tconst client = new SuiPythClient(\n\t\t\tthis.client,\n\t\t\tthis.config.pyth.pythStateId,\n\t\t\tthis.config.pyth.wormholeStateId,\n\t\t);\n\t\treturn client.getBaseUpdateFee();\n\t}\n\n\tasync getObjectType(objectId: string) {\n\t\tconst result = await this.client.core.getObject({ objectId });\n\n\t\tif (result.object?.type) {\n\t\t\treturn result.object.type;\n\t\t}\n\n\t\tthrow new Error(`Type information not found for object ID: ${objectId}`);\n\t}\n}\n"],"mappings":"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"}