@needmoretruth/nmts-cli 0.34.3 → 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 +24 -0
- package/README.ko.md +15 -15
- package/README.md +1 -1
- package/dist/api-advice.js +4 -0
- 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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// WHO PAYS FOR AN UPLOAD, and which options mean anything once that is answered.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ THE OPTIONS ARE REFUSED, NOT IGNORED. `--epochs 12 --pay credits` is somebody asking for a
|
|
4
|
+
// term credits do not sell; accepting it silently would upload for a different length than the
|
|
5
|
+
// person typed and charge them for it. Every refusal here happens before a file is measured.
|
|
6
|
+
//
|
|
7
|
+
// ⚠ They moved out of `put.ts` on 2026-09-16 (`--wallet` joined them and that file has a ceiling).
|
|
8
|
+
// `put.ts` re-exports them, so `push.ts` and the tests spell the same path they always did.
|
|
9
|
+
import { NmtsError } from "../errors.js";
|
|
10
|
+
/** Who pays, or a refusal for a payer this tool does not know. */
|
|
11
|
+
export function payerOf(pay) {
|
|
12
|
+
if (pay === undefined || pay === "credits")
|
|
13
|
+
return "credits";
|
|
14
|
+
if (pay === "wallet")
|
|
15
|
+
return "wallet";
|
|
16
|
+
throw new NmtsError(`--pay takes credits or wallet, not "${pay}".`, {
|
|
17
|
+
exitCode: 2,
|
|
18
|
+
nextStep: `Nothing was sent. --pay wallet buys the storage from the wallet this NMTS key derives; without it credits pay.`,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** The two options that only mean something when the wallet pays, refused when it does not. */
|
|
22
|
+
export function refuseWalletOnlyOptions(options) {
|
|
23
|
+
if (options.wallet !== undefined) {
|
|
24
|
+
throw new NmtsError("--wallet only applies with --pay wallet: credits are not held in a wallet.", {
|
|
25
|
+
exitCode: 2,
|
|
26
|
+
nextStep: `Nothing was sent. Add --pay wallet to pay from one of this key's wallets.`,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if (options.epochs !== undefined) {
|
|
30
|
+
throw new NmtsError("--epochs only applies with --pay wallet: one credit buys a fixed term.", {
|
|
31
|
+
exitCode: 2,
|
|
32
|
+
nextStep: `Nothing was sent. Add --pay wallet to choose the term, or leave --epochs off to pay with credits.`,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (options.storage !== undefined) {
|
|
36
|
+
throw new NmtsError("--storage only applies with --pay wallet: credits buy storage from the treasury.", {
|
|
37
|
+
exitCode: 2,
|
|
38
|
+
nextStep: `Nothing was sent. Add --pay wallet to use a storage resource this wallet holds.`,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -15,6 +15,8 @@ export interface WalletPayOptions {
|
|
|
15
15
|
epochs?: string | number | undefined;
|
|
16
16
|
/** `fit`, `whole`, or a held resource's object id. Absent = buy new storage. */
|
|
17
17
|
storage?: string | undefined;
|
|
18
|
+
/** `--wallet N`: which wallet pays, this run only. Absent = the account's own number. */
|
|
19
|
+
wallet?: string | undefined;
|
|
18
20
|
/** The instant the wallet agreement is measured against. */
|
|
19
21
|
now?: number;
|
|
20
22
|
/** ⚠ A SEAM, NOT AN OPTION — no flag reaches it. */
|
|
@@ -54,16 +56,20 @@ export interface WalletUploadContext {
|
|
|
54
56
|
onCollision?: OnCollision | undefined;
|
|
55
57
|
/** The sealed list's account settings, as read for this run — the standing tip lives in them. */
|
|
56
58
|
settings?: AccountSettings | undefined;
|
|
59
|
+
/** Which of this key's wallets pays (`wallet-pay-index.ts`). Read from the same list as above. */
|
|
60
|
+
wallet: number;
|
|
57
61
|
progress: Progress;
|
|
58
62
|
say: (line: string) => void;
|
|
59
63
|
json: boolean;
|
|
60
64
|
}
|
|
61
65
|
export declare function putWithWallet(target: string | undefined, options: PutWalletOptions): Promise<number>;
|
|
62
66
|
/**
|
|
63
|
-
* Review, agree, upload and record ONE file. Null when `--dry-run`
|
|
67
|
+
* Review, agree, upload and record ONE file for a person who is watching. Null when `--dry-run`
|
|
68
|
+
* stopped at the review.
|
|
64
69
|
*
|
|
65
|
-
* ⛔ THE
|
|
66
|
-
*
|
|
70
|
+
* ⛔ THE AGREEMENT HANDED IN BELOW IS THIS MACHINE'S GRANT, held against the total the review
|
|
71
|
+
* names. It is the one gate between a program and somebody's wallet, and the reason it is
|
|
72
|
+
* handed to the library rather than living inside it: a library call has nobody to ask.
|
|
67
73
|
*/
|
|
68
74
|
export declare function uploadOneWithWallet(ctx: WalletUploadContext, file: WalletUploadFile, options: WalletPayOptions & {
|
|
69
75
|
dryRun?: boolean | undefined;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
// `nmts put <file> --pay wallet` — one file in, sealed on this machine, its storage bought by the
|
|
2
2
|
// PERSON'S OWN WALLET on the storage network instead of by credits.
|
|
3
3
|
//
|
|
4
|
-
// ⛔
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// stops at the review and never loads the signing module.
|
|
4
|
+
// ⛔ THE UPLOAD ITSELF IS NOT HERE. `upload-wallet-put.ts` prices, refuses, signs, pushes and
|
|
5
|
+
// records; this file is the terminal around it — the arguments, the printed review, the wallet
|
|
6
|
+
// agreement this machine holds, the spending ledger and the standing gift. The split is what
|
|
7
|
+
// lets the SDK pay from a wallet without a second copy of the order that keeps money safe.
|
|
9
8
|
//
|
|
10
|
-
// ⛔ THE
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
// wallet-paid commit does (`upload-steps.ts`).
|
|
9
|
+
// ⛔ THE REVIEW IS PRINTED BEFORE ANY REFUSAL, and the agreement is asked for only after a known
|
|
10
|
+
// shortfall has already stopped the run — so a person who cannot afford it reads the numbers
|
|
11
|
+
// instead of a question. `--dry-run` stops at the review and never loads the signing module.
|
|
14
12
|
//
|
|
15
13
|
// ⛔ A HELD RESOURCE IS OFFERED, NEVER CHOSEN FOR THE PERSON — the owner's rule for storage
|
|
16
14
|
// resources: the leftover is shown in bytes and the person decides. The review names how many
|
|
@@ -20,27 +18,22 @@ import { basename, resolve } from "node:path";
|
|
|
20
18
|
import { identityOf } from "../account.js";
|
|
21
19
|
import { requireAccountCode } from "../code-access.js";
|
|
22
20
|
import { parseAsked } from "../collision.js";
|
|
23
|
-
import {
|
|
21
|
+
import { loadCrypto } from "../crypto.js";
|
|
24
22
|
import { API_KEY_ENV_VAR, readCredentialsFile, resolveApiKey } from "../credentials.js";
|
|
25
23
|
import { NmtsError } from "../errors.js";
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
24
|
+
import { payingWalletIndex } from "../wallet-pay-index.js";
|
|
25
|
+
import { activeWalletOf } from "../shared/lib/drive/manifest-settings.js";
|
|
28
26
|
import { paddingRuleOf, readFileList } from "../manifest.js";
|
|
29
27
|
import { resolveNetwork } from "../network.js";
|
|
30
28
|
import { BINARY_NAME } from "../product.js";
|
|
31
29
|
import { Progress, silentSink, stderrSink } from "../progress.js";
|
|
32
|
-
import { sealedLenFor } from "../seal.js";
|
|
33
30
|
import { resolveServer } from "../server.js";
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import { walletRail } from "../upload-wallet.js";
|
|
40
|
-
import { chooseUploadEpochs, daysOf, describeUploadReview, parseStorageAsk, pickResource, uploadBudget, uploadShortfallNextStep, walPrice, } from "../upload-wallet-plan.js";
|
|
41
|
-
import { coinAmount, walletAddress } from "../wallet.js";
|
|
31
|
+
import { fileSource } from "../upload-file.js";
|
|
32
|
+
import { measureLocal, partSizeFor } from "../upload-price.js";
|
|
33
|
+
import { describeUploadReview } from "../upload-wallet-plan.js";
|
|
34
|
+
import { walletPut } from "../upload-wallet-put.js";
|
|
35
|
+
import { coinAmount } from "../wallet.js";
|
|
42
36
|
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
43
|
-
import { createBlobProtocol } from "../walrus-write.js";
|
|
44
37
|
import { folderIdFor } from "./put.js";
|
|
45
38
|
export async function putWithWallet(target, options) {
|
|
46
39
|
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
@@ -65,6 +58,12 @@ export async function putWithWallet(target, options) {
|
|
|
65
58
|
const asked = parseAsked(options.onCollision);
|
|
66
59
|
const list = await readFileList(server, key.key, resolved.code, identity.accountId);
|
|
67
60
|
const rule = paddingRuleOf(list.manifest?.settings);
|
|
61
|
+
// ⛔ WHICH WALLET PAYS, BEFORE ANYTHING IS PRICED — out of the list that was just read, so this
|
|
62
|
+
// costs no second request and the address in the review is the address that signs.
|
|
63
|
+
const wallet = await payingWalletIndex({
|
|
64
|
+
...options,
|
|
65
|
+
readActiveWallet: async () => activeWalletOf(list.manifest?.settings),
|
|
66
|
+
});
|
|
68
67
|
const name = options.name ?? basename(localPath);
|
|
69
68
|
const destination = (options.to ?? "").replace(/^\.?\//, "").replace(/\/$/, "");
|
|
70
69
|
const parentId = folderIdFor(options.to, list.manifest?.entries ?? []);
|
|
@@ -79,6 +78,7 @@ export async function putWithWallet(target, options) {
|
|
|
79
78
|
rule,
|
|
80
79
|
onCollision: asked,
|
|
81
80
|
settings: list.manifest?.settings,
|
|
81
|
+
wallet,
|
|
82
82
|
progress: new Progress(options.json === true ? silentSink() : stderrSink(), "uploading"),
|
|
83
83
|
say,
|
|
84
84
|
json: options.json === true,
|
|
@@ -100,174 +100,75 @@ export async function putWithWallet(target, options) {
|
|
|
100
100
|
return 0;
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
|
-
* Review, agree, upload and record ONE file. Null when `--dry-run`
|
|
103
|
+
* Review, agree, upload and record ONE file for a person who is watching. Null when `--dry-run`
|
|
104
|
+
* stopped at the review.
|
|
104
105
|
*
|
|
105
|
-
* ⛔ THE
|
|
106
|
-
*
|
|
106
|
+
* ⛔ THE AGREEMENT HANDED IN BELOW IS THIS MACHINE'S GRANT, held against the total the review
|
|
107
|
+
* names. It is the one gate between a program and somebody's wallet, and the reason it is
|
|
108
|
+
* handed to the library rather than living inside it: a library call has nobody to ask.
|
|
107
109
|
*/
|
|
108
110
|
export async function uploadOneWithWallet(ctx, file, options) {
|
|
109
111
|
const { say } = ctx;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
112
|
+
let outcome;
|
|
113
|
+
try {
|
|
114
|
+
outcome = await walletPut({
|
|
115
|
+
code: ctx.code,
|
|
116
|
+
apiKey: ctx.apiKey,
|
|
117
|
+
server: ctx.server,
|
|
118
|
+
network: ctx.network,
|
|
119
|
+
accountId: ctx.accountId,
|
|
120
|
+
crypt: ctx.crypt,
|
|
121
|
+
wallet: ctx.wallet,
|
|
122
|
+
partSize: ctx.partSize,
|
|
123
|
+
rule: ctx.rule,
|
|
124
|
+
onCollision: ctx.onCollision,
|
|
125
|
+
}, { source: fileSource(file.localPath, file.size), name: file.name, parentId: file.parentId, destination: file.destination }, {
|
|
126
|
+
epochs: options.epochs,
|
|
127
|
+
storage: options.storage,
|
|
128
|
+
dryRun: options.dryRun,
|
|
129
|
+
readChain: options.readChain,
|
|
130
|
+
sign: options.sign,
|
|
131
|
+
protocol: options.protocol,
|
|
132
|
+
api: options.api,
|
|
133
|
+
onProgress: (sent, total) => ctx.progress.update(sent, total),
|
|
134
|
+
onStep: (step) => tell(ctx, file.size, step),
|
|
135
|
+
onReview: (review) => {
|
|
136
|
+
if (ctx.json)
|
|
137
|
+
return;
|
|
138
|
+
describeUploadReview(say, {
|
|
139
|
+
name: review.name,
|
|
140
|
+
bytes: review.bytes,
|
|
141
|
+
parts: review.parts,
|
|
142
|
+
epochs: review.epochs,
|
|
143
|
+
days: review.days,
|
|
144
|
+
endEpoch: review.endEpoch,
|
|
145
|
+
walNeeded: review.budget.walNeededFrost,
|
|
146
|
+
tipMist: review.tipMist,
|
|
147
|
+
storage: review.storage,
|
|
148
|
+
heldResources: review.heldResources,
|
|
149
|
+
}, review.budget);
|
|
150
|
+
},
|
|
151
|
+
agree: (review) => {
|
|
152
|
+
requireWalletGrant("seal", { walFrost: review.budget.walNeededFrost, suiMist: review.budget.suiNeededMist }, new Date(options.now ?? Date.now()));
|
|
153
|
+
},
|
|
154
|
+
onSpend: recordWalletSpend,
|
|
126
155
|
});
|
|
127
156
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const quotes = await reads.quoteParts(sealedLens, epochs);
|
|
131
|
-
const address = await walletAddress(ctx.code);
|
|
132
|
-
let storage = { kind: "buy" };
|
|
133
|
-
let heldResources = null;
|
|
134
|
-
if (storageAsk !== null) {
|
|
135
|
-
const firstQuote = quotes[0];
|
|
136
|
-
const firstLen = sealedLens[0];
|
|
137
|
-
if (firstQuote === undefined || firstLen === undefined)
|
|
138
|
-
throw new NmtsError("unreachable: a plan with no part");
|
|
139
|
-
let resources;
|
|
140
|
-
try {
|
|
141
|
-
resources = await reads.readStorage(address);
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
throw new NmtsError("The storage resources this wallet holds could not be read.", {
|
|
145
|
-
exitCode: 1,
|
|
146
|
-
nextStep: `Nothing was signed. That is not the same as holding none. Cause: ${error instanceof Error ? error.message : String(error)}`,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
const encoded = await reads.encodedLength(address, firstLen);
|
|
150
|
-
storage = pickResource(storageAsk, resources, encoded, window.clock.current, endEpoch, firstQuote.writeFrost);
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
heldResources = await reads.readStorage(address).then((rows) => rows.filter((r) => statusOf(r, window.clock.current) === "usable").length, () => null);
|
|
154
|
-
}
|
|
155
|
-
const [purse, feeMist] = await Promise.all([
|
|
156
|
-
reads.readWallet(address),
|
|
157
|
-
reads.estimateRegisterGas({
|
|
158
|
-
sender: address,
|
|
159
|
-
sealedLen: Math.max(...sealedLens),
|
|
160
|
-
epochs,
|
|
161
|
-
storage: storage.kind === "buy" ? storage : { kind: "reuse", objectId: storage.objectId, cutToBytes: storage.cutToBytes, writeFrost: storage.writeFrost },
|
|
162
|
-
}),
|
|
163
|
-
]);
|
|
164
|
-
const budget = uploadBudget({ address, purse, feeMist, quotes, storage });
|
|
165
|
-
const tipMist = quotes.reduce((sum, q) => sum + q.tipMist, 0n);
|
|
166
|
-
const facts = {
|
|
167
|
-
bytes: file.size,
|
|
168
|
-
sealedBytes,
|
|
169
|
-
parts: plan.length,
|
|
170
|
-
epochs,
|
|
171
|
-
days: daysOf(window, epochs),
|
|
172
|
-
endEpoch,
|
|
173
|
-
paidFrom: "wallet",
|
|
174
|
-
priceFrost: budget.walNeededFrost.toString(),
|
|
175
|
-
priceWal: coinAmount(budget.walNeededFrost),
|
|
176
|
-
tipMist: tipMist.toString(),
|
|
177
|
-
tipSui: coinAmount(tipMist),
|
|
178
|
-
feeMist: feeMist === null ? null : feeMist.toString(),
|
|
179
|
-
feeSui: feeMist === null ? null : coinAmount(feeMist),
|
|
180
|
-
wallet: address,
|
|
181
|
-
walletWal: budget.walFrost === null ? null : coinAmount(budget.walFrost),
|
|
182
|
-
walletSui: budget.suiMist === null ? null : coinAmount(budget.suiMist),
|
|
183
|
-
storage: storage.kind === "buy"
|
|
184
|
-
? { kind: "buy" }
|
|
185
|
-
: { kind: "reuse", objectId: storage.objectId, cutToBytes: storage.cutToBytes, leftoverBytes: storage.leftoverBytes },
|
|
186
|
-
};
|
|
187
|
-
if (!ctx.json) {
|
|
188
|
-
describeUploadReview(say, { name: file.name, bytes: file.size, parts: plan.length, epochs, days: daysOf(window, epochs), endEpoch, walNeeded: walPrice(quotes, storage), tipMist, storage, heldResources }, budget);
|
|
157
|
+
finally {
|
|
158
|
+
ctx.progress.done();
|
|
189
159
|
}
|
|
190
|
-
|
|
160
|
+
const facts = factsOf(outcome.review);
|
|
161
|
+
if (outcome.kind === "review") {
|
|
191
162
|
if (ctx.json)
|
|
192
|
-
say(JSON.stringify({ dryRun: true, name:
|
|
163
|
+
say(JSON.stringify({ dryRun: true, name: outcome.review.name, ...facts, signed: false }));
|
|
193
164
|
else {
|
|
194
165
|
say(``);
|
|
195
|
-
if (budget.shortfall !== null)
|
|
196
|
-
say(` ⚠ ${budget.shortfall} As it stands, it would be refused.`);
|
|
166
|
+
if (outcome.review.budget.shortfall !== null)
|
|
167
|
+
say(` ⚠ ${outcome.review.budget.shortfall} As it stands, it would be refused.`);
|
|
197
168
|
say(` Nothing was signed and nothing was sent. Run the same command without --dry-run to upload it.`);
|
|
198
169
|
}
|
|
199
170
|
return null;
|
|
200
171
|
}
|
|
201
|
-
// ⛔ A WALLET KNOWN TO BE SHORT IS REFUSED BEFORE THE AGREEMENT IS ASKED FOR (`extend-budget.ts`).
|
|
202
|
-
if (budget.shortfall !== null) {
|
|
203
|
-
throw new NmtsError(budget.shortfall, { exitCode: 4, nextStep: uploadShortfallNextStep(budget) });
|
|
204
|
-
}
|
|
205
|
-
// ⛔ THE ONE GATE BETWEEN A PROGRAM AND SOMEBODY'S WALLET. Everything above is a read.
|
|
206
|
-
requireWalletGrant("seal", { walFrost: budget.walNeededFrost, suiMist: budget.suiNeededMist }, new Date(options.now ?? Date.now()));
|
|
207
|
-
const sign = options.sign ?? (await signers());
|
|
208
|
-
const derived = ctx.crypt.kdf_derive(ctx.crypt.account_code_parse(ctx.code));
|
|
209
|
-
const dataKey = derived.slice(DERIVED.dataKey[0], DERIVED.dataKey[1]);
|
|
210
|
-
derived.fill(0);
|
|
211
|
-
let result;
|
|
212
|
-
try {
|
|
213
|
-
result = await uploadFile({
|
|
214
|
-
api: options.api ?? createUploadApi(ctx.server, ctx.apiKey),
|
|
215
|
-
protocol,
|
|
216
|
-
crypt: ctx.crypt,
|
|
217
|
-
dataKey,
|
|
218
|
-
source: fileSource(file.localPath, file.size),
|
|
219
|
-
name: file.name,
|
|
220
|
-
parentId: file.parentId,
|
|
221
|
-
destination: file.destination,
|
|
222
|
-
relayUrl: protocol.relayUrl,
|
|
223
|
-
epochs,
|
|
224
|
-
currentEpoch: window.clock.current,
|
|
225
|
-
partSize: ctx.partSize,
|
|
226
|
-
padding: { rule: ctx.rule, unitBytes: CREDIT_BYTES },
|
|
227
|
-
onStep: (step) => tell(ctx, file.size, step),
|
|
228
|
-
buy: walletRail({
|
|
229
|
-
network: ctx.network,
|
|
230
|
-
code: ctx.code,
|
|
231
|
-
relayUrl: protocol.relayUrl,
|
|
232
|
-
epochs,
|
|
233
|
-
storage,
|
|
234
|
-
quotes,
|
|
235
|
-
feeMist,
|
|
236
|
-
signRegister: sign.register,
|
|
237
|
-
signCertify: sign.certify,
|
|
238
|
-
onSpend: recordWalletSpend,
|
|
239
|
-
}),
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
finally {
|
|
243
|
-
ctx.progress.done();
|
|
244
|
-
dataKey.fill(0);
|
|
245
|
-
}
|
|
246
|
-
const now = Date.now();
|
|
247
|
-
const added = await addEntry({
|
|
248
|
-
server: ctx.server,
|
|
249
|
-
apiKey: ctx.apiKey,
|
|
250
|
-
code: ctx.code,
|
|
251
|
-
accountId: ctx.accountId,
|
|
252
|
-
...(ctx.onCollision !== undefined ? { onCollision: ctx.onCollision } : {}),
|
|
253
|
-
entry: {
|
|
254
|
-
id: result.itemId,
|
|
255
|
-
parentId: file.parentId,
|
|
256
|
-
kind: 1,
|
|
257
|
-
name: result.entry.name,
|
|
258
|
-
size: result.entry.plaintextLen,
|
|
259
|
-
createdAt: now,
|
|
260
|
-
updatedAt: now,
|
|
261
|
-
dekWrapped: result.entry.dekWrapped,
|
|
262
|
-
contentHashCt: result.entry.contentHashCt,
|
|
263
|
-
},
|
|
264
|
-
});
|
|
265
|
-
// ⛔ ONLY NOW, AND EVERY PART — the same order `put.ts` keeps and for the same reason.
|
|
266
|
-
clearItemRecord(result.fileKey);
|
|
267
|
-
for (const record of partKeysOf(result.fileKey, result.parts))
|
|
268
|
-
clearReservation(record);
|
|
269
|
-
if (added.replaced)
|
|
270
|
-
await setTrashed(ctx.server, ctx.apiKey, added.replaced.id, true);
|
|
271
172
|
// ⛔ THE STANDING SHARE, AFTER THE PAYMENT AND NEVER INSIDE IT: a gift that fails must not fail
|
|
272
173
|
// the upload. A resumed run paid nothing now, so it owes nothing now. In --json the tip speaks
|
|
273
174
|
// on stderr — stdout carries the machine's one answer and nothing else.
|
|
@@ -276,11 +177,37 @@ export async function uploadOneWithWallet(ctx, file, options) {
|
|
|
276
177
|
network: ctx.network,
|
|
277
178
|
code: ctx.code,
|
|
278
179
|
settings: ctx.settings,
|
|
279
|
-
|
|
180
|
+
wallet: ctx.wallet,
|
|
181
|
+
paidWalFrost: outcome.resumed ? 0n : outcome.review.budget.walNeededFrost,
|
|
280
182
|
say: ctx.json ? (line) => void process.stderr.write(`${line}\n`) : say,
|
|
281
183
|
...(options.tip ?? {}),
|
|
282
184
|
});
|
|
283
|
-
return { itemId:
|
|
185
|
+
return { itemId: outcome.itemId, savedAs: outcome.savedAs, replaced: outcome.replaced, seq: outcome.fileListVersion, resumed: outcome.resumed, facts };
|
|
186
|
+
}
|
|
187
|
+
/** The review as `--json` prints it: base units as strings, with the coin amounts beside them. */
|
|
188
|
+
function factsOf(review) {
|
|
189
|
+
const { budget, storage } = review;
|
|
190
|
+
return {
|
|
191
|
+
bytes: review.bytes,
|
|
192
|
+
sealedBytes: review.sealedBytes,
|
|
193
|
+
parts: review.parts,
|
|
194
|
+
epochs: review.epochs,
|
|
195
|
+
days: review.days,
|
|
196
|
+
endEpoch: review.endEpoch,
|
|
197
|
+
paidFrom: "wallet",
|
|
198
|
+
priceFrost: budget.walNeededFrost.toString(),
|
|
199
|
+
priceWal: coinAmount(budget.walNeededFrost),
|
|
200
|
+
tipMist: review.tipMist.toString(),
|
|
201
|
+
tipSui: coinAmount(review.tipMist),
|
|
202
|
+
feeMist: budget.feeMist === null ? null : budget.feeMist.toString(),
|
|
203
|
+
feeSui: budget.feeMist === null ? null : coinAmount(budget.feeMist),
|
|
204
|
+
wallet: budget.address,
|
|
205
|
+
walletWal: budget.walFrost === null ? null : coinAmount(budget.walFrost),
|
|
206
|
+
walletSui: budget.suiMist === null ? null : coinAmount(budget.suiMist),
|
|
207
|
+
storage: storage.kind === "buy"
|
|
208
|
+
? { kind: "buy" }
|
|
209
|
+
: { kind: "reuse", objectId: storage.objectId, cutToBytes: storage.cutToBytes, leftoverBytes: storage.leftoverBytes },
|
|
210
|
+
};
|
|
284
211
|
}
|
|
285
212
|
/** Each step, for a person watching. The signatures are named as what they are. */
|
|
286
213
|
function tell(ctx, size, step) {
|
|
@@ -307,10 +234,3 @@ function tell(ctx, size, step) {
|
|
|
307
234
|
if (step.step === "committing")
|
|
308
235
|
say(` saving to the drive`);
|
|
309
236
|
}
|
|
310
|
-
async function defaultReads(network, relayUrl) {
|
|
311
|
-
return (await import("../upload-wallet-chain.js")).walletUploadReads(network, relayUrl);
|
|
312
|
-
}
|
|
313
|
-
async function signers() {
|
|
314
|
-
const signing = await import("../wallet-sign.js");
|
|
315
|
-
return { register: signing.signBlobRegister, certify: signing.signBlobCertify };
|
|
316
|
-
}
|
package/dist/commands/put.d.ts
CHANGED
|
@@ -35,16 +35,14 @@ export interface PutOptions {
|
|
|
35
35
|
epochs?: string | number | undefined;
|
|
36
36
|
/** `--pay wallet`: a held storage resource to use — `fit`, `whole`, or its object id. */
|
|
37
37
|
storage?: string | undefined;
|
|
38
|
+
/** `--pay wallet`: which of this key's wallets pays, this run only. Absent = the account's own. */
|
|
39
|
+
wallet?: string | undefined;
|
|
38
40
|
json?: boolean;
|
|
39
41
|
write?: (line: string) => void;
|
|
40
42
|
}
|
|
41
|
-
/** Who pays,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
export declare function refuseWalletOnlyOptions(options: {
|
|
45
|
-
epochs?: string | number | undefined;
|
|
46
|
-
storage?: string | undefined;
|
|
47
|
-
}): void;
|
|
43
|
+
/** Who pays, and the options that lose their meaning under that answer — the rule is in `put-payer.ts`; this is its one road. */
|
|
44
|
+
import { payerOf, refuseWalletOnlyOptions } from "./put-payer.ts";
|
|
45
|
+
export { payerOf, refuseWalletOnlyOptions };
|
|
48
46
|
/**
|
|
49
47
|
* The folder id `--to` names, or null for the root. Refuses rather than guessing.
|
|
50
48
|
*
|
package/dist/commands/put.js
CHANGED
|
@@ -31,32 +31,9 @@ import { createUploadApi } from "../upload-api.js";
|
|
|
31
31
|
import { fileSource, partKeysOf, uploadFile } from "../upload-file.js";
|
|
32
32
|
import { CREDIT_BYTES, creditsFor, measureLocal, partSizeFor, planAndPrice, UPLOAD_EPOCHS, } from "../upload-price.js";
|
|
33
33
|
import { createBlobProtocol, readCurrentEpoch } from "../walrus-write.js";
|
|
34
|
-
/** Who pays,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
return "credits";
|
|
38
|
-
if (pay === "wallet")
|
|
39
|
-
return "wallet";
|
|
40
|
-
throw new NmtsError(`--pay takes credits or wallet, not "${pay}".`, {
|
|
41
|
-
exitCode: 2,
|
|
42
|
-
nextStep: `Nothing was sent. --pay wallet buys the storage from the wallet this NMTS key derives; without it credits pay.`,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
/** The two options that only mean something when the wallet pays, refused when it does not. */
|
|
46
|
-
export function refuseWalletOnlyOptions(options) {
|
|
47
|
-
if (options.epochs !== undefined) {
|
|
48
|
-
throw new NmtsError("--epochs only applies with --pay wallet: one credit buys a fixed term.", {
|
|
49
|
-
exitCode: 2,
|
|
50
|
-
nextStep: `Nothing was sent. Add --pay wallet to choose the term, or leave --epochs off to pay with credits.`,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (options.storage !== undefined) {
|
|
54
|
-
throw new NmtsError("--storage only applies with --pay wallet: credits buy storage from the treasury.", {
|
|
55
|
-
exitCode: 2,
|
|
56
|
-
nextStep: `Nothing was sent. Add --pay wallet to use a storage resource this wallet holds.`,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
34
|
+
/** Who pays, and the options that lose their meaning under that answer — the rule is in `put-payer.ts`; this is its one road. */
|
|
35
|
+
import { payerOf, refuseWalletOnlyOptions } from "./put-payer.js";
|
|
36
|
+
export { payerOf, refuseWalletOnlyOptions };
|
|
60
37
|
/**
|
|
61
38
|
* The folder id `--to` names, or null for the root. Refuses rather than guessing.
|
|
62
39
|
*
|
|
@@ -15,7 +15,10 @@ export interface WalletDonateOptions {
|
|
|
15
15
|
yes?: boolean;
|
|
16
16
|
dryRun?: boolean;
|
|
17
17
|
feeCap?: string | undefined;
|
|
18
|
+
/** `--wallet N`: which wallet the gift comes from, this run only. Absent = the account's number. */
|
|
19
|
+
wallet?: string | undefined;
|
|
18
20
|
/** ⚠ SEAMS, NOT OPTIONS — no flag reaches them. */
|
|
21
|
+
readActiveWallet?: () => Promise<number>;
|
|
19
22
|
readDonation?: (server: string) => Promise<DonationConfig>;
|
|
20
23
|
readChain?: (network: Network) => SendReads | Promise<SendReads>;
|
|
21
24
|
sign?: SignTransfer;
|
|
@@ -25,6 +25,7 @@ import { THANKS_EN, THANKS_KO } from "../standing-tip.js";
|
|
|
25
25
|
import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
26
26
|
import { clampGasBudgetMist, isValidSuiAddress, parseTokenAmountToBaseUnits, validateSendForm, } from "../shared/lib/wallet/send-rules.js";
|
|
27
27
|
import { coinAmount, walCoinType, walletAddress } from "../wallet.js";
|
|
28
|
+
import { payingWalletIndex } from "../wallet-pay-index.js";
|
|
28
29
|
/** The four facts and the promise, as the screens say them, in the order a person reads them. */
|
|
29
30
|
export const GIFT_NOTICES = [
|
|
30
31
|
"This is a voluntary gift. Nothing is provided in return, it is non-refundable, and it cannot be undone once sent.",
|
|
@@ -57,7 +58,10 @@ export async function walletDonate(operands, options = {}) {
|
|
|
57
58
|
throw new NmtsError("Say how much.", { exitCode: 2, nextStep: `\`${BINARY_NAME} wallet donate ${coin} <amount> --yes\`` });
|
|
58
59
|
}
|
|
59
60
|
const resolved = await requireAccountCode();
|
|
60
|
-
|
|
61
|
+
// ⛔ A gift leaves the same wallet storage is paid from, and it is resolved before the review is
|
|
62
|
+
// priced — so the address a person reads is the address the coins leave.
|
|
63
|
+
const wallet = await payingWalletIndex(options);
|
|
64
|
+
const address = await walletAddress(resolved.code, wallet);
|
|
61
65
|
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
62
66
|
const server = resolveServer(options.server ?? stored?.server);
|
|
63
67
|
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
@@ -143,7 +147,7 @@ export async function walletDonate(operands, options = {}) {
|
|
|
143
147
|
});
|
|
144
148
|
}
|
|
145
149
|
const sign = options.sign ?? (await import("../wallet-sign.js")).signTransfer;
|
|
146
|
-
const digest = await sign({ network, code: resolved.code, shape });
|
|
150
|
+
const digest = await sign({ network, code: resolved.code, wallet, shape });
|
|
147
151
|
if (options.json) {
|
|
148
152
|
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
149
153
|
return 0;
|
|
@@ -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>;
|