@needmoretruth/nmts-cli 0.17.2 → 0.34.2
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 +125 -517
- package/CHANGELOG.md +104 -0
- package/README.ko.md +377 -469
- package/README.md +394 -474
- package/dist/account-proof.d.ts +29 -0
- package/dist/account-proof.js +6 -6
- package/dist/account.d.ts +18 -0
- package/dist/account.js +5 -5
- package/dist/agent-host.d.ts +71 -0
- package/dist/agent-host.js +169 -0
- package/dist/api-advice.d.ts +2 -0
- package/dist/api-advice.js +210 -0
- package/dist/api-key.d.ts +79 -0
- package/dist/api-key.js +7 -7
- package/dist/api-refusal.d.ts +14 -0
- package/dist/api-refusal.js +8 -0
- package/dist/api.d.ts +114 -0
- package/dist/api.js +59 -162
- package/dist/args.d.ts +160 -0
- package/dist/args.js +118 -30
- package/dist/artifact-about.d.ts +49 -0
- package/dist/autonomy.d.ts +39 -0
- package/dist/autonomy.js +110 -32
- package/dist/code-access.d.ts +29 -0
- package/dist/code-access.js +3 -3
- package/dist/code-vault.d.ts +38 -0
- package/dist/code-vault.js +8 -8
- package/dist/collision.d.ts +67 -0
- package/dist/collision.js +1 -1
- package/dist/commands/accept-terms.d.ts +11 -0
- package/dist/commands/accept-terms.js +106 -0
- package/dist/commands/balance.d.ts +7 -0
- package/dist/commands/balance.js +54 -1
- package/dist/commands/create-code-file.d.ts +24 -0
- package/dist/commands/create-code-file.js +93 -0
- package/dist/commands/create-link.d.ts +10 -0
- package/dist/commands/create-link.js +232 -0
- package/dist/commands/create.d.ts +20 -0
- package/dist/commands/create.js +43 -102
- package/dist/commands/credits.d.ts +9 -0
- package/dist/commands/credits.js +110 -0
- package/dist/commands/delete-account.d.ts +11 -0
- package/dist/commands/delete-account.js +63 -0
- package/dist/commands/deposit.d.ts +7 -0
- package/dist/commands/deposit.js +85 -0
- package/dist/commands/devices.d.ts +12 -0
- package/dist/commands/devices.js +144 -0
- package/dist/commands/documents.d.ts +35 -0
- package/dist/commands/documents.js +212 -0
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.js +10 -2
- package/dist/commands/erase.d.ts +13 -0
- package/dist/commands/erase.js +193 -0
- package/dist/commands/expiring.d.ts +19 -0
- package/dist/commands/extend.d.ts +33 -0
- package/dist/commands/extend.js +54 -15
- package/dist/commands/get.d.ts +18 -0
- package/dist/commands/key-manage.d.ts +11 -0
- package/dist/commands/key-manage.js +162 -0
- package/dist/commands/key.d.ts +28 -0
- package/dist/commands/key.js +249 -0
- package/dist/commands/kit.d.ts +11 -0
- package/dist/commands/kit.js +7 -7
- package/dist/commands/listfile.d.ts +13 -0
- package/dist/commands/listfile.js +12 -5
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.js +19 -19
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +1 -1
- package/dist/commands/losses.d.ts +12 -0
- package/dist/commands/losses.js +180 -0
- package/dist/commands/ls.d.ts +23 -0
- package/dist/commands/marks-dispatch.d.ts +2 -0
- package/dist/commands/marks-dispatch.js +51 -0
- package/dist/commands/marks.d.ts +46 -0
- package/dist/commands/marks.js +80 -0
- package/dist/commands/mcp.d.ts +24 -0
- package/dist/commands/mcp.js +70 -5
- package/dist/commands/mode.d.ts +8 -0
- package/dist/commands/mode.js +60 -24
- package/dist/commands/on-collision.d.ts +6 -0
- package/dist/commands/organise.d.ts +46 -0
- package/dist/commands/padding.d.ts +7 -0
- package/dist/commands/padding.js +114 -0
- package/dist/commands/public-code.d.ts +9 -0
- package/dist/commands/public-code.js +8 -8
- package/dist/commands/pull.d.ts +27 -0
- package/dist/commands/push-wallet.d.ts +19 -0
- package/dist/commands/push-wallet.js +69 -0
- package/dist/commands/push.d.ts +47 -0
- package/dist/commands/push.js +37 -7
- package/dist/commands/put-wallet.d.ts +77 -0
- package/dist/commands/put-wallet.js +316 -0
- package/dist/commands/put.d.ts +56 -0
- package/dist/commands/put.js +53 -6
- package/dist/commands/rebuild.d.ts +18 -0
- package/dist/commands/rebuild.js +108 -10
- package/dist/commands/receive.d.ts +24 -0
- package/dist/commands/recovery-dispatch.d.ts +2 -0
- package/dist/commands/recovery-dispatch.js +34 -0
- package/dist/commands/recovery-list.d.ts +11 -0
- package/dist/commands/recovery-list.js +6 -6
- package/dist/commands/recovery.d.ts +25 -0
- package/dist/commands/recovery.js +2 -2
- package/dist/commands/rollback.d.ts +9 -0
- package/dist/commands/rollback.js +118 -0
- package/dist/commands/s3.d.ts +23 -0
- package/dist/commands/s3.js +6 -6
- package/dist/commands/settings.d.ts +5 -0
- package/dist/commands/settings.js +21 -6
- package/dist/commands/share-dispatch.d.ts +2 -0
- package/dist/commands/share-dispatch.js +46 -0
- package/dist/commands/share.d.ts +14 -0
- package/dist/commands/share.js +13 -3
- package/dist/commands/shares-sent.d.ts +18 -0
- package/dist/commands/shares-sent.js +95 -0
- package/dist/commands/support.d.ts +24 -0
- package/dist/commands/support.js +321 -0
- package/dist/commands/sweep.d.ts +11 -0
- package/dist/commands/tip.d.ts +11 -0
- package/dist/commands/tip.js +86 -0
- package/dist/commands/trash.d.ts +17 -0
- package/dist/commands/trash.js +1 -1
- package/dist/commands/trial.d.ts +7 -0
- package/dist/commands/trial.js +16 -18
- package/dist/commands/unlock.d.ts +16 -0
- package/dist/commands/unlock.js +159 -0
- package/dist/commands/update.d.ts +42 -0
- package/dist/commands/update.js +1 -1
- package/dist/commands/usage.d.ts +8 -0
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.js +10 -6
- package/dist/commands/wallet-activity.d.ts +26 -0
- package/dist/commands/wallet-activity.js +117 -0
- package/dist/commands/wallet-donate.d.ts +26 -0
- package/dist/commands/wallet-donate.js +163 -0
- package/dist/commands/wallet-hall.d.ts +35 -0
- package/dist/commands/wallet-hall.js +214 -0
- package/dist/commands/wallet-send.d.ts +22 -0
- package/dist/commands/wallet-send.js +153 -0
- package/dist/commands/wallet-storage-ops.d.ts +24 -0
- package/dist/commands/wallet-storage-ops.js +215 -0
- package/dist/commands/wallet-storage.d.ts +18 -0
- package/dist/commands/wallet-storage.js +95 -0
- package/dist/commands/wallet-swap.d.ts +32 -0
- package/dist/commands/wallet-swap.js +287 -0
- package/dist/commands/wallet.d.ts +36 -0
- package/dist/commands/wallet.js +57 -15
- package/dist/commands/whoami.d.ts +10 -0
- package/dist/commands/whoami.js +24 -6
- package/dist/consent.d.ts +140 -0
- package/dist/consent.js +89 -30
- package/dist/credentials.d.ts +146 -0
- package/dist/credentials.js +10 -10
- package/dist/crypto-surface.d.ts +197 -0
- package/dist/crypto.d.ts +81 -0
- package/dist/crypto.js +13 -0
- package/dist/deposit.d.ts +25 -0
- package/dist/deposit.js +78 -0
- package/dist/download-part.d.ts +40 -0
- package/dist/download-sink.d.ts +76 -0
- package/dist/download.d.ts +79 -0
- package/dist/download.js +3 -3
- package/dist/drive-paths.d.ts +73 -0
- package/dist/environment.d.ts +62 -0
- package/dist/environment.js +29 -9
- package/dist/errors.d.ts +22 -0
- package/dist/errors.js +3 -3
- package/dist/exit.d.ts +44 -0
- package/dist/expiry.d.ts +58 -0
- package/dist/extend-budget.d.ts +35 -0
- package/dist/extend-budget.js +81 -0
- package/dist/extend-chain.d.ts +43 -0
- package/dist/extend-chain.js +40 -3
- package/dist/extend-plan.d.ts +148 -0
- package/dist/extend-plan.js +1 -1
- package/dist/gate.d.ts +18 -0
- package/dist/gate.js +100 -0
- package/dist/guards.d.ts +2 -0
- package/dist/help.d.ts +20 -0
- package/dist/help.js +199 -33
- package/dist/human-check.d.ts +23 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.js +49 -0
- package/dist/item-trash.d.ts +1 -0
- package/dist/kit-file.d.ts +56 -0
- package/dist/kit-file.js +10 -10
- package/dist/list-file.d.ts +88 -0
- package/dist/list-file.js +90 -7
- package/dist/list-view-find.d.ts +15 -0
- package/dist/list-view-order.d.ts +32 -0
- package/dist/main.d.ts +12 -0
- package/dist/main.js +112 -113
- package/dist/manifest-chunk-cache.d.ts +14 -0
- package/dist/manifest-chunk-cache.js +103 -0
- package/dist/manifest-chunk-flow.d.ts +81 -0
- package/dist/manifest-chunk-flow.js +307 -0
- package/dist/manifest-create.d.ts +23 -0
- package/dist/manifest-create.js +26 -30
- package/dist/manifest-write.d.ts +161 -0
- package/dist/manifest-write.js +42 -33
- package/dist/manifest.d.ts +82 -0
- package/dist/manifest.js +69 -21
- package/dist/mark-render.d.ts +26 -0
- package/dist/mcp-args.d.ts +17 -0
- package/dist/mcp-ask.d.ts +43 -0
- package/dist/mcp-ask.js +100 -0
- package/dist/mcp-tools/context.d.ts +46 -0
- package/dist/mcp-tools/credits.d.ts +3 -0
- package/dist/mcp-tools/credits.js +61 -0
- package/dist/mcp-tools/files.d.ts +3 -0
- package/dist/mcp-tools/files.js +34 -0
- package/dist/mcp-tools/organise.d.ts +3 -0
- package/dist/mcp-tools/organise.js +82 -2
- package/dist/mcp-tools/reads.d.ts +3 -0
- package/dist/mcp-tools/reads.js +132 -4
- package/dist/mcp-tools/share.d.ts +3 -0
- package/dist/mcp-tools/share.js +26 -7
- package/dist/mcp-tools/support.d.ts +3 -0
- package/dist/mcp-tools/support.js +140 -0
- package/dist/mcp-tools/tiers.d.ts +31 -0
- package/dist/mcp-tools/tiers.js +147 -0
- package/dist/mcp.d.ts +80 -0
- package/dist/mcp.js +110 -9
- package/dist/net-retry.d.ts +38 -0
- package/dist/network.d.ts +10 -0
- package/dist/notice.d.ts +7 -0
- package/dist/notice.js +7 -7
- package/dist/product.d.ts +29 -0
- package/dist/product.js +1 -1
- package/dist/progress.d.ts +37 -0
- package/dist/prompt.d.ts +31 -0
- package/dist/prompt.js +1 -1
- package/dist/rebuild-key-check.d.ts +26 -0
- package/dist/rebuild-key-check.js +90 -0
- package/dist/rebuild.d.ts +87 -0
- package/dist/rebuild.js +26 -4
- package/dist/recovery-assemble.d.ts +40 -0
- package/dist/recovery-assemble.js +1 -1
- package/dist/recovery-build.d.ts +40 -0
- package/dist/recovery-map-file.d.ts +53 -0
- package/dist/recovery-map-file.js +4 -4
- package/dist/recovery-map.d.ts +197 -0
- package/dist/recovery-release.d.ts +104 -0
- package/dist/recovery-seq.d.ts +10 -0
- package/dist/recovery-source.d.ts +50 -0
- package/dist/redact.d.ts +70 -0
- package/dist/redact.js +212 -0
- package/dist/registration.d.ts +30 -0
- package/dist/registration.js +5 -5
- package/dist/risk.d.ts +380 -0
- package/dist/risk.js +206 -0
- package/dist/run-log.d.ts +78 -0
- package/dist/run-log.js +301 -0
- package/dist/s3/listing.d.ts +47 -0
- package/dist/s3/multipart.d.ts +21 -0
- package/dist/s3/response-sink.d.ts +8 -0
- package/dist/s3/same-file.d.ts +54 -0
- package/dist/s3/server.d.ts +58 -0
- package/dist/s3/server.js +2 -2
- package/dist/s3/sigv4.d.ts +46 -0
- package/dist/s3/sigv4.js +1 -1
- package/dist/s3/staging.d.ts +10 -0
- package/dist/s3/xml.d.ts +28 -0
- package/dist/safe-path.d.ts +41 -0
- package/dist/seal.d.ts +120 -0
- package/dist/secret-reader.d.ts +28 -0
- package/dist/server.d.ts +9 -0
- package/dist/session.d.ts +32 -0
- package/dist/session.js +1 -1
- package/dist/setup-questions.d.ts +7 -0
- package/dist/share.d.ts +108 -0
- package/dist/share.js +1 -1
- package/dist/shared/lib/crypto/size-padding.d.ts +55 -0
- package/dist/shared/lib/crypto/size-padding.js +5 -0
- package/dist/shared/lib/drive/manifest-chunks.d.ts +95 -0
- package/dist/shared/lib/drive/manifest-chunks.js +252 -0
- package/dist/shared/lib/drive/manifest-codec.d.ts +214 -0
- package/dist/shared/lib/drive/manifest-codec.js +7 -7
- package/dist/shared/lib/drive/manifest-index.d.ts +98 -0
- package/dist/shared/lib/drive/manifest-ops.d.ts +167 -0
- package/dist/shared/lib/drive/manifest-ops.js +9 -4
- package/dist/shared/lib/drive/manifest-pack.d.ts +65 -0
- package/dist/shared/lib/drive/manifest-pack.js +287 -0
- package/dist/shared/lib/drive/manifest-settings.d.ts +113 -0
- package/dist/shared/lib/drive/manifest-settings.js +87 -6
- package/dist/shared/lib/drive/name-conflict.d.ts +76 -0
- package/dist/shared/lib/drive/rebuild-verify.d.ts +73 -0
- package/dist/shared/lib/drive/rebuild-verify.js +112 -0
- package/dist/shared/lib/drive/unique-name.d.ts +9 -0
- package/dist/shared/lib/drive/zstd.d.ts +27 -0
- package/dist/shared/lib/drive/zstd.js +74 -0
- package/dist/shared/lib/extend/epochs.d.ts +205 -0
- package/dist/shared/lib/net/retry-budget.d.ts +79 -0
- package/dist/shared/lib/share/shared-file-info.d.ts +21 -0
- package/dist/shared/lib/storage-control/chain.d.ts +57 -0
- package/dist/shared/lib/storage-control/chain.js +82 -0
- package/dist/shared/lib/storage-control/plan.d.ts +40 -0
- package/dist/shared/lib/storage-control/plan.js +72 -0
- package/dist/shared/lib/storage-control/reuse.d.ts +72 -0
- package/dist/shared/lib/storage-control/reuse.js +95 -0
- package/dist/shared/lib/storage-network.d.ts +25 -0
- package/dist/shared/lib/upload/part-plan.d.ts +24 -0
- package/dist/shared/lib/wallet/activity.d.ts +138 -0
- package/dist/shared/lib/wallet/activity.js +208 -0
- package/dist/shared/lib/wallet/send-rules.d.ts +68 -0
- package/dist/shared/lib/wallet/send-rules.js +116 -0
- package/dist/shared/lib/wallet/swap-rules.d.ts +115 -0
- package/dist/shared/lib/wallet/swap-rules.js +199 -0
- package/dist/shared/lib/wallet/tip.d.ts +14 -0
- package/dist/shared/lib/wallet/tip.js +34 -0
- package/dist/shared/lib/wallet/venue-ids.d.ts +23 -0
- package/dist/shared/lib/wallet/venue-ids.js +53 -0
- package/dist/standing-tip.d.ts +24 -0
- package/dist/standing-tip.js +52 -0
- package/dist/stdout.d.ts +38 -0
- package/dist/storage-control-chain.d.ts +38 -0
- package/dist/storage-control-chain.js +78 -0
- package/dist/sui-rpc.d.ts +15 -0
- package/dist/sui-rpc.js +59 -0
- package/dist/support-attach.d.ts +24 -0
- package/dist/support-attach.js +72 -0
- package/dist/support-copy.d.ts +42 -0
- package/dist/support-copy.js +109 -0
- package/dist/support-wire.d.ts +49 -0
- package/dist/support-wire.js +194 -0
- package/dist/trash-sweep.d.ts +55 -0
- package/dist/units.d.ts +2 -0
- package/dist/update-check.d.ts +50 -0
- package/dist/update-source.d.ts +63 -0
- package/dist/upload-api.d.ts +3 -0
- package/dist/upload-file.d.ts +97 -0
- package/dist/upload-file.js +3 -2
- package/dist/upload-price.d.ts +50 -0
- package/dist/upload-steps.d.ts +26 -0
- package/dist/upload-steps.js +29 -9
- package/dist/upload-store.d.ts +182 -0
- package/dist/upload-wallet-chain.d.ts +54 -0
- package/dist/upload-wallet-chain.js +203 -0
- package/dist/upload-wallet-plan.d.ts +128 -0
- package/dist/upload-wallet-plan.js +184 -0
- package/dist/upload-wallet.d.ts +24 -0
- package/dist/upload-wallet.js +208 -0
- package/dist/upload-wire.d.ts +244 -0
- package/dist/upload.d.ts +18 -0
- package/dist/upload.js +10 -0
- package/dist/usage-report.d.ts +35 -0
- package/dist/wallet-activity-chain.d.ts +5 -0
- package/dist/wallet-activity-chain.js +31 -0
- package/dist/wallet-chain.d.ts +19 -0
- package/dist/wallet-chain.js +2 -2
- package/dist/wallet-grant.d.ts +50 -0
- package/dist/wallet-grant.js +218 -0
- package/dist/wallet-send-chain.d.ts +24 -0
- package/dist/wallet-send-chain.js +49 -0
- package/dist/wallet-sign.d.ts +133 -0
- package/dist/wallet-sign.js +317 -0
- package/dist/wallet-storage-chain.d.ts +3 -0
- package/dist/wallet-storage-chain.js +20 -0
- package/dist/wallet-swap-chain.d.ts +52 -0
- package/dist/wallet-swap-chain.js +186 -0
- package/dist/wallet-swap-quote.d.ts +22 -0
- package/dist/wallet-swap-quote.js +126 -0
- package/dist/wallet.d.ts +135 -0
- package/dist/wallet.js +5 -5
- package/dist/walrus-write.d.ts +56 -0
- package/dist/walrus-write.js +5 -4
- package/dist/walrus.d.ts +86 -0
- package/dist/walrus.js +48 -10
- package/dist/zstd-node.d.ts +11 -0
- package/dist/zstd-node.js +71 -0
- package/docs/commands/accept-terms.md +15 -0
- package/docs/commands/create.md +17 -0
- package/docs/commands/credits.md +34 -0
- package/docs/commands/delete-account.md +14 -0
- package/docs/commands/deposit.md +27 -0
- package/docs/commands/devices.md +9 -0
- package/docs/commands/documents.md +10 -0
- package/docs/commands/env.md +21 -0
- package/docs/commands/extend.md +27 -0
- package/docs/commands/folders.md +10 -0
- package/docs/commands/get.md +14 -0
- package/docs/commands/key.md +14 -0
- package/docs/commands/login.md +23 -0
- package/docs/commands/logout.md +11 -0
- package/docs/commands/losses.md +13 -0
- package/docs/commands/ls.md +16 -0
- package/docs/commands/marks.md +9 -0
- package/docs/commands/mcp.md +33 -0
- package/docs/commands/mode.md +29 -0
- package/docs/commands/on-collision.md +13 -0
- package/docs/commands/padding.md +13 -0
- package/docs/commands/public-code.md +9 -0
- package/docs/commands/pull.md +9 -0
- package/docs/commands/push.md +12 -0
- package/docs/commands/put.md +36 -0
- package/docs/commands/rebuild.md +13 -0
- package/docs/commands/recovery.md +18 -0
- package/docs/commands/s3.md +17 -0
- package/docs/commands/share.md +15 -0
- package/docs/commands/support.md +16 -0
- package/docs/commands/tip.md +21 -0
- package/docs/commands/trash.md +25 -0
- package/docs/commands/trial.md +12 -0
- package/docs/commands/unlock.md +25 -0
- package/docs/commands/update.md +14 -0
- package/docs/commands/usage.md +14 -0
- package/docs/commands/verify.md +18 -0
- package/docs/commands/wallet.md +67 -0
- package/docs/commands/whoami.md +12 -0
- package/package.json +67 -8
- package/server.json +28 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm.d.ts +23 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm.js +44 -2
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm +0 -0
- package/vendor/nmts-crypto/nmts_crypto_wasm_bg.wasm.d.ts +1 -0
- package/vendor/nmts-crypto/package.json +1 -1
- package/dist/commands/consent.js +0 -77
- package/dist/extend-sign.js +0 -130
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Network } from "../network.ts";
|
|
2
|
+
import type { SendReads } from "../wallet-send-chain.ts";
|
|
3
|
+
import type { SignTransfer } from "../wallet-sign.ts";
|
|
4
|
+
/** What `GET /api/donation` says. */
|
|
5
|
+
export interface DonationConfig {
|
|
6
|
+
devAddress: string;
|
|
7
|
+
sendEnabled: boolean;
|
|
8
|
+
walEnabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface WalletDonateOptions {
|
|
11
|
+
server?: string | undefined;
|
|
12
|
+
network?: string | undefined;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
write?: (line: string) => void;
|
|
15
|
+
yes?: boolean;
|
|
16
|
+
dryRun?: boolean;
|
|
17
|
+
feeCap?: string | undefined;
|
|
18
|
+
/** ⚠ SEAMS, NOT OPTIONS — no flag reaches them. */
|
|
19
|
+
readDonation?: (server: string) => Promise<DonationConfig>;
|
|
20
|
+
readChain?: (network: Network) => SendReads | Promise<SendReads>;
|
|
21
|
+
sign?: SignTransfer;
|
|
22
|
+
}
|
|
23
|
+
/** The four facts and the promise, as the screens say them, in the order a person reads them. */
|
|
24
|
+
export declare const GIFT_NOTICES: readonly string[];
|
|
25
|
+
export declare function asDonationConfig(value: unknown): DonationConfig;
|
|
26
|
+
export declare function walletDonate(operands: readonly string[], options?: WalletDonateOptions): Promise<number>;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// `nmts wallet donate <SUI|WAL> <amount>` — a voluntary gift to the developer, from the wallet this
|
|
2
|
+
// NMTS key derives. A HIGH act (2026-09-06): the `donate` unlock opens it, and every run
|
|
3
|
+
// still needs `--yes` — the tier gate in main.ts holds the door; this file holds the sending.
|
|
4
|
+
//
|
|
5
|
+
// ⛔ THE WALLET AGREEMENT DOES NOT REACH A GIFT. A gift is neither covered by that agreement nor
|
|
6
|
+
// counted against its ceiling: the agreement is what lets a PROGRAM sign for storage; a gift is
|
|
7
|
+
// approved on the run that sends it, under the `donate` unlock the person gave.
|
|
8
|
+
//
|
|
9
|
+
// ⛔ WHERE THE ADDRESS COMES FROM. The server publishes it at `GET /api/donation`, the same value the
|
|
10
|
+
// wallet screen's card is built from. It is read on every run and never written down here — an
|
|
11
|
+
// address baked into this tool would go on receiving gifts after the owner changed it.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ WHAT IS SAID BEFORE SIGNING, every time: the four facts (voluntary · nothing in return ·
|
|
14
|
+
// non-refundable · cannot be undone) and the one the terms promise — no record is kept, the
|
|
15
|
+
// transaction id is the only proof (terms 11.5).
|
|
16
|
+
import { request } from "../api.js";
|
|
17
|
+
import { requireAccountCode } from "../code-access.js";
|
|
18
|
+
import { readCredentialsFile } from "../credentials.js";
|
|
19
|
+
import { NmtsError } from "../errors.js";
|
|
20
|
+
import { isRecord } from "../guards.js";
|
|
21
|
+
import { resolveNetwork } from "../network.js";
|
|
22
|
+
import { BINARY_NAME } from "../product.js";
|
|
23
|
+
import { resolveServer } from "../server.js";
|
|
24
|
+
import { THANKS_EN, THANKS_KO } from "../standing-tip.js";
|
|
25
|
+
import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
26
|
+
import { clampGasBudgetMist, isValidSuiAddress, parseTokenAmountToBaseUnits, validateSendForm, } from "../shared/lib/wallet/send-rules.js";
|
|
27
|
+
import { coinAmount, walCoinType, walletAddress } from "../wallet.js";
|
|
28
|
+
/** The four facts and the promise, as the screens say them, in the order a person reads them. */
|
|
29
|
+
export const GIFT_NOTICES = [
|
|
30
|
+
"This is a voluntary gift. Nothing is provided in return, it is non-refundable, and it cannot be undone once sent.",
|
|
31
|
+
"We keep no record of who sent what. If you ever need to ask about this gift, keep the transaction id shown after sending — it is the only proof.",
|
|
32
|
+
];
|
|
33
|
+
export function asDonationConfig(value) {
|
|
34
|
+
if (!isRecord(value))
|
|
35
|
+
throw new NmtsError("The server's donation answer is not the shape this version reads.", { exitCode: 1 });
|
|
36
|
+
const devAddress = value["devAddress"];
|
|
37
|
+
return {
|
|
38
|
+
devAddress: typeof devAddress === "string" ? devAddress : "",
|
|
39
|
+
sendEnabled: value["sendEnabled"] === true,
|
|
40
|
+
walEnabled: value["walEnabled"] === true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function coinOf(raw) {
|
|
44
|
+
const up = (raw ?? "").toUpperCase();
|
|
45
|
+
if (up === "SUI" || up === "WAL")
|
|
46
|
+
return up;
|
|
47
|
+
throw new NmtsError("Say which coin: SUI or WAL.", {
|
|
48
|
+
exitCode: 2,
|
|
49
|
+
nextStep: `\`${BINARY_NAME} wallet donate <SUI|WAL> <amount> --yes\``,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export async function walletDonate(operands, options = {}) {
|
|
53
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
54
|
+
const [coinRaw, amountRaw] = operands;
|
|
55
|
+
const coin = coinOf(coinRaw);
|
|
56
|
+
if (amountRaw === undefined) {
|
|
57
|
+
throw new NmtsError("Say how much.", { exitCode: 2, nextStep: `\`${BINARY_NAME} wallet donate ${coin} <amount> --yes\`` });
|
|
58
|
+
}
|
|
59
|
+
const resolved = await requireAccountCode();
|
|
60
|
+
const address = await walletAddress(resolved.code);
|
|
61
|
+
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
62
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
63
|
+
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
64
|
+
const config = await (options.readDonation ?? (async (base) => asDonationConfig(await request(base, "/api/donation"))))(server);
|
|
65
|
+
if (!config.sendEnabled || !isValidSuiAddress(config.devAddress)) {
|
|
66
|
+
throw new NmtsError("Gifts are not open right now.", {
|
|
67
|
+
exitCode: 4,
|
|
68
|
+
nextStep: "Nothing was signed. The donation card in the browser says the same when it is off.",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (coin === "WAL" && !config.walEnabled) {
|
|
72
|
+
throw new NmtsError("Gifts in WAL are not open right now; SUI is.", { exitCode: 4, nextStep: "Nothing was signed." });
|
|
73
|
+
}
|
|
74
|
+
const reads = await (options.readChain ?? (async (net) => (await import("../wallet-send-chain.js")).sendReads(net)))(network);
|
|
75
|
+
const purse = await reads.readWallet(address);
|
|
76
|
+
if (!purse.sui.read || !purse.wal.read) {
|
|
77
|
+
throw new NmtsError("A balance could not be read, so this tool cannot tell whether the wallet can give this.", {
|
|
78
|
+
exitCode: 1,
|
|
79
|
+
nextStep: "Nothing was signed. That is not an empty wallet.",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
const verdict = validateSendForm({
|
|
83
|
+
coin,
|
|
84
|
+
amountInput: amountRaw,
|
|
85
|
+
addressInput: config.devAddress,
|
|
86
|
+
suiBalance: purse.sui.baseUnits,
|
|
87
|
+
walBalance: purse.wal.baseUnits,
|
|
88
|
+
});
|
|
89
|
+
if (!verdict.ok) {
|
|
90
|
+
const why = verdict.error === "invalidAmount"
|
|
91
|
+
? "The amount must be a number above zero, with at most nine decimals."
|
|
92
|
+
: verdict.error === "noGasForWal"
|
|
93
|
+
? "A WAL gift pays its fee in SUI, and the wallet holds less SUI than the reserve kept for that."
|
|
94
|
+
: verdict.error === "invalidAddress"
|
|
95
|
+
? "The published address is not a Sui address; nothing was sent."
|
|
96
|
+
: "The wallet does not hold that much.";
|
|
97
|
+
throw new NmtsError(why, {
|
|
98
|
+
exitCode: verdict.error === "invalidAmount" ? 2 : 4,
|
|
99
|
+
nextStep: `Nothing was signed. The wallet holds ${coinAmount(purse.sui.baseUnits)} SUI and ${coinAmount(purse.wal.baseUnits)} WAL.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const gasBudgetMist = options.feeCap === undefined ? undefined : clampGasBudgetMist(parseTokenAmountToBaseUnits(options.feeCap) ?? 0n);
|
|
103
|
+
const shape = {
|
|
104
|
+
coin,
|
|
105
|
+
amountBaseUnits: verdict.amountBaseUnits,
|
|
106
|
+
destination: verdict.address,
|
|
107
|
+
walType: walCoinType(network),
|
|
108
|
+
gasBudgetMist,
|
|
109
|
+
};
|
|
110
|
+
const feeMist = await reads.estimateFee(shape, address);
|
|
111
|
+
const facts = {
|
|
112
|
+
to: shape.destination,
|
|
113
|
+
coin,
|
|
114
|
+
amountBaseUnits: shape.amountBaseUnits.toString(),
|
|
115
|
+
amount: coinAmount(shape.amountBaseUnits),
|
|
116
|
+
feeMist: feeMist === null ? null : feeMist.toString(),
|
|
117
|
+
feeSui: feeMist === null ? null : coinAmount(feeMist),
|
|
118
|
+
network,
|
|
119
|
+
};
|
|
120
|
+
if (!options.json) {
|
|
121
|
+
say(`Gift of ${facts.amount} ${coin} to the developer`);
|
|
122
|
+
say(` ${shape.destination}`);
|
|
123
|
+
say(feeMist === null
|
|
124
|
+
? ` Chain fee (SUI): could not be measured just now; it is charged with the signature.`
|
|
125
|
+
: ` Chain fee about ${facts.feeSui} SUI, measured by a dry run just now.`);
|
|
126
|
+
say(``);
|
|
127
|
+
for (const line of GIFT_NOTICES)
|
|
128
|
+
say(` ${line}`);
|
|
129
|
+
}
|
|
130
|
+
if (options.dryRun === true || options.yes !== true) {
|
|
131
|
+
if (options.json) {
|
|
132
|
+
say(JSON.stringify({ ...facts, signed: false, dryRun: options.dryRun === true }));
|
|
133
|
+
return options.dryRun === true ? 0 : 4;
|
|
134
|
+
}
|
|
135
|
+
say(``);
|
|
136
|
+
if (options.dryRun === true) {
|
|
137
|
+
say(` Nothing was signed. Run the same command with --yes to send it.`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
throw new NmtsError("Nothing was signed: a gift needs --yes, every time.", {
|
|
141
|
+
exitCode: 4,
|
|
142
|
+
nextStep: `Read the lines above, then: \`${BINARY_NAME} wallet donate ${coin} ${amountRaw} --yes\``,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
const sign = options.sign ?? (await import("../wallet-sign.js")).signTransfer;
|
|
146
|
+
const digest = await sign({ network, code: resolved.code, shape });
|
|
147
|
+
if (options.json) {
|
|
148
|
+
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
say(``);
|
|
152
|
+
say(` Thank you — your gift was sent. Transaction ${digest}`);
|
|
153
|
+
say(` ${explorerTxUrl(digest, network)}`);
|
|
154
|
+
say(` Keep that id: no record of this gift exists anywhere else.`);
|
|
155
|
+
// ⛔ THE OWNER'S OWN THANKS, IN BOTH LANGUAGES, after every gift whatever its size — the same two
|
|
156
|
+
// sentences the browser shows and the standing tip prints, held in one place so they cannot
|
|
157
|
+
// drift apart.
|
|
158
|
+
say(``);
|
|
159
|
+
say(` ${THANKS_EN}`);
|
|
160
|
+
say(` ${THANKS_KO}`);
|
|
161
|
+
say(` To be listed by name on nmts.me/hall: ${BINARY_NAME} wallet hall --name <name>`);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SignMessage } from "../wallet-sign.ts";
|
|
2
|
+
export interface WalletHallOptions {
|
|
3
|
+
server?: string | undefined;
|
|
4
|
+
json?: boolean;
|
|
5
|
+
write?: (line: string) => void;
|
|
6
|
+
/** `--name`: be listed under this. `--remove`: go back to a shortened address. */
|
|
7
|
+
name?: string | undefined;
|
|
8
|
+
remove?: boolean;
|
|
9
|
+
/** ⚠ A SEAM, NOT AN OPTION — no flag reaches it. It exists so a test never signs for real. */
|
|
10
|
+
sign?: SignMessage;
|
|
11
|
+
}
|
|
12
|
+
/** One row of the hall, as this tool needs it. Amounts are base units, exactly as they arrived. */
|
|
13
|
+
export interface HallEntry {
|
|
14
|
+
rank: number;
|
|
15
|
+
label: string;
|
|
16
|
+
wal: bigint;
|
|
17
|
+
sui: bigint;
|
|
18
|
+
}
|
|
19
|
+
export interface Hall {
|
|
20
|
+
developerLabel: string;
|
|
21
|
+
developerAddress: string;
|
|
22
|
+
/** The day the SUI/WAL comparison rate was measured, as the server states it. */
|
|
23
|
+
measuredOn: string;
|
|
24
|
+
entries: HallEntry[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The bytes both sides sign and check, spelled once.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ EXPORTED SO A TEST CAN HOLD IT. What makes this right is not that it looks right but that the
|
|
30
|
+
* server builds the same four lines; a test that rebuilt them from this function would only
|
|
31
|
+
* prove the function agrees with itself, so the test writes the four lines out by hand.
|
|
32
|
+
*/
|
|
33
|
+
export declare function hallMessage(address: string, name: string | null, issuedAt: string): string;
|
|
34
|
+
export declare function asHall(value: unknown): Hall;
|
|
35
|
+
export declare function walletHall(options?: WalletHallOptions): Promise<number>;
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
// `nmts wallet hall` — who has sent the developer a gift, and the one way to be named in that list.
|
|
2
|
+
//
|
|
3
|
+
// ⛔ THE LIST IS READ OFF A PUBLIC CHAIN, NOT OUT OF AN ACCOUNT. Every gift is a transfer anybody
|
|
4
|
+
// can see on an explorer; the server only groups them by the address that sent them. So reading
|
|
5
|
+
// the hall asks for no NMTS key and signs nothing, and there is no "hide me" — what can be
|
|
6
|
+
// taken off is the NAME, which puts the entry back to a shortened address.
|
|
7
|
+
//
|
|
8
|
+
// ⛔ A NAME IS PUBLISHED, AND THAT IS WHY IT IS SIGNED. The proof that an address is yours is a
|
|
9
|
+
// signature by that address, so `--name` signs a short message with the wallet this NMTS
|
|
10
|
+
// key derives (`wallet-sign.ts`) and sends the signature. No session, no API key, no account
|
|
11
|
+
// id goes with it — the server is told an address, a name and a signature, and nothing else.
|
|
12
|
+
//
|
|
13
|
+
// ⛔ THE MESSAGE IS BUILT IN ONE PLACE, `hallMessage` BELOW, because the server rebuilds the same
|
|
14
|
+
// bytes from the fields it was sent and compares. A space added on either side makes every
|
|
15
|
+
// signature invalid, and the failure would read as a rejected signature rather than as a typo.
|
|
16
|
+
import { request, ServerError, HttpError } from "../api.js";
|
|
17
|
+
import { requireAccountCode } from "../code-access.js";
|
|
18
|
+
import { readCredentialsFile } from "../credentials.js";
|
|
19
|
+
import { NmtsError } from "../errors.js";
|
|
20
|
+
import { isRecord } from "../guards.js";
|
|
21
|
+
import { BINARY_NAME, HOME_URL } from "../product.js";
|
|
22
|
+
import { resolveServer } from "../server.js";
|
|
23
|
+
import { coinAmount, walletAddress } from "../wallet.js";
|
|
24
|
+
/** How many rows a terminal gets. The page at the site carries the rest. */
|
|
25
|
+
const SHOWN = 10;
|
|
26
|
+
/**
|
|
27
|
+
* The bytes both sides sign and check, spelled once.
|
|
28
|
+
*
|
|
29
|
+
* ⛔ EXPORTED SO A TEST CAN HOLD IT. What makes this right is not that it looks right but that the
|
|
30
|
+
* server builds the same four lines; a test that rebuilt them from this function would only
|
|
31
|
+
* prove the function agrees with itself, so the test writes the four lines out by hand.
|
|
32
|
+
*/
|
|
33
|
+
export function hallMessage(address, name, issuedAt) {
|
|
34
|
+
return `NMTS hall of fame\naddress: ${address}\nname: ${name ?? "(none)"}\nat: ${issuedAt}`;
|
|
35
|
+
}
|
|
36
|
+
/** Base units as the server sends them: a decimal string, because a JSON number would round it. */
|
|
37
|
+
function baseUnits(value, field) {
|
|
38
|
+
if (typeof value === "string" && /^[0-9]+$/u.test(value))
|
|
39
|
+
return BigInt(value);
|
|
40
|
+
throw new NmtsError(`The server's hall answer carries a ${field} this version cannot read.`, { exitCode: 1 });
|
|
41
|
+
}
|
|
42
|
+
function text(value) {
|
|
43
|
+
return typeof value === "string" ? value : "";
|
|
44
|
+
}
|
|
45
|
+
export function asHall(value) {
|
|
46
|
+
if (!isRecord(value))
|
|
47
|
+
throw new NmtsError("The server's hall answer is not the shape this version reads.", { exitCode: 1 });
|
|
48
|
+
const developer = value["developer"];
|
|
49
|
+
const rate = value["referenceRate"];
|
|
50
|
+
const rows = value["entries"];
|
|
51
|
+
const entries = [];
|
|
52
|
+
for (const row of Array.isArray(rows) ? rows : []) {
|
|
53
|
+
if (!isRecord(row))
|
|
54
|
+
continue;
|
|
55
|
+
const rank = row["rank"];
|
|
56
|
+
entries.push({
|
|
57
|
+
rank: typeof rank === "number" ? rank : entries.length + 1,
|
|
58
|
+
label: text(row["label"]),
|
|
59
|
+
wal: baseUnits(row["wal"], "WAL amount"),
|
|
60
|
+
sui: baseUnits(row["sui"], "SUI amount"),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
developerLabel: isRecord(developer) ? text(developer["label"]) : "",
|
|
65
|
+
developerAddress: isRecord(developer) ? text(developer["address"]) : "",
|
|
66
|
+
measuredOn: isRecord(rate) ? text(rate["measuredOn"]) : "",
|
|
67
|
+
entries,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* What the server refused, in one sentence a person or an agent can act on.
|
|
72
|
+
*
|
|
73
|
+
* ⛔ BY CODE, NEVER BY MESSAGE. The server's own words may be rewritten any day; the codes are the
|
|
74
|
+
* contract. A code this version does not know falls through to the server's own sentence rather
|
|
75
|
+
* than to a guess.
|
|
76
|
+
*/
|
|
77
|
+
const REFUSALS = {
|
|
78
|
+
gift_not_found: {
|
|
79
|
+
line: "Your gift is not in the list yet. It appears within an hour; try again then.",
|
|
80
|
+
exitCode: 4,
|
|
81
|
+
nextStep: "Nothing was published. The gift itself is on the chain either way.",
|
|
82
|
+
},
|
|
83
|
+
name_invalid: {
|
|
84
|
+
line: "A name is 1 to 24 characters, no links, not an address.",
|
|
85
|
+
exitCode: 2,
|
|
86
|
+
nextStep: "Nothing was published. Choose another name and run the same command again.",
|
|
87
|
+
},
|
|
88
|
+
listing_stale: {
|
|
89
|
+
line: "A newer listing request exists; try again.",
|
|
90
|
+
exitCode: 4,
|
|
91
|
+
nextStep: "Nothing was published. Something else set this address's name after this run started.",
|
|
92
|
+
},
|
|
93
|
+
signature_invalid: {
|
|
94
|
+
line: "The server did not accept the signature.",
|
|
95
|
+
exitCode: 1,
|
|
96
|
+
nextStep: `Nothing was published. \`${BINARY_NAME} wallet address\` shows the wallet this run signs as.`,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
/** The server the sealed sign-in points at, when there is one this run can read. */
|
|
100
|
+
function storedServer() {
|
|
101
|
+
try {
|
|
102
|
+
return readCredentialsFile()?.server;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// A sign-in file this run cannot open is no reason to refuse a public listing: the default
|
|
106
|
+
// server answers it. Every command that actually needs that file still reports what is wrong.
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
export async function walletHall(options = {}) {
|
|
111
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
112
|
+
const server = resolveServer(options.server ?? storedServer());
|
|
113
|
+
if (options.name !== undefined && options.remove === true) {
|
|
114
|
+
throw new NmtsError("Say one of --name and --remove, not both.", {
|
|
115
|
+
exitCode: 2,
|
|
116
|
+
nextStep: `\`${BINARY_NAME} wallet hall --name <name>\` lists a name; \`--remove\` takes it off.`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (options.name === undefined && options.remove !== true)
|
|
120
|
+
return await show(server, say, options);
|
|
121
|
+
return await setName(server, say, options);
|
|
122
|
+
}
|
|
123
|
+
/** `nmts wallet hall` — the developer, the top rows, and where the rest of them are. */
|
|
124
|
+
async function show(server, say, options) {
|
|
125
|
+
const answer = await readHall(server);
|
|
126
|
+
if (!answer.mounted) {
|
|
127
|
+
if (options.json === true)
|
|
128
|
+
say(JSON.stringify({ entries: [] }));
|
|
129
|
+
else
|
|
130
|
+
say(`No gift has arrived yet.`);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (options.json === true) {
|
|
134
|
+
say(JSON.stringify(answer.body));
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
const hall = asHall(answer.body);
|
|
138
|
+
say(`Hall of fame — everyone who sent the developer a gift`);
|
|
139
|
+
say(` ${hall.developerLabel} — builds NMTS`);
|
|
140
|
+
say(` ${hall.developerAddress}`);
|
|
141
|
+
say(``);
|
|
142
|
+
if (hall.entries.length === 0) {
|
|
143
|
+
say(` No gift has arrived yet.`);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
const shown = hall.entries.slice(0, SHOWN).map((row) => ({ ...row, walText: coinAmount(row.wal), suiText: coinAmount(row.sui) }));
|
|
147
|
+
// ⛔ THE AMOUNTS ARE RIGHT-ALIGNED AND NEVER SHORTENED. `coinAmount` prints the exact value, so
|
|
148
|
+
// the columns are as wide as the widest row rather than rounded to fit.
|
|
149
|
+
const label = Math.max(...shown.map((row) => row.label.length));
|
|
150
|
+
const wal = Math.max(...shown.map((row) => row.walText.length));
|
|
151
|
+
const sui = Math.max(...shown.map((row) => row.suiText.length));
|
|
152
|
+
for (const row of shown) {
|
|
153
|
+
say(` ${String(row.rank).padStart(3)}. ${row.label.padEnd(label)} ${row.walText.padStart(wal)} WAL ${row.suiText.padStart(sui)} SUI`);
|
|
154
|
+
}
|
|
155
|
+
const rest = hall.entries.length - shown.length;
|
|
156
|
+
say(``);
|
|
157
|
+
if (rest > 0)
|
|
158
|
+
say(` ${rest} more ${rest === 1 ? "entry is" : "entries are"} in the list at ${HOME_URL}/hall.`);
|
|
159
|
+
say(` Ranked by cumulative amount; SUI and WAL are compared at a reference rate measured on ${hall.measuredOn}.`);
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* The read itself.
|
|
164
|
+
*
|
|
165
|
+
* ⛔ A 404 IS "NOTHING TO SHOW", NOT A BREAKAGE. The routes are mounted only where the server holds
|
|
166
|
+
* a gift address, so a server that takes no gifts answers 404 — the same answer the browser
|
|
167
|
+
* page draws its empty state from. Every other failure is reported as itself.
|
|
168
|
+
*/
|
|
169
|
+
async function readHall(server) {
|
|
170
|
+
try {
|
|
171
|
+
return { mounted: true, body: await request(server, "/v1/gifts/hall") };
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
const status = error instanceof HttpError || error instanceof ServerError ? error.status : 0;
|
|
175
|
+
if (status === 404)
|
|
176
|
+
return { mounted: false, body: null };
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** `--name` and `--remove`: sign the message, send it, and say what the hall shows now. */
|
|
181
|
+
async function setName(server, say, options) {
|
|
182
|
+
const resolved = await requireAccountCode();
|
|
183
|
+
// ⛔ The address is derived here rather than taken from a flag: a signature only proves ownership
|
|
184
|
+
// of the address it was made by, so the two must come from the same NMTS key.
|
|
185
|
+
const address = await walletAddress(resolved.code);
|
|
186
|
+
const name = options.remove === true ? null : (options.name ?? null);
|
|
187
|
+
const issuedAt = new Date(Date.now()).toISOString();
|
|
188
|
+
const message = hallMessage(address, name, issuedAt);
|
|
189
|
+
const sign = options.sign ?? (await import("../wallet-sign.js")).signMessage;
|
|
190
|
+
const signature = await sign({ code: resolved.code, message });
|
|
191
|
+
let answer;
|
|
192
|
+
try {
|
|
193
|
+
answer = await request(server, "/v1/gifts/listing", {
|
|
194
|
+
method: "POST",
|
|
195
|
+
body: { address, name, issuedAt, signature },
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const known = error instanceof ServerError ? REFUSALS[error.code] : undefined;
|
|
200
|
+
if (known === undefined)
|
|
201
|
+
throw error;
|
|
202
|
+
throw new NmtsError(known.line, { exitCode: known.exitCode, nextStep: known.nextStep });
|
|
203
|
+
}
|
|
204
|
+
if (options.json === true) {
|
|
205
|
+
say(JSON.stringify(answer));
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
const label = isRecord(answer) ? text(answer["label"]) : "";
|
|
209
|
+
say(`Listed as ${label}`);
|
|
210
|
+
say(``);
|
|
211
|
+
say(` The hall of fame is at ${HOME_URL}/hall. Run \`${BINARY_NAME} wallet hall --remove\` to go`);
|
|
212
|
+
say(` back to a shortened address; the gifts themselves stay on the chain either way.`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Network } from "../network.ts";
|
|
2
|
+
import type { SendReads } from "../wallet-send-chain.ts";
|
|
3
|
+
import type { SignTransfer } from "../wallet-sign.ts";
|
|
4
|
+
export interface WalletSendOptions {
|
|
5
|
+
server?: string | undefined;
|
|
6
|
+
network?: string | undefined;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
write?: (line: string) => void;
|
|
9
|
+
/** Actually send. Without it the review is printed and nothing is signed. */
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
/** Print the review and stop, even with --yes. */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
/** A ceiling on the fee, in SUI ("0.01"). Clamped to the usable range, never refused. */
|
|
14
|
+
feeCap?: string | undefined;
|
|
15
|
+
/** The instant to measure the wallet agreement against. */
|
|
16
|
+
now?: number;
|
|
17
|
+
/** ⚠ A SEAM, NOT AN OPTION — no flag reaches it. */
|
|
18
|
+
readChain?: (network: Network) => SendReads | Promise<SendReads>;
|
|
19
|
+
/** ⛔ SEPARATE FROM THE READS so a test can prove the review stops before this. */
|
|
20
|
+
sign?: SignTransfer;
|
|
21
|
+
}
|
|
22
|
+
export declare function walletSend(operands: readonly string[], options?: WalletSendOptions): Promise<number>;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// `nmts wallet send <SUI|WAL> <amount|max> <address>` — move coins out of the wallet this NMTS
|
|
2
|
+
// key derives, to one address. ⛔ THE ONE COMMAND BESIDES `extend` THAT SIGNS.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ THE ORDER IS THE SAFETY. ① the address and the amount are judged by the browser's own rules
|
|
5
|
+
// (`shared/lib/wallet/send-rules.ts`, copied byte-for-byte) ② both balances are READ, and an
|
|
6
|
+
// unread one stops the run — it is not zero ③ the exact transfer is dry-run for its fee ④ the
|
|
7
|
+
// review is printed: the whole address, the amount, the fee, what is left ⑤ without `--yes`
|
|
8
|
+
// that is the end — a transfer to a typed address cannot be undone, and this tool has no
|
|
9
|
+
// prompt (`consent.ts`: a command, not a keystroke) ⑥ the wallet agreement is held against
|
|
10
|
+
// the amount and the fee, scope `all` (`wallet-grant.ts`) ⑦ only then the signature.
|
|
11
|
+
//
|
|
12
|
+
// ⛔ NOT ON THE MCP SURFACE. A model can ask a person to run this; it cannot run it through a tool
|
|
13
|
+
// call. Whether an agent may send money on its own is the person's `mode` and `--scope all`
|
|
14
|
+
// decision at the command line, said in AGENTS.md — not a tool a model finds in a list.
|
|
15
|
+
import { requireAccountCode } from "../code-access.js";
|
|
16
|
+
import { readCredentialsFile } from "../credentials.js";
|
|
17
|
+
import { NmtsError } from "../errors.js";
|
|
18
|
+
import { resolveNetwork } from "../network.js";
|
|
19
|
+
import { BINARY_NAME } from "../product.js";
|
|
20
|
+
import { resolveServer } from "../server.js";
|
|
21
|
+
import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
22
|
+
import { clampGasBudgetMist, maxSendableBaseUnits, parseTokenAmountToBaseUnits, validateSendForm, } from "../shared/lib/wallet/send-rules.js";
|
|
23
|
+
import { coinAmount, walCoinType, walletAddress } from "../wallet.js";
|
|
24
|
+
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
25
|
+
const COIN_WORDS = {
|
|
26
|
+
invalidAddress: "That is not a Sui address: it is 0x followed by 64 hexadecimal characters.",
|
|
27
|
+
invalidAmount: "The amount must be a number above zero, with at most nine decimals.",
|
|
28
|
+
insufficientBalance: "The wallet does not hold that much.",
|
|
29
|
+
noGasForWal: "A WAL transfer pays its fee in SUI, and the wallet holds less SUI than the reserve kept for that.",
|
|
30
|
+
};
|
|
31
|
+
function coinOf(raw) {
|
|
32
|
+
const up = (raw ?? "").toUpperCase();
|
|
33
|
+
if (up === "SUI" || up === "WAL")
|
|
34
|
+
return up;
|
|
35
|
+
throw new NmtsError("Say which coin: SUI or WAL.", {
|
|
36
|
+
exitCode: 2,
|
|
37
|
+
nextStep: `\`${BINARY_NAME} wallet send <SUI|WAL> <amount|max> <address>\``,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
export async function walletSend(operands, options = {}) {
|
|
41
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
42
|
+
const [coinRaw, amountRaw, destinationRaw] = operands;
|
|
43
|
+
const coin = coinOf(coinRaw);
|
|
44
|
+
if (amountRaw === undefined || destinationRaw === undefined) {
|
|
45
|
+
throw new NmtsError("Say how much, and to which address.", {
|
|
46
|
+
exitCode: 2,
|
|
47
|
+
nextStep: `\`${BINARY_NAME} wallet send ${coin} <amount|max> <address>\` — "max" sends everything that can be sent.`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const resolved = await requireAccountCode();
|
|
51
|
+
const address = await walletAddress(resolved.code);
|
|
52
|
+
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
53
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
54
|
+
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
55
|
+
const reads = await (options.readChain ?? (async (net) => (await import("../wallet-send-chain.js")).sendReads(net)))(network);
|
|
56
|
+
// ② The balances — read, and refused as unread rather than treated as zero.
|
|
57
|
+
const purse = await reads.readWallet(address);
|
|
58
|
+
if (!purse.sui.read || !purse.wal.read) {
|
|
59
|
+
const which = [!purse.sui.read ? `SUI: ${purse.sui.why}` : null, !purse.wal.read ? `WAL: ${purse.wal.why}` : null].filter((w) => w !== null);
|
|
60
|
+
throw new NmtsError("A balance could not be read, so this tool cannot tell what can be sent.", {
|
|
61
|
+
exitCode: 1,
|
|
62
|
+
nextStep: `Nothing was signed. That is not an empty wallet — ${which.join("; ")}.`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const suiBalance = purse.sui.baseUnits;
|
|
66
|
+
const walBalance = purse.wal.baseUnits;
|
|
67
|
+
// ① The rules — the browser's, unchanged.
|
|
68
|
+
const amountInput = amountRaw.toLowerCase() === "max"
|
|
69
|
+
? coinAmount(maxSendableBaseUnits({ coin, suiBalance, walBalance }))
|
|
70
|
+
: amountRaw;
|
|
71
|
+
const verdict = validateSendForm({ coin, amountInput, addressInput: destinationRaw, suiBalance, walBalance });
|
|
72
|
+
if (!verdict.ok) {
|
|
73
|
+
const exit = verdict.error === "invalidAddress" || verdict.error === "invalidAmount" ? 2 : 4;
|
|
74
|
+
throw new NmtsError(COIN_WORDS[verdict.error], {
|
|
75
|
+
exitCode: exit,
|
|
76
|
+
nextStep: `Nothing was signed. The wallet holds ${coinAmount(suiBalance)} SUI and ${coinAmount(walBalance)} WAL; ` +
|
|
77
|
+
`the most that can be sent is ${coinAmount(maxSendableBaseUnits({ coin, suiBalance, walBalance }))} ${coin} ` +
|
|
78
|
+
`(SUI keeps a reserve back for fees).`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
const gasBudgetMist = options.feeCap === undefined ? undefined : clampGasBudgetMist(parseTokenAmountToBaseUnits(options.feeCap) ?? 0n);
|
|
82
|
+
const shape = {
|
|
83
|
+
coin,
|
|
84
|
+
amountBaseUnits: verdict.amountBaseUnits,
|
|
85
|
+
destination: verdict.address,
|
|
86
|
+
walType: walCoinType(network),
|
|
87
|
+
gasBudgetMist,
|
|
88
|
+
};
|
|
89
|
+
// ③ The fee of this exact transfer.
|
|
90
|
+
const feeMist = await reads.estimateFee(shape, address);
|
|
91
|
+
const suiAfter = suiBalance - (coin === "SUI" ? shape.amountBaseUnits : 0n) - (feeMist ?? 0n);
|
|
92
|
+
const walAfter = walBalance - (coin === "WAL" ? shape.amountBaseUnits : 0n);
|
|
93
|
+
const facts = {
|
|
94
|
+
from: address,
|
|
95
|
+
to: shape.destination,
|
|
96
|
+
coin,
|
|
97
|
+
amountBaseUnits: shape.amountBaseUnits.toString(),
|
|
98
|
+
amount: coinAmount(shape.amountBaseUnits),
|
|
99
|
+
feeMist: feeMist === null ? null : feeMist.toString(),
|
|
100
|
+
feeSui: feeMist === null ? null : coinAmount(feeMist),
|
|
101
|
+
feeCapMist: gasBudgetMist === undefined ? null : gasBudgetMist.toString(),
|
|
102
|
+
network,
|
|
103
|
+
suiAfter: coinAmount(suiAfter < 0n ? 0n : suiAfter),
|
|
104
|
+
walAfter: coinAmount(walAfter < 0n ? 0n : walAfter),
|
|
105
|
+
};
|
|
106
|
+
// ④ The review, every time.
|
|
107
|
+
if (!options.json) {
|
|
108
|
+
say(`Sending ${facts.amount} ${coin}`);
|
|
109
|
+
say(` to ${shape.destination}`);
|
|
110
|
+
say(` from ${address}`);
|
|
111
|
+
say(feeMist === null
|
|
112
|
+
? ` Chain fee (SUI): could not be measured just now; it is charged with the signature.`
|
|
113
|
+
: ` Chain fee about ${facts.feeSui} SUI, measured by a dry run just now — the amount charged is fixed when it executes.`);
|
|
114
|
+
if (gasBudgetMist !== undefined)
|
|
115
|
+
say(` Fee cap ${coinAmount(gasBudgetMist)} SUI — only up to this is used; the rest stays.`);
|
|
116
|
+
say(` Afterwards the wallet holds about ${facts.suiAfter} SUI and ${facts.walAfter} WAL.`);
|
|
117
|
+
say(``);
|
|
118
|
+
say(` ⛔ A transfer cannot be undone by anybody, NMTS included. Check the address once more.`);
|
|
119
|
+
}
|
|
120
|
+
if (options.dryRun === true || options.yes !== true) {
|
|
121
|
+
if (options.json) {
|
|
122
|
+
say(JSON.stringify({ ...facts, signed: false, dryRun: options.dryRun === true }));
|
|
123
|
+
return options.dryRun === true ? 0 : 4;
|
|
124
|
+
}
|
|
125
|
+
say(``);
|
|
126
|
+
if (options.dryRun === true) {
|
|
127
|
+
say(` Nothing was signed. Run the same command with --yes to send it.`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
throw new NmtsError("Nothing was signed: sending needs --yes.", {
|
|
131
|
+
exitCode: 4,
|
|
132
|
+
nextStep: `Read the review above, then: \`${BINARY_NAME} wallet send ${coin} ${amountRaw} ${shape.destination} --yes\``,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// ⑥ The agreement — scope `all`, the amount and the fee held against its ceiling.
|
|
136
|
+
const spend = {
|
|
137
|
+
walFrost: coin === "WAL" ? shape.amountBaseUnits : 0n,
|
|
138
|
+
suiMist: (coin === "SUI" ? shape.amountBaseUnits : 0n) + (feeMist ?? 0n),
|
|
139
|
+
};
|
|
140
|
+
requireWalletGrant("send", spend, new Date(options.now ?? Date.now()));
|
|
141
|
+
// ⑦ The signature.
|
|
142
|
+
const sign = options.sign ?? (await import("../wallet-sign.js")).signTransfer;
|
|
143
|
+
const digest = await sign({ network, code: resolved.code, shape });
|
|
144
|
+
recordWalletSpend(spend);
|
|
145
|
+
if (options.json) {
|
|
146
|
+
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
say(``);
|
|
150
|
+
say(` Sent. Transaction ${digest}`);
|
|
151
|
+
say(` ${explorerTxUrl(digest, network)}`);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Network } from "../network.ts";
|
|
2
|
+
import { type StorageOpsReads } from "../storage-control-chain.ts";
|
|
3
|
+
import type { SignStorageOp } from "../wallet-sign.ts";
|
|
4
|
+
export type StorageOp = "split" | "merge" | "transfer";
|
|
5
|
+
export interface StorageOpsOptions {
|
|
6
|
+
server?: string | undefined;
|
|
7
|
+
network?: string | undefined;
|
|
8
|
+
json?: boolean;
|
|
9
|
+
write?: (line: string) => void;
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
dryRun?: boolean;
|
|
12
|
+
/** `split --size <bytes|KiB|MiB|GiB>`: what the named resource keeps; the rest becomes a new one. */
|
|
13
|
+
size?: string | undefined;
|
|
14
|
+
/** `split --epochs <n>`: how many epochs, from its start, the named resource keeps. */
|
|
15
|
+
epochs?: string | undefined;
|
|
16
|
+
now?: number;
|
|
17
|
+
/** ⚠ A SEAM, NOT AN OPTION — no flag reaches it. */
|
|
18
|
+
storageReads?: (network: Network) => StorageOpsReads;
|
|
19
|
+
/** ⛔ SEPARATE FROM THE READS so a test can prove the review stops before this. */
|
|
20
|
+
signStorage?: SignStorageOp;
|
|
21
|
+
}
|
|
22
|
+
export declare function walletStorageOps(op: StorageOp, rest: readonly string[], options?: StorageOpsOptions): Promise<number>;
|
|
23
|
+
/** "500MiB", "1GiB", "4096" — bytes, in binary units, the way the listing prints them. */
|
|
24
|
+
export declare function parseBytes(text: string): number;
|