@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.
- package/CHANGELOG.md +505 -0
- package/README.md +9 -0
- package/dist/cjs/constants.d.ts +11 -0
- package/dist/cjs/constants.js +120 -0
- package/dist/cjs/constants.js.map +7 -0
- package/dist/cjs/haneulns-client.d.ts +40 -0
- package/dist/cjs/haneulns-client.js +261 -0
- package/dist/cjs/haneulns-client.js.map +7 -0
- package/dist/cjs/haneulns-transaction.d.ts +105 -0
- package/dist/cjs/haneulns-transaction.js +465 -0
- package/dist/cjs/haneulns-transaction.js.map +7 -0
- package/dist/cjs/helpers.d.ts +18 -0
- package/dist/cjs/helpers.js +63 -0
- package/dist/cjs/helpers.js.map +7 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +39 -0
- package/dist/cjs/index.js.map +7 -0
- package/dist/cjs/package.json +5 -0
- package/dist/cjs/pyth/PriceServiceConnection.d.ts +26 -0
- package/dist/cjs/pyth/PriceServiceConnection.js +68 -0
- package/dist/cjs/pyth/PriceServiceConnection.js.map +7 -0
- package/dist/cjs/pyth/pyth-helpers.d.ts +7 -0
- package/dist/cjs/pyth/pyth-helpers.js +36 -0
- package/dist/cjs/pyth/pyth-helpers.js.map +7 -0
- package/dist/cjs/pyth/pyth.d.ts +66 -0
- package/dist/cjs/pyth/pyth.js +270 -0
- package/dist/cjs/pyth/pyth.js.map +7 -0
- package/dist/cjs/types.d.ts +89 -0
- package/dist/cjs/types.js +17 -0
- package/dist/cjs/types.js.map +7 -0
- package/dist/esm/constants.d.ts +11 -0
- package/dist/esm/constants.js +100 -0
- package/dist/esm/constants.js.map +7 -0
- package/dist/esm/haneulns-client.d.ts +40 -0
- package/dist/esm/haneulns-client.js +249 -0
- package/dist/esm/haneulns-client.js.map +7 -0
- package/dist/esm/haneulns-transaction.d.ts +105 -0
- package/dist/esm/haneulns-transaction.js +445 -0
- package/dist/esm/haneulns-transaction.js.map +7 -0
- package/dist/esm/helpers.d.ts +18 -0
- package/dist/esm/helpers.js +43 -0
- package/dist/esm/helpers.js.map +7 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +28 -0
- package/dist/esm/index.js.map +7 -0
- package/dist/esm/package.json +5 -0
- package/dist/esm/pyth/PriceServiceConnection.d.ts +26 -0
- package/dist/esm/pyth/PriceServiceConnection.js +38 -0
- package/dist/esm/pyth/PriceServiceConnection.js.map +7 -0
- package/dist/esm/pyth/pyth-helpers.d.ts +7 -0
- package/dist/esm/pyth/pyth-helpers.js +16 -0
- package/dist/esm/pyth/pyth-helpers.js.map +7 -0
- package/dist/esm/pyth/pyth.d.ts +66 -0
- package/dist/esm/pyth/pyth.js +250 -0
- package/dist/esm/pyth/pyth.js.map +7 -0
- package/dist/esm/types.d.ts +89 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/types.js.map +7 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +63 -0
- package/src/constants.ts +106 -0
- package/src/haneulns-client.ts +368 -0
- package/src/haneulns-transaction.ts +555 -0
- package/src/helpers.ts +52 -0
- package/src/index.ts +16 -0
- package/src/pyth/PriceServiceConnection.ts +48 -0
- package/src/pyth/pyth-helpers.ts +23 -0
- package/src/pyth/pyth.ts +312 -0
- 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
|
+
}
|