@0xobelisk/graphql-client 1.2.0-pre.123 → 1.2.0-pre.125

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/dist/types.d.ts CHANGED
@@ -118,6 +118,48 @@ export interface DubheUserStorageRow {
118
118
  lastUpdateDigest: string;
119
119
  lastEventSeq: string;
120
120
  }
121
+ export interface SceneStorageRow {
122
+ sceneId: string;
123
+ dappKey: string;
124
+ sceneType: string;
125
+ sceneTypeRaw: string;
126
+ authorizationKind?: string | null;
127
+ authorizationKindRaw?: string | null;
128
+ authorizedPermitId?: string | null;
129
+ isDestroyed: boolean;
130
+ createdAtCheckpoint: string;
131
+ destroyedAtCheckpoint?: string | null;
132
+ updatedAtCheckpoint: string;
133
+ lastUpdateDigest: string;
134
+ lastEventSeq: string;
135
+ }
136
+ export interface SceneStorageFieldRow {
137
+ sceneId: string;
138
+ fieldNameRaw: string;
139
+ dappKey: string;
140
+ sceneType: string;
141
+ sceneTypeRaw: string;
142
+ fieldName: string;
143
+ fieldValueRaw?: string | null;
144
+ isDeleted: boolean;
145
+ deletedAtCheckpoint?: string | null;
146
+ updatedAtCheckpoint: string;
147
+ lastUpdateDigest: string;
148
+ lastEventSeq: string;
149
+ }
150
+ export interface ObjectStorageRow {
151
+ objectId: string;
152
+ dappKey: string;
153
+ objectType: string;
154
+ objectTypeRaw: string;
155
+ entityIdRaw: string;
156
+ isDestroyed: boolean;
157
+ createdAtCheckpoint: string;
158
+ destroyedAtCheckpoint?: string | null;
159
+ updatedAtCheckpoint: string;
160
+ lastUpdateDigest: string;
161
+ lastEventSeq: string;
162
+ }
121
163
  export interface DubheDappRuntimeStateRow {
122
164
  dappKey: string;
123
165
  admin?: string | null;
@@ -254,6 +296,27 @@ export interface DubheClientConfig {
254
296
  fetchOptions?: RequestInit;
255
297
  retryOptions?: RetryOptions;
256
298
  dubheMetadata?: any;
299
+ /**
300
+ * When true, outgoing HTTP queries are collected within `batchInterval` ms
301
+ * and sent as a single batched POST request. The server must have
302
+ * `enableQueryBatching` / `allowBatchedHttpRequests` enabled (PostGraphile
303
+ * already sets this by default).
304
+ *
305
+ * Default: false
306
+ */
307
+ batchRequests?: boolean;
308
+ /**
309
+ * Time window (ms) to collect queries before flushing a batch.
310
+ * Only used when `batchRequests` is true.
311
+ * Default: 10
312
+ */
313
+ batchInterval?: number;
314
+ /**
315
+ * Maximum number of operations per batch.
316
+ * Only used when `batchRequests` is true.
317
+ * Default: 20
318
+ */
319
+ batchMax?: number;
257
320
  cacheConfig?: {
258
321
  paginatedTables?: string[];
259
322
  strategy?: PaginationCacheStrategy;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@0xobelisk/graphql-client",
3
- "version": "1.2.0-pre.123",
3
+ "version": "1.2.0-pre.125",
4
4
  "description": "Tookit for interacting with dubhe graphql client",
5
5
  "keywords": [
6
6
  "sui",
package/src/client.ts CHANGED
@@ -11,12 +11,98 @@ import {
11
11
  FetchPolicy,
12
12
  OperationVariables
13
13
  } from '@apollo/client';
14
+ import { BatchHttpLink } from '@apollo/client/link/batch-http';
14
15
  import { RetryLink } from '@apollo/client/link/retry';
15
16
  import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
16
17
  import { getMainDefinition } from '@apollo/client/utilities';
17
18
  import { createClient } from 'graphql-ws';
18
19
  import pluralize from 'pluralize';
19
20
 
21
+ /**
22
+ * Static field registry for Dubhe system tables.
23
+ *
24
+ * These tables are exposed via `store_dubhe_*` views in the graphql-server.
25
+ * They are NOT in the DApp's dubhe.config schema, so `convertTableFields`
26
+ * cannot auto-discover their fields. Registering them here means callers
27
+ * can use `getAllTables('dubheDappMarketplaceFees', ...)` without passing
28
+ * `fields` explicitly.
29
+ *
30
+ * Key: the simplified GraphQL field name (after SimpleNamingPlugin strips
31
+ * the `storeDubhe` prefix and removes the `all` prefix).
32
+ * Value: camelCase GraphQL field names matching the PostGraphile schema.
33
+ */
34
+ const SYSTEM_TABLE_FIELDS: Record<string, string[]> = {
35
+ dubheDappMarketplaceFees: [
36
+ 'dappKey',
37
+ 'listingId',
38
+ 'coinType',
39
+ 'totalFee',
40
+ 'treasuryAmount',
41
+ 'dappAmount',
42
+ 'updatedAtCheckpoint',
43
+ 'lastUpdateDigest'
44
+ ],
45
+ dubheDappRuntimeState: [
46
+ 'dappKey',
47
+ 'admin',
48
+ 'paused',
49
+ 'settlementMode',
50
+ 'creditPool',
51
+ 'writeFeeShareBps',
52
+ 'lastRuntimeEvent',
53
+ 'lastRuntimeActor',
54
+ 'lastRuntimeAmount',
55
+ 'updatedAtCheckpoint',
56
+ 'lastUpdateDigest'
57
+ ],
58
+ dubheDappFeeState: [
59
+ 'entityId',
60
+ 'baseFeePerWrite',
61
+ 'bytesFeePerByte',
62
+ 'freeCredit',
63
+ 'creditPool',
64
+ 'totalSettled',
65
+ 'updatedAtTimestampMs',
66
+ 'lastUpdateDigest'
67
+ ],
68
+ dubheDappRevenueState: [
69
+ 'entityId',
70
+ 'dappRevenue',
71
+ 'coinType',
72
+ 'updatedAtTimestampMs',
73
+ 'lastUpdateDigest'
74
+ ],
75
+ dubheMarketplaceListings: [
76
+ 'dappKey',
77
+ 'listingId',
78
+ 'seller',
79
+ 'recordType',
80
+ 'price',
81
+ 'coinType',
82
+ 'isFungible',
83
+ 'listedUntil',
84
+ 'status',
85
+ 'updatedAtCheckpoint',
86
+ 'lastUpdateDigest'
87
+ ],
88
+ dubheSessions: [
89
+ 'dappKey',
90
+ 'canonical',
91
+ 'sessionWallet',
92
+ 'expiresAt',
93
+ 'active',
94
+ 'updatedAtCheckpoint',
95
+ 'lastUpdateDigest'
96
+ ],
97
+ dubheUserStorages: [
98
+ 'dappKey',
99
+ 'canonicalOwner',
100
+ 'userStorageId',
101
+ 'updatedAtCheckpoint',
102
+ 'lastUpdateDigest'
103
+ ]
104
+ };
105
+
20
106
  import {
21
107
  DubheClientConfig,
22
108
  Connection,
@@ -36,7 +122,10 @@ import {
36
122
  MarketplaceListingRow,
37
123
  DubheSessionRow,
38
124
  DubheUserStorageRow,
39
- DubheDappRuntimeStateRow
125
+ DubheDappRuntimeStateRow,
126
+ SceneStorageRow,
127
+ SceneStorageFieldRow,
128
+ ObjectStorageRow
40
129
  } from './types';
41
130
 
42
131
  // Convert cache policy type
@@ -57,6 +146,37 @@ function mapCachePolicyToFetchPolicy(cachePolicy: CachePolicy): FetchPolicy {
57
146
  }
58
147
  }
59
148
 
149
+ /**
150
+ * Build the HTTP transport link.
151
+ *
152
+ * When `config.batchRequests` is true, returns a `BatchHttpLink` that collects
153
+ * queries fired within `batchInterval` ms (default 10 ms) and sends them as a
154
+ * single POST request. The PostGraphile server already has `enableQueryBatching`
155
+ * enabled, so no server-side changes are needed.
156
+ *
157
+ * Falls back to the standard `createHttpLink` when batching is disabled.
158
+ */
159
+ function buildHttpLink(config: DubheClientConfig): ApolloLink {
160
+ const fetchFn = (input: RequestInfo | URL, init?: RequestInit) =>
161
+ fetch(input, { ...config.fetchOptions, ...(init ?? {}) });
162
+
163
+ if (config.batchRequests) {
164
+ return new BatchHttpLink({
165
+ uri: config.endpoint,
166
+ headers: config.headers,
167
+ fetch: fetchFn,
168
+ batchInterval: config.batchInterval ?? 10,
169
+ batchMax: config.batchMax ?? 20
170
+ });
171
+ }
172
+
173
+ return createHttpLink({
174
+ uri: config.endpoint,
175
+ headers: config.headers,
176
+ fetch: fetchFn
177
+ });
178
+ }
179
+
60
180
  export class DubheGraphqlClient {
61
181
  private apolloClient: ApolloClient<NormalizedCacheObject>;
62
182
  private subscriptionClient?: any;
@@ -77,12 +197,8 @@ export class DubheGraphqlClient {
77
197
  this.parseTableInfoFromConfig();
78
198
  }
79
199
 
80
- // Create HTTP Link
81
- const httpLink = createHttpLink({
82
- uri: config.endpoint,
83
- headers: config.headers,
84
- fetch: (input, init) => fetch(input, { ...config.fetchOptions, ...init })
85
- });
200
+ // Create HTTP link (batched or standard depending on config.batchRequests)
201
+ const httpLink = buildHttpLink(config);
86
202
 
87
203
  // Create retry link
88
204
  const retryLink = new RetryLink({
@@ -248,12 +364,8 @@ export class DubheGraphqlClient {
248
364
  this.subscriptionClient = undefined;
249
365
  }
250
366
 
251
- // Recreate HTTP Link
252
- const httpLink = createHttpLink({
253
- uri: this.currentConfig.endpoint,
254
- headers: this.currentConfig.headers,
255
- fetch: (input, init) => fetch(input, { ...this.currentConfig.fetchOptions, ...init })
256
- });
367
+ // Recreate HTTP link (batched or standard depending on config)
368
+ const httpLink = buildHttpLink(this.currentConfig);
257
369
 
258
370
  // Recreate retry link with current config
259
371
  const retryLink = new RetryLink({
@@ -1099,6 +1211,228 @@ export class DubheGraphqlClient {
1099
1211
  );
1100
1212
  }
1101
1213
 
1214
+ /**
1215
+ * Query SceneStorage system rows indexed by the Dubhe indexer
1216
+ * (scene_storages table, exposed as dubheSceneStorages in GraphQL).
1217
+ * Field values live in the companion scene_storage_fields table —
1218
+ * see getSceneStorageFields.
1219
+ */
1220
+ async getSceneStorages(options?: {
1221
+ dappKey?: string;
1222
+ sceneType?: string;
1223
+ sceneId?: string;
1224
+ isDestroyed?: boolean;
1225
+ first?: number;
1226
+ after?: string;
1227
+ }): Promise<Connection<SceneStorageRow>> {
1228
+ const filter: Record<string, any> = {};
1229
+ if (options?.dappKey) filter.dappKey = { equalTo: options.dappKey };
1230
+ if (options?.sceneType) filter.sceneType = { equalTo: options.sceneType };
1231
+ if (options?.sceneId) filter.sceneId = { equalTo: options.sceneId };
1232
+ if (options?.isDestroyed !== undefined) filter.isDestroyed = { equalTo: options.isDestroyed };
1233
+
1234
+ const query = gql`
1235
+ query GetSceneStorages(
1236
+ $first: Int
1237
+ $after: Cursor
1238
+ $filter: StoreDubheSceneStorageFilter
1239
+ $orderBy: [StoreDubheSceneStoragesOrderBy!]
1240
+ ) {
1241
+ dubheSceneStorages(first: $first, after: $after, filter: $filter, orderBy: $orderBy) {
1242
+ totalCount
1243
+ pageInfo {
1244
+ hasNextPage
1245
+ hasPreviousPage
1246
+ startCursor
1247
+ endCursor
1248
+ }
1249
+ edges {
1250
+ cursor
1251
+ node {
1252
+ sceneId
1253
+ dappKey
1254
+ sceneType
1255
+ authorizationKind
1256
+ authorizedPermitId
1257
+ isDestroyed
1258
+ createdAtCheckpoint
1259
+ updatedAtCheckpoint
1260
+ lastUpdateDigest
1261
+ lastEventSeq
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+ `;
1267
+
1268
+ const result = await this.apolloClient.query({
1269
+ query,
1270
+ variables: {
1271
+ first: options?.first ?? 100,
1272
+ after: options?.after,
1273
+ filter: Object.keys(filter).length > 0 ? filter : undefined,
1274
+ orderBy: ['UPDATED_AT_CHECKPOINT_DESC']
1275
+ },
1276
+ fetchPolicy: 'network-only'
1277
+ });
1278
+
1279
+ if (result.error) throw result.error;
1280
+ return (
1281
+ (result.data as any)?.dubheSceneStorages ?? {
1282
+ edges: [],
1283
+ pageInfo: { hasNextPage: false, hasPreviousPage: false },
1284
+ totalCount: 0
1285
+ }
1286
+ );
1287
+ }
1288
+
1289
+ /**
1290
+ * Query raw field rows of SceneStorages (scene_storage_fields table,
1291
+ * exposed as dubheSceneStorageFields in GraphQL). Values are hex-encoded
1292
+ * BCS — decode with the decoders exported from this package.
1293
+ */
1294
+ async getSceneStorageFields(options?: {
1295
+ dappKey?: string;
1296
+ sceneIds?: string[];
1297
+ sceneId?: string;
1298
+ fieldName?: string;
1299
+ isDeleted?: boolean;
1300
+ first?: number;
1301
+ after?: string;
1302
+ }): Promise<Connection<SceneStorageFieldRow>> {
1303
+ const filter: Record<string, any> = {};
1304
+ if (options?.dappKey) filter.dappKey = { equalTo: options.dappKey };
1305
+ if (options?.sceneIds && options.sceneIds.length > 0) filter.sceneId = { in: options.sceneIds };
1306
+ if (options?.sceneId) filter.sceneId = { equalTo: options.sceneId };
1307
+ if (options?.fieldName) filter.fieldName = { equalTo: options.fieldName };
1308
+ if (options?.isDeleted !== undefined) filter.isDeleted = { equalTo: options.isDeleted };
1309
+
1310
+ const query = gql`
1311
+ query GetSceneStorageFields(
1312
+ $first: Int
1313
+ $after: Cursor
1314
+ $filter: StoreDubheSceneStorageFieldFilter
1315
+ $orderBy: [StoreDubheSceneStorageFieldsOrderBy!]
1316
+ ) {
1317
+ dubheSceneStorageFields(first: $first, after: $after, filter: $filter, orderBy: $orderBy) {
1318
+ totalCount
1319
+ pageInfo {
1320
+ hasNextPage
1321
+ hasPreviousPage
1322
+ startCursor
1323
+ endCursor
1324
+ }
1325
+ edges {
1326
+ cursor
1327
+ node {
1328
+ sceneId
1329
+ dappKey
1330
+ sceneType
1331
+ fieldName
1332
+ fieldValueRaw
1333
+ isDeleted
1334
+ updatedAtCheckpoint
1335
+ lastUpdateDigest
1336
+ lastEventSeq
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+ `;
1342
+
1343
+ const result = await this.apolloClient.query({
1344
+ query,
1345
+ variables: {
1346
+ first: options?.first ?? 1000,
1347
+ after: options?.after,
1348
+ filter: Object.keys(filter).length > 0 ? filter : undefined,
1349
+ orderBy: ['UPDATED_AT_CHECKPOINT_DESC']
1350
+ },
1351
+ fetchPolicy: 'network-only'
1352
+ });
1353
+
1354
+ if (result.error) throw result.error;
1355
+ return (
1356
+ (result.data as any)?.dubheSceneStorageFields ?? {
1357
+ edges: [],
1358
+ pageInfo: { hasNextPage: false, hasPreviousPage: false },
1359
+ totalCount: 0
1360
+ }
1361
+ );
1362
+ }
1363
+
1364
+ /**
1365
+ * Query ObjectStorage system rows indexed by the Dubhe indexer
1366
+ * (object_storages table, exposed as dubheObjectStorages in GraphQL).
1367
+ */
1368
+ async getObjectStorages(options?: {
1369
+ dappKey?: string;
1370
+ objectType?: string;
1371
+ objectId?: string;
1372
+ isDestroyed?: boolean;
1373
+ first?: number;
1374
+ after?: string;
1375
+ }): Promise<Connection<ObjectStorageRow>> {
1376
+ const filter: Record<string, any> = {};
1377
+ if (options?.dappKey) filter.dappKey = { equalTo: options.dappKey };
1378
+ if (options?.objectType) filter.objectType = { equalTo: options.objectType };
1379
+ if (options?.objectId) filter.objectId = { equalTo: options.objectId };
1380
+ if (options?.isDestroyed !== undefined) filter.isDestroyed = { equalTo: options.isDestroyed };
1381
+
1382
+ const query = gql`
1383
+ query GetObjectStorages(
1384
+ $first: Int
1385
+ $after: Cursor
1386
+ $filter: StoreDubheObjectStorageFilter
1387
+ $orderBy: [StoreDubheObjectStoragesOrderBy!]
1388
+ ) {
1389
+ dubheObjectStorages(first: $first, after: $after, filter: $filter, orderBy: $orderBy) {
1390
+ totalCount
1391
+ pageInfo {
1392
+ hasNextPage
1393
+ hasPreviousPage
1394
+ startCursor
1395
+ endCursor
1396
+ }
1397
+ edges {
1398
+ cursor
1399
+ node {
1400
+ objectId
1401
+ dappKey
1402
+ objectType
1403
+ entityIdRaw
1404
+ isDestroyed
1405
+ createdAtCheckpoint
1406
+ updatedAtCheckpoint
1407
+ lastUpdateDigest
1408
+ lastEventSeq
1409
+ }
1410
+ }
1411
+ }
1412
+ }
1413
+ `;
1414
+
1415
+ const result = await this.apolloClient.query({
1416
+ query,
1417
+ variables: {
1418
+ first: options?.first ?? 100,
1419
+ after: options?.after,
1420
+ filter: Object.keys(filter).length > 0 ? filter : undefined,
1421
+ orderBy: ['UPDATED_AT_CHECKPOINT_DESC']
1422
+ },
1423
+ fetchPolicy: 'network-only'
1424
+ });
1425
+
1426
+ if (result.error) throw result.error;
1427
+ return (
1428
+ (result.data as any)?.dubheObjectStorages ?? {
1429
+ edges: [],
1430
+ pageInfo: { hasNextPage: false, hasPreviousPage: false },
1431
+ totalCount: 0
1432
+ }
1433
+ );
1434
+ }
1435
+
1102
1436
  /**
1103
1437
  * Query DApp runtime state (credit pool, admin, package version, etc.).
1104
1438
  * Exposed as dubheDappRuntimeStates in GraphQL.
@@ -1491,12 +1825,24 @@ export class DubheGraphqlClient {
1491
1825
  if (customFields && customFields.length > 0) {
1492
1826
  fields = customFields;
1493
1827
  } else {
1494
- // Try to get fields from dubhe configuration
1495
- const autoFields = this.getTableFields(tableName);
1496
- if (autoFields.length > 0) {
1497
- fields = autoFields;
1828
+ // 1. Check system table registry first
1829
+ const systemFields = SYSTEM_TABLE_FIELDS[tableName];
1830
+ if (systemFields) {
1831
+ fields = systemFields;
1498
1832
  } else {
1499
- fields = ['createdAtTimestampMs', 'updatedAtTimestampMs', 'isDeleted', 'lastUpdateDigest'];
1833
+ // 2. Try to get fields from dubhe configuration (DApp store tables)
1834
+ const autoFields = this.getTableFields(tableName);
1835
+ if (autoFields.length > 0) {
1836
+ fields = autoFields;
1837
+ } else {
1838
+ // 3. Generic fallback for store_* tables
1839
+ fields = [
1840
+ 'createdAtTimestampMs',
1841
+ 'updatedAtTimestampMs',
1842
+ 'isDeleted',
1843
+ 'lastUpdateDigest'
1844
+ ];
1845
+ }
1500
1846
  }
1501
1847
  }
1502
1848
 
@@ -1504,6 +1850,74 @@ export class DubheGraphqlClient {
1504
1850
 
1505
1851
  return fields.join('\n ');
1506
1852
  }
1853
+
1854
+ // ── Typed system-table query methods ─────────────────────────────────────
1855
+
1856
+ /** Latest DApp fee state snapshot (credit_pool, total_settled, fee rates). */
1857
+ async getDappFeeState(): Promise<{
1858
+ entityId: string;
1859
+ baseFeePerWrite: string;
1860
+ bytesFeePerByte: string;
1861
+ freeCredit: string;
1862
+ creditPool: string;
1863
+ totalSettled: string;
1864
+ updatedAtTimestampMs: string;
1865
+ } | null> {
1866
+ const result = await this.getAllTables<any>('dubheDappFeeState', { first: 1 }).catch(
1867
+ () => null
1868
+ );
1869
+ return result?.edges?.[0]?.node ?? null;
1870
+ }
1871
+
1872
+ /** Latest DApp revenue balance (USER_PAYS mode collected revenue). */
1873
+ async getDappRevenueState(): Promise<{
1874
+ entityId: string;
1875
+ dappRevenue: string;
1876
+ coinType: string;
1877
+ updatedAtTimestampMs: string;
1878
+ } | null> {
1879
+ const result = await this.getAllTables<any>('dubheDappRevenueState', { first: 1 }).catch(
1880
+ () => null
1881
+ );
1882
+ return result?.edges?.[0]?.node ?? null;
1883
+ }
1884
+
1885
+ /** Latest DApp runtime state (admin, settlement mode, last event). */
1886
+ async getDappRuntimeState(): Promise<{
1887
+ dappKey: string;
1888
+ admin: string;
1889
+ paused: boolean;
1890
+ settlementMode: number;
1891
+ creditPool: string;
1892
+ writeFeeShareBps: number;
1893
+ lastRuntimeEvent: string;
1894
+ lastRuntimeActor: string;
1895
+ lastRuntimeAmount: string;
1896
+ } | null> {
1897
+ const result = await this.getAllTables<any>('dubheDappRuntimeState', { first: 1 }).catch(
1898
+ () => null
1899
+ );
1900
+ return result?.edges?.[0]?.node ?? null;
1901
+ }
1902
+
1903
+ /** Marketplace fee records (one row per listing sold). */
1904
+ async getDappMarketplaceFees(options?: { first?: number; after?: string }): Promise<
1905
+ Connection<{
1906
+ dappKey: string;
1907
+ listingId: string;
1908
+ coinType: string;
1909
+ totalFee: string;
1910
+ treasuryAmount: string;
1911
+ dappAmount: string;
1912
+ updatedAtCheckpoint: string;
1913
+ }>
1914
+ > {
1915
+ return this.getAllTables<any>('dubheDappMarketplaceFees', {
1916
+ first: options?.first ?? 20,
1917
+ after: options?.after,
1918
+ orderBy: [{ field: 'updatedAtCheckpoint', direction: 'DESC' }]
1919
+ });
1920
+ }
1507
1921
  }
1508
1922
 
1509
1923
  // Export convenience function
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Minimal BCS decoders for indexer raw values.
3
+ *
4
+ * The Dubhe indexer stores scene/object field values and marketplace record
5
+ * data as hex-encoded BCS bytes (e.g. "0x0c00000000000000" for u64 12).
6
+ * These helpers decode the primitive types commonly stored in system tables.
7
+ */
8
+
9
+ export const ZERO_ADDRESS = '0x' + '0'.repeat(64);
10
+
11
+ export function hexToBytes(hex: string): Uint8Array {
12
+ const clean = hex.replace(/^0x/i, '');
13
+ const pairs = clean.match(/../g) ?? [];
14
+ return Uint8Array.from(pairs.map((b) => parseInt(b, 16)));
15
+ }
16
+
17
+ function readUIntLE(bytes: Uint8Array, offset: number, width: number): bigint {
18
+ let n = 0n;
19
+ for (let i = width - 1; i >= 0; i--) {
20
+ n = (n << 8n) | BigInt(bytes[offset + i] ?? 0);
21
+ }
22
+ return n;
23
+ }
24
+
25
+ /** Read a ULEB128-encoded length prefix. Returns [value, bytesConsumed]. */
26
+ function readUleb(bytes: Uint8Array, offset: number): [number, number] {
27
+ let value = 0;
28
+ let shift = 0;
29
+ let consumed = 0;
30
+ for (;;) {
31
+ const byte = bytes[offset + consumed] ?? 0;
32
+ value |= (byte & 0x7f) << shift;
33
+ consumed++;
34
+ if ((byte & 0x80) === 0) break;
35
+ shift += 7;
36
+ }
37
+ return [value, consumed];
38
+ }
39
+
40
+ export function decodeU8(hex: string): number {
41
+ return Number(readUIntLE(hexToBytes(hex), 0, 1));
42
+ }
43
+
44
+ export function decodeU16(hex: string): number {
45
+ return Number(readUIntLE(hexToBytes(hex), 0, 2));
46
+ }
47
+
48
+ export function decodeU32(hex: string): number {
49
+ return Number(readUIntLE(hexToBytes(hex), 0, 4));
50
+ }
51
+
52
+ export function decodeU64(hex: string): bigint {
53
+ return readUIntLE(hexToBytes(hex), 0, 8);
54
+ }
55
+
56
+ export function decodeU128(hex: string): bigint {
57
+ return readUIntLE(hexToBytes(hex), 0, 16);
58
+ }
59
+
60
+ export function decodeBool(hex: string): boolean {
61
+ return (hexToBytes(hex)[0] ?? 0) !== 0;
62
+ }
63
+
64
+ export function decodeAddress(hex: string): string {
65
+ const bytes = hexToBytes(hex);
66
+ return '0x' + Array.from(bytes.slice(0, 32), (b) => b.toString(16).padStart(2, '0')).join('');
67
+ }
68
+
69
+ export function decodeVectorAddress(hex: string): string[] {
70
+ const bytes = hexToBytes(hex);
71
+ const [len, consumed] = readUleb(bytes, 0);
72
+ const out: string[] = [];
73
+ for (let i = 0; i < len; i++) {
74
+ const start = consumed + i * 32;
75
+ out.push(
76
+ '0x' +
77
+ Array.from(bytes.slice(start, start + 32), (b) => b.toString(16).padStart(2, '0')).join('')
78
+ );
79
+ }
80
+ return out;
81
+ }
82
+
83
+ /** Decode a BCS ascii/utf8 String (ULEB length prefix + bytes). */
84
+ export function decodeString(hex: string): string {
85
+ const bytes = hexToBytes(hex);
86
+ const [len, consumed] = readUleb(bytes, 0);
87
+ return new TextDecoder().decode(bytes.slice(consumed, consumed + len));
88
+ }
89
+
90
+ /**
91
+ * Marketplace recordDataRaw is a JSON array of per-field hex strings
92
+ * (non-key fields, in schema order).
93
+ */
94
+ export function parseRecordData(recordDataRaw: string): string[] {
95
+ try {
96
+ return JSON.parse(recordDataRaw) as string[];
97
+ } catch {
98
+ return [recordDataRaw];
99
+ }
100
+ }
package/src/index.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { DubheGraphqlClient, createDubheGraphqlClient, QueryBuilders } from './client';
2
2
  export * from './types';
3
3
  export type * from './types';
4
+ export * from './decoders';