@interest-protocol/blast-admin-sdk 0.0.0-stage → 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/README.md +8 -2
- package/dist/aborts.d.ts +10 -0
- package/dist/aborts.d.ts.map +1 -0
- package/dist/aborts.js +40 -0
- package/dist/aborts.js.map +1 -0
- package/dist/events.d.ts +392 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +365 -0
- package/dist/events.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/reducer.d.ts +55 -0
- package/dist/reducer.d.ts.map +1 -0
- package/dist/reducer.js +161 -0
- package/dist/reducer.js.map +1 -0
- package/dist/state.d.ts +234 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +245 -0
- package/dist/state.js.map +1 -0
- package/dist/transactions.d.ts +146 -0
- package/dist/transactions.d.ts.map +1 -0
- package/dist/transactions.js +292 -0
- package/dist/transactions.js.map +1 -0
- package/package.json +59 -4
- package/src/aborts.ts +43 -0
- package/src/events.ts +650 -0
- package/src/index.ts +5 -0
- package/src/reducer.ts +239 -0
- package/src/state.ts +422 -0
- package/src/transactions.ts +439 -0
package/src/reducer.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
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";
|
|
10
|
+
import { aclEventBcs, aclEventId } from "./events.ts";
|
|
11
|
+
import type { PendingSuperAdmin, UpgradeAction } from "./state.ts";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The ACL rebuilt from its events. Its `init` emits nothing, so
|
|
15
|
+
* `superAdminCapId` stays null until the first accepted transfer; the publish
|
|
16
|
+
* effects name the first root. `admins` matches `getAcl(...).admins` once every
|
|
17
|
+
* event is applied.
|
|
18
|
+
*/
|
|
19
|
+
export type AclProjection = {
|
|
20
|
+
appliedEventIds: Readonly<Record<string, true>>;
|
|
21
|
+
/** ACL events by kind; super admin transfer lifecycle events are not counted. */
|
|
22
|
+
eventCounts: Readonly<Record<AclEventKind, number>>;
|
|
23
|
+
superAdminCapId: string | null;
|
|
24
|
+
admins: Readonly<Record<string, readonly string[]>>;
|
|
25
|
+
pendingSuperAdmin: PendingSuperAdmin | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function createAclProjection(): AclProjection {
|
|
29
|
+
return {
|
|
30
|
+
appliedEventIds: {},
|
|
31
|
+
eventCounts: Object.fromEntries(
|
|
32
|
+
Object.keys(aclEventBcs).map((kind) => [kind, 0]),
|
|
33
|
+
) as Record<AclEventKind, number>,
|
|
34
|
+
superAdminCapId: null,
|
|
35
|
+
admins: {},
|
|
36
|
+
pendingSuperAdmin: null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Applies one event once; replaying an applied event returns `state` unchanged.
|
|
42
|
+
* The transfer's `delayed` events come from `decodeAclDelayedEvent`.
|
|
43
|
+
*/
|
|
44
|
+
export function reduceAclEvent(
|
|
45
|
+
state: AclProjection,
|
|
46
|
+
event: DecodedAclEvent | DecodedAclDelayedEvent,
|
|
47
|
+
): AclProjection {
|
|
48
|
+
const eventId = aclEventId(event);
|
|
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
|
+
}
|
|
70
|
+
const next: AclProjection = {
|
|
71
|
+
...state,
|
|
72
|
+
appliedEventIds: { ...state.appliedEventIds, [eventId]: true },
|
|
73
|
+
eventCounts: {
|
|
74
|
+
...state.eventCounts,
|
|
75
|
+
[event.kind]: state.eventCounts[event.kind] + 1,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
switch (event.kind) {
|
|
79
|
+
case "AdminGranted":
|
|
80
|
+
return withRoles(next, event.payload.admin_cap_id, []);
|
|
81
|
+
case "AdminRevoked":
|
|
82
|
+
case "AdminDestroyed":
|
|
83
|
+
return withRoles(next, event.payload.admin_cap_id, null);
|
|
84
|
+
case "AdminRoleGranted": {
|
|
85
|
+
const { admin_cap_id, role } = event.payload;
|
|
86
|
+
const roles = next.admins[admin_cap_id] ?? [];
|
|
87
|
+
return withRoles(next, admin_cap_id, [...roles, role]);
|
|
88
|
+
}
|
|
89
|
+
case "AdminRoleRevoked": {
|
|
90
|
+
const { admin_cap_id, role } = event.payload;
|
|
91
|
+
const roles = next.admins[admin_cap_id] ?? [];
|
|
92
|
+
return withRoles(
|
|
93
|
+
next,
|
|
94
|
+
admin_cap_id,
|
|
95
|
+
roles.filter((held) => held !== role),
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
case "SuperAdminTransferred":
|
|
99
|
+
return {
|
|
100
|
+
...next,
|
|
101
|
+
superAdminCapId: event.payload.new_super_admin_cap_id,
|
|
102
|
+
pendingSuperAdmin: null,
|
|
103
|
+
};
|
|
104
|
+
case "SuperAdminCapDestroyed":
|
|
105
|
+
return next;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function withRoles(
|
|
110
|
+
state: AclProjection,
|
|
111
|
+
adminCapId: string,
|
|
112
|
+
roles: readonly string[] | null,
|
|
113
|
+
): AclProjection {
|
|
114
|
+
const { [adminCapId]: _, ...admins } = state.admins;
|
|
115
|
+
return {
|
|
116
|
+
...state,
|
|
117
|
+
admins: roles === null ? admins : { ...admins, [adminCapId]: roles },
|
|
118
|
+
};
|
|
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
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import type { DelayedAction } from "@interest-protocol/blast-core/bcs";
|
|
2
|
+
import {
|
|
3
|
+
addressBcs,
|
|
4
|
+
delayedActionBcs,
|
|
5
|
+
moveTypeNameBcs,
|
|
6
|
+
suiObjectIdBcs,
|
|
7
|
+
suiUidBcs,
|
|
8
|
+
u64Bcs,
|
|
9
|
+
unitKeyBcs,
|
|
10
|
+
} from "@interest-protocol/blast-core/bcs";
|
|
11
|
+
import { normalizePositiveU64 } from "@interest-protocol/blast-core/integers";
|
|
12
|
+
import type { SharedObjectIdentity } from "@interest-protocol/blast-core/sui";
|
|
13
|
+
import { bcs } from "@mysten/sui/bcs";
|
|
14
|
+
import type { ClientWithCoreApi } from "@mysten/sui/client";
|
|
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";
|
|
22
|
+
|
|
23
|
+
/** BCS layout of `blast_admin::blast_acl::ACL`. */
|
|
24
|
+
export const aclBcs = bcs.struct("ACL", {
|
|
25
|
+
id: suiUidBcs,
|
|
26
|
+
super_admin_cap_id: suiObjectIdBcs,
|
|
27
|
+
admins: bcs.struct("Table", { id: suiUidBcs, size: u64Bcs }),
|
|
28
|
+
super_admin_transfer: delayedActionBcs(unitKeyBcs, addressBcs),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
/** A nominated super admin, who may accept from `readyAtMs`. */
|
|
32
|
+
export type PendingSuperAdmin = { recipient: string; readyAtMs: bigint };
|
|
33
|
+
|
|
34
|
+
export type AclState = SharedObjectIdentity & {
|
|
35
|
+
/** The one live `SuperAdminCap`. */
|
|
36
|
+
superAdminCapId: string;
|
|
37
|
+
adminsTableId: string;
|
|
38
|
+
adminCount: bigint;
|
|
39
|
+
pendingSuperAdmin: PendingSuperAdmin | null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type AclObject = SharedObjectIdentity & {
|
|
43
|
+
type: string;
|
|
44
|
+
content: Uint8Array;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function decodeAclObject(
|
|
48
|
+
object: AclObject,
|
|
49
|
+
adminPackageId: string,
|
|
50
|
+
): AclState {
|
|
51
|
+
const tag = parseStructTag(object.type);
|
|
52
|
+
if (
|
|
53
|
+
tag.address !== normalizeSuiObjectId(adminPackageId) ||
|
|
54
|
+
tag.module !== "blast_acl" ||
|
|
55
|
+
tag.name !== "ACL" ||
|
|
56
|
+
tag.typeParams.length !== 0
|
|
57
|
+
) {
|
|
58
|
+
throw new TypeError(`object ${object.objectId} is not the Blast ACL`);
|
|
59
|
+
}
|
|
60
|
+
const parsed = aclBcs.parse(object.content);
|
|
61
|
+
const objectId = normalizeSuiObjectId(object.objectId);
|
|
62
|
+
if (
|
|
63
|
+
normalizeSuiObjectId(parsed.id.id.bytes) !== objectId ||
|
|
64
|
+
parsed.super_admin_transfer.parent !== objectId
|
|
65
|
+
) {
|
|
66
|
+
throw new TypeError(`ACL ${object.objectId} has a mismatched UID`);
|
|
67
|
+
}
|
|
68
|
+
const proposal = parsed.super_admin_transfer.pending;
|
|
69
|
+
return {
|
|
70
|
+
objectId,
|
|
71
|
+
initialSharedVersion: normalizePositiveU64(
|
|
72
|
+
"initialSharedVersion",
|
|
73
|
+
object.initialSharedVersion,
|
|
74
|
+
),
|
|
75
|
+
superAdminCapId: parsed.super_admin_cap_id,
|
|
76
|
+
adminsTableId: normalizeSuiObjectId(parsed.admins.id.id.bytes),
|
|
77
|
+
adminCount: parsed.admins.size,
|
|
78
|
+
pendingSuperAdmin:
|
|
79
|
+
proposal?.value.kind === "Action"
|
|
80
|
+
? { recipient: proposal.value.action, readyAtMs: proposal.ready_at_ms }
|
|
81
|
+
: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function getAcl(
|
|
86
|
+
client: ClientWithCoreApi,
|
|
87
|
+
objectId: string,
|
|
88
|
+
adminPackageId: string,
|
|
89
|
+
): Promise<AclState & { admins: Readonly<Record<string, readonly string[]>> }> {
|
|
90
|
+
const { object } = await client.core.getObject({
|
|
91
|
+
objectId,
|
|
92
|
+
include: { content: true },
|
|
93
|
+
});
|
|
94
|
+
if (object.owner.$kind !== "Shared") {
|
|
95
|
+
throw new TypeError(`ACL ${object.objectId} is not a shared object`);
|
|
96
|
+
}
|
|
97
|
+
const state = decodeAclObject(
|
|
98
|
+
{
|
|
99
|
+
objectId: object.objectId,
|
|
100
|
+
initialSharedVersion: object.owner.Shared.initialSharedVersion,
|
|
101
|
+
type: object.type,
|
|
102
|
+
content: object.content,
|
|
103
|
+
},
|
|
104
|
+
adminPackageId,
|
|
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
|
+
};
|
|
422
|
+
}
|