@needmoretruth/nmts-cli 0.34.4 → 0.36.1
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 +11 -12
- package/CHANGELOG.md +43 -0
- package/README.ko.md +33 -35
- package/README.md +21 -22
- package/dist/account.js +2 -1
- package/dist/arg-options.d.ts +169 -0
- package/dist/arg-options.js +10 -0
- package/dist/args.d.ts +2 -157
- package/dist/args.js +2 -0
- package/dist/artifact-about.d.ts +1 -1
- package/dist/autonomy.d.ts +3 -10
- package/dist/autonomy.js +15 -17
- package/dist/bytes.d.ts +15 -0
- package/dist/bytes.js +63 -0
- package/dist/collision.d.ts +5 -5
- package/dist/collision.js +19 -22
- package/dist/commands/delete-account.js +1 -1
- package/dist/commands/erase.js +1 -1
- package/dist/commands/extend.d.ts +2 -0
- package/dist/commands/extend.js +8 -1
- package/dist/commands/get.js +1 -1
- package/dist/commands/listfile.js +1 -1
- package/dist/commands/mcp.js +5 -1
- package/dist/commands/mode.js +2 -2
- package/dist/commands/on-collision.d.ts +1 -1
- package/dist/commands/on-collision.js +3 -3
- package/dist/commands/platform.d.ts +9 -0
- package/dist/commands/platform.js +124 -0
- package/dist/commands/pull.js +1 -1
- package/dist/commands/push-wallet.js +8 -1
- package/dist/commands/push.d.ts +2 -0
- package/dist/commands/push.js +6 -4
- package/dist/commands/put-payer.d.ts +8 -0
- package/dist/commands/put-payer.js +41 -0
- package/dist/commands/put-wallet.d.ts +9 -3
- package/dist/commands/put-wallet.js +108 -186
- package/dist/commands/put.d.ts +5 -7
- package/dist/commands/put.js +11 -31
- package/dist/commands/receive.js +1 -1
- package/dist/commands/settings.js +1 -1
- package/dist/commands/support.js +3 -3
- package/dist/commands/unlock.js +2 -2
- package/dist/commands/wallet-donate.d.ts +3 -0
- package/dist/commands/wallet-donate.js +6 -2
- package/dist/commands/wallet-hall.d.ts +4 -1
- package/dist/commands/wallet-hall.js +16 -8
- package/dist/commands/wallet-list.d.ts +18 -0
- package/dist/commands/wallet-list.js +116 -0
- package/dist/commands/wallet-send.d.ts +4 -0
- package/dist/commands/wallet-send.js +6 -2
- package/dist/commands/wallet-storage-ops.d.ts +4 -1
- package/dist/commands/wallet-storage-ops.js +7 -2
- package/dist/commands/wallet-swap.d.ts +3 -0
- package/dist/commands/wallet-swap.js +8 -4
- package/dist/commands/wallet-use.d.ts +7 -0
- package/dist/commands/wallet-use.js +55 -0
- package/dist/commands/wallet.d.ts +4 -0
- package/dist/commands/wallet.js +20 -8
- package/dist/crypto.d.ts +8 -5
- package/dist/crypto.js +15 -61
- package/dist/download-sink-node.d.ts +49 -0
- package/dist/download-sink-node.js +204 -0
- package/dist/download-sink.d.ts +0 -48
- package/dist/download-sink.js +6 -199
- package/dist/download.js +5 -4
- package/dist/engine-node.d.ts +19 -0
- package/dist/engine-node.js +77 -0
- package/dist/env-vars.d.ts +20 -0
- package/dist/env-vars.js +27 -0
- package/dist/extend-plan.d.ts +2 -0
- package/dist/gate.js +1 -1
- package/dist/help.js +12 -5
- package/dist/host-contract.d.ts +7 -0
- package/dist/host-contract.js +95 -0
- package/dist/host-node.d.ts +27 -0
- package/dist/host-node.js +192 -0
- package/dist/host.d.ts +99 -0
- package/dist/host.js +59 -0
- package/dist/index.d.ts +6 -40
- package/dist/index.js +20 -32
- package/dist/main.js +11 -10
- package/dist/manifest-chunk-cache.d.ts +3 -3
- package/dist/manifest-chunk-cache.js +39 -44
- package/dist/manifest-chunk-flow.js +15 -14
- package/dist/manifest-write.js +1 -1
- package/dist/manifest.d.ts +2 -2
- package/dist/manifest.js +37 -34
- package/dist/mcp-tools/tiers.js +1 -1
- package/dist/network.d.ts +1 -1
- package/dist/network.js +6 -2
- package/dist/platform-sign.d.ts +143 -0
- package/dist/platform-sign.js +220 -0
- package/dist/portable.d.ts +59 -0
- package/dist/portable.js +82 -0
- package/dist/product.d.ts +1 -1
- package/dist/product.js +1 -1
- package/dist/progress-node.d.ts +2 -0
- package/dist/progress-node.js +14 -0
- package/dist/progress.d.ts +0 -1
- package/dist/progress.js +4 -8
- package/dist/redact.js +3 -2
- package/dist/registration.js +5 -2
- package/dist/risk.d.ts +11 -0
- package/dist/risk.js +16 -0
- package/dist/run-log.d.ts +4 -4
- package/dist/run-log.js +29 -36
- package/dist/seal.js +5 -4
- package/dist/server.d.ts +1 -1
- package/dist/server.js +6 -2
- package/dist/setup-questions.js +2 -2
- package/dist/shared/lib/drive/manifest-ops.d.ts +8 -30
- package/dist/shared/lib/drive/manifest-ops.js +7 -39
- package/dist/shared/lib/drive/manifest-settings-patch.d.ts +52 -0
- package/dist/shared/lib/drive/manifest-settings-patch.js +110 -0
- package/dist/shared/lib/drive/manifest-settings.d.ts +55 -4
- package/dist/shared/lib/drive/manifest-settings.js +66 -28
- package/dist/shared/lib/wallet/discover.d.ts +54 -0
- package/dist/shared/lib/wallet/discover.js +66 -0
- package/dist/standing-tip.d.ts +3 -0
- package/dist/standing-tip.js +1 -0
- package/dist/upload-file-node.d.ts +3 -0
- package/dist/upload-file-node.js +38 -0
- package/dist/upload-file.d.ts +2 -2
- package/dist/upload-file.js +15 -45
- package/dist/upload-price-node.d.ts +8 -0
- package/dist/upload-price-node.js +37 -0
- package/dist/upload-price.d.ts +0 -8
- package/dist/upload-price.js +4 -33
- package/dist/upload-steps.js +3 -3
- package/dist/upload-store.d.ts +14 -14
- package/dist/upload-store.js +68 -75
- package/dist/upload-wallet-chain.js +3 -2
- package/dist/upload-wallet-put.d.ts +118 -0
- package/dist/upload-wallet-put.js +221 -0
- package/dist/upload-wallet.d.ts +3 -0
- package/dist/upload-wallet.js +18 -15
- package/dist/upload.js +16 -15
- package/dist/wallet-list-chain.d.ts +4 -0
- package/dist/wallet-list-chain.js +21 -0
- package/dist/wallet-pay-index.d.ts +21 -0
- package/dist/wallet-pay-index.js +68 -0
- package/dist/wallet-sign-seams.d.ts +69 -0
- package/dist/wallet-sign-seams.js +16 -0
- package/dist/wallet-sign.d.ts +3 -53
- package/dist/wallet-sign.js +22 -18
- package/dist/wallet.d.ts +13 -13
- package/dist/wallet.js +15 -15
- package/dist/walrus-write.js +3 -2
- package/dist/walrus.d.ts +1 -16
- package/dist/walrus.js +11 -22
- package/docs/commands/credits.md +1 -1
- package/docs/commands/platform.md +14 -0
- package/docs/commands/support.md +1 -1
- package/docs/commands/trash.md +1 -1
- package/docs/commands/wallet.md +26 -8
- package/package.json +13 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Network } from "../network.ts";
|
|
2
|
+
import { type ChainReader } from "../wallet.ts";
|
|
3
|
+
export interface WalletListOptions {
|
|
4
|
+
server?: string | undefined;
|
|
5
|
+
network?: string | undefined;
|
|
6
|
+
json?: boolean;
|
|
7
|
+
write?: (line: string) => void;
|
|
8
|
+
/** ⚠ SEAMS, NOT OPTIONS — no flag reaches either. A live chain cannot be asked to fail on demand. */
|
|
9
|
+
openChain?: (network: Network, address: string) => Promise<ChainReader> | ChainReader;
|
|
10
|
+
/** Has this address any transaction at all? A wallet that has been emptied is still in use. */
|
|
11
|
+
hasHistory?: (network: Network, address: string) => Promise<boolean>;
|
|
12
|
+
/** The account's own numbers, out of the sealed list. */
|
|
13
|
+
readWalletSettings?: () => Promise<{
|
|
14
|
+
active: number;
|
|
15
|
+
count: number;
|
|
16
|
+
}>;
|
|
17
|
+
}
|
|
18
|
+
export declare function walletList(options?: WalletListOptions): Promise<number>;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// `nmts wallet list` — every wallet this NMTS key has, what is in it, and which one pays.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ IT READS. Nothing here signs, and nothing here writes the account's choice: `wallet use N`
|
|
4
|
+
// does that. Running this repeatedly costs nothing but questions to a public node.
|
|
5
|
+
//
|
|
6
|
+
// ⛔ WHICH WALLETS EXIST IS NOT A LIST ANYBODY KEEPS. One key derives a wallet at every index
|
|
7
|
+
// (NCF-3 §1.3), so "how many do I have" is answered by WALKING — ask about a number, then the
|
|
8
|
+
// next, and stop after twenty unused ones in a row. That rule is the browser's own, copied
|
|
9
|
+
// byte-for-byte (`shared/lib/wallet/discover.ts`), so the same key shows the same wallets in
|
|
10
|
+
// both programs. ⚠ A wallet funded further out than the gap is not found by the walk; it is not
|
|
11
|
+
// lost — `--wallet N` and `wallet use N` reach any number directly.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ A BALANCE THAT COULD NOT BE READ IS PRINTED AS THAT, NEVER AS ZERO, and the run exits
|
|
14
|
+
// non-zero — the same rule as `nmts wallet`, for the same reason: an empty wallet and an
|
|
15
|
+
// unanswered question look identical on a screen, and one of them is a reason to stop.
|
|
16
|
+
import { requireAccountCode } from "../code-access.js";
|
|
17
|
+
import { readCredentialsFile } from "../credentials.js";
|
|
18
|
+
import { readFileList } from "../manifest.js";
|
|
19
|
+
import { resolveNetwork } from "../network.js";
|
|
20
|
+
import { BINARY_NAME } from "../product.js";
|
|
21
|
+
import { resolveServer } from "../server.js";
|
|
22
|
+
import { openSession } from "../session.js";
|
|
23
|
+
import { activeWalletOf, walletCountOf } from "../shared/lib/drive/manifest-settings.js";
|
|
24
|
+
import { discoverWallets } from "../shared/lib/wallet/discover.js";
|
|
25
|
+
import { coinAmount, readBalances, walCoinType, walletAddress, SUI_COIN_TYPE, } from "../wallet.js";
|
|
26
|
+
/** The table's column widths. A Sui address is 66 characters and gets two spaces after it. */
|
|
27
|
+
const NUMBER_W = 8;
|
|
28
|
+
const ADDRESS_W = 68;
|
|
29
|
+
const AMOUNT_W = 21;
|
|
30
|
+
export async function walletList(options = {}) {
|
|
31
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
32
|
+
const resolved = await requireAccountCode();
|
|
33
|
+
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
34
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
35
|
+
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
36
|
+
const walType = walCoinType(network);
|
|
37
|
+
const settings = await (options.readWalletSettings ?? (() => walletSettings(options)))();
|
|
38
|
+
const open = options.openChain ??
|
|
39
|
+
(async (net, addr) => (await import("../wallet-chain.js")).chainReader(net, addr));
|
|
40
|
+
const history = options.hasHistory ??
|
|
41
|
+
(async (net, addr) => (await import("../wallet-list-chain.js")).hasHistory(net, addr));
|
|
42
|
+
// Every number the walk asked about, so nothing is derived or read twice.
|
|
43
|
+
const seen = new Map();
|
|
44
|
+
const scan = await discoverWallets(async (index) => {
|
|
45
|
+
const address = await walletAddress(resolved.code, index);
|
|
46
|
+
const balances = await readBalances(await open(network, address), walType);
|
|
47
|
+
seen.set(index, { index, address, balances });
|
|
48
|
+
const held = (b) => b.read && b.baseUnits > 0n;
|
|
49
|
+
if (held(balances.sui) || held(balances.wal))
|
|
50
|
+
return true;
|
|
51
|
+
return history(network, address);
|
|
52
|
+
}, { count: settings.count });
|
|
53
|
+
// What is printed: the wallets this account has made, plus any the walk found beyond them. The
|
|
54
|
+
// rest of the walk is the gap — empty wallets nobody has asked for.
|
|
55
|
+
const rows = [...seen.values()]
|
|
56
|
+
.filter((row) => row.index < settings.count || scan.used.includes(row.index))
|
|
57
|
+
.sort((a, b) => a.index - b.index);
|
|
58
|
+
const unread = rows.some((row) => !row.balances.sui.read || !row.balances.wal.read);
|
|
59
|
+
if (options.json === true) {
|
|
60
|
+
say(JSON.stringify({
|
|
61
|
+
network,
|
|
62
|
+
active: settings.active,
|
|
63
|
+
scanned: scan.scanned,
|
|
64
|
+
wallets: rows.map((row) => ({
|
|
65
|
+
index: row.index,
|
|
66
|
+
address: row.address,
|
|
67
|
+
pays: row.index === settings.active,
|
|
68
|
+
sui: asJson(row.balances.sui, SUI_COIN_TYPE),
|
|
69
|
+
wal: asJson(row.balances.wal, walType),
|
|
70
|
+
})),
|
|
71
|
+
}));
|
|
72
|
+
return unread ? 1 : 0;
|
|
73
|
+
}
|
|
74
|
+
// ⚠ The columns are laid out with the same widths the rows use, from the same constants — a
|
|
75
|
+
// header written out by hand drifts one space at a time until nothing lines up.
|
|
76
|
+
say(`${"Wallet".padEnd(NUMBER_W)}${"Address".padEnd(ADDRESS_W)}${"SUI".padEnd(AMOUNT_W)}WAL`);
|
|
77
|
+
for (const row of rows) {
|
|
78
|
+
const pays = row.index === settings.active ? " pays" : "";
|
|
79
|
+
say(`${String(row.index).padEnd(NUMBER_W)}${row.address.padEnd(ADDRESS_W)}` +
|
|
80
|
+
`${inWords(row.balances.sui).padEnd(AMOUNT_W)}${inWords(row.balances.wal)}${pays}`);
|
|
81
|
+
}
|
|
82
|
+
say(``);
|
|
83
|
+
say(` Every wallet above comes from this NMTS key, at the number in the first column. The one`);
|
|
84
|
+
say(` marked "pays" is the one storage is paid from — \`${BINARY_NAME} wallet use <number>\` moves it,`);
|
|
85
|
+
say(` on this account rather than on this machine. Numbers come from the key, so a wallet`);
|
|
86
|
+
say(` cannot be deleted.`);
|
|
87
|
+
say(` The list stops after twenty unused wallets in a row; a wallet further out than that is`);
|
|
88
|
+
say(` reached by its number.`);
|
|
89
|
+
if (unread) {
|
|
90
|
+
say(` A balance that could not be read is printed as that and never as zero, and this command`);
|
|
91
|
+
say(` exits non-zero when it happens.`);
|
|
92
|
+
}
|
|
93
|
+
return unread ? 1 : 0;
|
|
94
|
+
}
|
|
95
|
+
/** The account's own numbers, read out of the sealed file list. */
|
|
96
|
+
async function walletSettings(options) {
|
|
97
|
+
const session = await openSession({ server: options.server, network: options.network });
|
|
98
|
+
const list = await readFileList(session.server, session.apiKey, session.code, session.accountId);
|
|
99
|
+
const settings = list.manifest?.settings;
|
|
100
|
+
return { active: activeWalletOf(settings), count: walletCountOf(settings) };
|
|
101
|
+
}
|
|
102
|
+
/** One balance, for a person. The same two sentences `nmts wallet` prints. */
|
|
103
|
+
function inWords(balance) {
|
|
104
|
+
return balance.read ? coinAmount(balance.baseUnits) : `⛔ could not be read`;
|
|
105
|
+
}
|
|
106
|
+
/** One balance, for a program. `baseUnits` is a string: a balance outruns a JSON number. */
|
|
107
|
+
function asJson(balance, coinType) {
|
|
108
|
+
if (!balance.read)
|
|
109
|
+
return { coinType, read: false, error: balance.why };
|
|
110
|
+
return {
|
|
111
|
+
coinType,
|
|
112
|
+
read: true,
|
|
113
|
+
baseUnits: balance.baseUnits.toString(),
|
|
114
|
+
amount: coinAmount(balance.baseUnits),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -12,6 +12,10 @@ export interface WalletSendOptions {
|
|
|
12
12
|
dryRun?: boolean;
|
|
13
13
|
/** A ceiling on the fee, in SUI ("0.01"). Clamped to the usable range, never refused. */
|
|
14
14
|
feeCap?: string | undefined;
|
|
15
|
+
/** `--wallet N`: which wallet pays, this run only. Absent = the account's own number. */
|
|
16
|
+
wallet?: string | undefined;
|
|
17
|
+
/** ⚠ A SEAM, NOT AN OPTION — where that number is read from (`wallet-pay-index.ts`). */
|
|
18
|
+
readActiveWallet?: () => Promise<number>;
|
|
15
19
|
/** The instant to measure the wallet agreement against. */
|
|
16
20
|
now?: number;
|
|
17
21
|
/** ⚠ A SEAM, NOT AN OPTION — no flag reaches it. */
|
|
@@ -21,6 +21,7 @@ import { resolveServer } from "../server.js";
|
|
|
21
21
|
import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
22
22
|
import { clampGasBudgetMist, maxSendableBaseUnits, parseTokenAmountToBaseUnits, validateSendForm, } from "../shared/lib/wallet/send-rules.js";
|
|
23
23
|
import { coinAmount, walCoinType, walletAddress } from "../wallet.js";
|
|
24
|
+
import { payingWalletIndex } from "../wallet-pay-index.js";
|
|
24
25
|
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
25
26
|
const COIN_WORDS = {
|
|
26
27
|
invalidAddress: "That is not a Sui address: it is 0x followed by 64 hexadecimal characters.",
|
|
@@ -48,7 +49,10 @@ export async function walletSend(operands, options = {}) {
|
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
51
|
const resolved = await requireAccountCode();
|
|
51
|
-
|
|
52
|
+
// ⛔ WHICH WALLET PAYS, FIRST — everything below is about one address: the balances that are
|
|
53
|
+
// read, the fee that is measured, the line the review prints, and the key that signs.
|
|
54
|
+
const wallet = await payingWalletIndex(options);
|
|
55
|
+
const address = await walletAddress(resolved.code, wallet);
|
|
52
56
|
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
53
57
|
const server = resolveServer(options.server ?? stored?.server);
|
|
54
58
|
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
@@ -140,7 +144,7 @@ export async function walletSend(operands, options = {}) {
|
|
|
140
144
|
requireWalletGrant("send", spend, new Date(options.now ?? Date.now()));
|
|
141
145
|
// ⑦ The signature.
|
|
142
146
|
const sign = options.sign ?? (await import("../wallet-sign.js")).signTransfer;
|
|
143
|
-
const digest = await sign({ network, code: resolved.code, shape });
|
|
147
|
+
const digest = await sign({ network, code: resolved.code, wallet, shape });
|
|
144
148
|
recordWalletSpend(spend);
|
|
145
149
|
if (options.json) {
|
|
146
150
|
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
@@ -13,8 +13,11 @@ export interface StorageOpsOptions {
|
|
|
13
13
|
size?: string | undefined;
|
|
14
14
|
/** `split --epochs <n>`: how many epochs, from its start, the named resource keeps. */
|
|
15
15
|
epochs?: string | undefined;
|
|
16
|
+
/** `--wallet N`: whose resources these are, this run only. Absent = the account's own number. */
|
|
17
|
+
wallet?: string | undefined;
|
|
16
18
|
now?: number;
|
|
17
|
-
/** ⚠
|
|
19
|
+
/** ⚠ SEAMS, NOT OPTIONS — no flag reaches them. */
|
|
20
|
+
readActiveWallet?: () => Promise<number>;
|
|
18
21
|
storageReads?: (network: Network) => StorageOpsReads;
|
|
19
22
|
/** ⛔ SEPARATE FROM THE READS so a test can prove the review stops before this. */
|
|
20
23
|
signStorage?: SignStorageOp;
|
|
@@ -23,6 +23,7 @@ import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
|
23
23
|
import { isValidSuiAddress } from "../shared/lib/wallet/send-rules.js";
|
|
24
24
|
import { storageOpsReads } from "../storage-control-chain.js";
|
|
25
25
|
import { coinAmount, walletAddress } from "../wallet.js";
|
|
26
|
+
import { payingWalletIndex } from "../wallet-pay-index.js";
|
|
26
27
|
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
27
28
|
import { formatBytes } from "./wallet-storage.js";
|
|
28
29
|
const FUSE_WHY = {
|
|
@@ -34,7 +35,10 @@ const FUSE_WHY = {
|
|
|
34
35
|
export async function walletStorageOps(op, rest, options = {}) {
|
|
35
36
|
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
36
37
|
const resolved = await requireAccountCode();
|
|
37
|
-
|
|
38
|
+
// ⛔ WHICH WALLET, FIRST — the resources are read from this address, the review names it, and the
|
|
39
|
+
// same wallet signs; a resource is reshaped by the wallet that holds it and by no other.
|
|
40
|
+
const wallet = await payingWalletIndex(options);
|
|
41
|
+
const address = await walletAddress(resolved.code, wallet);
|
|
38
42
|
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
39
43
|
const server = resolveServer(options.server ?? stored?.server);
|
|
40
44
|
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
@@ -76,6 +80,7 @@ export async function walletStorageOps(op, rest, options = {}) {
|
|
|
76
80
|
// ④ The review.
|
|
77
81
|
if (!options.json) {
|
|
78
82
|
say(`Would ${lines.what}`);
|
|
83
|
+
say(` from ${address} (wallet ${wallet})`);
|
|
79
84
|
for (const l of lines.detail)
|
|
80
85
|
say(` ${l}`);
|
|
81
86
|
say(feeMist === null
|
|
@@ -106,7 +111,7 @@ export async function walletStorageOps(op, rest, options = {}) {
|
|
|
106
111
|
const spend = { walFrost: 0n, suiMist: feeMist ?? 0n };
|
|
107
112
|
requireWalletGrant(action, spend, new Date(options.now ?? Date.now()));
|
|
108
113
|
const sign = options.signStorage ?? (await import("../wallet-sign.js")).signStorageOp;
|
|
109
|
-
const digest = await sign({ network, code: resolved.code, shape, walrusPackageId });
|
|
114
|
+
const digest = await sign({ network, code: resolved.code, wallet, shape, walrusPackageId });
|
|
110
115
|
recordWalletSpend(spend);
|
|
111
116
|
if (options.json) {
|
|
112
117
|
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
@@ -22,8 +22,11 @@ export interface WalletSwapOptions {
|
|
|
22
22
|
slippageBps?: string | undefined;
|
|
23
23
|
/** `--accept-extremes`: go on past the extremes gate. A person's act — refused outside mode off. */
|
|
24
24
|
acceptExtremes?: boolean;
|
|
25
|
+
/** `--wallet N`: which wallet swaps, this run only. Absent = the account's own number. */
|
|
26
|
+
wallet?: string | undefined;
|
|
25
27
|
now?: number;
|
|
26
28
|
/** ⚠ SEAMS, NOT OPTIONS — no flag reaches them. */
|
|
29
|
+
readActiveWallet?: () => Promise<number>;
|
|
27
30
|
readChain?: (network: Network) => SwapReads | Promise<SwapReads>;
|
|
28
31
|
readPrices?: (server: string) => Promise<MarketPrices | null>;
|
|
29
32
|
sign?: SignSwap;
|
|
@@ -25,6 +25,7 @@ import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
|
25
25
|
import { clampGasBudgetMist, parseTokenAmountToBaseUnits, SUI_GAS_RESERVE_MIST } from "../shared/lib/wallet/send-rules.js";
|
|
26
26
|
import { clampSlippageBps, impliedRate, marketRate, maxSwappableSuiMist, minOutFromQuote, priceDeviationBps, SLIPPAGE_BPS_DEFAULT, SLIPPAGE_BPS_MAX, SLIPPAGE_BPS_MIN, swapExtremes, } from "../shared/lib/wallet/swap-rules.js";
|
|
27
27
|
import { coinAmount, walletAddress } from "../wallet.js";
|
|
28
|
+
import { payingWalletIndex } from "../wallet-pay-index.js";
|
|
28
29
|
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
29
30
|
import { railsFor } from "../wallet-swap-chain.js";
|
|
30
31
|
const EXTREME_WORDS = {
|
|
@@ -73,7 +74,10 @@ export async function walletSwap(operands, options = {}) {
|
|
|
73
74
|
throw new NmtsError("Say how much.", { exitCode: 2, nextStep: `\`${BINARY_NAME} wallet swap ${coinIn} <amount|max>\` — "max" swaps everything that can be swapped.` });
|
|
74
75
|
}
|
|
75
76
|
const resolved = await requireAccountCode();
|
|
76
|
-
|
|
77
|
+
// ⛔ WHICH WALLET SWAPS, FIRST — the coins that are read, the quote that is priced, the address
|
|
78
|
+
// the review names and the key that signs are all this one wallet's.
|
|
79
|
+
const wallet = await payingWalletIndex(options);
|
|
80
|
+
const address = await walletAddress(resolved.code, wallet);
|
|
77
81
|
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
78
82
|
const server = resolveServer(options.server ?? stored?.server);
|
|
79
83
|
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
@@ -211,7 +215,7 @@ export async function walletSwap(operands, options = {}) {
|
|
|
211
215
|
// ⑥ The review, every time.
|
|
212
216
|
if (!options.json) {
|
|
213
217
|
say(`Swapping ${facts.amountIn} ${coinIn} for ${coinOut} on ${venueName(venue)}`);
|
|
214
|
-
say(` from ${address}`);
|
|
218
|
+
say(` from ${address} (wallet ${wallet})`);
|
|
215
219
|
say(` Quoted ${facts.quotedOut} ${coinOut} — the chain's answer just now, not a promise${quote.leftoverInUnits > 0n ? `; ${coinAmount(quote.leftoverInUnits)} ${coinIn} would come back unused` : ""}`);
|
|
216
220
|
if (venue === "exchange")
|
|
217
221
|
say(` Rate ${exchange?.rateWal} WAL per ${exchange?.rateSui} SUI, read off the facility; it takes no minimum`);
|
|
@@ -244,7 +248,7 @@ export async function walletSwap(operands, options = {}) {
|
|
|
244
248
|
nextStep: `Choose again (--slippage-bps 50, a different --fee-cap, the other venue, or later), or — as a person — add --accept-extremes to go on anyway.`,
|
|
245
249
|
});
|
|
246
250
|
}
|
|
247
|
-
const mode = currentMode();
|
|
251
|
+
const mode = await currentMode();
|
|
248
252
|
if (mode === "auto-low" || mode === "auto-high") {
|
|
249
253
|
throw new NmtsError("Going past the extremes gate is a person's act.", { exitCode: 5, nextStep: `Nothing was signed. --accept-extremes is refused in mode auto and with --skip-permissions.` });
|
|
250
254
|
}
|
|
@@ -273,7 +277,7 @@ export async function walletSwap(operands, options = {}) {
|
|
|
273
277
|
requireWalletGrant("exchange", spend, new Date(options.now ?? Date.now()));
|
|
274
278
|
// ⑩ The signature.
|
|
275
279
|
const sign = options.sign ?? (await import("../wallet-sign.js")).signSwap;
|
|
276
|
-
const digest = await sign({ network, code: resolved.code, shape });
|
|
280
|
+
const digest = await sign({ network, code: resolved.code, wallet, shape });
|
|
277
281
|
recordWalletSpend(spend);
|
|
278
282
|
if (options.json) {
|
|
279
283
|
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// `nmts wallet use <number>` — which of this key's wallets pays for storage from now on.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ IT WRITES THE ACCOUNT'S CHOICE, NOT THIS MACHINE'S. The number lives inside the sealed file
|
|
4
|
+
// list (`activeWallet`), where the server cannot read it and where a phone, a laptop and this
|
|
5
|
+
// tool all find the same answer. That is the whole point: money leaving from one address on one
|
|
6
|
+
// device and another address on another is how a balance goes missing without anything failing.
|
|
7
|
+
//
|
|
8
|
+
// ⛔ THE LIST IS WHERE THE SETTING LIVES, so an account with no list has nowhere to put it — the
|
|
9
|
+
// same refusal `nmts padding` gives, for the same reason, and the same way out: upload once.
|
|
10
|
+
//
|
|
11
|
+
// ⛔ NOTHING IS DELETED AND NOTHING IS CREATED. Every number a key can derive already exists
|
|
12
|
+
// (NCF-3 §1.3); this says which of them the next payment comes out of, and takes the count up
|
|
13
|
+
// with it so every screen lists the wallet it just named.
|
|
14
|
+
import { NmtsError } from "../errors.js";
|
|
15
|
+
import { readFileList } from "../manifest.js";
|
|
16
|
+
import { applyManyToList } from "../manifest-write.js";
|
|
17
|
+
import { BINARY_NAME } from "../product.js";
|
|
18
|
+
import { openSession } from "../session.js";
|
|
19
|
+
import { walletIndexOf } from "../wallet-pay-index.js";
|
|
20
|
+
import { walletAddress } from "../wallet.js";
|
|
21
|
+
export async function walletUse(said, options = {}) {
|
|
22
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
23
|
+
if (said === undefined || said === "") {
|
|
24
|
+
throw new NmtsError(`Say which wallet: \`${BINARY_NAME} wallet use <number>\`.`, {
|
|
25
|
+
exitCode: 2,
|
|
26
|
+
nextStep: `\`${BINARY_NAME} wallet list\` shows this key's wallets and which one pays now.`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
// ⛔ BEFORE THE NETWORK. A misspelled number is a command line to fix, not a question to ask the
|
|
30
|
+
// server, and a run that opened a session first would refuse for the wrong reason.
|
|
31
|
+
const index = walletIndexOf(said);
|
|
32
|
+
const session = await openSession({ server: options.server, network: options.network });
|
|
33
|
+
const before = await readFileList(session.server, session.apiKey, session.code, session.accountId);
|
|
34
|
+
if (before.manifest === null) {
|
|
35
|
+
throw new NmtsError(`This account has no file list yet; which wallet pays lives in the list, and there is nothing to write it into.`, { exitCode: 4, nextStep: `Upload once (\`${BINARY_NAME} put\`) and set it after.` });
|
|
36
|
+
}
|
|
37
|
+
const result = await applyManyToList(session, () => [], { activeWallet: index });
|
|
38
|
+
const address = await walletAddress(session.code, index);
|
|
39
|
+
if (options.json === true) {
|
|
40
|
+
say(JSON.stringify({ wallet: index, address, changed: result.changed }));
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
if (!result.changed) {
|
|
44
|
+
say(`Wallet ${index} was already the one that pays. Nothing changed.`);
|
|
45
|
+
say(`Address ${address}`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
say(`Wallet ${index} pays from now on.`);
|
|
49
|
+
say(`Address ${address}`);
|
|
50
|
+
say(``);
|
|
51
|
+
say(` Storage paid from this account's wallet now leaves this address, on every device — the`);
|
|
52
|
+
say(` number rides in the sealed file list, not on this machine. What is already stored is`);
|
|
53
|
+
say(` unaffected, and the wallets you were using keep whatever is in them.`);
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
@@ -7,6 +7,10 @@ export interface WalletOptions {
|
|
|
7
7
|
write?: (line: string) => void;
|
|
8
8
|
/** `wallet address --qr`: the address as a code a phone can scan, drawn in the terminal. */
|
|
9
9
|
qr?: boolean;
|
|
10
|
+
/** `wallet address --index N`: which of this key's wallets to print. Absent = the first one. */
|
|
11
|
+
index?: string | undefined;
|
|
12
|
+
/** `--wallet N` on `send`/`donate`/`swap`/`storage`/`hall`: which wallet, this run only. */
|
|
13
|
+
wallet?: string | undefined;
|
|
10
14
|
/** `wallet send`: the operands after "send", and its flags (`wallet-send.ts`). */
|
|
11
15
|
rest?: readonly string[];
|
|
12
16
|
yes?: boolean;
|
package/dist/commands/wallet.js
CHANGED
|
@@ -22,9 +22,10 @@ import { NmtsError } from "../errors.js";
|
|
|
22
22
|
import { resolveNetwork } from "../network.js";
|
|
23
23
|
import { BINARY_NAME } from "../product.js";
|
|
24
24
|
import { resolveServer } from "../server.js";
|
|
25
|
-
import {
|
|
25
|
+
import { walletIndexOf } from "../wallet-pay-index.js";
|
|
26
|
+
import { BUILT_IN_WALLET_INDEX, coinAmount, readBalances, walCoinType, walletAddress, SUI_COIN_TYPE, } from "../wallet.js";
|
|
26
27
|
/** What the operand may say. Anything else is a command line to correct, not a guess to act on. */
|
|
27
|
-
const MODES = ["address", "balance", "activity", "storage", "send", "donate", "swap", "hall"];
|
|
28
|
+
const MODES = ["address", "balance", "list", "use", "activity", "storage", "send", "donate", "swap", "hall"];
|
|
28
29
|
function modeOf(what) {
|
|
29
30
|
if (what === undefined)
|
|
30
31
|
return "balance";
|
|
@@ -33,7 +34,9 @@ function modeOf(what) {
|
|
|
33
34
|
return found;
|
|
34
35
|
throw new NmtsError(`\`${BINARY_NAME} wallet ${what}\` is not something this command does.`, {
|
|
35
36
|
exitCode: 2,
|
|
36
|
-
nextStep: `\`${BINARY_NAME} wallet\` shows the address and the balances; \`${BINARY_NAME} wallet
|
|
37
|
+
nextStep: `\`${BINARY_NAME} wallet\` shows the address and the balances; \`${BINARY_NAME} wallet list\` ` +
|
|
38
|
+
`shows every wallet this key has and which one pays, and \`${BINARY_NAME} wallet use <number>\` ` +
|
|
39
|
+
`moves that. \`${BINARY_NAME} wallet address\` ` +
|
|
37
40
|
`shows the address alone, without touching a network (add --qr for a code to scan); ` +
|
|
38
41
|
`\`${BINARY_NAME} wallet activity\` lists the recent transactions; \`${BINARY_NAME} wallet storage\` ` +
|
|
39
42
|
`lists the storage resources it holds. None of those signs. \`${BINARY_NAME} wallet send <SUI|WAL> ` +
|
|
@@ -48,6 +51,11 @@ export async function wallet(what, options = {}) {
|
|
|
48
51
|
// The two lists live in files of their own; each reads through a seam of its own.
|
|
49
52
|
if (mode === "activity")
|
|
50
53
|
return (await import("./wallet-activity.js")).walletActivity(options);
|
|
54
|
+
// Every wallet of this key and which one pays: listing reads, choosing writes one line of the list.
|
|
55
|
+
if (mode === "list")
|
|
56
|
+
return (await import("./wallet-list.js")).walletList(options);
|
|
57
|
+
if (mode === "use")
|
|
58
|
+
return (await import("./wallet-use.js")).walletUse(options.rest?.[0], options);
|
|
51
59
|
if (mode === "storage") {
|
|
52
60
|
const [sub, ...more] = options.rest ?? [];
|
|
53
61
|
if (sub === "split" || sub === "merge" || sub === "transfer") {
|
|
@@ -70,13 +78,17 @@ export async function wallet(what, options = {}) {
|
|
|
70
78
|
if (mode === "hall")
|
|
71
79
|
return (await import("./wallet-hall.js")).walletHall(options);
|
|
72
80
|
const resolved = await requireAccountCode();
|
|
73
|
-
|
|
81
|
+
// ⛔ `wallet address --index N` IS OFFLINE — the person named the number, so there is no list to
|
|
82
|
+
// read; without a number it is the first wallet. "Which one pays" is a question the list
|
|
83
|
+
// answers, and `wallet list` is where it is asked.
|
|
84
|
+
const index = options.index === undefined || options.index === "" ? BUILT_IN_WALLET_INDEX : walletIndexOf(options.index);
|
|
85
|
+
const address = await walletAddress(resolved.code, index);
|
|
74
86
|
if (mode === "address") {
|
|
75
87
|
if (options.json) {
|
|
76
|
-
// ⛔ NO `network` FIELD. There is no network in this answer —
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
say(JSON.stringify({ address }));
|
|
88
|
+
// ⛔ NO `network` FIELD. There is no network in this answer — a wallet is the same wallet on
|
|
89
|
+
// every chain — and a field naming one would invite a reader to believe this address was
|
|
90
|
+
// looked up somewhere. The NUMBER is here, because it is what the address came from.
|
|
91
|
+
say(JSON.stringify({ address, index }));
|
|
80
92
|
return 0;
|
|
81
93
|
}
|
|
82
94
|
say(`Address ${address}`);
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CryptoGlue } from "./crypto-surface.ts";
|
|
2
2
|
export type { CryptoGlue, StreamOpener, StreamSealer } from "./crypto-surface.ts";
|
|
3
3
|
/**
|
|
4
4
|
* The associated-data strings of NCF-3, for the envelopes this tool opens.
|
|
@@ -74,8 +74,11 @@ export declare const DERIVED: {
|
|
|
74
74
|
*/
|
|
75
75
|
readonly aiAccountRoot: readonly [256, 288];
|
|
76
76
|
};
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
/**
|
|
78
|
+
* The engine, loaded once by whichever host this program registered.
|
|
79
|
+
*
|
|
80
|
+
* Every derivation in the package goes through this one call, so a program that has not registered
|
|
81
|
+
* a host is told so here rather than somewhere deeper, where the message would be about a missing
|
|
82
|
+
* function instead of a missing entry point.
|
|
83
|
+
*/
|
|
79
84
|
export declare function loadCrypto(): Promise<CryptoGlue>;
|
|
80
|
-
/** For tests that need a fresh load. */
|
|
81
|
-
export declare function forgetCrypto(): void;
|
package/dist/crypto.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// The frozen tables of NCF-3, and the one door to the engine that reads them.
|
|
2
2
|
//
|
|
3
3
|
// ⛔ NOTHING IS RE-IMPLEMENTED HERE. The bytes that derive an account's keys are the same
|
|
4
4
|
// WebAssembly the browser runs, built from the same Rust crate. Two harnesses in this repo
|
|
@@ -7,24 +7,12 @@
|
|
|
7
7
|
// be a second implementation that can drift from the first, silently and in the direction that
|
|
8
8
|
// loses files.
|
|
9
9
|
//
|
|
10
|
-
// ⛔
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
17
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
return path;
|
|
21
|
-
};
|
|
22
|
-
import { readFile } from "node:fs/promises";
|
|
23
|
-
import { existsSync } from "node:fs";
|
|
24
|
-
import { dirname, join } from "node:path";
|
|
25
|
-
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
|
-
import { NmtsError } from "./errors.js";
|
|
27
|
-
import { isCryptoGlue, missingExports } from "./crypto-surface.js";
|
|
10
|
+
// ⛔ WHERE THE ENGINE COMES FROM IS THE HOST'S BUSINESS, not this file's. Node finds it on disk
|
|
11
|
+
// and a browser fetches it, and both answers land in `host().engine` — so the tables below, and
|
|
12
|
+
// every module that derives anything from them, are the same code in both. The shape check
|
|
13
|
+
// stays on the host's side of the door for the same reason: `crypto-surface.ts` is one gate
|
|
14
|
+
// that both loads pass, rather than two guards that can come to disagree.
|
|
15
|
+
import { host } from "./host.js";
|
|
28
16
|
/**
|
|
29
17
|
* The associated-data strings of NCF-3, for the envelopes this tool opens.
|
|
30
18
|
*
|
|
@@ -104,47 +92,13 @@ export const DERIVED = {
|
|
|
104
92
|
*/
|
|
105
93
|
aiAccountRoot: [256, 288],
|
|
106
94
|
};
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
for (const dir of candidates) {
|
|
115
|
-
if (existsSync(join(dir, "nmts_crypto_wasm_bg.wasm")))
|
|
116
|
-
return dir;
|
|
117
|
-
}
|
|
118
|
-
throw new NmtsError("The NMTS crypto engine is missing from this installation.", {
|
|
119
|
-
exitCode: 1,
|
|
120
|
-
nextStep: "Reinstall the package. Nothing can be encrypted or decrypted without it.",
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
let cached = null;
|
|
124
|
-
/** Load the engine once per process. */
|
|
95
|
+
/**
|
|
96
|
+
* The engine, loaded once by whichever host this program registered.
|
|
97
|
+
*
|
|
98
|
+
* Every derivation in the package goes through this one call, so a program that has not registered
|
|
99
|
+
* a host is told so here rather than somewhere deeper, where the message would be about a missing
|
|
100
|
+
* function instead of a missing entry point.
|
|
101
|
+
*/
|
|
125
102
|
export async function loadCrypto() {
|
|
126
|
-
|
|
127
|
-
return cached;
|
|
128
|
-
const dir = engineDir();
|
|
129
|
-
const module = await import(__rewriteRelativeImportExtension(pathToFileURL(join(dir, "nmts_crypto_wasm.js")).href));
|
|
130
|
-
if (typeof module !== "object" || module === null || !("default" in module)) {
|
|
131
|
-
throw new NmtsError("The NMTS crypto engine did not load (no initialiser).", { exitCode: 1 });
|
|
132
|
-
}
|
|
133
|
-
const init = Reflect.get(module, "default");
|
|
134
|
-
if (typeof init !== "function") {
|
|
135
|
-
throw new NmtsError("The NMTS crypto engine did not load (initialiser is not callable).", {
|
|
136
|
-
exitCode: 1,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
await init({ module_or_path: await readFile(join(dir, "nmts_crypto_wasm_bg.wasm")) });
|
|
140
|
-
if (!isCryptoGlue(module)) {
|
|
141
|
-
const missing = missingExports(module);
|
|
142
|
-
throw new NmtsError(`The NMTS crypto engine is missing: ${missing.join(", ")}. This build does not match this tool.`, { exitCode: 1, nextStep: "Reinstall the package." });
|
|
143
|
-
}
|
|
144
|
-
cached = module;
|
|
145
|
-
return module;
|
|
146
|
-
}
|
|
147
|
-
/** For tests that need a fresh load. */
|
|
148
|
-
export function forgetCrypto() {
|
|
149
|
-
cached = null;
|
|
103
|
+
return host().engine.load();
|
|
150
104
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { PlaintextSink } from "./download-sink.ts";
|
|
2
|
+
import { type ByteDestination } from "./stdout.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Write to a file, through a temporary name beside it, renamed into place only when the whole
|
|
5
|
+
* file has been proved.
|
|
6
|
+
*
|
|
7
|
+
* ⛔ THE TEMPORARY FILE IS IN THE SAME DIRECTORY AS THE DESTINATION, not in a system temporary
|
|
8
|
+
* directory, and that is the whole mechanism: `rename` is atomic only within one filesystem,
|
|
9
|
+
* and only an atomic rename guarantees that a reader either sees nothing under the real name or
|
|
10
|
+
* sees the finished file. A temporary directory can be — and on the machines this runs on
|
|
11
|
+
* usually is — a different filesystem, where the same call becomes copy-then-delete and a
|
|
12
|
+
* reader can catch the copy half done.
|
|
13
|
+
*
|
|
14
|
+
* ⛔ IT IS CREATED 0600 AND THE RENAME KEEPS THAT. The file is somebody's plaintext; it must not
|
|
15
|
+
* be readable by other accounts on the machine for the seconds it takes to download, any more
|
|
16
|
+
* than it may be afterwards.
|
|
17
|
+
*/
|
|
18
|
+
export declare function fileSink(destination: string, options: {
|
|
19
|
+
force: boolean;
|
|
20
|
+
}): PlaintextSink;
|
|
21
|
+
/**
|
|
22
|
+
* How much plaintext `--out -` will hold before it hands anything over. One part's worth.
|
|
23
|
+
*
|
|
24
|
+
* ⛔ NOT AN ARBITRARY NUMBER: it is the upload path's default part size, which is the most memory
|
|
25
|
+
* this tool has ever asked for. Keeping the stdout ceiling there means the whole tool's bound
|
|
26
|
+
* is one part plus one chunk whichever direction the bytes are going.
|
|
27
|
+
*/
|
|
28
|
+
export declare const STDOUT_HOLD_LIMIT: number;
|
|
29
|
+
/**
|
|
30
|
+
* Hand the whole file to whatever is reading stdout — after it has been proved, not during.
|
|
31
|
+
*
|
|
32
|
+
* ⛔ THERE IS NO RENAME ON A PIPE, so the trick the file path uses is not available: a byte handed
|
|
33
|
+
* to a reader cannot be taken back, and a reader that has already consumed half a file cannot
|
|
34
|
+
* be told afterwards that the half was wrong. An exit code does not fix that — `nmts get x
|
|
35
|
+
* --out - > y` leaves the truncated bytes in `y` whatever the exit code says, and an agent
|
|
36
|
+
* reading a pipe usually acts on what it read. So this branch keeps the ORIGINAL guarantee
|
|
37
|
+
* rather than the original size: the file is held, checked, and only then handed over, exactly
|
|
38
|
+
* as before streaming existed.
|
|
39
|
+
*
|
|
40
|
+
* ⛔ WHICH MEANS IT HAS TO REFUSE SOMEWHERE, AND IT REFUSES OUT LOUD AND EARLY. Above the ceiling
|
|
41
|
+
* the answer is a refusal naming `--out <name>`, which streams and is checked before the file
|
|
42
|
+
* appears — not a silent trade of the guarantee for the size, and not the out-of-memory crash
|
|
43
|
+
* that this used to be. `put` already refuses this way rather than discovering a limit halfway.
|
|
44
|
+
*
|
|
45
|
+
* ⚠ Every protection this branch already had is still here and still applies to the whole file:
|
|
46
|
+
* bytes a terminal would act on are refused (`readableOnATerminal`), a reader that closed the
|
|
47
|
+
* pipe first is an ordinary end and not a failure, and any other write failure throws.
|
|
48
|
+
*/
|
|
49
|
+
export declare function stdoutSink(to: ByteDestination, limit?: number): PlaintextSink;
|