@interest-protocol/blast-admin-sdk 0.1.0 → 0.2.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/src/reducer.ts CHANGED
@@ -1,15 +1,24 @@
1
- import type { AclEventKind, DecodedAclEvent } from "./events.ts";
1
+ import type { DelayedChange } from "@interest-protocol/blast-core/bcs";
2
+ import { suiEventId } from "@interest-protocol/blast-core/events";
3
+ import type {
4
+ AclEventKind,
5
+ DecodedAclDelayedEvent,
6
+ DecodedAclEvent,
7
+ DecodedPackageAdministrationEvent,
8
+ DecodedUpgradeDelayedEvent,
9
+ } from "./events.ts";
2
10
  import { aclEventBcs, aclEventId } from "./events.ts";
3
- import type { PendingSuperAdmin } from "./state.ts";
11
+ import type { PendingSuperAdmin, UpgradeAction } from "./state.ts";
4
12
 
5
13
  /**
6
14
  * The ACL rebuilt from its events. Its `init` emits nothing, so
7
15
  * `superAdminCapId` stays null until the first accepted transfer; the publish
8
- * effects name the first root. `admins` matches `AclState.admins` once every
16
+ * effects name the first root. `admins` matches `getAcl(...).admins` once every
9
17
  * event is applied.
10
18
  */
11
19
  export type AclProjection = {
12
20
  appliedEventIds: Readonly<Record<string, true>>;
21
+ /** ACL events by kind; super admin transfer lifecycle events are not counted. */
13
22
  eventCounts: Readonly<Record<AclEventKind, number>>;
14
23
  superAdminCapId: string | null;
15
24
  admins: Readonly<Record<string, readonly string[]>>;
@@ -28,13 +37,36 @@ export function createAclProjection(): AclProjection {
28
37
  };
29
38
  }
30
39
 
31
- /** Applies one event once; replaying an applied event returns `state` unchanged. */
40
+ /**
41
+ * Applies one event once; replaying an applied event returns `state` unchanged.
42
+ * The transfer's `delayed` events come from `decodeAclDelayedEvent`.
43
+ */
32
44
  export function reduceAclEvent(
33
45
  state: AclProjection,
34
- event: DecodedAclEvent,
46
+ event: DecodedAclEvent | DecodedAclDelayedEvent,
35
47
  ): AclProjection {
36
48
  const eventId = aclEventId(event);
37
49
  if (state.appliedEventIds[eventId]) return state;
50
+ if (event.source === "delayed") {
51
+ const applied = {
52
+ ...state,
53
+ appliedEventIds: { ...state.appliedEventIds, [eventId]: true as const },
54
+ };
55
+ if (
56
+ event.kind === "ActionScheduled" &&
57
+ event.payload.change.kind === "Action"
58
+ )
59
+ return {
60
+ ...applied,
61
+ pendingSuperAdmin: {
62
+ recipient: event.payload.change.action,
63
+ readyAtMs: event.payload.ready_at_ms,
64
+ },
65
+ };
66
+ if (event.kind === "ActionCancelled" || event.kind === "ActionApplied")
67
+ return { ...applied, pendingSuperAdmin: null };
68
+ return applied;
69
+ }
38
70
  const next: AclProjection = {
39
71
  ...state,
40
72
  appliedEventIds: { ...state.appliedEventIds, [eventId]: true },
@@ -63,16 +95,6 @@ export function reduceAclEvent(
63
95
  roles.filter((held) => held !== role),
64
96
  );
65
97
  }
66
- case "SuperAdminTransferStarted":
67
- return {
68
- ...next,
69
- pendingSuperAdmin: {
70
- recipient: event.payload.recipient,
71
- executeAfterMs: event.payload.execute_after_ms,
72
- },
73
- };
74
- case "SuperAdminTransferCancelled":
75
- return { ...next, pendingSuperAdmin: null };
76
98
  case "SuperAdminTransferred":
77
99
  return {
78
100
  ...next,
@@ -95,3 +117,123 @@ function withRoles(
95
117
  admins: roles === null ? admins : { ...admins, [adminCapId]: roles },
96
118
  };
97
119
  }
120
+
121
+ /** One package's administration, rebuilt from its `blast_admin` events. */
122
+ export type PackageAdministration = {
123
+ packageVersionId: string | null;
124
+ packageAdminId: string | null;
125
+ /** The package witness, defining-ID form, pinned at publication. */
126
+ packageWitness: string | null;
127
+ version: bigint | null;
128
+ validPauseBits: number | null;
129
+ paused: number | null;
130
+ upgradeCapId: string | null;
131
+ upgradeCapVersion: bigint | null;
132
+ policy: number | null;
133
+ /** True once `make_immutable` destroyed the UpgradeCap. */
134
+ immutable: boolean;
135
+ /** The applied delay of upgrade actions; null before `ActionCreated`. */
136
+ upgradeDelayMs: bigint | null;
137
+ /** The scheduled upgrade action or delay change, if any. */
138
+ pendingUpgrade: {
139
+ change: DelayedChange<UpgradeAction>;
140
+ delayMs: bigint;
141
+ readyAtMs: bigint;
142
+ } | null;
143
+ };
144
+
145
+ /** Every package's administration by OTW type, from version, pause, upgrade and upgrade-slot events. */
146
+ export type PackageAdministrationProjection = {
147
+ appliedEventIds: Readonly<Record<string, true>>;
148
+ packages: Readonly<Record<string, PackageAdministration>>;
149
+ };
150
+
151
+ export function createPackageAdministrationProjection(): PackageAdministrationProjection {
152
+ return { appliedEventIds: {}, packages: {} };
153
+ }
154
+
155
+ const EMPTY_ADMINISTRATION: PackageAdministration = {
156
+ packageVersionId: null,
157
+ packageAdminId: null,
158
+ packageWitness: null,
159
+ version: null,
160
+ validPauseBits: null,
161
+ paused: null,
162
+ upgradeCapId: null,
163
+ upgradeCapVersion: null,
164
+ policy: null,
165
+ immutable: false,
166
+ upgradeDelayMs: null,
167
+ pendingUpgrade: null,
168
+ };
169
+
170
+ /** Applies one event once; replaying an applied event returns `state` unchanged. */
171
+ export function reducePackageAdministrationEvent(
172
+ state: PackageAdministrationProjection,
173
+ event: DecodedPackageAdministrationEvent | DecodedUpgradeDelayedEvent,
174
+ ): PackageAdministrationProjection {
175
+ const eventId = suiEventId(event.position);
176
+ if (state.appliedEventIds[eventId]) return state;
177
+ const current = state.packages[event.otw] ?? EMPTY_ADMINISTRATION;
178
+ return {
179
+ appliedEventIds: { ...state.appliedEventIds, [eventId]: true },
180
+ packages: {
181
+ ...state.packages,
182
+ [event.otw]: { ...current, ...administrationChange(event) },
183
+ },
184
+ };
185
+ }
186
+
187
+ function administrationChange(
188
+ event: DecodedPackageAdministrationEvent | DecodedUpgradeDelayedEvent,
189
+ ): Partial<PackageAdministration> {
190
+ if (event.source === "delayed") {
191
+ switch (event.kind) {
192
+ case "ActionCreated":
193
+ return { upgradeDelayMs: event.payload.delay_ms };
194
+ case "ActionScheduled":
195
+ return {
196
+ pendingUpgrade: {
197
+ change: event.payload.change,
198
+ delayMs: event.payload.delay_ms,
199
+ readyAtMs: event.payload.ready_at_ms,
200
+ },
201
+ };
202
+ case "ActionCancelled":
203
+ return { pendingUpgrade: null };
204
+ case "ActionApplied":
205
+ return { pendingUpgrade: null, upgradeDelayMs: event.payload.delay_ms };
206
+ case "ActionDestroyed":
207
+ return {};
208
+ }
209
+ }
210
+ switch (event.kind) {
211
+ case "PackageVersionCreated":
212
+ return {
213
+ packageVersionId: event.payload.package_version_id,
214
+ packageAdminId: event.payload.package_admin_id,
215
+ packageWitness: event.payload.package_witness,
216
+ version: event.payload.version,
217
+ validPauseBits: event.payload.valid_pause_bits,
218
+ paused: event.payload.paused,
219
+ };
220
+ case "PackageAdminCreated":
221
+ return {
222
+ packageAdminId: event.payload.package_admin_id,
223
+ upgradeCapId: event.payload.upgrade_cap_id,
224
+ upgradeCapVersion: event.payload.upgrade_cap_version,
225
+ policy: event.payload.policy,
226
+ };
227
+ case "VersionAdvanced":
228
+ return { version: event.payload.new_version };
229
+ case "Paused":
230
+ case "Unpaused":
231
+ return { paused: event.payload.paused };
232
+ case "UpgradeAuthorized":
233
+ return {};
234
+ case "UpgradeCommitted":
235
+ return { upgradeCapVersion: event.payload.new_upgrade_cap_version };
236
+ case "UpgradeCapDestroyed":
237
+ return { immutable: true, upgradeCapId: null };
238
+ }
239
+ }
package/src/state.ts CHANGED
@@ -1,37 +1,41 @@
1
+ import type { DelayedAction } from "@interest-protocol/blast-core/bcs";
1
2
  import {
2
3
  addressBcs,
4
+ delayedActionBcs,
3
5
  moveTypeNameBcs,
4
6
  suiObjectIdBcs,
5
7
  suiUidBcs,
8
+ u64Bcs,
9
+ unitKeyBcs,
6
10
  } from "@interest-protocol/blast-core/bcs";
7
11
  import { normalizePositiveU64 } from "@interest-protocol/blast-core/integers";
8
12
  import type { SharedObjectIdentity } from "@interest-protocol/blast-core/sui";
9
- import { timelockedBcs } from "@interest-protocol/blast-core/timelock";
10
13
  import { bcs } from "@mysten/sui/bcs";
11
14
  import type { ClientWithCoreApi } from "@mysten/sui/client";
12
- import { normalizeSuiObjectId, parseStructTag } from "@mysten/sui/utils";
15
+ import { ObjectError } from "@mysten/sui/client";
16
+ import {
17
+ normalizeStructTag,
18
+ normalizeSuiObjectId,
19
+ parseStructTag,
20
+ } from "@mysten/sui/utils";
21
+ import type { AdminDeployment } from "./transactions.ts";
13
22
 
14
23
  /** BCS layout of `blast_admin::blast_acl::ACL`. */
15
24
  export const aclBcs = bcs.struct("ACL", {
16
25
  id: suiUidBcs,
17
26
  super_admin_cap_id: suiObjectIdBcs,
18
- admins: bcs.vector(
19
- bcs.struct("Entry", {
20
- key: suiObjectIdBcs,
21
- value: bcs.vector(moveTypeNameBcs),
22
- }),
23
- ),
24
- pending_super_admin: timelockedBcs(bcs.option(addressBcs)),
27
+ admins: bcs.struct("Table", { id: suiUidBcs, size: u64Bcs }),
28
+ super_admin_transfer: delayedActionBcs(unitKeyBcs, addressBcs),
25
29
  });
26
30
 
27
- /** A nominated super admin, who may accept from `executeAfterMs`. */
28
- export type PendingSuperAdmin = { recipient: string; executeAfterMs: bigint };
31
+ /** A nominated super admin, who may accept from `readyAtMs`. */
32
+ export type PendingSuperAdmin = { recipient: string; readyAtMs: bigint };
29
33
 
30
34
  export type AclState = SharedObjectIdentity & {
31
35
  /** The one live `SuperAdminCap`. */
32
36
  superAdminCapId: string;
33
- /** Every live `AdminCap` and the role types it holds. */
34
- admins: Readonly<Record<string, readonly string[]>>;
37
+ adminsTableId: string;
38
+ adminCount: bigint;
35
39
  pendingSuperAdmin: PendingSuperAdmin | null;
36
40
  };
37
41
 
@@ -48,16 +52,20 @@ export function decodeAclObject(
48
52
  if (
49
53
  tag.address !== normalizeSuiObjectId(adminPackageId) ||
50
54
  tag.module !== "blast_acl" ||
51
- tag.name !== "ACL"
55
+ tag.name !== "ACL" ||
56
+ tag.typeParams.length !== 0
52
57
  ) {
53
58
  throw new TypeError(`object ${object.objectId} is not the Blast ACL`);
54
59
  }
55
60
  const parsed = aclBcs.parse(object.content);
56
61
  const objectId = normalizeSuiObjectId(object.objectId);
57
- if (normalizeSuiObjectId(parsed.id.id.bytes) !== objectId) {
62
+ if (
63
+ normalizeSuiObjectId(parsed.id.id.bytes) !== objectId ||
64
+ parsed.super_admin_transfer.parent !== objectId
65
+ ) {
58
66
  throw new TypeError(`ACL ${object.objectId} has a mismatched UID`);
59
67
  }
60
- const nominee = parsed.pending_super_admin.after;
68
+ const proposal = parsed.super_admin_transfer.pending;
61
69
  return {
62
70
  objectId,
63
71
  initialSharedVersion: normalizePositiveU64(
@@ -65,16 +73,12 @@ export function decodeAclObject(
65
73
  object.initialSharedVersion,
66
74
  ),
67
75
  superAdminCapId: parsed.super_admin_cap_id,
68
- admins: Object.fromEntries(
69
- parsed.admins.map(({ key, value }) => [key, value]),
70
- ),
76
+ adminsTableId: normalizeSuiObjectId(parsed.admins.id.id.bytes),
77
+ adminCount: parsed.admins.size,
71
78
  pendingSuperAdmin:
72
- nominee == null
73
- ? null
74
- : {
75
- recipient: nominee,
76
- executeAfterMs: parsed.pending_super_admin.atMs,
77
- },
79
+ proposal?.value.kind === "Action"
80
+ ? { recipient: proposal.value.action, readyAtMs: proposal.ready_at_ms }
81
+ : null,
78
82
  };
79
83
  }
80
84
 
@@ -82,7 +86,7 @@ export async function getAcl(
82
86
  client: ClientWithCoreApi,
83
87
  objectId: string,
84
88
  adminPackageId: string,
85
- ): Promise<AclState> {
89
+ ): Promise<AclState & { admins: Readonly<Record<string, readonly string[]>> }> {
86
90
  const { object } = await client.core.getObject({
87
91
  objectId,
88
92
  include: { content: true },
@@ -90,7 +94,7 @@ export async function getAcl(
90
94
  if (object.owner.$kind !== "Shared") {
91
95
  throw new TypeError(`ACL ${object.objectId} is not a shared object`);
92
96
  }
93
- return decodeAclObject(
97
+ const state = decodeAclObject(
94
98
  {
95
99
  objectId: object.objectId,
96
100
  initialSharedVersion: object.owner.Shared.initialSharedVersion,
@@ -99,4 +103,320 @@ export async function getAcl(
99
103
  },
100
104
  adminPackageId,
101
105
  );
106
+ const admins: Record<string, readonly string[]> = {};
107
+ let cursor: string | null = null;
108
+ const seen = new Set<string>();
109
+ for (let page = 0; page < 100; page++) {
110
+ const response = await client.core.listDynamicFields({
111
+ parentId: state.adminsTableId,
112
+ cursor,
113
+ limit: 50,
114
+ });
115
+ await appendAclAdmins(
116
+ client,
117
+ state.adminsTableId,
118
+ response.dynamicFields,
119
+ admins,
120
+ );
121
+ if (!response.hasNextPage) {
122
+ if (BigInt(Object.keys(admins).length) !== state.adminCount)
123
+ throw new TypeError("ACL changed during enumeration; retry the read");
124
+ return { ...state, admins };
125
+ }
126
+ if (!response.cursor || seen.has(response.cursor))
127
+ throw new TypeError("ACL pagination did not advance");
128
+ seen.add(response.cursor);
129
+ cursor = response.cursor;
130
+ }
131
+ throw new RangeError(
132
+ "ACL enumeration exceeds 100 pages; use getAclAdminRoles for targeted reads",
133
+ );
134
+ }
135
+
136
+ async function appendAclAdmins(
137
+ client: ClientWithCoreApi,
138
+ adminsTableId: string,
139
+ fields: readonly { $kind: string; name: { type: string; bcs: Uint8Array } }[],
140
+ admins: Record<string, readonly string[]>,
141
+ ): Promise<void> {
142
+ for (const field of fields) {
143
+ if (
144
+ field.$kind !== "DynamicField" ||
145
+ normalizeStructTag(field.name.type) !==
146
+ normalizeStructTag("0x2::object::ID")
147
+ )
148
+ throw new TypeError("ACL contains an unexpected dynamic field");
149
+ const id = suiObjectIdBcs.parse(field.name.bcs);
150
+ if (id in admins) throw new TypeError("duplicate ACL administrator");
151
+ const roles = await getAclAdminRoles(client, adminsTableId, id);
152
+ if (roles === null)
153
+ throw new TypeError("ACL changed during enumeration; retry the read");
154
+ admins[id] = roles;
155
+ }
156
+ }
157
+
158
+ /** Reads one Table entry. Only an absent field means the cap has no roles. */
159
+ export async function getAclAdminRoles(
160
+ client: ClientWithCoreApi,
161
+ adminsTableId: string,
162
+ adminCapId: string,
163
+ options: { signal?: AbortSignal } = {},
164
+ ): Promise<readonly string[] | null> {
165
+ try {
166
+ const { dynamicField } = await client.core.getDynamicField({
167
+ ...options,
168
+ parentId: normalizeSuiObjectId(adminsTableId),
169
+ name: aclAdminFieldName(adminCapId),
170
+ });
171
+ return decodeAclAdminRolesField(dynamicField, adminCapId);
172
+ } catch (error) {
173
+ if (error instanceof ObjectError && error.reason === "notFound")
174
+ return null;
175
+ throw error;
176
+ }
177
+ }
178
+
179
+ const asciiBytesBcs = bcs.vector(bcs.u8()).transform({
180
+ input: (value: string) => Array.from(new TextEncoder().encode(value)),
181
+ output: (bytes) => new TextDecoder().decode(Uint8Array.from(bytes)),
182
+ });
183
+
184
+ /** `0x2::package::Publisher`; `package` and `module_name` are ASCII strings. */
185
+ export const publisherBcs = bcs.struct("Publisher", {
186
+ id: suiUidBcs,
187
+ package: asciiBytesBcs,
188
+ module_name: asciiBytesBcs,
189
+ });
190
+
191
+ /** `0x2::package::UpgradeCap`. */
192
+ export const upgradeCapBcs = bcs.struct("UpgradeCap", {
193
+ id: suiUidBcs,
194
+ package: suiObjectIdBcs,
195
+ version: u64Bcs,
196
+ policy: bcs.u8(),
197
+ });
198
+
199
+ /** `blast_admin::package_admin::UpgradeAction`. */
200
+ export const upgradeActionBcs = bcs.enum("UpgradeAction", {
201
+ Upgrade: bcs.vector(bcs.u8()),
202
+ MakeImmutable: null,
203
+ });
204
+ export type UpgradeAction = ReturnType<typeof upgradeActionBcs.parse>;
205
+
206
+ /**
207
+ * `blast_admin::package_version::PackageVersion<OTW>`: one package's
208
+ * version and pause bits. The `OTW` is phantom: only the object type names it.
209
+ */
210
+ export const packageVersionBcs = bcs.struct("PackageVersion", {
211
+ id: suiUidBcs,
212
+ valid_pause_bits: bcs.u8(),
213
+ paused: bcs.u8(),
214
+ package_witness: moveTypeNameBcs,
215
+ version: u64Bcs,
216
+ });
217
+
218
+ /**
219
+ * `blast_admin::package_admin::PackageAdmin<OTW>`: the sealed Publisher and
220
+ * UpgradeCap and the delayed upgrade slot keyed by `UpgradeKey<OTW>`.
221
+ */
222
+ export const packageAdminBcs = bcs.struct("PackageAdmin", {
223
+ id: suiUidBcs,
224
+ publisher: publisherBcs,
225
+ upgrade_cap: bcs.option(upgradeCapBcs),
226
+ upgrade: delayedActionBcs(unitKeyBcs, upgradeActionBcs),
227
+ });
228
+
229
+ /** `blast_admin::package_version::Publication<OTW>`: the deployer's owned proof before `wrap`. */
230
+ export const publicationBcs = bcs.struct("Publication", {
231
+ id: suiUidBcs,
232
+ publisher: publisherBcs,
233
+ valid_pause_bits: bcs.u8(),
234
+ package_witness: moveTypeNameBcs,
235
+ });
236
+
237
+ /** The package an administration object belongs to, by its one-time witness type. */
238
+ export type PackageIdentity = {
239
+ admin: Pick<AdminDeployment, "packageId">;
240
+ /** The package's OTW type, original package ID form. */
241
+ otw: string;
242
+ };
243
+
244
+ export type PackageVersionState = SharedObjectIdentity & {
245
+ otw: string;
246
+ validPauseBits: number;
247
+ paused: number;
248
+ /** The package witness type, defining-ID form, pinned at publication. */
249
+ packageWitness: string;
250
+ version: bigint;
251
+ };
252
+
253
+ export type PackageAdminState = SharedObjectIdentity & {
254
+ otw: string;
255
+ publisherId: string;
256
+ /** Null once the package is immutable. */
257
+ upgradeCap: {
258
+ id: string;
259
+ package: string;
260
+ version: bigint;
261
+ policy: number;
262
+ } | null;
263
+ /** The applied delay of upgrade actions and delay changes. */
264
+ delayMs: bigint;
265
+ /** The proposed upgrade action or delay change, if any. */
266
+ pending: DelayedAction<null, UpgradeAction>["pending"];
267
+ };
268
+
269
+ /** Decodes a `PackageVersion<otw>`; any other package's version is rejected. */
270
+ export function decodePackageVersionObject(
271
+ object: AclObject,
272
+ identity: PackageIdentity,
273
+ ): PackageVersionState {
274
+ const otw = assertPackageObjectType(
275
+ object,
276
+ identity,
277
+ "package_version",
278
+ "PackageVersion",
279
+ );
280
+ const parsed = packageVersionBcs.parse(object.content);
281
+ return {
282
+ ...adminIdentity(object, parsed.id.id.bytes),
283
+ otw,
284
+ validPauseBits: parsed.valid_pause_bits,
285
+ paused: parsed.paused,
286
+ packageWitness: parsed.package_witness,
287
+ version: parsed.version,
288
+ };
289
+ }
290
+
291
+ /** Decodes a `PackageAdmin<otw>`; any other package's administration is rejected. */
292
+ export function decodePackageAdminObject(
293
+ object: AclObject,
294
+ identity: PackageIdentity,
295
+ ): PackageAdminState {
296
+ const otw = assertPackageObjectType(
297
+ object,
298
+ identity,
299
+ "package_admin",
300
+ "PackageAdmin",
301
+ );
302
+ const parsed = packageAdminBcs.parse(object.content);
303
+ if (parsed.upgrade.parent !== normalizeSuiObjectId(parsed.id.id.bytes))
304
+ throw new TypeError("administration object UID mismatch");
305
+ const cap = parsed.upgrade_cap;
306
+ return {
307
+ ...adminIdentity(object, parsed.id.id.bytes),
308
+ otw,
309
+ publisherId: normalizeSuiObjectId(parsed.publisher.id.id.bytes),
310
+ upgradeCap: cap && {
311
+ id: normalizeSuiObjectId(cap.id.id.bytes),
312
+ package: cap.package,
313
+ version: cap.version,
314
+ policy: cap.policy,
315
+ },
316
+ delayMs: parsed.upgrade.delay_ms,
317
+ pending: parsed.upgrade.pending,
318
+ };
319
+ }
320
+
321
+ function assertPackageObjectType(
322
+ object: AclObject,
323
+ identity: PackageIdentity,
324
+ module: string,
325
+ name: string,
326
+ ): string {
327
+ const otw = normalizeStructTag(identity.otw);
328
+ if (
329
+ normalizeStructTag(object.type) !==
330
+ normalizeStructTag(
331
+ `${normalizeSuiObjectId(identity.admin.packageId)}::${module}::${name}<${otw}>`,
332
+ )
333
+ )
334
+ throw new TypeError(`unexpected administration object ${object.objectId}`);
335
+ return otw;
336
+ }
337
+
338
+ function adminIdentity(object: AclObject, id: string): SharedObjectIdentity {
339
+ const objectId = normalizeSuiObjectId(object.objectId);
340
+ if (normalizeSuiObjectId(id) !== objectId)
341
+ throw new TypeError("administration object UID mismatch");
342
+ return {
343
+ objectId,
344
+ initialSharedVersion: normalizePositiveU64(
345
+ "initialSharedVersion",
346
+ object.initialSharedVersion,
347
+ ),
348
+ };
349
+ }
350
+
351
+ export function aclAdminFieldName(adminCapId: string) {
352
+ return {
353
+ type: normalizeStructTag("0x2::object::ID"),
354
+ bcs: suiObjectIdBcs.serialize(adminCapId).toBytes(),
355
+ };
356
+ }
357
+
358
+ /** Decodes the targeted Table read separately from transport/provider accounting. */
359
+ export function decodeAclAdminRolesField(
360
+ dynamicField: {
361
+ name: { type: string; bcs: Uint8Array };
362
+ value: { type: string; bcs: Uint8Array };
363
+ },
364
+ adminCapId: string,
365
+ ): readonly string[] {
366
+ if (
367
+ normalizeStructTag(dynamicField.name.type) !==
368
+ normalizeStructTag("0x2::object::ID") ||
369
+ suiObjectIdBcs.parse(dynamicField.name.bcs) !==
370
+ normalizeSuiObjectId(adminCapId)
371
+ )
372
+ throw new TypeError("ACL role field has an unexpected cap ID");
373
+ if (
374
+ normalizeStructTag(dynamicField.value.type) !==
375
+ normalizeStructTag("0x2::vec_set::VecSet<0x1::type_name::TypeName>")
376
+ ) {
377
+ throw new TypeError("ACL role field has an unexpected value type");
378
+ }
379
+ return bcs.vector(moveTypeNameBcs).parse(dynamicField.value.bcs);
380
+ }
381
+
382
+ export async function getPackageVersion(
383
+ client: ClientWithCoreApi,
384
+ objectId: string,
385
+ identity: PackageIdentity,
386
+ ): Promise<PackageVersionState> {
387
+ return decodePackageVersionObject(
388
+ await readAdministrationObject(client, objectId),
389
+ identity,
390
+ );
391
+ }
392
+
393
+ export async function getPackageAdmin(
394
+ client: ClientWithCoreApi,
395
+ objectId: string,
396
+ identity: PackageIdentity,
397
+ ): Promise<PackageAdminState> {
398
+ return decodePackageAdminObject(
399
+ await readAdministrationObject(client, objectId),
400
+ identity,
401
+ );
402
+ }
403
+
404
+ async function readAdministrationObject(
405
+ client: ClientWithCoreApi,
406
+ objectId: string,
407
+ ): Promise<AclObject> {
408
+ const { object } = await client.core.getObject({
409
+ objectId,
410
+ include: { content: true },
411
+ });
412
+ if (object.owner.$kind !== "Shared")
413
+ throw new TypeError(
414
+ `administration object ${object.objectId} is not shared`,
415
+ );
416
+ return {
417
+ objectId: object.objectId,
418
+ initialSharedVersion: object.owner.Shared.initialSharedVersion,
419
+ type: object.type,
420
+ content: object.content,
421
+ };
102
422
  }