@needmoretruth/nmts-cli 0.38.1 → 0.40.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/AGENTS.md +14 -35
- package/CHANGELOG.md +16 -0
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/dist/artifact-about.d.ts +1 -1
- package/dist/commands/erase.js +12 -107
- package/dist/commands/extend.d.ts +3 -3
- package/dist/commands/extend.js +52 -161
- package/dist/commands/wallet-storage-ops.d.ts +2 -2
- package/dist/commands/wallet-storage-ops.js +108 -134
- package/dist/commands/wallet-storage.d.ts +3 -8
- package/dist/commands/wallet-storage.js +16 -37
- package/dist/drive-erase.d.ts +83 -0
- package/dist/drive-erase.js +161 -0
- package/dist/platform-sign.d.ts +9 -1
- package/dist/platform-sign.js +9 -1
- package/dist/product.d.ts +1 -1
- package/dist/product.js +1 -1
- package/dist/storage-control/extend.d.ts +124 -0
- package/dist/storage-control/extend.js +189 -0
- package/dist/storage-control/hints.d.ts +20 -0
- package/dist/storage-control/hints.js +11 -0
- package/dist/storage-control/list.d.ts +55 -0
- package/dist/storage-control/list.js +71 -0
- package/dist/storage-control/reshape.d.ts +120 -0
- package/dist/storage-control/reshape.js +188 -0
- package/dist/storage-control-chain.d.ts +1 -1
- package/dist/storage-control.d.ts +13 -0
- package/dist/storage-control.js +24 -0
- package/dist/wallet-storage-chain.d.ts +1 -1
- package/package.json +9 -1
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// What a wallet holds in storage — the free resources, ordered and judged, without a screen.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ EVERYTHING HERE IS A READ. What the storage network sells is size and time, not a file;
|
|
4
|
+
// deleting a file from the network returns the remaining time to the wallet that bought it, and
|
|
5
|
+
// that is what this lists. Splitting, joining and handing one over are next door in
|
|
6
|
+
// `reshape.ts`, because each of them signs.
|
|
7
|
+
//
|
|
8
|
+
// ⛔ "NONE" AND "COULD NOT READ" ARE DIFFERENT ANSWERS, and this file is where that is kept: the
|
|
9
|
+
// reader throws rather than answering an empty list, and `readOrRefuse` turns that into one
|
|
10
|
+
// refusal with the chain's own cause in it. Flattening the two would draw a wallet full of
|
|
11
|
+
// storage as a wallet with none, on the screen somebody decides what to buy from.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ AND THE JUDGEMENT IS THE BROWSER'S, not a second copy of it: `shared/lib/storage-control/plan.ts`
|
|
14
|
+
// is copied byte for byte from the browser and says what "usable" means and in what order these
|
|
15
|
+
// are shown. Nothing here reaches for `node:`.
|
|
16
|
+
import { NmtsError } from "../errors.js";
|
|
17
|
+
import { statusOf, totalUsableBytes, usableFirst } from "../shared/lib/storage-control/plan.js";
|
|
18
|
+
/** Every free storage resource one address holds, in the order a person reads them. */
|
|
19
|
+
export async function listStorage(input, read = defaultRead, hints = {}) {
|
|
20
|
+
const got = await readOrRefuse(() => read(input.network, input.address), hints);
|
|
21
|
+
const epoch = got.currentEpoch;
|
|
22
|
+
const ordered = epoch === null ? [...got.items] : usableFirst(got.items, epoch);
|
|
23
|
+
return {
|
|
24
|
+
address: input.address,
|
|
25
|
+
network: input.network,
|
|
26
|
+
currentEpoch: epoch,
|
|
27
|
+
items: ordered.map((r) => ({ ...r, status: epoch === null ? null : statusOf(r, epoch) })),
|
|
28
|
+
usableBytes: epoch === null ? null : totalUsableBytes(ordered, epoch),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The one refusal for a chain that did not answer.
|
|
33
|
+
*
|
|
34
|
+
* ⛔ IT IS NOT AN EMPTY LIST. Holding no resource is normal; failing to read is a reason to look
|
|
35
|
+
* again, and the chain's own words are carried so that whoever reads it knows which it was.
|
|
36
|
+
*/
|
|
37
|
+
export async function readOrRefuse(read, hints = {}) {
|
|
38
|
+
try {
|
|
39
|
+
return await read();
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const said = hints.cannotRead ?? "That is not the same as holding none, and nothing was signed.";
|
|
43
|
+
throw new NmtsError("The storage resources could not be read from the chain.", {
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
nextStep: `${said} Cause: ${error instanceof Error ? error.message : String(error)}`,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** The real read. Imported only when no seam was supplied — it loads the storage network's client. */
|
|
50
|
+
async function defaultRead(network, address) {
|
|
51
|
+
return (await import("../wallet-storage-chain.js")).readWalletStorage(network, address);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Bytes as a person reads them: binary units, two decimals, whole bytes below a KiB.
|
|
55
|
+
*
|
|
56
|
+
* ⚠ HERE RATHER THAN BESIDE THE SCREEN THAT PRINTS THEM, because the refusals in `reshape.ts` name
|
|
57
|
+
* a resource's size too, and a refusal that said `4294967296` about a resource a listing calls
|
|
58
|
+
* `4.00 GiB` would be two spellings of one number in front of the same person.
|
|
59
|
+
*/
|
|
60
|
+
export function formatBytes(bytes) {
|
|
61
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
62
|
+
let value = bytes;
|
|
63
|
+
let unit = "B";
|
|
64
|
+
for (const next of units) {
|
|
65
|
+
if (value < 1024)
|
|
66
|
+
break;
|
|
67
|
+
value /= 1024;
|
|
68
|
+
unit = next;
|
|
69
|
+
}
|
|
70
|
+
return unit === "B" ? `${bytes} B` : `${value.toFixed(2)} ${unit}`;
|
|
71
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import type { Network } from "../network.ts";
|
|
2
|
+
import type { StorageResource } from "../shared/lib/storage-control/chain.ts";
|
|
3
|
+
import type { StorageOpShape, StorageOpsReads } from "../storage-control-chain.ts";
|
|
4
|
+
import type { WalletAction } from "../wallet-grant.ts";
|
|
5
|
+
import type { SignStorageOp } from "../wallet-sign-seams.ts";
|
|
6
|
+
import type { StorageHints } from "./hints.ts";
|
|
7
|
+
/** The three things that can be done to a resource the wallet holds free. */
|
|
8
|
+
export type StorageOp = "split" | "merge" | "transfer";
|
|
9
|
+
/**
|
|
10
|
+
* One request, in the values it is made of rather than in what anybody typed.
|
|
11
|
+
*
|
|
12
|
+
* ⚠ `keepEpochs` IS A COUNT AND THE CHAIN TAKES AN EPOCH. The resource keeps its first
|
|
13
|
+
* `keepEpochs` epochs; which absolute epoch that is depends on where it starts, which is a fact
|
|
14
|
+
* read from the chain below rather than one a caller should have to work out.
|
|
15
|
+
*/
|
|
16
|
+
export type StorageOpAsk = {
|
|
17
|
+
kind: "splitSize";
|
|
18
|
+
objectId: string;
|
|
19
|
+
keepBytes: number;
|
|
20
|
+
} | {
|
|
21
|
+
kind: "splitEpochs";
|
|
22
|
+
objectId: string;
|
|
23
|
+
keepEpochs: number;
|
|
24
|
+
} | {
|
|
25
|
+
kind: "merge";
|
|
26
|
+
first: string;
|
|
27
|
+
second: string;
|
|
28
|
+
} | {
|
|
29
|
+
kind: "transfer";
|
|
30
|
+
objectId: string;
|
|
31
|
+
to: string;
|
|
32
|
+
};
|
|
33
|
+
/** A resource as the chain would hold it. A resource that does not exist yet has no id. */
|
|
34
|
+
export interface StorageShape {
|
|
35
|
+
sizeBytes: number;
|
|
36
|
+
startEpoch: number;
|
|
37
|
+
endEpoch: number;
|
|
38
|
+
}
|
|
39
|
+
/** What one change leaves behind — the numbers, without a word of prose about them. */
|
|
40
|
+
export type StorageOpResult = {
|
|
41
|
+
kind: "split";
|
|
42
|
+
keeps: StorageShape;
|
|
43
|
+
creates: StorageShape;
|
|
44
|
+
} | {
|
|
45
|
+
kind: "merge";
|
|
46
|
+
becomes: StorageShape;
|
|
47
|
+
consumed: string;
|
|
48
|
+
how: "amount" | "periods";
|
|
49
|
+
} | {
|
|
50
|
+
kind: "transfer";
|
|
51
|
+
moves: StorageShape;
|
|
52
|
+
to: string;
|
|
53
|
+
};
|
|
54
|
+
/** One request, judged: what the chain is asked for, what it needs, and what it would leave. */
|
|
55
|
+
export interface StorageOpPlan {
|
|
56
|
+
op: StorageOp;
|
|
57
|
+
/** The transaction shape — what is priced, and what is signed. One builder for both. */
|
|
58
|
+
shape: StorageOpShape;
|
|
59
|
+
/** The wallet unlock this needs: `reshape` for cutting and joining, `give` for handing over. */
|
|
60
|
+
action: WalletAction;
|
|
61
|
+
/** The resources it is about, as the chain has them. */
|
|
62
|
+
named: readonly StorageResource[];
|
|
63
|
+
result: StorageOpResult;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Judge one request against the resources this wallet actually holds.
|
|
67
|
+
*
|
|
68
|
+
* ⛔ EVERY REFUSAL HAPPENS HERE, BEFORE THE CHAIN IS ASKED ANYTHING. Letting the contract refuse it
|
|
69
|
+
* instead leaves somebody having paid gas with no idea why — the rule the browser's own
|
|
70
|
+
* judgement (`shared/lib/storage-control/plan.ts`) was written for.
|
|
71
|
+
*/
|
|
72
|
+
export declare function planStorageOp(held: readonly StorageResource[], ask: StorageOpAsk, hints?: StorageHints): StorageOpPlan;
|
|
73
|
+
/** What one change would do, priced, before anything is signed. */
|
|
74
|
+
export interface StorageOpReview {
|
|
75
|
+
plan: StorageOpPlan;
|
|
76
|
+
/** The wallet whose resources these are — the one that would sign. */
|
|
77
|
+
address: string;
|
|
78
|
+
network: Network;
|
|
79
|
+
currentEpoch: number | null;
|
|
80
|
+
/**
|
|
81
|
+
* The chain fee the dry run measured, in MIST — or null when it could not be measured.
|
|
82
|
+
* ⛔ NEVER ZERO FOR "UNKNOWN": the fee is charged with the signature either way.
|
|
83
|
+
*/
|
|
84
|
+
feeMist: bigint | null;
|
|
85
|
+
}
|
|
86
|
+
/** Stopped at the review, or done and signed. */
|
|
87
|
+
export type ReshapeOutcome = {
|
|
88
|
+
kind: "review";
|
|
89
|
+
review: StorageOpReview;
|
|
90
|
+
} | {
|
|
91
|
+
kind: "done";
|
|
92
|
+
review: StorageOpReview;
|
|
93
|
+
digest: string;
|
|
94
|
+
};
|
|
95
|
+
/** Whose resources these are: the wallet that holds them, and the key that derives it. */
|
|
96
|
+
export interface ReshapeContext {
|
|
97
|
+
network: Network;
|
|
98
|
+
/** ⛔ The NMTS key. It never leaves this process: it derives the wallet and nothing else. */
|
|
99
|
+
code: string;
|
|
100
|
+
/** Which of this key's wallets holds the resources. */
|
|
101
|
+
wallet: number;
|
|
102
|
+
/** That wallet's address — read, priced and signed with as one. */
|
|
103
|
+
address: string;
|
|
104
|
+
}
|
|
105
|
+
/** The chain, the signature, and the two places a caller gets a word in. ⚠ Seams, not options. */
|
|
106
|
+
export interface ReshapeSeams {
|
|
107
|
+
reads?: ((network: Network) => StorageOpsReads) | undefined;
|
|
108
|
+
/** ⛔ SEPARATE FROM THE READS so a test can prove the review stops before this. */
|
|
109
|
+
sign?: SignStorageOp | undefined;
|
|
110
|
+
/** Told once the change is priced, and before anything is signed. */
|
|
111
|
+
onReview?: ((review: StorageOpReview) => void) | undefined;
|
|
112
|
+
/** ⛔ THE GATE. Throwing here stops the run with nothing signed. */
|
|
113
|
+
agree?: ((review: StorageOpReview) => void) | undefined;
|
|
114
|
+
/** Stop at the review. Nothing is signed and no signer is even loaded. */
|
|
115
|
+
dryRun?: boolean | undefined;
|
|
116
|
+
/** What this caller wants said in a refusal instead of the neutral sentence. */
|
|
117
|
+
hints?: StorageHints | undefined;
|
|
118
|
+
}
|
|
119
|
+
/** Read, judge, price and — unless somebody stops it — sign one change to a storage resource. */
|
|
120
|
+
export declare function reshapeStorage(context: ReshapeContext, ask: StorageOpAsk, seams?: ReshapeSeams): Promise<ReshapeOutcome>;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// Cutting a storage resource, joining two of them, and handing one over — decided, priced, signed
|
|
2
|
+
// and handed back rather than printed.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ ONE IMPLEMENTATION, TWO CALLERS, which is the whole reason this file exists. `nmts wallet
|
|
5
|
+
// storage split|merge|transfer` is a terminal's shape: it prints a review, asks for `--yes` and
|
|
6
|
+
// answers an exit code. The SDK is somebody else's program and needs the same acts with none of
|
|
7
|
+
// those. A second implementation of "what may be joined with what" would be a second place for
|
|
8
|
+
// the contract's rules to be got right, and the copy nobody re-reads is the one that quietly
|
|
9
|
+
// disagrees — about transactions that spend a fee and cannot be undone.
|
|
10
|
+
//
|
|
11
|
+
// ⛔ THE ORDER IS THE SAFETY, and it is here rather than in either caller: ① the resources are READ
|
|
12
|
+
// from the chain and the named ones must be this wallet's ② the browser's rules judge the request
|
|
13
|
+
// and say why not ③ the exact transaction is dry-run for its fee, and a refusal there ends the
|
|
14
|
+
// run ④ the review is handed out ⑤ `dryRun` stops here ⑥ `agree` is the gate — whatever it throws
|
|
15
|
+
// stops the run with nothing signed ⑦ only then the signature.
|
|
16
|
+
//
|
|
17
|
+
// ⛔ HANDING ONE OVER MOVES NO FILE. What goes is size and remaining time; a file's bytes are sealed
|
|
18
|
+
// with keys the NMTS key derives and stay unreadable to whoever receives the resource. It cannot
|
|
19
|
+
// be undone and NMTS cannot recall it.
|
|
20
|
+
//
|
|
21
|
+
// ⚠ NOTHING HERE WRITES TO A STREAM, ASKS ANYBODY ANYTHING OR PICKS AN EXIT CODE, and nothing
|
|
22
|
+
// reaches for `node:` — the chain and the signature are loaded only when no seam was supplied.
|
|
23
|
+
import { NmtsError } from "../errors.js";
|
|
24
|
+
import { canFuse } from "../shared/lib/storage-control/plan.js";
|
|
25
|
+
import { isValidSuiAddress } from "../shared/lib/wallet/send-rules.js";
|
|
26
|
+
import { formatBytes, readOrRefuse } from "./list.js";
|
|
27
|
+
/** Why two resources cannot be joined, in the contract's own terms. */
|
|
28
|
+
const FUSE_WHY = {
|
|
29
|
+
same: "those are the same resource.",
|
|
30
|
+
differentSize: "their periods differ and so do their sizes — the contract joins sizes only over an identical period, and periods only at an identical size.",
|
|
31
|
+
differentPeriod: "their periods differ.",
|
|
32
|
+
notAdjacent: "their sizes are equal but their periods do not touch — one has to end where the other starts.",
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Judge one request against the resources this wallet actually holds.
|
|
36
|
+
*
|
|
37
|
+
* ⛔ EVERY REFUSAL HAPPENS HERE, BEFORE THE CHAIN IS ASKED ANYTHING. Letting the contract refuse it
|
|
38
|
+
* instead leaves somebody having paid gas with no idea why — the rule the browser's own
|
|
39
|
+
* judgement (`shared/lib/storage-control/plan.ts`) was written for.
|
|
40
|
+
*/
|
|
41
|
+
export function planStorageOp(held, ask, hints = {}) {
|
|
42
|
+
const one = (id) => {
|
|
43
|
+
const found = held.find((r) => r.objectId === id);
|
|
44
|
+
if (found === undefined) {
|
|
45
|
+
throw new NmtsError(`This wallet holds no free storage resource ${id}.`, {
|
|
46
|
+
exitCode: 4,
|
|
47
|
+
nextStep: hints.notHeld ??
|
|
48
|
+
"Nothing was signed. A storage listing shows the free ones it holds; a resource bound " +
|
|
49
|
+
"inside a file is not free.",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return found;
|
|
53
|
+
};
|
|
54
|
+
// ⛔ THE JUDGEMENT IS HERE AND THE WORD FOR IT IS THE CALLER'S. What was too big has a different
|
|
55
|
+
// name at a prompt and in a program, and neither should be told about the other's.
|
|
56
|
+
const tooBig = (what, limit) => hints.cut?.(what, limit) ??
|
|
57
|
+
new NmtsError(what === "size"
|
|
58
|
+
? `A cut by size keeps above 0 and below the resource's ${limit}.`
|
|
59
|
+
: `A cut by period keeps a whole number of epochs above 0 and below the resource's ${limit}.`, { exitCode: 2, nextStep: "Nothing was signed. The rest becomes a second resource, so there has to be a rest." });
|
|
60
|
+
const shapeOf = (r) => ({
|
|
61
|
+
sizeBytes: r.sizeBytes,
|
|
62
|
+
startEpoch: r.startEpoch,
|
|
63
|
+
endEpoch: r.endEpoch,
|
|
64
|
+
});
|
|
65
|
+
if (ask.kind === "splitSize") {
|
|
66
|
+
const r = one(ask.objectId);
|
|
67
|
+
if (ask.keepBytes <= 0 || ask.keepBytes >= r.sizeBytes)
|
|
68
|
+
throw tooBig("size", formatBytes(r.sizeBytes));
|
|
69
|
+
return {
|
|
70
|
+
op: "split",
|
|
71
|
+
shape: { kind: "splitSize", objectId: ask.objectId, keepBytes: ask.keepBytes },
|
|
72
|
+
action: "reshape",
|
|
73
|
+
named: [r],
|
|
74
|
+
result: {
|
|
75
|
+
kind: "split",
|
|
76
|
+
keeps: { ...shapeOf(r), sizeBytes: ask.keepBytes },
|
|
77
|
+
creates: { ...shapeOf(r), sizeBytes: r.sizeBytes - ask.keepBytes },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (ask.kind === "splitEpochs") {
|
|
82
|
+
const r = one(ask.objectId);
|
|
83
|
+
const span = r.endEpoch - r.startEpoch;
|
|
84
|
+
if (!Number.isSafeInteger(ask.keepEpochs) || ask.keepEpochs <= 0 || ask.keepEpochs >= span) {
|
|
85
|
+
throw tooBig("period", String(span));
|
|
86
|
+
}
|
|
87
|
+
const at = r.startEpoch + ask.keepEpochs;
|
|
88
|
+
return {
|
|
89
|
+
op: "split",
|
|
90
|
+
shape: { kind: "splitEpoch", objectId: ask.objectId, splitEpoch: at },
|
|
91
|
+
action: "reshape",
|
|
92
|
+
named: [r],
|
|
93
|
+
result: {
|
|
94
|
+
kind: "split",
|
|
95
|
+
keeps: { ...shapeOf(r), endEpoch: at },
|
|
96
|
+
creates: { ...shapeOf(r), startEpoch: at },
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (ask.kind === "merge") {
|
|
101
|
+
const first = one(ask.first);
|
|
102
|
+
const second = one(ask.second);
|
|
103
|
+
const verdict = canFuse(first, second);
|
|
104
|
+
if (!verdict.can) {
|
|
105
|
+
throw new NmtsError(`These two cannot be joined: ${FUSE_WHY[verdict.why]}`, {
|
|
106
|
+
exitCode: 4,
|
|
107
|
+
nextStep: "Nothing was signed. A storage listing shows each one's size and period.",
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
op: "merge",
|
|
112
|
+
shape: { kind: "fuse", first: ask.first, second: ask.second, how: verdict.kind },
|
|
113
|
+
action: "reshape",
|
|
114
|
+
named: [first, second],
|
|
115
|
+
result: {
|
|
116
|
+
kind: "merge",
|
|
117
|
+
how: verdict.kind,
|
|
118
|
+
consumed: ask.second,
|
|
119
|
+
becomes: verdict.kind === "amount"
|
|
120
|
+
? { ...shapeOf(first), sizeBytes: first.sizeBytes + second.sizeBytes }
|
|
121
|
+
: {
|
|
122
|
+
sizeBytes: first.sizeBytes,
|
|
123
|
+
startEpoch: Math.min(first.startEpoch, second.startEpoch),
|
|
124
|
+
endEpoch: Math.max(first.endEpoch, second.endEpoch),
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const r = one(ask.objectId);
|
|
130
|
+
// ⛔ THE ADDRESS IS JUDGED BEFORE THE CHAIN IS ASKED. A resource sent to a mistyped address is
|
|
131
|
+
// gone: nothing here, and nobody at NMTS, can recall it.
|
|
132
|
+
if (!isValidSuiAddress(ask.to)) {
|
|
133
|
+
throw new NmtsError("That is not a Sui address: it is 0x followed by 64 hexadecimal characters.", {
|
|
134
|
+
exitCode: 2,
|
|
135
|
+
nextStep: "Nothing was signed. Check the address it should go to.",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
op: "transfer",
|
|
140
|
+
shape: { kind: "transfer", objectId: ask.objectId, to: ask.to },
|
|
141
|
+
action: "give",
|
|
142
|
+
named: [r],
|
|
143
|
+
result: { kind: "transfer", moves: shapeOf(r), to: ask.to },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** Read, judge, price and — unless somebody stops it — sign one change to a storage resource. */
|
|
147
|
+
export async function reshapeStorage(context, ask, seams = {}) {
|
|
148
|
+
const reads = seams.reads === undefined
|
|
149
|
+
? (await import("../storage-control-chain.js")).storageOpsReads(context.network)
|
|
150
|
+
: seams.reads(context.network);
|
|
151
|
+
// ① The resources, and which epoch it is.
|
|
152
|
+
const read = await readOrRefuse(() => reads.readStorage(context.address), seams.hints ?? {});
|
|
153
|
+
// ② The request, judged.
|
|
154
|
+
const plan = planStorageOp(read.items, ask, seams.hints ?? {});
|
|
155
|
+
const walrusPackageId = await reads.walrusPackageId();
|
|
156
|
+
// ③ The dry run: the fee, or the contract's own refusal.
|
|
157
|
+
const verdict = await reads.dryRun(plan.shape, context.address);
|
|
158
|
+
if (verdict.refusal !== null) {
|
|
159
|
+
throw new NmtsError(`The chain would refuse this: ${verdict.refusal}`, {
|
|
160
|
+
exitCode: 4,
|
|
161
|
+
nextStep: "Nothing was signed and no fee was spent.",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// ④ The review.
|
|
165
|
+
const review = {
|
|
166
|
+
plan,
|
|
167
|
+
address: context.address,
|
|
168
|
+
network: context.network,
|
|
169
|
+
currentEpoch: read.currentEpoch,
|
|
170
|
+
feeMist: verdict.feeMist,
|
|
171
|
+
};
|
|
172
|
+
seams.onReview?.(review);
|
|
173
|
+
// ⑤ Stopped here, with nothing signed.
|
|
174
|
+
if (seams.dryRun === true)
|
|
175
|
+
return { kind: "review", review };
|
|
176
|
+
// ⑥ The gate.
|
|
177
|
+
seams.agree?.(review);
|
|
178
|
+
// ⑦ The signature.
|
|
179
|
+
const sign = seams.sign ?? (await import("../wallet-sign.js")).signStorageOp;
|
|
180
|
+
const digest = await sign({
|
|
181
|
+
network: context.network,
|
|
182
|
+
code: context.code,
|
|
183
|
+
wallet: context.wallet,
|
|
184
|
+
shape: plan.shape,
|
|
185
|
+
walrusPackageId,
|
|
186
|
+
});
|
|
187
|
+
return { kind: "done", review, digest };
|
|
188
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Transaction } from "@mysten/sui/transactions";
|
|
2
2
|
import type { Network } from "./network.ts";
|
|
3
|
-
import type { StorageRead } from "./
|
|
3
|
+
import type { StorageRead } from "./storage-control/list.ts";
|
|
4
4
|
export type StorageOpShape = {
|
|
5
5
|
kind: "splitSize";
|
|
6
6
|
objectId: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type { StorageHints } from "./storage-control/hints.ts";
|
|
2
|
+
export { formatBytes, listStorage, readOrRefuse } from "./storage-control/list.ts";
|
|
3
|
+
export type { ReadWalletStorage, StorageItem, StorageListing, StorageRead, StorageStatus, } from "./storage-control/list.ts";
|
|
4
|
+
export { applyExtension, nothingToExtend, planExtension } from "./storage-control/extend.ts";
|
|
5
|
+
export type { ExtendApplySeams, ExtendFacts, ExtendInput, ExtendOutcome, ExtendPlan, ExtendPlanSeams, } from "./storage-control/extend.ts";
|
|
6
|
+
export { planStorageOp, reshapeStorage } from "./storage-control/reshape.ts";
|
|
7
|
+
export type { ReshapeContext, ReshapeOutcome, ReshapeSeams, StorageOp, StorageOpAsk, StorageOpPlan, StorageOpResult, StorageOpReview, StorageShape, } from "./storage-control/reshape.ts";
|
|
8
|
+
export type { StorageOpShape, StorageOpsReads } from "./storage-control-chain.ts";
|
|
9
|
+
export type { StorageResource } from "./shared/lib/storage-control/chain.ts";
|
|
10
|
+
export { DEFAULT_EXTEND_EPOCHS } from "./extend-plan.ts";
|
|
11
|
+
export type { ExtendReads, SignExtension } from "./extend-plan.ts";
|
|
12
|
+
export type { Budget } from "./extend-budget.ts";
|
|
13
|
+
export type { SignStorageOp } from "./wallet-sign-seams.ts";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Storage control without a terminal: what a wallet holds, extending a file's term, and cutting,
|
|
2
|
+
// joining and handing over a resource — decided, priced, signed and handed back rather than printed.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ ONE IMPLEMENTATION, TWO CALLERS, which is the whole reason this entry exists. `nmts extend` and
|
|
5
|
+
// `nmts wallet storage …` are a terminal over these functions: the review a person reads, the
|
|
6
|
+
// question that is asked, the wallet unlock this machine keeps, the exit code. The SDK is
|
|
7
|
+
// somebody else's program and needs the same acts with none of those. A second implementation of
|
|
8
|
+
// "what may be joined with what", or of the order an extension is bought in, would be a second
|
|
9
|
+
// place for rules about money that does not come back to be got right — and the copy nobody
|
|
10
|
+
// re-reads is the one that quietly disagrees.
|
|
11
|
+
//
|
|
12
|
+
// ⛔ NOTHING HERE WRITES TO A STREAM, PICKS AN EXIT CODE OR ASKS ANYBODY ANYTHING. Every refusal is
|
|
13
|
+
// thrown and every outcome is returned; the words a person reads are the caller's, and so is the
|
|
14
|
+
// agreement — a terminal asks for one, a program makes it by calling.
|
|
15
|
+
//
|
|
16
|
+
// ⚠ THE PIECES LIVE IN `storage-control/`, AND THIS IS THE DOOR TO THEM. What a caller imports is
|
|
17
|
+
// this name — `@needmoretruth/nmts-cli/storage-control` — so the pieces can be split and joined
|
|
18
|
+
// without a single caller changing. Nothing in the folder reaches for `node:`: the SDK's browser
|
|
19
|
+
// entry bundles what this exports, and the chain and the signer are loaded only when no seam was
|
|
20
|
+
// supplied.
|
|
21
|
+
export { formatBytes, listStorage, readOrRefuse } from "./storage-control/list.js";
|
|
22
|
+
export { applyExtension, nothingToExtend, planExtension } from "./storage-control/extend.js";
|
|
23
|
+
export { planStorageOp, reshapeStorage } from "./storage-control/reshape.js";
|
|
24
|
+
export { DEFAULT_EXTEND_EPOCHS } from "./extend-plan.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { StorageRead } from "./
|
|
1
|
+
import type { StorageRead } from "./storage-control/list.ts";
|
|
2
2
|
import type { Network } from "./network.ts";
|
|
3
3
|
export declare function readWalletStorage(network: Network, address: string): Promise<StorageRead>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@needmoretruth/nmts-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "Command-line and MCP access to NMTS (NeedMoreTruthStorage, https://nmts.me): open-source, end-to-end encrypted storage on the Walrus network. No fee to NMTS; you pay the network from your own wallet. For people and for their agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"publishConfig": {
|
|
@@ -81,6 +81,14 @@
|
|
|
81
81
|
"types": "./dist/drive-edit.d.ts",
|
|
82
82
|
"default": "./dist/drive-edit.js"
|
|
83
83
|
},
|
|
84
|
+
"./drive-erase": {
|
|
85
|
+
"types": "./dist/drive-erase.d.ts",
|
|
86
|
+
"default": "./dist/drive-erase.js"
|
|
87
|
+
},
|
|
88
|
+
"./storage-control": {
|
|
89
|
+
"types": "./dist/storage-control.d.ts",
|
|
90
|
+
"default": "./dist/storage-control.js"
|
|
91
|
+
},
|
|
84
92
|
"./s3-gateway": {
|
|
85
93
|
"types": "./dist/s3-gateway.d.ts",
|
|
86
94
|
"default": "./dist/s3-gateway.js"
|