@arcadiasystems/morse-cli 0.8.0 → 0.9.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 +26 -0
- package/README.md +12 -11
- package/dist/index.js +96 -15
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,32 @@ All notable changes to `@arcadiasystems/morse-cli` are documented here. The
|
|
|
4
4
|
format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this
|
|
5
5
|
project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.9.0] - 2026-09-10
|
|
8
|
+
### Fixed (config state, found in a whole-package bug hunt)
|
|
9
|
+
|
|
10
|
+
- **A one-off `--network` permanently rewrote the profile's stored network.** `updateActiveProfile` resolved the network the same way `resolveSettings` does (flag > env > stored > default) and then wrote the result back, even though the caller was only changing the account, publication or collection. So `morse --network testnet use --clear` against a mainnet profile left `"network": "testnet"` on disk, and every later command silently targeted the wrong chain with nothing said. Every active-state mutation was affected: `account import`, `account use`, `use`, `use --clear`, `publication create`, `publication delete`, `collection create`, `collection delete`. An existing profile now keeps its stored network; only a profile being created takes the resolved one. Flags and env vars are per-invocation overrides again, as `CLAUDE.md`'s precedence rule always said.
|
|
11
|
+
- **`config add` on an existing profile destroyed every field it was not given.** It replaced the profile object instead of merging, so `config add default --network testnet` on a profile that had an `rpc`, `uploadRelay`, `account`, `publication` or `collection` silently dropped all of them. The command has always been documented as "create or **update**". It merges now.
|
|
12
|
+
- **`config remove` deleted a profile with no confirmation.** Every other destructive command confirms and honours `--yes`; this one took no `GlobalOptions` at all, so there was not even a flag to skip. It deletes a profile plus its account, publication and collection links and can silently reassign the default, so it now says which profile and, when relevant, what the default becomes.
|
|
13
|
+
- **Collection selection ignored which publication a collection belonged to.** `collection create` selected the new collection as active even when created under an explicit `-P` pointing at some other publication, pairing the active publication with a collection it does not contain. `collection delete` had the mirror bug, clearing a still-valid active collection whenever a same-named one was deleted from an unrelated publication. Collection names are scoped per publication, so both now check the publication too. The existing test asserted the old behaviour and passed vacuously, with no profile written; it has been given a real one and joined by cases for both mismatch directions.
|
|
14
|
+
- `file register --public --recipient` is now refused, matching `file upload`. A public file is readable by anyone, so a recipient list on one is a contradiction rather than a no-op.
|
|
15
|
+
- `--max-tip` help text says it only applies alongside `--upload-relay`.
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- **`morse file list` works again, on every network.** It walked `suix_queryEvents` over JSON-RPC, which public Sui fullnodes have retired: they answer every JSON-RPC method with "Method not found". The command has been failing on testnet and mainnet alike unless the user passed `--indexer-url` at a source that still spoke the old protocol.
|
|
21
|
+
|
|
22
|
+
gRPC is the documented replacement for most Sui reads, but `@mysten/sui`'s gRPC client exposes no event API at all, so it could not be the answer here. Events now come from a Sui GraphQL endpoint, defaulting to the canonical Mysten one for the network. `--indexer-url` still overrides it and now means "a Sui GraphQL endpoint".
|
|
23
|
+
|
|
24
|
+
The querier is hand-rolled over `fetch` against one query with one cursor, rather than pulling in a GraphQL client, keeping the dependency surface as-is. A GraphQL error or a non-2xx response raises a network `CliError` (exit 5) rather than an empty page, because reporting "No files" for a broken endpoint is the worst available answer.
|
|
25
|
+
|
|
26
|
+
Verified live: a file uploaded to testnet appears in `file list` with no `--indexer-url`, `--hydrate` and `--json` both work against it, and deleting the file reconciles it back out of the listing.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `--indexer-url` now expects a Sui GraphQL endpoint rather than a `suix_queryEvents` JSON-RPC source. Anyone relying on the old flag against a JSON-RPC indexer needs a GraphQL one instead; the previous behaviour could not have been working against a public fullnode regardless.
|
|
31
|
+
- Listing on localnet requires `--indexer-url`, since there is no canonical GraphQL endpoint for it. Previously it would have used the local RPC URL and failed.
|
|
32
|
+
|
|
7
33
|
## [0.8.0] - 2026-09-10
|
|
8
34
|
|
|
9
35
|
Usability pass. Nothing is renamed or removed; every existing spelling keeps working.
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ content entries from your terminal, signing with a locally encrypted key.
|
|
|
6
6
|
Content is stored on [Walrus](https://walrus.xyz); private entries are encrypted
|
|
7
7
|
with [Seal](https://github.com/MystenLabs/seal).
|
|
8
8
|
|
|
9
|
-
> Status: v0.
|
|
9
|
+
> Status: v0.9.0. Mainnet and testnet are both supported for public content;
|
|
10
10
|
> the command surface is stable.
|
|
11
11
|
>
|
|
12
12
|
> **Encrypted commands are testnet-only.** `entry add-encrypted`, `entry
|
|
@@ -281,13 +281,13 @@ also returns the raw `sealIdPrefix` and `sealNonce` as hex, usable as `--prefix`
|
|
|
281
281
|
|
|
282
282
|
`file list` reconstructs the file set from contract events. `RecipientFile`
|
|
283
283
|
objects have no on-chain owner index for the shared case, so listing is
|
|
284
|
-
event-derived, not a direct query.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
lag and retention). Summary rows omit `blobId`/`blobObjectId`; add `--hydrate`
|
|
284
|
+
event-derived, not a direct query. The command reads events from a Sui GraphQL
|
|
285
|
+
endpoint, defaulting to the canonical Mysten one for the network. It used to use
|
|
286
|
+
`suix_queryEvents` over JSON-RPC, which public fullnodes have since retired;
|
|
287
|
+
`@mysten/sui`'s gRPC client exposes no event API, so GraphQL is the transport
|
|
288
|
+
that remains. Point `--indexer-url <url>` at a different GraphQL endpoint (a
|
|
289
|
+
self-hosted indexer, a third-party endpoint) to stay in control. Results are
|
|
290
|
+
best-effort and eventually consistent (subject to indexer lag and retention). Summary rows omit `blobId`/`blobObjectId`; add `--hydrate`
|
|
291
291
|
to fetch the full record per file (one read each) when you need them.
|
|
292
292
|
|
|
293
293
|
## Output and scripting
|
|
@@ -348,9 +348,10 @@ to fetch the full record per file (one read each) when you need them.
|
|
|
348
348
|
an operator, use the SDK directly for now.
|
|
349
349
|
- `localnet` is accepted as a network but has no canonical deployment, so it
|
|
350
350
|
fails at startup unless you point the SDK at your own package and registry.
|
|
351
|
-
- `file list`
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
- `file list` reads events from a Sui GraphQL endpoint, defaulting to the
|
|
352
|
+
canonical one for the network. `--indexer-url` points it at a different
|
|
353
|
+
GraphQL endpoint. There is no canonical endpoint for localnet, so listing
|
|
354
|
+
there requires the flag.
|
|
354
355
|
|
|
355
356
|
## Command spellings
|
|
356
357
|
|
package/dist/index.js
CHANGED
|
@@ -148,7 +148,7 @@ import { Command } from "commander";
|
|
|
148
148
|
// package.json
|
|
149
149
|
var package_default = {
|
|
150
150
|
name: "@arcadiasystems/morse-cli",
|
|
151
|
-
version: "0.
|
|
151
|
+
version: "0.9.0",
|
|
152
152
|
description: "Command-line interface for the Morse decentralized CMS on Sui.",
|
|
153
153
|
license: "MIT",
|
|
154
154
|
type: "module",
|
|
@@ -203,7 +203,7 @@ var package_default = {
|
|
|
203
203
|
prepublishOnly: "bun run check && bun run build"
|
|
204
204
|
},
|
|
205
205
|
dependencies: {
|
|
206
|
-
"@arcadiasystems/morse-sdk": "^0.
|
|
206
|
+
"@arcadiasystems/morse-sdk": "^0.8.0",
|
|
207
207
|
"@mysten/seal": "1.1.3",
|
|
208
208
|
"@mysten/sui": "2.16.2",
|
|
209
209
|
"@mysten/walrus": "1.1.6",
|
|
@@ -220,7 +220,7 @@ var package_default = {
|
|
|
220
220
|
var DEFAULT_MAX_TIP_MIST = 1e7;
|
|
221
221
|
function buildProgram() {
|
|
222
222
|
const program = new Command;
|
|
223
|
-
program.name("morse").description("Command-line interface for the Morse decentralized CMS on Sui.").version(package_default.version, "-V, --version", "Print the version and exit").option("--network <network>", "Sui network: mainnet, testnet, or localnet [env: MORSE_NETWORK]").option("-p, --profile <name>", "Config profile to use [env: MORSE_PROFILE]").option("--rpc <url>", "Override the Sui RPC URL [env: MORSE_RPC_URL]").option("--upload-relay <url|auto>", "Upload Walrus blobs through a relay instead of fanning out to every storage node; 'auto' picks the canonical relay for the network. Costs a tip per upload [env: MORSE_WALRUS_UPLOAD_RELAY]").option("--max-tip <mist>", `Cap the per-upload relay tip, in MIST (default: ${DEFAULT_MAX_TIP_MIST}) [env: MORSE_WALRUS_MAX_TIP]`).option("--json", "Output machine-readable JSON on stdout").option("-q, --quiet", "Suppress progress and informational output").option("-y, --yes", "Assume yes for confirmation prompts").option("--debug", "Print stack traces on error").enablePositionalOptions().showHelpAfterError().exitOverride();
|
|
223
|
+
program.name("morse").description("Command-line interface for the Morse decentralized CMS on Sui.").version(package_default.version, "-V, --version", "Print the version and exit").option("--network <network>", "Sui network: mainnet, testnet, or localnet [env: MORSE_NETWORK]").option("-p, --profile <name>", "Config profile to use [env: MORSE_PROFILE]").option("--rpc <url>", "Override the Sui RPC URL [env: MORSE_RPC_URL]").option("--upload-relay <url|auto>", "Upload Walrus blobs through a relay instead of fanning out to every storage node; 'auto' picks the canonical relay for the network. Costs a tip per upload [env: MORSE_WALRUS_UPLOAD_RELAY]").option("--max-tip <mist>", `Cap the per-upload relay tip, in MIST (default: ${DEFAULT_MAX_TIP_MIST}). Only applies with --upload-relay [env: MORSE_WALRUS_MAX_TIP]`).option("--json", "Output machine-readable JSON on stdout").option("-q, --quiet", "Suppress progress and informational output").option("-y, --yes", "Assume yes for confirmation prompts").option("--debug", "Print stack traces on error").enablePositionalOptions().showHelpAfterError().exitOverride();
|
|
224
224
|
return program;
|
|
225
225
|
}
|
|
226
226
|
|
|
@@ -559,7 +559,7 @@ async function updateActiveProfile(opts, patch, env = process.env) {
|
|
|
559
559
|
const config = await loadConfig();
|
|
560
560
|
const profileName = opts.profile ?? env.MORSE_PROFILE ?? config.defaultProfile;
|
|
561
561
|
const existing = config.profiles[profileName];
|
|
562
|
-
const network = coerceNetwork(opts.network ?? env.MORSE_NETWORK ??
|
|
562
|
+
const network = existing?.network ?? coerceNetwork(opts.network ?? env.MORSE_NETWORK ?? "testnet");
|
|
563
563
|
const merged = { ...existing, network, ...patch };
|
|
564
564
|
const profiles = { ...config.profiles, [profileName]: merged };
|
|
565
565
|
const defaultProfile = Object.keys(config.profiles).length === 0 ? profileName : config.defaultProfile;
|
|
@@ -988,7 +988,70 @@ import {
|
|
|
988
988
|
RpcRecipientFilesReader
|
|
989
989
|
} from "@arcadiasystems/morse-sdk";
|
|
990
990
|
import { SuiGrpcClient } from "@mysten/sui/grpc";
|
|
991
|
-
|
|
991
|
+
|
|
992
|
+
// src/cli/graphql-events.ts
|
|
993
|
+
function canonicalGraphqlUrl(network) {
|
|
994
|
+
if (network === "mainnet" || network === "testnet") {
|
|
995
|
+
return `https://graphql.${network}.sui.io/graphql`;
|
|
996
|
+
}
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
var QUERY = `query Events($type: String!, $first: Int!, $after: String) {
|
|
1000
|
+
events(filter: { type: $type }, first: $first, after: $after) {
|
|
1001
|
+
pageInfo { hasNextPage endCursor }
|
|
1002
|
+
nodes { timestamp contents { json type { repr } } }
|
|
1003
|
+
}
|
|
1004
|
+
}`;
|
|
1005
|
+
|
|
1006
|
+
class GraphqlEventQuerier {
|
|
1007
|
+
#url;
|
|
1008
|
+
#fetch;
|
|
1009
|
+
constructor(url, fetchImpl = fetch) {
|
|
1010
|
+
this.#url = url;
|
|
1011
|
+
this.#fetch = fetchImpl;
|
|
1012
|
+
}
|
|
1013
|
+
async queryEvents(params) {
|
|
1014
|
+
const body = JSON.stringify({
|
|
1015
|
+
query: QUERY,
|
|
1016
|
+
variables: {
|
|
1017
|
+
type: params.query.MoveEventType,
|
|
1018
|
+
first: params.limit ?? 50,
|
|
1019
|
+
after: typeof params.cursor === "string" ? params.cursor : null
|
|
1020
|
+
}
|
|
1021
|
+
});
|
|
1022
|
+
let response;
|
|
1023
|
+
try {
|
|
1024
|
+
response = await this.#fetch(this.#url, {
|
|
1025
|
+
method: "POST",
|
|
1026
|
+
headers: { "content-type": "application/json" },
|
|
1027
|
+
body,
|
|
1028
|
+
...params.signal === undefined ? {} : { signal: params.signal }
|
|
1029
|
+
});
|
|
1030
|
+
} catch (cause) {
|
|
1031
|
+
throw new CliError(`Could not reach the event source at ${this.#url}.`, ExitCode.Network, { cause });
|
|
1032
|
+
}
|
|
1033
|
+
if (!response.ok) {
|
|
1034
|
+
throw new CliError(`Event source at ${this.#url} returned HTTP ${response.status}.`, ExitCode.Network);
|
|
1035
|
+
}
|
|
1036
|
+
const payload = await response.json();
|
|
1037
|
+
if (payload.errors && payload.errors.length > 0) {
|
|
1038
|
+
throw new CliError(`Event source rejected the query: ${payload.errors[0]?.message ?? "unknown error"}`, ExitCode.Network);
|
|
1039
|
+
}
|
|
1040
|
+
const events = payload.data?.events;
|
|
1041
|
+
const nodes = events?.nodes ?? [];
|
|
1042
|
+
return {
|
|
1043
|
+
data: nodes.map((node) => ({
|
|
1044
|
+
type: node.contents?.type?.repr ?? "",
|
|
1045
|
+
parsedJson: node.contents?.json ?? {},
|
|
1046
|
+
timestampMs: node.timestamp == null ? null : String(Date.parse(node.timestamp) || 0)
|
|
1047
|
+
})),
|
|
1048
|
+
hasNextPage: Boolean(events?.pageInfo?.hasNextPage),
|
|
1049
|
+
nextCursor: events?.pageInfo?.endCursor ?? null
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/cli/context.ts
|
|
992
1055
|
async function buildReadContext(command) {
|
|
993
1056
|
const opts = globalOptions(command);
|
|
994
1057
|
const output = outputFor(command);
|
|
@@ -1138,16 +1201,16 @@ async function buildFileListContext(command, opts = {}) {
|
|
|
1138
1201
|
if (originPackageId === undefined) {
|
|
1139
1202
|
throw new CliError("File listing is unavailable on this network: no recipientFileEventOriginPackageId in the config. Use testnet, or supply a config that sets it.", ExitCode.Usage);
|
|
1140
1203
|
}
|
|
1141
|
-
const
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
}
|
|
1204
|
+
const indexerUrl = opts.indexerUrl ?? canonicalGraphqlUrl(base.settings.network);
|
|
1205
|
+
if (indexerUrl === undefined) {
|
|
1206
|
+
throw new CliError(`File listing has no canonical event source for ${base.settings.network}. Pass --indexer-url pointing at a Sui GraphQL endpoint.`, ExitCode.Usage);
|
|
1207
|
+
}
|
|
1145
1208
|
return {
|
|
1146
1209
|
...base,
|
|
1147
1210
|
filesReader: RpcRecipientFilesReader.fromConfig(base.client, {
|
|
1148
1211
|
packageId: base.config.packageId
|
|
1149
1212
|
}),
|
|
1150
|
-
events,
|
|
1213
|
+
events: new GraphqlEventQuerier(indexerUrl),
|
|
1151
1214
|
eventTypes: buildRecipientFileEventTypes(originPackageId)
|
|
1152
1215
|
};
|
|
1153
1216
|
}
|
|
@@ -1528,6 +1591,9 @@ async function runCollectionList(ctx, options) {
|
|
|
1528
1591
|
collections: publication.collections
|
|
1529
1592
|
});
|
|
1530
1593
|
}
|
|
1594
|
+
function isActivePublication(ctx, target) {
|
|
1595
|
+
return ctx.settings.publication === target;
|
|
1596
|
+
}
|
|
1531
1597
|
async function runCollectionCreate(ctx, name, options, gopts) {
|
|
1532
1598
|
const id = await resolvePublication(ctx, options.publication);
|
|
1533
1599
|
const storageMode = coerceStorageMode(options.mode);
|
|
@@ -1543,8 +1609,11 @@ async function runCollectionCreate(ctx, name, options, gopts) {
|
|
|
1543
1609
|
storageMode,
|
|
1544
1610
|
signal: ctx.signal
|
|
1545
1611
|
});
|
|
1546
|
-
|
|
1547
|
-
|
|
1612
|
+
const selected = isActivePublication(ctx, id);
|
|
1613
|
+
if (selected) {
|
|
1614
|
+
await updateActiveProfile(gopts, { collection: name });
|
|
1615
|
+
}
|
|
1616
|
+
ctx.output.result(`Created collection "${name}".${selected ? " Selected as the active collection." : ""} (tx: ${result.digest})`, result);
|
|
1548
1617
|
}
|
|
1549
1618
|
async function runCollectionDelete(ctx, name, options, gopts) {
|
|
1550
1619
|
const id = await resolvePublication(ctx, options.publication);
|
|
@@ -1566,7 +1635,7 @@ async function runCollectionDelete(ctx, name, options, gopts) {
|
|
|
1566
1635
|
name,
|
|
1567
1636
|
signal: ctx.signal
|
|
1568
1637
|
});
|
|
1569
|
-
if (ctx.settings.collection === name) {
|
|
1638
|
+
if (ctx.settings.collection === name && isActivePublication(ctx, id)) {
|
|
1570
1639
|
await updateActiveProfile(gopts, { collection: undefined });
|
|
1571
1640
|
}
|
|
1572
1641
|
ctx.output.result(`Deleted collection "${name}". (tx: ${result.digest})`, result);
|
|
@@ -1606,6 +1675,7 @@ async function runConfigAdd(output, name, options) {
|
|
|
1606
1675
|
const profiles = {
|
|
1607
1676
|
...cfg.profiles,
|
|
1608
1677
|
[name]: {
|
|
1678
|
+
...cfg.profiles[name],
|
|
1609
1679
|
network,
|
|
1610
1680
|
...options.rpc === undefined ? {} : { rpc: options.rpc },
|
|
1611
1681
|
...options.uploadRelay === undefined ? {} : { uploadRelay: options.uploadRelay }
|
|
@@ -1627,11 +1697,19 @@ async function runConfigUse(output, name) {
|
|
|
1627
1697
|
await saveConfig({ ...cfg, defaultProfile: name });
|
|
1628
1698
|
output.result(`Default profile set to "${name}".`, { defaultProfile: name });
|
|
1629
1699
|
}
|
|
1630
|
-
async function runConfigRemove(output, name) {
|
|
1700
|
+
async function runConfigRemove(output, name, gopts = {}, signal) {
|
|
1631
1701
|
const cfg = await loadConfig();
|
|
1632
1702
|
requireProfile(cfg, name);
|
|
1633
1703
|
const { [name]: _removed, ...rest } = cfg.profiles;
|
|
1634
1704
|
const defaultProfile = cfg.defaultProfile === name ? Object.keys(rest)[0] ?? "default" : cfg.defaultProfile;
|
|
1705
|
+
const reassigns = cfg.defaultProfile === name && defaultProfile !== name;
|
|
1706
|
+
const proceed = await confirm(`Delete profile "${name}"?${reassigns ? ` The default becomes "${defaultProfile}".` : ""}`, {
|
|
1707
|
+
assumeYes: Boolean(gopts.yes),
|
|
1708
|
+
...signal === undefined ? {} : { signal }
|
|
1709
|
+
});
|
|
1710
|
+
if (!proceed) {
|
|
1711
|
+
cancelled();
|
|
1712
|
+
}
|
|
1635
1713
|
await saveConfig({ ...cfg, profiles: rest, defaultProfile });
|
|
1636
1714
|
output.result(`Removed profile "${name}".`, {
|
|
1637
1715
|
removed: name,
|
|
@@ -1654,7 +1732,7 @@ function registerConfigCommands(program) {
|
|
|
1654
1732
|
await runConfigUse(outputFor(command), name);
|
|
1655
1733
|
});
|
|
1656
1734
|
config.command("remove <name>").alias("delete").description("Delete a profile").action(async (name, _options, command) => {
|
|
1657
|
-
await runConfigRemove(outputFor(command), name);
|
|
1735
|
+
await runConfigRemove(outputFor(command), name, globalOptions(command), sigintSignal());
|
|
1658
1736
|
});
|
|
1659
1737
|
}
|
|
1660
1738
|
function requireProfile(config, name) {
|
|
@@ -2129,6 +2207,9 @@ async function runFileRegister(ctx, options) {
|
|
|
2129
2207
|
throw new UsageError("Pass --public or --encrypted, not both.");
|
|
2130
2208
|
}
|
|
2131
2209
|
const recipients = (options.recipient ?? []).map((r) => toSuiAddress4(r));
|
|
2210
|
+
if (options.public && recipients.length > 0) {
|
|
2211
|
+
throw new UsageError("--recipient applies to encrypted files; a public file is readable by anyone.");
|
|
2212
|
+
}
|
|
2132
2213
|
const blobId = toWalrusBlobId(options.blobId);
|
|
2133
2214
|
const size = parseByteSize(options.size, "--size");
|
|
2134
2215
|
const blobObjectId = options.blobObjectId === undefined ? undefined : toBlobObjectId(options.blobObjectId);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcadiasystems/morse-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Command-line interface for the Morse decentralized CMS on Sui.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"prepublishOnly": "bun run check && bun run build"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@arcadiasystems/morse-sdk": "^0.
|
|
58
|
+
"@arcadiasystems/morse-sdk": "^0.8.0",
|
|
59
59
|
"@mysten/seal": "1.1.3",
|
|
60
60
|
"@mysten/sui": "2.16.2",
|
|
61
61
|
"@mysten/walrus": "1.1.6",
|