@haneullabs/haneulns 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/CHANGELOG.md +505 -0
  2. package/README.md +9 -0
  3. package/dist/cjs/constants.d.ts +11 -0
  4. package/dist/cjs/constants.js +120 -0
  5. package/dist/cjs/constants.js.map +7 -0
  6. package/dist/cjs/haneulns-client.d.ts +40 -0
  7. package/dist/cjs/haneulns-client.js +261 -0
  8. package/dist/cjs/haneulns-client.js.map +7 -0
  9. package/dist/cjs/haneulns-transaction.d.ts +105 -0
  10. package/dist/cjs/haneulns-transaction.js +465 -0
  11. package/dist/cjs/haneulns-transaction.js.map +7 -0
  12. package/dist/cjs/helpers.d.ts +18 -0
  13. package/dist/cjs/helpers.js +63 -0
  14. package/dist/cjs/helpers.js.map +7 -0
  15. package/dist/cjs/index.d.ts +5 -0
  16. package/dist/cjs/index.js +39 -0
  17. package/dist/cjs/index.js.map +7 -0
  18. package/dist/cjs/package.json +5 -0
  19. package/dist/cjs/pyth/PriceServiceConnection.d.ts +26 -0
  20. package/dist/cjs/pyth/PriceServiceConnection.js +68 -0
  21. package/dist/cjs/pyth/PriceServiceConnection.js.map +7 -0
  22. package/dist/cjs/pyth/pyth-helpers.d.ts +7 -0
  23. package/dist/cjs/pyth/pyth-helpers.js +36 -0
  24. package/dist/cjs/pyth/pyth-helpers.js.map +7 -0
  25. package/dist/cjs/pyth/pyth.d.ts +66 -0
  26. package/dist/cjs/pyth/pyth.js +270 -0
  27. package/dist/cjs/pyth/pyth.js.map +7 -0
  28. package/dist/cjs/types.d.ts +89 -0
  29. package/dist/cjs/types.js +17 -0
  30. package/dist/cjs/types.js.map +7 -0
  31. package/dist/esm/constants.d.ts +11 -0
  32. package/dist/esm/constants.js +100 -0
  33. package/dist/esm/constants.js.map +7 -0
  34. package/dist/esm/haneulns-client.d.ts +40 -0
  35. package/dist/esm/haneulns-client.js +249 -0
  36. package/dist/esm/haneulns-client.js.map +7 -0
  37. package/dist/esm/haneulns-transaction.d.ts +105 -0
  38. package/dist/esm/haneulns-transaction.js +445 -0
  39. package/dist/esm/haneulns-transaction.js.map +7 -0
  40. package/dist/esm/helpers.d.ts +18 -0
  41. package/dist/esm/helpers.js +43 -0
  42. package/dist/esm/helpers.js.map +7 -0
  43. package/dist/esm/index.d.ts +5 -0
  44. package/dist/esm/index.js +28 -0
  45. package/dist/esm/index.js.map +7 -0
  46. package/dist/esm/package.json +5 -0
  47. package/dist/esm/pyth/PriceServiceConnection.d.ts +26 -0
  48. package/dist/esm/pyth/PriceServiceConnection.js +38 -0
  49. package/dist/esm/pyth/PriceServiceConnection.js.map +7 -0
  50. package/dist/esm/pyth/pyth-helpers.d.ts +7 -0
  51. package/dist/esm/pyth/pyth-helpers.js +16 -0
  52. package/dist/esm/pyth/pyth-helpers.js.map +7 -0
  53. package/dist/esm/pyth/pyth.d.ts +66 -0
  54. package/dist/esm/pyth/pyth.js +250 -0
  55. package/dist/esm/pyth/pyth.js.map +7 -0
  56. package/dist/esm/types.d.ts +89 -0
  57. package/dist/esm/types.js +1 -0
  58. package/dist/esm/types.js.map +7 -0
  59. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  60. package/dist/tsconfig.tsbuildinfo +1 -0
  61. package/package.json +63 -0
  62. package/src/constants.ts +106 -0
  63. package/src/haneulns-client.ts +368 -0
  64. package/src/haneulns-transaction.ts +555 -0
  65. package/src/helpers.ts +52 -0
  66. package/src/index.ts +16 -0
  67. package/src/pyth/PriceServiceConnection.ts +48 -0
  68. package/src/pyth/pyth-helpers.ts +23 -0
  69. package/src/pyth/pyth.ts +312 -0
  70. package/src/types.ts +111 -0
@@ -0,0 +1,368 @@
1
+ // Copyright (c) Mysten Labs, Inc.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import type { HaneulClient } from '@haneullabs/haneul/client';
4
+ import type { Transaction, TransactionObjectArgument } from '@haneullabs/haneul/transactions';
5
+ import { isValidHaneulNSName, normalizeHaneulNSName } from '@haneullabs/haneul/utils';
6
+
7
+ import { mainPackage } from './constants.js';
8
+ import {
9
+ getCoinDiscountConfigType,
10
+ getConfigType,
11
+ getDomainType,
12
+ getPricelistConfigType,
13
+ getRenewalPricelistConfigType,
14
+ isSubName,
15
+ validateYears,
16
+ } from './helpers.js';
17
+ import { HaneulPriceServiceConnection, HaneulPythClient } from './pyth/pyth.js';
18
+ import type {
19
+ CoinTypeDiscount,
20
+ NameRecord,
21
+ Network,
22
+ PackageInfo,
23
+ HaneulnsClientConfig,
24
+ SuinsPriceList,
25
+ } from './types.js';
26
+
27
+ /// The HaneulnsClient is the main entry point for the Haneulns SDK.
28
+ /// It allows you to interact with HaneulNS.
29
+ export class HaneulnsClient {
30
+ client: HaneulClient;
31
+ network: Network;
32
+ config: PackageInfo;
33
+
34
+ constructor(config: HaneulnsClientConfig) {
35
+ this.client = config.client;
36
+ this.network = config.network || 'mainnet';
37
+
38
+ if (this.network === 'mainnet') {
39
+ this.config = mainPackage.mainnet;
40
+ } else if (this.network === 'testnet') {
41
+ this.config = mainPackage.testnet;
42
+ } else {
43
+ throw new Error('Invalid network');
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Returns the price list for HaneulNS names in the base asset.
49
+ */
50
+
51
+ // Format:
52
+ // {
53
+ // [ 3, 3 ] => 500000000,
54
+ // [ 4, 4 ] => 100000000,
55
+ // [ 5, 63 ] => 20000000
56
+ // }
57
+ async getPriceList(): Promise<SuinsPriceList> {
58
+ if (!this.config.haneulns) throw new Error('Suins object ID is not set');
59
+ if (!this.config.packageId) throw new Error('Price list config not found');
60
+
61
+ const priceList = await this.client.getDynamicFieldObject({
62
+ parentId: this.config.haneulns,
63
+ name: {
64
+ type: getConfigType(
65
+ this.config.packageIdV1,
66
+ getPricelistConfigType(this.config.packageIdPricing),
67
+ ),
68
+ value: { dummy_field: false },
69
+ },
70
+ });
71
+
72
+ // Ensure the content exists and is a MoveStruct with expected fields
73
+ if (
74
+ !priceList?.data?.content ||
75
+ priceList.data.content.dataType !== 'moveObject' ||
76
+ !('fields' in priceList.data.content)
77
+ ) {
78
+ throw new Error('Price list not found or content is invalid');
79
+ }
80
+
81
+ // Safely extract fields
82
+ const fields = priceList.data.content.fields as Record<string, any>;
83
+ if (!fields.value || !fields.value.fields || !fields.value.fields.pricing) {
84
+ throw new Error('Pricing fields not found in the price list');
85
+ }
86
+
87
+ const contentArray = fields.value.fields.pricing.fields.contents;
88
+ const priceMap = new Map();
89
+
90
+ for (const entry of contentArray) {
91
+ const keyFields = entry.fields.key.fields;
92
+ const key = [Number(keyFields.pos0), Number(keyFields.pos1)]; // Convert keys to numbers
93
+ const value = Number(entry.fields.value); // Convert value to a number
94
+
95
+ priceMap.set(key, value);
96
+ }
97
+
98
+ return priceMap;
99
+ }
100
+
101
+ /**
102
+ * Returns the renewal price list for HaneulNS names in the base asset.
103
+ */
104
+
105
+ // Format:
106
+ // {
107
+ // [ 3, 3 ] => 500000000,
108
+ // [ 4, 4 ] => 100000000,
109
+ // [ 5, 63 ] => 20000000
110
+ // }
111
+ async getRenewalPriceList(): Promise<SuinsPriceList> {
112
+ if (!this.config.haneulns) throw new Error('Suins object ID is not set');
113
+ if (!this.config.packageId) throw new Error('Price list config not found');
114
+
115
+ const priceList = await this.client.getDynamicFieldObject({
116
+ parentId: this.config.haneulns,
117
+ name: {
118
+ type: getConfigType(
119
+ this.config.packageIdV1,
120
+ getRenewalPricelistConfigType(this.config.packageIdPricing),
121
+ ),
122
+ value: { dummy_field: false },
123
+ },
124
+ });
125
+
126
+ if (
127
+ !priceList ||
128
+ !priceList.data ||
129
+ !priceList.data.content ||
130
+ priceList.data.content.dataType !== 'moveObject' ||
131
+ !('fields' in priceList.data.content)
132
+ ) {
133
+ throw new Error('Price list not found or content structure is invalid');
134
+ }
135
+
136
+ // Safely extract fields
137
+ const fields = priceList.data.content.fields as Record<string, any>;
138
+ if (
139
+ !fields.value ||
140
+ !fields.value.fields ||
141
+ !fields.value.fields.config ||
142
+ !fields.value.fields.config.fields.pricing ||
143
+ !fields.value.fields.config.fields.pricing.fields.contents
144
+ ) {
145
+ throw new Error('Pricing fields not found in the price list');
146
+ }
147
+
148
+ const contentArray = fields.value.fields.config.fields.pricing.fields.contents;
149
+ const priceMap = new Map();
150
+
151
+ for (const entry of contentArray) {
152
+ const keyFields = entry.fields.key.fields;
153
+ const key = [Number(keyFields.pos0), Number(keyFields.pos1)]; // Convert keys to numbers
154
+ const value = Number(entry.fields.value); // Convert value to a number
155
+
156
+ priceMap.set(key, value);
157
+ }
158
+
159
+ return priceMap;
160
+ }
161
+
162
+ /**
163
+ * Returns the coin discount list for HaneulNS names.
164
+ */
165
+
166
+ // Format:
167
+ // {
168
+ // 'b48aac3f53bab328e1eb4c5b3c34f55e760f2fb3f2305ee1a474878d80f650f0::TESTUSDC::TESTUSDC' => 0,
169
+ // '0000000000000000000000000000000000000000000000000000000000000002::haneul::HANEUL' => 0,
170
+ // 'b48aac3f53bab328e1eb4c5b3c34f55e760f2fb3f2305ee1a474878d80f650f0::TESTNS::TESTNS' => 25
171
+ // }
172
+ async getCoinTypeDiscount(): Promise<CoinTypeDiscount> {
173
+ if (!this.config.haneulns) throw new Error('Suins object ID is not set');
174
+ if (!this.config.packageId) throw new Error('Price list config not found');
175
+
176
+ const dfValue = await this.client.getDynamicFieldObject({
177
+ parentId: this.config.haneulns,
178
+ name: {
179
+ type: getConfigType(
180
+ this.config.packageIdV1,
181
+ getCoinDiscountConfigType(this.config.payments.packageId),
182
+ ),
183
+ value: { dummy_field: false },
184
+ },
185
+ });
186
+
187
+ if (
188
+ !dfValue ||
189
+ !dfValue.data ||
190
+ !dfValue.data.content ||
191
+ dfValue.data.content.dataType !== 'moveObject' ||
192
+ !('fields' in dfValue.data.content)
193
+ ) {
194
+ throw new Error('dfValue not found or content structure is invalid');
195
+ }
196
+
197
+ // Safely extract fields
198
+ const fields = dfValue.data.content.fields as Record<string, any>;
199
+ if (
200
+ !fields.value ||
201
+ !fields.value.fields ||
202
+ !fields.value.fields.base_currency ||
203
+ !fields.value.fields.base_currency.fields ||
204
+ !fields.value.fields.base_currency.fields.name ||
205
+ !fields.value.fields.currencies ||
206
+ !fields.value.fields.currencies.fields ||
207
+ !fields.value.fields.currencies.fields.contents
208
+ ) {
209
+ throw new Error('Required fields are missing in dfValue');
210
+ }
211
+
212
+ // Safely extract content
213
+ const content = fields.value.fields;
214
+ const currencyDiscounts = content.currencies.fields.contents;
215
+ const discountMap = new Map();
216
+
217
+ for (const entry of currencyDiscounts) {
218
+ const key = entry.fields.key.fields.name;
219
+ const value = Number(entry.fields.value.fields.discount_percentage);
220
+
221
+ discountMap.set(key, value);
222
+ }
223
+
224
+ return discountMap;
225
+ }
226
+
227
+ async getNameRecord(name: string): Promise<NameRecord | null> {
228
+ if (!isValidHaneulNSName(name)) throw new Error('Invalid HaneulNS name');
229
+ if (!this.config.registryTableId) throw new Error('Suins package ID is not set');
230
+
231
+ const nameRecord = await this.client.getDynamicFieldObject({
232
+ parentId: this.config.registryTableId,
233
+ name: {
234
+ type: getDomainType(this.config.packageIdV1),
235
+ value: normalizeHaneulNSName(name, 'dot').split('.').reverse(),
236
+ },
237
+ });
238
+
239
+ const fields = nameRecord.data?.content;
240
+
241
+ // in case the name record is not found, return null
242
+ if (nameRecord.error?.code === 'dynamicFieldNotFound') return null;
243
+
244
+ if (nameRecord.error || !fields || fields.dataType !== 'moveObject')
245
+ throw new Error('Name record not found. This domain is not registered.');
246
+ const content = fields.fields as Record<string, any>;
247
+
248
+ const data: Record<string, string> = {};
249
+ content.value.fields.data.fields.contents.forEach((item: any) => {
250
+ // @ts-ignore-next-line
251
+ data[item.fields.key as string] = item.fields.value;
252
+ });
253
+
254
+ return {
255
+ name,
256
+ nftId: content.value.fields?.nft_id,
257
+ targetAddress: content.value.fields?.target_address!,
258
+ expirationTimestampMs: content.value.fields?.expiration_timestamp_ms,
259
+ data,
260
+ avatar: data.avatar,
261
+ contentHash: data.content_hash,
262
+ walrusSiteId: data.walrus_site_id,
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Calculates the registration or renewal price for an SLD (Second Level Domain).
268
+ * It expects a domain name, the number of years and a `SuinsPriceList` object,
269
+ * as returned from `haneulnsClient.getPriceList()` function, or `haneulns.getRenewalPriceList()` function.
270
+ *
271
+ * It throws an error:
272
+ * 1. if the name is a subdomain
273
+ * 2. if the name is not a valid HaneulNS name
274
+ * 3. if the years are not between 1 and 5
275
+ */
276
+ async calculatePrice({
277
+ name,
278
+ years,
279
+ isRegistration = true,
280
+ }: {
281
+ name: string;
282
+ years: number;
283
+ isRegistration?: boolean;
284
+ }) {
285
+ if (!isValidHaneulNSName(name)) {
286
+ throw new Error('Invalid HaneulNS name');
287
+ }
288
+ validateYears(years);
289
+
290
+ if (isSubName(name)) {
291
+ throw new Error('Subdomains do not have a registration fee');
292
+ }
293
+
294
+ const length = normalizeHaneulNSName(name, 'dot').split('.')[0].length;
295
+ const priceList = await this.getPriceList();
296
+ const renewalPriceList = await this.getRenewalPriceList();
297
+ let yearsRemain = years;
298
+ let price = 0;
299
+
300
+ if (isRegistration) {
301
+ for (const [[minLength, maxLength], pricePerYear] of priceList.entries()) {
302
+ if (length >= minLength && length <= maxLength) {
303
+ price += pricePerYear; // Registration is always 1 year
304
+ yearsRemain -= 1;
305
+ break;
306
+ }
307
+ }
308
+ }
309
+
310
+ for (const [[minLength, maxLength], pricePerYear] of renewalPriceList.entries()) {
311
+ if (length >= minLength && length <= maxLength) {
312
+ price += yearsRemain * pricePerYear;
313
+ break;
314
+ }
315
+ }
316
+
317
+ return price;
318
+ }
319
+
320
+ async getPriceInfoObject(tx: Transaction, feed: string, feeCoin?: TransactionObjectArgument) {
321
+ // Initialize connection to the Haneul Price Service
322
+ const endpoint =
323
+ this.network === 'testnet'
324
+ ? 'https://hermes-beta.pyth.network'
325
+ : 'https://hermes.pyth.network';
326
+ const connection = new HaneulPriceServiceConnection(endpoint);
327
+
328
+ // List of price feed IDs
329
+ const priceIDs = [
330
+ feed, // ASSET/USD price ID
331
+ ];
332
+
333
+ // Fetch price feed update data
334
+ const priceUpdateData = await connection.getPriceFeedsUpdateData(priceIDs);
335
+
336
+ // Initialize Sui Client and Pyth Client
337
+ const wormholeStateId = this.config.pyth.wormholeStateId;
338
+ const pythStateId = this.config.pyth.pythStateId;
339
+
340
+ const client = new HaneulPythClient(this.client, pythStateId, wormholeStateId);
341
+
342
+ return await client.updatePriceFeeds(tx, priceUpdateData, priceIDs, feeCoin); // returns priceInfoObjectIds
343
+ }
344
+
345
+ async getPythBaseUpdateFee(): Promise<number> {
346
+ const pythStateId = this.config.pyth.pythStateId;
347
+ const wormholeStateId = this.config.pyth.wormholeStateId;
348
+
349
+ const client = new HaneulPythClient(this.client, pythStateId, wormholeStateId);
350
+ return await client.getBaseUpdateFee();
351
+ }
352
+
353
+ async getObjectType(objectId: string) {
354
+ // Fetch the object details from the Haneul client
355
+ const objectResponse = await this.client.getObject({
356
+ id: objectId,
357
+ options: { showType: true },
358
+ });
359
+
360
+ // Extract and return the type if available
361
+ if (objectResponse && objectResponse.data && objectResponse.data.type) {
362
+ return objectResponse.data.type;
363
+ }
364
+
365
+ // Throw an error if the type is not found
366
+ throw new Error(`Type information not found for object ID: ${objectId}`);
367
+ }
368
+ }