@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
|
@@ -51,12 +51,58 @@ export interface AccountSettings {
|
|
|
51
51
|
* = never agreed: raising the tip above 0 asks for that agreement once, and later changes do not.
|
|
52
52
|
*/
|
|
53
53
|
tipConsentAt?: number;
|
|
54
|
+
/**
|
|
55
|
+
* WHICH WALLET PAYS — the index the NMTS key derives it at (NCF-3 §1.3, `walletSeed(N)`). Absent
|
|
56
|
+
* = 0, the wallet every account has had since the beginning.
|
|
57
|
+
*
|
|
58
|
+
* ⛔ HERE, IN THE SEALED LIST, AND NOT BESIDE THE derived/imported SWITCH. That switch is about
|
|
59
|
+
* this DEVICE (which key this browser opens), so it lives in device storage; the number is about
|
|
60
|
+
* the ACCOUNT — it decides which address the storage is paid from, and a phone and a laptop that
|
|
61
|
+
* disagreed about it would spend from two different balances for one person.
|
|
62
|
+
*
|
|
63
|
+
* Whole, 0 to just under 2^31: the format derives a wallet at every index and this is the range
|
|
64
|
+
* an index is written in.
|
|
65
|
+
*/
|
|
66
|
+
activeWallet?: number;
|
|
67
|
+
/**
|
|
68
|
+
* HOW MANY WALLETS THIS ACCOUNT HAS MADE — the list the wallets screen draws, 1 to 1000. Absent
|
|
69
|
+
* = 1, the one wallet an account starts with.
|
|
70
|
+
*
|
|
71
|
+
* ⚠ IT IS A COUNT, NOT A SET. Numbers come from the key, so a wallet cannot be deleted and the
|
|
72
|
+
* list is always 0…count-1; "making the next wallet" is this number going up by one, and a scan
|
|
73
|
+
* that finds a funded wallet further out pulls it up to that number + 1.
|
|
74
|
+
*/
|
|
75
|
+
walletCount?: number;
|
|
54
76
|
}
|
|
55
77
|
/** The sanity bounds a stored text scale must sit in to be USED. One place; codec and UI agree. */
|
|
56
78
|
export declare const TEXT_SCALE_MIN_PCT = 80;
|
|
57
79
|
export declare const TEXT_SCALE_MAX_PCT = 160;
|
|
58
80
|
/** Follow the device. Not written to the wire — absence is the only spelling of it. */
|
|
59
81
|
export declare const TEXT_SCALE_DEFAULT_PCT = 100;
|
|
82
|
+
/**
|
|
83
|
+
* The index range a wallet number is written in: whole, 0 to just under 2^31.
|
|
84
|
+
*
|
|
85
|
+
* ⛔ THE CEILING IS THE WIRE'S, NOT THE FORMAT'S. `walletSeed(N)` is defined for every N the
|
|
86
|
+
* engine can be handed; what is bounded here is what this build will WRITE and read back, so a
|
|
87
|
+
* number some other build miswrote cannot come back as something no screen can draw.
|
|
88
|
+
*/
|
|
89
|
+
export declare const WALLET_INDEX_LIMIT: number;
|
|
90
|
+
/** The wallet an account pays from when nobody chose. Not written to the wire — absence spells it. */
|
|
91
|
+
export declare const ACTIVE_WALLET_DEFAULT = 0;
|
|
92
|
+
/** The most wallets one account's list holds. */
|
|
93
|
+
export declare const WALLET_COUNT_MAX = 1000;
|
|
94
|
+
/** What an account's list holds before anybody made a second one. Absence spells it. */
|
|
95
|
+
export declare const WALLET_COUNT_DEFAULT = 1;
|
|
96
|
+
/** Which wallet this account pays from. Absence is wallet 0, never "unknown". */
|
|
97
|
+
export declare function activeWalletOf(settings: AccountSettings | null | undefined): number;
|
|
98
|
+
/**
|
|
99
|
+
* How many wallets this account's list holds.
|
|
100
|
+
*
|
|
101
|
+
* ⛔ THE INVARIANT LIVES HERE: the paying wallet is always IN the list. A stored count that does
|
|
102
|
+
* not reach the active number is raised to hold it — never the other way round, because lowering
|
|
103
|
+
* it would hide a wallet somebody is paying from, and a wallet cannot be deleted anyway.
|
|
104
|
+
*/
|
|
105
|
+
export declare function walletCountOf(settings: AccountSettings | null | undefined): number;
|
|
60
106
|
export interface WireSettings {
|
|
61
107
|
/** developerMode. */
|
|
62
108
|
dm?: 1;
|
|
@@ -84,6 +130,15 @@ export interface WireSettings {
|
|
|
84
130
|
tp?: number;
|
|
85
131
|
/** tipConsentAt. */
|
|
86
132
|
tc?: number;
|
|
133
|
+
/** activeWallet, present only when it is not wallet 0. */
|
|
134
|
+
aw?: number;
|
|
135
|
+
/**
|
|
136
|
+
* walletCount, present only when the account has made more than one.
|
|
137
|
+
*
|
|
138
|
+
* ⚠ A count above the ceiling is DROPPED on both sides, and nothing is lost by that: the read
|
|
139
|
+
* below raises the count to hold `aw` again, so the paying wallet stays in the list either way.
|
|
140
|
+
*/
|
|
141
|
+
wc?: number;
|
|
87
142
|
}
|
|
88
143
|
/** The most a standing tip can be: the whole payment. Above the dial's 10 % it is typed and confirmed. */
|
|
89
144
|
export declare const TIP_TENTHS_MAX = 1000;
|
|
@@ -107,7 +162,3 @@ export declare function settingsToWire(s: AccountSettings | undefined): WireSett
|
|
|
107
162
|
* to the device's own size, which is always readable.
|
|
108
163
|
*/
|
|
109
164
|
export declare function settingsFromWire(w: unknown): AccountSettings | undefined;
|
|
110
|
-
/** Folds a deposit patch into a settings copy: out-of-range is clamped, the full deposit clears. */
|
|
111
|
-
export declare function applyDepositPatch(next: AccountSettings, depositDefault?: number): void;
|
|
112
|
-
/** Folds a tip patch into a settings copy: 0 clears, above the cap is capped, fractions are rounded. */
|
|
113
|
-
export declare function applyTipPatch(next: AccountSettings, tipTenths?: number, tipConsentAt?: number): void;
|
|
@@ -14,6 +14,48 @@ export const TEXT_SCALE_MIN_PCT = 80;
|
|
|
14
14
|
export const TEXT_SCALE_MAX_PCT = 160;
|
|
15
15
|
/** Follow the device. Not written to the wire — absence is the only spelling of it. */
|
|
16
16
|
export const TEXT_SCALE_DEFAULT_PCT = 100;
|
|
17
|
+
/**
|
|
18
|
+
* The index range a wallet number is written in: whole, 0 to just under 2^31.
|
|
19
|
+
*
|
|
20
|
+
* ⛔ THE CEILING IS THE WIRE'S, NOT THE FORMAT'S. `walletSeed(N)` is defined for every N the
|
|
21
|
+
* engine can be handed; what is bounded here is what this build will WRITE and read back, so a
|
|
22
|
+
* number some other build miswrote cannot come back as something no screen can draw.
|
|
23
|
+
*/
|
|
24
|
+
export const WALLET_INDEX_LIMIT = 2 ** 31;
|
|
25
|
+
/** The wallet an account pays from when nobody chose. Not written to the wire — absence spells it. */
|
|
26
|
+
export const ACTIVE_WALLET_DEFAULT = 0;
|
|
27
|
+
/** The most wallets one account's list holds. */
|
|
28
|
+
export const WALLET_COUNT_MAX = 1000;
|
|
29
|
+
/** What an account's list holds before anybody made a second one. Absence spells it. */
|
|
30
|
+
export const WALLET_COUNT_DEFAULT = 1;
|
|
31
|
+
/** Is this a wallet index this build writes and reads? */
|
|
32
|
+
function usableWalletIndex(value) {
|
|
33
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value < WALLET_INDEX_LIMIT;
|
|
34
|
+
}
|
|
35
|
+
/** Is this a wallet count this build writes and reads? */
|
|
36
|
+
function usableWalletCount(value) {
|
|
37
|
+
return (typeof value === "number" &&
|
|
38
|
+
Number.isInteger(value) &&
|
|
39
|
+
value >= WALLET_COUNT_DEFAULT &&
|
|
40
|
+
value <= WALLET_COUNT_MAX);
|
|
41
|
+
}
|
|
42
|
+
/** Which wallet this account pays from. Absence is wallet 0, never "unknown". */
|
|
43
|
+
export function activeWalletOf(settings) {
|
|
44
|
+
const stored = settings?.activeWallet;
|
|
45
|
+
return usableWalletIndex(stored) ? stored : ACTIVE_WALLET_DEFAULT;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* How many wallets this account's list holds.
|
|
49
|
+
*
|
|
50
|
+
* ⛔ THE INVARIANT LIVES HERE: the paying wallet is always IN the list. A stored count that does
|
|
51
|
+
* not reach the active number is raised to hold it — never the other way round, because lowering
|
|
52
|
+
* it would hide a wallet somebody is paying from, and a wallet cannot be deleted anyway.
|
|
53
|
+
*/
|
|
54
|
+
export function walletCountOf(settings) {
|
|
55
|
+
const stored = settings?.walletCount;
|
|
56
|
+
const held = usableWalletCount(stored) ? stored : WALLET_COUNT_DEFAULT;
|
|
57
|
+
return Math.max(held, activeWalletOf(settings) + 1);
|
|
58
|
+
}
|
|
17
59
|
/** The most a standing tip can be: the whole payment. Above the dial's 10 % it is typed and confirmed. */
|
|
18
60
|
export const TIP_TENTHS_MAX = 1000;
|
|
19
61
|
/**
|
|
@@ -59,12 +101,18 @@ export function settingsToWire(s) {
|
|
|
59
101
|
if (typeof s.tipConsentAt === "number" && Number.isFinite(s.tipConsentAt) && s.tipConsentAt > 0) {
|
|
60
102
|
w.tc = Math.round(s.tipConsentAt);
|
|
61
103
|
}
|
|
104
|
+
if (usableWalletIndex(s.activeWallet) && s.activeWallet !== ACTIVE_WALLET_DEFAULT)
|
|
105
|
+
w.aw = s.activeWallet;
|
|
106
|
+
if (usableWalletCount(s.walletCount) && s.walletCount !== WALLET_COUNT_DEFAULT)
|
|
107
|
+
w.wc = s.walletCount;
|
|
62
108
|
return w.dm !== undefined ||
|
|
63
109
|
w.tx !== undefined ||
|
|
64
110
|
w.pd !== undefined ||
|
|
65
111
|
w.dd !== undefined ||
|
|
66
112
|
w.tp !== undefined ||
|
|
67
|
-
w.tc !== undefined
|
|
113
|
+
w.tc !== undefined ||
|
|
114
|
+
w.aw !== undefined ||
|
|
115
|
+
w.wc !== undefined
|
|
68
116
|
? w
|
|
69
117
|
: null;
|
|
70
118
|
}
|
|
@@ -84,6 +132,8 @@ export function settingsFromWire(w) {
|
|
|
84
132
|
const dd = Reflect.get(w, "dd");
|
|
85
133
|
const tp = Reflect.get(w, "tp");
|
|
86
134
|
const tc = Reflect.get(w, "tc");
|
|
135
|
+
const aw = Reflect.get(w, "aw");
|
|
136
|
+
const wc = Reflect.get(w, "wc");
|
|
87
137
|
const s = {};
|
|
88
138
|
if (dm === 1)
|
|
89
139
|
s.developerMode = true;
|
|
@@ -109,38 +159,26 @@ export function settingsFromWire(w) {
|
|
|
109
159
|
s.tipTenths = tp;
|
|
110
160
|
if (typeof tc === "number" && Number.isFinite(tc) && tc > 0)
|
|
111
161
|
s.tipConsentAt = Math.round(tc);
|
|
162
|
+
// A wallet number outside the range is DROPPED, not clamped: paying from a wallet some other
|
|
163
|
+
// build miswrote would spend from an address this person has never seen, and wallet 0 is the one
|
|
164
|
+
// every account already has.
|
|
165
|
+
if (usableWalletIndex(aw) && aw !== ACTIVE_WALLET_DEFAULT)
|
|
166
|
+
s.activeWallet = aw;
|
|
167
|
+
if (usableWalletCount(wc) && wc !== WALLET_COUNT_DEFAULT)
|
|
168
|
+
s.walletCount = wc;
|
|
169
|
+
// ⛔ AND THE INVARIANT IS RESTORED HERE, UPWARDS ONLY (`walletCountOf`). A list whose count does
|
|
170
|
+
// not reach the paying wallet would draw a screen the paying wallet is missing from.
|
|
171
|
+
const counted = walletCountOf(s);
|
|
172
|
+
if (counted !== WALLET_COUNT_DEFAULT)
|
|
173
|
+
s.walletCount = counted;
|
|
112
174
|
return s.developerMode !== undefined ||
|
|
113
175
|
s.textScalePct !== undefined ||
|
|
114
176
|
s.paddingMode !== undefined ||
|
|
115
177
|
s.depositDefault !== undefined ||
|
|
116
178
|
s.tipTenths !== undefined ||
|
|
117
|
-
s.tipConsentAt !== undefined
|
|
179
|
+
s.tipConsentAt !== undefined ||
|
|
180
|
+
s.activeWallet !== undefined ||
|
|
181
|
+
s.walletCount !== undefined
|
|
118
182
|
? s
|
|
119
183
|
: undefined;
|
|
120
184
|
}
|
|
121
|
-
/** Folds a deposit patch into a settings copy: out-of-range is clamped, the full deposit clears. */
|
|
122
|
-
export function applyDepositPatch(next, depositDefault) {
|
|
123
|
-
if (depositDefault === undefined || !Number.isFinite(depositDefault))
|
|
124
|
-
return;
|
|
125
|
-
const credits = Math.round(Math.min(DEPOSIT_MAX_CREDITS, Math.max(0, depositDefault)));
|
|
126
|
-
if (credits === DEPOSIT_DEFAULT_CREDITS)
|
|
127
|
-
delete next.depositDefault;
|
|
128
|
-
else
|
|
129
|
-
next.depositDefault = credits;
|
|
130
|
-
}
|
|
131
|
-
/** Folds a tip patch into a settings copy: 0 clears, above the cap is capped, fractions are rounded. */
|
|
132
|
-
export function applyTipPatch(next, tipTenths, tipConsentAt) {
|
|
133
|
-
if (tipTenths !== undefined && Number.isFinite(tipTenths)) {
|
|
134
|
-
const t = Math.round(Math.min(TIP_TENTHS_MAX, Math.max(0, tipTenths)));
|
|
135
|
-
if (t === 0)
|
|
136
|
-
delete next.tipTenths;
|
|
137
|
-
else
|
|
138
|
-
next.tipTenths = t;
|
|
139
|
-
}
|
|
140
|
-
if (tipConsentAt !== undefined && Number.isFinite(tipConsentAt)) {
|
|
141
|
-
if (tipConsentAt <= 0)
|
|
142
|
-
delete next.tipConsentAt;
|
|
143
|
-
else
|
|
144
|
-
next.tipConsentAt = Math.round(tipConsentAt);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many unused wallets in a row end the walk.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ TWENTY, THE SAME NUMBER EVERY OTHER WALLET USES. Somebody who has funded a wallet in another
|
|
5
|
+
* tool and comes looking for it here has been taught what a scan finds by that tool; a different
|
|
6
|
+
* number here would make the same key look like it holds different wallets in two programs.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WALLET_SCAN_GAP = 20;
|
|
9
|
+
/** What a walk found: the wallets that are in use, lowest first, and how many were asked about. */
|
|
10
|
+
export interface WalletScan {
|
|
11
|
+
/** Every index the probe said was in use, ascending. */
|
|
12
|
+
readonly used: readonly number[];
|
|
13
|
+
/** How many times the probe was called — what the walk cost, in questions. */
|
|
14
|
+
readonly scanned: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Is this wallet IN USE? Answered by the caller, because what counts as "in use" is a question
|
|
18
|
+
* about a chain and this file has none: a balance in either coin, or any transaction the address
|
|
19
|
+
* has ever been in.
|
|
20
|
+
*
|
|
21
|
+
* ⚠ A PROBE THAT FAILS SHOULD THROW, not answer `false`. "Nobody could ask" and "nothing is there"
|
|
22
|
+
* are opposite facts, and answering the second for the first would quietly end the walk early and
|
|
23
|
+
* report a wallet with money in it as one that does not exist.
|
|
24
|
+
*/
|
|
25
|
+
export type WalletProbe = (index: number) => Promise<boolean>;
|
|
26
|
+
/**
|
|
27
|
+
* Walk the wallets of one NMTS key from 0 upwards, and stop when the walk has learned enough.
|
|
28
|
+
*
|
|
29
|
+
* TWO RULES, AND THEY ARE DIFFERENT RULES:
|
|
30
|
+
* · every wallet the account has already MADE is asked about, whatever the answers are. Those
|
|
31
|
+
* wallets are on the person's screen; a walk that skipped one because its neighbour was empty
|
|
32
|
+
* would be reporting about a wallet it never asked about.
|
|
33
|
+
* · past those, the walk keeps going as long as it keeps finding wallets in use, and ends after
|
|
34
|
+
* `gap` unused ones in a row.
|
|
35
|
+
*
|
|
36
|
+
* ⚠ THE RUN CARRIES OVER from the made wallets into the walk past them. A list of thirty wallets
|
|
37
|
+
* nobody ever funded is thirty unused answers, and starting the count again at the end of it
|
|
38
|
+
* would ask for twenty more that nobody has any reason to expect anything from.
|
|
39
|
+
*/
|
|
40
|
+
export declare function discoverWallets(probe: WalletProbe, { count, gap }: {
|
|
41
|
+
count: number;
|
|
42
|
+
gap?: number;
|
|
43
|
+
}): Promise<WalletScan>;
|
|
44
|
+
/**
|
|
45
|
+
* How far a walk can reach as it stands — the number a progress line counts towards.
|
|
46
|
+
*
|
|
47
|
+
* ⛔ IT MOVES, because the walk's end moves: finding a wallet in use buys another `gap` questions
|
|
48
|
+
* past it. A progress line drawn against the number the walk STARTED with would sit at "20/20"
|
|
49
|
+
* while the walk kept going, which reads as a screen that has stopped answering.
|
|
50
|
+
*/
|
|
51
|
+
export declare function walletScanReach({ count, gap }: {
|
|
52
|
+
count: number;
|
|
53
|
+
gap?: number;
|
|
54
|
+
}, highestUsed: number | null): number;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Finding the wallets an NMTS key has already used — the scan every HD wallet does, and where it
|
|
2
|
+
// stops. ⚠ PUBLISHED — copied byte-for-byte into the `nmts` command-line package; keep comments
|
|
3
|
+
// self-contained English.
|
|
4
|
+
//
|
|
5
|
+
// ⛔ PURE, AND THAT IS THE WHOLE POINT. One NMTS key derives a wallet at every index (NCF-3 §1.3),
|
|
6
|
+
// so "which of them exist" is not a question anybody can answer by looking: nothing is
|
|
7
|
+
// registered anywhere, and an unused wallet and a wallet nobody has funded yet are the same
|
|
8
|
+
// thing. The answer is a WALK — ask about one number, then the next — and the only real decision
|
|
9
|
+
// in it is when to stop. That decision is arithmetic, so it is written here, once, with no
|
|
10
|
+
// chain, no fetch and no clock in it; the browser and the `nmts` command each bring their own
|
|
11
|
+
// `probe` and get the same answer out.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ WHY IT STOPS AT ALL. The index space is unbounded, so a walk that asked for proof of absence
|
|
14
|
+
// would never end. Stopping after a run of unused wallets is what every HD wallet does (the
|
|
15
|
+
// convention BIP-44 §Address gap limit wrote down), and the cost of the rule is stated rather
|
|
16
|
+
// than hidden: a wallet funded further out than the gap is NOT found by a scan. It is not lost —
|
|
17
|
+
// numbers come from the key, so asking for that number directly still opens it.
|
|
18
|
+
/**
|
|
19
|
+
* How many unused wallets in a row end the walk.
|
|
20
|
+
*
|
|
21
|
+
* ⛔ TWENTY, THE SAME NUMBER EVERY OTHER WALLET USES. Somebody who has funded a wallet in another
|
|
22
|
+
* tool and comes looking for it here has been taught what a scan finds by that tool; a different
|
|
23
|
+
* number here would make the same key look like it holds different wallets in two programs.
|
|
24
|
+
*/
|
|
25
|
+
export const WALLET_SCAN_GAP = 20;
|
|
26
|
+
/**
|
|
27
|
+
* Walk the wallets of one NMTS key from 0 upwards, and stop when the walk has learned enough.
|
|
28
|
+
*
|
|
29
|
+
* TWO RULES, AND THEY ARE DIFFERENT RULES:
|
|
30
|
+
* · every wallet the account has already MADE is asked about, whatever the answers are. Those
|
|
31
|
+
* wallets are on the person's screen; a walk that skipped one because its neighbour was empty
|
|
32
|
+
* would be reporting about a wallet it never asked about.
|
|
33
|
+
* · past those, the walk keeps going as long as it keeps finding wallets in use, and ends after
|
|
34
|
+
* `gap` unused ones in a row.
|
|
35
|
+
*
|
|
36
|
+
* ⚠ THE RUN CARRIES OVER from the made wallets into the walk past them. A list of thirty wallets
|
|
37
|
+
* nobody ever funded is thirty unused answers, and starting the count again at the end of it
|
|
38
|
+
* would ask for twenty more that nobody has any reason to expect anything from.
|
|
39
|
+
*/
|
|
40
|
+
export async function discoverWallets(probe, { count, gap = WALLET_SCAN_GAP }) {
|
|
41
|
+
const used = [];
|
|
42
|
+
let scanned = 0;
|
|
43
|
+
let unusedInARow = 0;
|
|
44
|
+
for (let index = 0; index < count || unusedInARow < gap; index += 1) {
|
|
45
|
+
const inUse = await probe(index);
|
|
46
|
+
scanned += 1;
|
|
47
|
+
if (inUse) {
|
|
48
|
+
used.push(index);
|
|
49
|
+
unusedInARow = 0;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
unusedInARow += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { used, scanned };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* How far a walk can reach as it stands — the number a progress line counts towards.
|
|
59
|
+
*
|
|
60
|
+
* ⛔ IT MOVES, because the walk's end moves: finding a wallet in use buys another `gap` questions
|
|
61
|
+
* past it. A progress line drawn against the number the walk STARTED with would sit at "20/20"
|
|
62
|
+
* while the walk kept going, which reads as a screen that has stopped answering.
|
|
63
|
+
*/
|
|
64
|
+
export function walletScanReach({ count, gap = WALLET_SCAN_GAP }, highestUsed) {
|
|
65
|
+
return Math.max(count, highestUsed === null ? 0 : highestUsed + 1) + gap;
|
|
66
|
+
}
|
package/dist/standing-tip.d.ts
CHANGED
|
@@ -15,6 +15,9 @@ export interface StandingTipInput {
|
|
|
15
15
|
} | undefined;
|
|
16
16
|
/** What was just paid for storage, in WAL base units. */
|
|
17
17
|
paidWalFrost: bigint;
|
|
18
|
+
/** ⛔ The wallet the storage was just paid from — a gift from a different one would come out of
|
|
19
|
+
* a balance nobody was looking at, and the payment above named this one. */
|
|
20
|
+
wallet: number;
|
|
18
21
|
say: (line: string) => void;
|
|
19
22
|
/** Seams for tests. */
|
|
20
23
|
readDonation?: (server: string) => Promise<DonationConfig>;
|
package/dist/standing-tip.js
CHANGED
|
@@ -37,6 +37,7 @@ export async function standingTipAfter(input) {
|
|
|
37
37
|
const digest = await sign({
|
|
38
38
|
network: input.network,
|
|
39
39
|
code: input.code,
|
|
40
|
+
wallet: input.wallet,
|
|
40
41
|
shape: { coin: "WAL", amountBaseUnits: amount, destination: config.devAddress, walType: walCoinType(input.network) },
|
|
41
42
|
});
|
|
42
43
|
input.say(` Your standing ${percentText(tenths)} % gift — ${coinAmount(amount)} WAL — went to the developer. Transaction ${digest}`);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// A file on THIS MACHINE, read a chunk at a time, as a plaintext source.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ THE SEAM IS `upload-file.ts`'S AND THIS IS THE ONE IMPLEMENTATION THAT NEEDS A DISK. A caller
|
|
4
|
+
// in a browser hands in a `Blob` instead (the SDK's `blobSource`), and everything downstream —
|
|
5
|
+
// the part plan, the sealing, the reservation records — is the same code for both.
|
|
6
|
+
import { open } from "node:fs/promises";
|
|
7
|
+
import { NmtsError } from "./errors.js";
|
|
8
|
+
import { READ_CHUNK_BYTES } from "./upload-file.js";
|
|
9
|
+
/** Read a file off the disk, a chunk at a time. */
|
|
10
|
+
export function fileSource(path, size) {
|
|
11
|
+
return {
|
|
12
|
+
size,
|
|
13
|
+
async *read(offset, length) {
|
|
14
|
+
const handle = await open(path, "r");
|
|
15
|
+
try {
|
|
16
|
+
const buffer = new Uint8Array(Math.min(READ_CHUNK_BYTES, length));
|
|
17
|
+
let at = 0;
|
|
18
|
+
while (at < length) {
|
|
19
|
+
const want = Math.min(buffer.length, length - at);
|
|
20
|
+
const { bytesRead } = await handle.read(buffer, 0, want, offset + at);
|
|
21
|
+
if (bytesRead === 0) {
|
|
22
|
+
// ⛔ SHORT IS NOT DONE. The plan was made from the size this file had when it was
|
|
23
|
+
// measured; a read that ends early means it shrank underneath us, and sealing what
|
|
24
|
+
// arrived would declare a length the bytes do not match.
|
|
25
|
+
throw new NmtsError(`${path} ended after ${at} of ${length} bytes.`, {
|
|
26
|
+
nextStep: "Nothing was sent. The file changed while it was being read.",
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
at += bytesRead;
|
|
30
|
+
yield new Uint8Array(buffer.subarray(0, bytesRead));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
await handle.close();
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/upload-file.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { type CryptoGlue } from "./crypto.ts";
|
|
|
2
2
|
import { type PaddingRule } from "./shared/lib/crypto/size-padding.ts";
|
|
3
3
|
import { entryOf } from "./upload.ts";
|
|
4
4
|
import type { BlobProtocol, PaidPart, UploadApi, UploadInput, UploadResult, UploadStep } from "./upload-wire.ts";
|
|
5
|
+
/** How much plaintext is handed to the engine at a time. Matches the format's own chunk size. */
|
|
6
|
+
export declare const READ_CHUNK_BYTES: number;
|
|
5
7
|
/**
|
|
6
8
|
* Where the plaintext comes from.
|
|
7
9
|
*
|
|
@@ -15,8 +17,6 @@ export interface PlaintextSource {
|
|
|
15
17
|
/** Read `[offset, offset + length)`, in pieces small enough to hold. */
|
|
16
18
|
read(offset: number, length: number): AsyncIterable<Uint8Array>;
|
|
17
19
|
}
|
|
18
|
-
/** Read a file off the disk, a chunk at a time. */
|
|
19
|
-
export declare function fileSource(path: string, size: number): PlaintextSource;
|
|
20
20
|
export interface FileUploadInput {
|
|
21
21
|
api: UploadApi;
|
|
22
22
|
protocol: BlobProtocol;
|
package/dist/upload-file.js
CHANGED
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
// the reservation key, and the SHA-256 the account will check the contents against — because
|
|
16
16
|
// both are needed BEFORE the first part is sealed. The second pass is the sealing itself. The
|
|
17
17
|
// alternative is holding the file, which is the thing this module exists to avoid.
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
18
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
19
|
+
import { fromBase64Url } from "./bytes.js";
|
|
20
20
|
import { AAD } from "./crypto.js";
|
|
21
21
|
import { NmtsError } from "./errors.js";
|
|
22
22
|
import { fileSecrets, sealPart } from "./seal.js";
|
|
@@ -27,37 +27,7 @@ import { buyAndPushPart, entryOf } from "./upload.js";
|
|
|
27
27
|
import { commitItem } from "./upload-steps.js";
|
|
28
28
|
import { finishReservationKey, partKey, readItemRecord, readReservationBytes, readReservationRecord, startReservationKey, } from "./upload-store.js";
|
|
29
29
|
/** How much plaintext is handed to the engine at a time. Matches the format's own chunk size. */
|
|
30
|
-
const READ_CHUNK_BYTES = 4 * 2 ** 20;
|
|
31
|
-
/** Read a file off the disk, a chunk at a time. */
|
|
32
|
-
export function fileSource(path, size) {
|
|
33
|
-
return {
|
|
34
|
-
size,
|
|
35
|
-
async *read(offset, length) {
|
|
36
|
-
const handle = await open(path, "r");
|
|
37
|
-
try {
|
|
38
|
-
const buffer = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, length));
|
|
39
|
-
let at = 0;
|
|
40
|
-
while (at < length) {
|
|
41
|
-
const want = Math.min(buffer.length, length - at);
|
|
42
|
-
const { bytesRead } = await handle.read(buffer, 0, want, offset + at);
|
|
43
|
-
if (bytesRead === 0) {
|
|
44
|
-
// ⛔ SHORT IS NOT DONE. The plan was made from the size this file had when it was
|
|
45
|
-
// measured; a read that ends early means it shrank underneath us, and sealing what
|
|
46
|
-
// arrived would declare a length the bytes do not match.
|
|
47
|
-
throw new NmtsError(`${path} ended after ${at} of ${length} bytes.`, {
|
|
48
|
-
nextStep: "Nothing was sent. The file changed while it was being read.",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
at += bytesRead;
|
|
52
|
-
yield new Uint8Array(buffer.subarray(0, bytesRead));
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
finally {
|
|
56
|
-
await handle.close();
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
30
|
+
export const READ_CHUNK_BYTES = 4 * 2 ** 20;
|
|
61
31
|
/**
|
|
62
32
|
* Upload one file and return what the caller must write into the account's file list.
|
|
63
33
|
*
|
|
@@ -83,15 +53,15 @@ export async function uploadFile(input) {
|
|
|
83
53
|
// `POST /v1/items` exists and is paid for; all that can still be missing is the account's own
|
|
84
54
|
// list. Asking the server about every part again would be a round trip per part to learn
|
|
85
55
|
// something the record already says.
|
|
86
|
-
const committed = readItemRecord(fileKey);
|
|
56
|
+
const committed = await readItemRecord(fileKey);
|
|
87
57
|
if (committed?.itemId !== undefined) {
|
|
88
|
-
const entry = recordedEntry(fileKey, plan.length, source.size);
|
|
58
|
+
const entry = await recordedEntry(fileKey, plan.length, source.size);
|
|
89
59
|
if (entry !== null) {
|
|
90
60
|
return { itemId: committed.itemId, resumed: true, ledgerIds: [], fileKey, parts: plan.length, entry };
|
|
91
61
|
}
|
|
92
62
|
}
|
|
93
63
|
// ── the file's secrets: from the record if one exists, otherwise made now ──
|
|
94
|
-
const secrets = openSecrets(input, fileKey, plan.length, contentDigest);
|
|
64
|
+
const secrets = await openSecrets(input, fileKey, plan.length, contentDigest);
|
|
95
65
|
try {
|
|
96
66
|
const entry = {
|
|
97
67
|
name: input.name,
|
|
@@ -103,7 +73,7 @@ export async function uploadFile(input) {
|
|
|
103
73
|
const paid = [];
|
|
104
74
|
for (const range of plan) {
|
|
105
75
|
const key = partKey(fileKey, range.partIndex);
|
|
106
|
-
const stored = readReservationRecord(key);
|
|
76
|
+
const stored = await readReservationRecord(key);
|
|
107
77
|
// ⛔ A PART THAT IS WRITTEN DOWN IS NEVER SEALED AGAIN. Its bytes are a particular sealing
|
|
108
78
|
// the treasury may already have paid to register; a fresh one is a different blob.
|
|
109
79
|
// ⛔ THE LAST PART ONLY. Every reader recovers the parts' real lengths from the file's size
|
|
@@ -119,7 +89,7 @@ export async function uploadFile(input) {
|
|
|
119
89
|
: range.length;
|
|
120
90
|
const sealed = stored === null
|
|
121
91
|
? await sealPartOf(input, secrets.dek, range, plan.length, sealFrom)
|
|
122
|
-
: readReservationBytes(key);
|
|
92
|
+
: await readReservationBytes(key);
|
|
123
93
|
paid.push(await (input.buy ?? buyAndPushPart)({
|
|
124
94
|
api: input.api,
|
|
125
95
|
protocol: input.protocol,
|
|
@@ -162,12 +132,12 @@ export function partKeysOf(fileKey, parts) {
|
|
|
162
132
|
}
|
|
163
133
|
/** Read the whole file once, feeding the key hash and returning the content digest. */
|
|
164
134
|
async function hashWhole(source, keyHash) {
|
|
165
|
-
const content =
|
|
135
|
+
const content = sha256.create();
|
|
166
136
|
for await (const chunk of source.read(0, source.size)) {
|
|
167
137
|
keyHash.update(chunk);
|
|
168
138
|
content.update(chunk);
|
|
169
139
|
}
|
|
170
|
-
return
|
|
140
|
+
return content.digest();
|
|
171
141
|
}
|
|
172
142
|
/**
|
|
173
143
|
* The file's key and sealed content hash — from a written-down part if there is one.
|
|
@@ -176,12 +146,12 @@ async function hashWhole(source, keyHash) {
|
|
|
176
146
|
* the bytes already on the network cannot be re-sealed. Unwrapping the recorded one is what
|
|
177
147
|
* makes the parts still to come belong to the same file.
|
|
178
148
|
*/
|
|
179
|
-
function openSecrets(input, fileKey, parts, contentDigest) {
|
|
149
|
+
async function openSecrets(input, fileKey, parts, contentDigest) {
|
|
180
150
|
for (const key of partKeysOf(fileKey, parts)) {
|
|
181
|
-
const record = readReservationRecord(key);
|
|
151
|
+
const record = await readReservationRecord(key);
|
|
182
152
|
if (record === null)
|
|
183
153
|
continue;
|
|
184
|
-
const wrapped =
|
|
154
|
+
const wrapped = fromBase64Url(record.dekWrapped);
|
|
185
155
|
const dek = input.crypt.envelope_open(input.dataKey, new TextEncoder().encode(AAD.dekWrap), wrapped);
|
|
186
156
|
return { dek, dekWrapped: record.dekWrapped, contentHashCt: record.contentHashCt };
|
|
187
157
|
}
|
|
@@ -226,9 +196,9 @@ export { entryOf };
|
|
|
226
196
|
* sealed with. A run that wrote its own freshly generated one into the list would produce a
|
|
227
197
|
* file that is paid for, present, correctly named and impossible to open.
|
|
228
198
|
*/
|
|
229
|
-
function recordedEntry(fileKey, parts, size) {
|
|
199
|
+
async function recordedEntry(fileKey, parts, size) {
|
|
230
200
|
for (const key of partKeysOf(fileKey, parts)) {
|
|
231
|
-
const record = readReservationRecord(key);
|
|
201
|
+
const record = await readReservationRecord(key);
|
|
232
202
|
if (record === null)
|
|
233
203
|
continue;
|
|
234
204
|
return {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How big the local file is, with the two refusals that are worth their own words.
|
|
3
|
+
*
|
|
4
|
+
* ⛔ IT IS NOT READ HERE. A file large enough to need several parts is a file too large to hold,
|
|
5
|
+
* and the size is all that is needed to plan the upload and quote its price. The bytes are read
|
|
6
|
+
* later, a slice at a time, by the part that is being sealed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function measureLocal(path: string): number;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// How big a file on THIS MACHINE is — the one thing the upload price needs that a browser has no
|
|
2
|
+
// equivalent of.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ THE ARITHMETIC IS NOT HERE. `upload-price.ts` turns a size into parts and a price and runs
|
|
5
|
+
// anywhere; this is the one call that asks a disk, and it is separate so that the price is
|
|
6
|
+
// reachable from a page where bytes arrive as a `Blob` and their length is already known.
|
|
7
|
+
import { statSync } from "node:fs";
|
|
8
|
+
import { NmtsError } from "./errors.js";
|
|
9
|
+
/**
|
|
10
|
+
* How big the local file is, with the two refusals that are worth their own words.
|
|
11
|
+
*
|
|
12
|
+
* ⛔ IT IS NOT READ HERE. A file large enough to need several parts is a file too large to hold,
|
|
13
|
+
* and the size is all that is needed to plan the upload and quote its price. The bytes are read
|
|
14
|
+
* later, a slice at a time, by the part that is being sealed.
|
|
15
|
+
*/
|
|
16
|
+
export function measureLocal(path) {
|
|
17
|
+
let stat;
|
|
18
|
+
try {
|
|
19
|
+
stat = statSync(path);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new NmtsError(`There is no file at ${path}.`, { exitCode: 4 });
|
|
23
|
+
}
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
throw new NmtsError(`${path} is a folder.`, {
|
|
26
|
+
exitCode: 4,
|
|
27
|
+
nextStep: "This version uploads one file at a time.",
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (stat.size === 0) {
|
|
31
|
+
throw new NmtsError(`${path} is empty.`, {
|
|
32
|
+
exitCode: 4,
|
|
33
|
+
nextStep: "The storage network has nothing to store and would refuse the reservation.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return stat.size;
|
|
37
|
+
}
|
package/dist/upload-price.d.ts
CHANGED
|
@@ -18,14 +18,6 @@ export declare const CREDIT_BYTES: number;
|
|
|
18
18
|
* shows afterwards. The server is still the authority; this is a quote, not a promise.
|
|
19
19
|
*/
|
|
20
20
|
export declare function creditsFor(sealedBytes: number, epochs?: number): number;
|
|
21
|
-
/**
|
|
22
|
-
* How big the local file is, with the two refusals that are worth their own words.
|
|
23
|
-
*
|
|
24
|
-
* ⛔ IT IS NOT READ HERE. A file large enough to need several parts is a file too large to hold,
|
|
25
|
-
* and the size is all that is needed to plan the upload and quote its price. The bytes are read
|
|
26
|
-
* later, a slice at a time, by the part that is being sealed.
|
|
27
|
-
*/
|
|
28
|
-
export declare function measureLocal(path: string): number;
|
|
29
21
|
/**
|
|
30
22
|
* How much of the file goes into one part.
|
|
31
23
|
*
|
package/dist/upload-price.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// What an upload will cost, and how the file is cut up to pay for it.
|
|
2
2
|
//
|
|
3
3
|
// ⛔ SPLIT OUT OF `put.ts` SO THAT FILE STAYS READABLE IN ONE SITTING. Everything here is
|
|
4
|
-
// arithmetic over numbers — no network, no crypto
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
|
|
4
|
+
// arithmetic over numbers — no network, no crypto and no disk at all — which is what lets
|
|
5
|
+
// `--dry-run` answer without reading a very large file, and what lets a test drive the price
|
|
6
|
+
// without an account. Measuring a file on this machine is Node's, and is in
|
|
7
|
+
// `upload-price-node.ts`.
|
|
8
8
|
import { NmtsError } from "./errors.js";
|
|
9
9
|
import { DEFAULT_PART_BYTES, NCF3_SHAPE, sealedLenFor } from "./seal.js";
|
|
10
10
|
import { paddedPlaintextLen } from "./shared/lib/crypto/size-padding.js";
|
|
@@ -30,35 +30,6 @@ export function creditsFor(sealedBytes, epochs = UPLOAD_EPOCHS) {
|
|
|
30
30
|
const mib = Math.ceil(sealedBytes / CREDIT_BYTES);
|
|
31
31
|
return Math.ceil((mib * epochs) / UPLOAD_EPOCHS);
|
|
32
32
|
}
|
|
33
|
-
/**
|
|
34
|
-
* How big the local file is, with the two refusals that are worth their own words.
|
|
35
|
-
*
|
|
36
|
-
* ⛔ IT IS NOT READ HERE. A file large enough to need several parts is a file too large to hold,
|
|
37
|
-
* and the size is all that is needed to plan the upload and quote its price. The bytes are read
|
|
38
|
-
* later, a slice at a time, by the part that is being sealed.
|
|
39
|
-
*/
|
|
40
|
-
export function measureLocal(path) {
|
|
41
|
-
let stat;
|
|
42
|
-
try {
|
|
43
|
-
stat = statSync(path);
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
throw new NmtsError(`There is no file at ${path}.`, { exitCode: 4 });
|
|
47
|
-
}
|
|
48
|
-
if (stat.isDirectory()) {
|
|
49
|
-
throw new NmtsError(`${path} is a folder.`, {
|
|
50
|
-
exitCode: 4,
|
|
51
|
-
nextStep: "This version uploads one file at a time.",
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (stat.size === 0) {
|
|
55
|
-
throw new NmtsError(`${path} is empty.`, {
|
|
56
|
-
exitCode: 4,
|
|
57
|
-
nextStep: "The storage network has nothing to store and would refuse the reservation.",
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
return stat.size;
|
|
61
|
-
}
|
|
62
33
|
/**
|
|
63
34
|
* How much of the file goes into one part.
|
|
64
35
|
*
|