@needmoretruth/nmts-cli 0.34.4 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.ko.md +15 -15
- package/README.md +1 -1
- package/dist/arg-options.d.ts +167 -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/commands/extend.d.ts +2 -0
- package/dist/commands/extend.js +8 -1
- package/dist/commands/push-wallet.js +6 -0
- package/dist/commands/push.d.ts +2 -0
- 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 +105 -185
- package/dist/commands/put.d.ts +5 -7
- package/dist/commands/put.js +3 -26
- package/dist/commands/wallet-donate.d.ts +3 -0
- package/dist/commands/wallet-donate.js +6 -2
- 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-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/extend-plan.d.ts +2 -0
- package/dist/help.js +3 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +14 -1
- package/dist/main.js +4 -4
- package/dist/product.d.ts +1 -1
- package/dist/product.js +1 -1
- package/dist/risk.d.ts +4 -0
- package/dist/risk.js +6 -0
- 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-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 +2 -0
- 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 +57 -0
- package/dist/wallet-sign-seams.js +16 -0
- package/dist/wallet-sign.d.ts +3 -53
- package/dist/wallet-sign.js +22 -15
- package/dist/wallet.d.ts +13 -13
- package/dist/wallet.js +15 -15
- package/docs/commands/wallet.md +18 -4
- package/package.json +1 -1
|
@@ -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,118 @@
|
|
|
1
|
+
import type { OnCollision } from "./collision.ts";
|
|
2
|
+
import { type CryptoGlue } from "./crypto.ts";
|
|
3
|
+
import type { Network } from "./network.ts";
|
|
4
|
+
import type { PaddingRule } from "./shared/lib/crypto/size-padding.ts";
|
|
5
|
+
import { type FileUploadStep, type PlaintextSource } from "./upload-file.ts";
|
|
6
|
+
import { type StorageChoice, type UploadBudget, type WalletUploadReads } from "./upload-wallet-plan.ts";
|
|
7
|
+
import type { BlobProtocol, UploadApi } from "./upload-wire.ts";
|
|
8
|
+
import type { Spend } from "./wallet-grant.ts";
|
|
9
|
+
import type { SignBlobCertify, SignBlobRegister } from "./wallet-sign.ts";
|
|
10
|
+
/** The account this upload belongs to, and how it seals. */
|
|
11
|
+
export interface WalletPutContext {
|
|
12
|
+
/** ⛔ The NMTS key. It seals the file AND derives the wallet that pays. It is never written down. */
|
|
13
|
+
code: string;
|
|
14
|
+
apiKey: string;
|
|
15
|
+
server: string;
|
|
16
|
+
network: Network;
|
|
17
|
+
accountId: string;
|
|
18
|
+
crypt: CryptoGlue;
|
|
19
|
+
/** How much of the file goes into one part. */
|
|
20
|
+
partSize: number;
|
|
21
|
+
/** The rounding rule from the account's sealed list — it changes the stored size, so the price. */
|
|
22
|
+
rule: PaddingRule;
|
|
23
|
+
/** What to do about a name already in use. Absent = the machine's setting, as `addEntry` reads it. */
|
|
24
|
+
onCollision?: OnCollision | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Which of this key's wallets pays, by index (0 = the first one).
|
|
27
|
+
*
|
|
28
|
+
* ⛔ THE CALLER RESOLVES IT, BEFORE ANYTHING IS PRICED. It is the account's own number, kept in
|
|
29
|
+
* the sealed file list (`wallet-pay-index.ts`), and a library that guessed at it here would
|
|
30
|
+
* price one address and sign with another.
|
|
31
|
+
*/
|
|
32
|
+
wallet: number;
|
|
33
|
+
}
|
|
34
|
+
/** One file and where it goes — already resolved, because a library resolves nothing by asking. */
|
|
35
|
+
export interface WalletPutFile {
|
|
36
|
+
source: PlaintextSource;
|
|
37
|
+
name: string;
|
|
38
|
+
parentId: string | null;
|
|
39
|
+
/** The destination AS TYPED — part of the reservation key, so both runs of a resume agree. */
|
|
40
|
+
destination: string;
|
|
41
|
+
}
|
|
42
|
+
/** The chain, the signatures, the wire and the four things a caller may be told. */
|
|
43
|
+
export interface WalletPutSeams {
|
|
44
|
+
/** How many of the storage network's epochs to buy. Default `DEFAULT_UPLOAD_EPOCHS`. */
|
|
45
|
+
epochs?: string | number | undefined;
|
|
46
|
+
/** `fit`, `whole`, or a held resource's object id. Absent = buy new storage. */
|
|
47
|
+
storage?: string | undefined;
|
|
48
|
+
/** Stop at the review. Nothing is sealed, signed or sent, and no signer is even loaded. */
|
|
49
|
+
dryRun?: boolean | undefined;
|
|
50
|
+
/** ⚠ A SEAM, NOT AN OPTION — what the chain is asked. */
|
|
51
|
+
readChain?: ((network: Network, relayUrl: string) => WalletUploadReads | Promise<WalletUploadReads>) | undefined;
|
|
52
|
+
/** ⛔ SEPARATE FROM THE READS so a caller can prove the review stops before this. */
|
|
53
|
+
sign?: {
|
|
54
|
+
register: SignBlobRegister;
|
|
55
|
+
certify: SignBlobCertify;
|
|
56
|
+
} | undefined;
|
|
57
|
+
/** ⚠ SEAMS, NOT OPTIONS — the storage-network protocol and the server calls. */
|
|
58
|
+
protocol?: ((network: Network, bodyBytes: number, onSent: (sent: number, total: number) => void) => BlobProtocol & {
|
|
59
|
+
relayUrl: string;
|
|
60
|
+
}) | undefined;
|
|
61
|
+
api?: UploadApi | undefined;
|
|
62
|
+
/** Told as sealed bytes leave for the relay. */
|
|
63
|
+
onProgress?: ((sent: number, total: number) => void) | undefined;
|
|
64
|
+
/** Told about each step as it starts. */
|
|
65
|
+
onStep?: ((step: FileUploadStep) => void) | undefined;
|
|
66
|
+
/** Told the numbers the moment they are known — before the dry run returns and before a refusal. */
|
|
67
|
+
onReview?: ((review: WalletPutReview) => void) | undefined;
|
|
68
|
+
/** ⛔ THE GATE. Throwing here stops the upload with nothing signed. See the module header. */
|
|
69
|
+
agree?: ((review: WalletPutReview) => void) | undefined;
|
|
70
|
+
/** Told what left the wallet, after each signature. */
|
|
71
|
+
onSpend?: ((spend: Spend) => void) | undefined;
|
|
72
|
+
}
|
|
73
|
+
/** What this upload would buy, in the order somebody deciding needs it. Base units throughout. */
|
|
74
|
+
export interface WalletPutReview {
|
|
75
|
+
name: string;
|
|
76
|
+
/** Plaintext bytes. */
|
|
77
|
+
bytes: number;
|
|
78
|
+
/** Bytes the storage network holds, padding and sealing included. */
|
|
79
|
+
sealedBytes: number;
|
|
80
|
+
parts: number;
|
|
81
|
+
epochs: number;
|
|
82
|
+
/** The term in days, read from the network's own clock. */
|
|
83
|
+
days: string;
|
|
84
|
+
/** The epoch the storage runs to. */
|
|
85
|
+
endEpoch: number;
|
|
86
|
+
/** The relay's tip over every part, in MIST. Paid inside the register signatures. */
|
|
87
|
+
tipMist: bigint;
|
|
88
|
+
/** Where the storage comes from. */
|
|
89
|
+
storage: StorageChoice;
|
|
90
|
+
/** Free storage resources the wallet holds when none was asked for; null when unreadable. */
|
|
91
|
+
heldResources: number | null;
|
|
92
|
+
/** The wallet against the price: what is needed, what is held, and the shortfall if there is one. */
|
|
93
|
+
budget: UploadBudget;
|
|
94
|
+
}
|
|
95
|
+
/** A dry run's answer, or a finished upload's. The review is in both, so both report one price. */
|
|
96
|
+
export type WalletPutOutcome = {
|
|
97
|
+
kind: "review";
|
|
98
|
+
review: WalletPutReview;
|
|
99
|
+
} | {
|
|
100
|
+
kind: "uploaded";
|
|
101
|
+
review: WalletPutReview;
|
|
102
|
+
itemId: string;
|
|
103
|
+
/** The name it got — numbered if the one asked for was taken. */
|
|
104
|
+
savedAs: string;
|
|
105
|
+
/** The id of the file this one displaced into the trash, when the machine overwrites. */
|
|
106
|
+
replaced: string | null;
|
|
107
|
+
/** The file-list version this write produced. */
|
|
108
|
+
fileListVersion: number;
|
|
109
|
+
/** True when every part was already signed for by an earlier run: this call spent nothing. */
|
|
110
|
+
resumed: boolean;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Price, agree, sign, upload and record ONE file, paid from the wallet the NMTS key derives.
|
|
114
|
+
*
|
|
115
|
+
* ⛔ THE SIGNING MODULE IS LOADED ONLY AFTER `agree` HAS RETURNED. A dry run and a refusal never
|
|
116
|
+
* bring the code that can spend into memory.
|
|
117
|
+
*/
|
|
118
|
+
export declare function walletPut(ctx: WalletPutContext, file: WalletPutFile, seams?: WalletPutSeams): Promise<WalletPutOutcome>;
|