@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,215 @@
|
|
|
1
|
+
// `nmts wallet storage split|merge|transfer` — reshaping the storage resources the wallet holds,
|
|
2
|
+
// and handing one over. ⛔ EACH SIGNS. The storage-control half a terminal can do: the browser's
|
|
3
|
+
// rules (`shared/lib/storage-control/plan.ts`, copied byte-for-byte) say what the contract allows;
|
|
4
|
+
// the dry run is the final judge; the review comes before the signature; `--yes` is the answer.
|
|
5
|
+
//
|
|
6
|
+
// ⛔ THE ORDER IS THE SAFETY, as in `wallet-send.ts`: ① the resources are READ from the chain and
|
|
7
|
+
// the named ones must be this wallet's ② the rules judge the request and say why not ③ the
|
|
8
|
+
// exact transaction is dry-run for its fee, and a refusal there is printed and ends the run
|
|
9
|
+
// ④ the review ⑤ without `--yes` that is the end ⑥ the wallet unlock is held against the fee —
|
|
10
|
+
// scope `storage` for cutting and joining, scope `all` for handing over ⑦ only then the signature.
|
|
11
|
+
//
|
|
12
|
+
// ⛔ HANDING OVER MOVES NO FILE. The owner's sentence for the browser holds here: what goes is size
|
|
13
|
+
// and remaining time; a file's bytes are sealed with keys the NMTS key derives and stay
|
|
14
|
+
// unreadable to whoever receives the resource. It cannot be undone and NMTS cannot recall it.
|
|
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 { canFuse, statusOf } from "../shared/lib/storage-control/plan.js";
|
|
22
|
+
import { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
23
|
+
import { isValidSuiAddress } from "../shared/lib/wallet/send-rules.js";
|
|
24
|
+
import { storageOpsReads } from "../storage-control-chain.js";
|
|
25
|
+
import { coinAmount, walletAddress } from "../wallet.js";
|
|
26
|
+
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
27
|
+
import { formatBytes } from "./wallet-storage.js";
|
|
28
|
+
const FUSE_WHY = {
|
|
29
|
+
same: "those are the same resource.",
|
|
30
|
+
differentSize: "their periods differ and so do their sizes — the contract joins sizes only over an identical period, and periods only at an identical size.",
|
|
31
|
+
differentPeriod: "their periods differ.",
|
|
32
|
+
notAdjacent: "their sizes are equal but their periods do not touch — one has to end where the other starts.",
|
|
33
|
+
};
|
|
34
|
+
export async function walletStorageOps(op, rest, options = {}) {
|
|
35
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
36
|
+
const resolved = await requireAccountCode();
|
|
37
|
+
const address = await walletAddress(resolved.code);
|
|
38
|
+
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
39
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
40
|
+
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
41
|
+
const reads = (options.storageReads ?? storageOpsReads)(network);
|
|
42
|
+
// ① The resources, and which epoch it is.
|
|
43
|
+
let read;
|
|
44
|
+
try {
|
|
45
|
+
read = await reads.readStorage(address);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw new NmtsError("The storage resources could not be read from the chain.", {
|
|
49
|
+
exitCode: 1,
|
|
50
|
+
nextStep: `Nothing was signed. Cause: ${error instanceof Error ? error.message : String(error)}`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const held = (id) => {
|
|
54
|
+
const found = read.items.find((r) => r.objectId === id);
|
|
55
|
+
if (found === undefined) {
|
|
56
|
+
throw new NmtsError(`This wallet holds no free storage resource ${id}.`, {
|
|
57
|
+
exitCode: 4,
|
|
58
|
+
nextStep: `\`${BINARY_NAME} wallet storage\` lists the ones it holds. A resource bound inside a file is not free.`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return found;
|
|
62
|
+
};
|
|
63
|
+
// ② The request, judged.
|
|
64
|
+
const { shape, action, lines, command } = plan(op, rest, options, held);
|
|
65
|
+
const walrusPackageId = await reads.walrusPackageId();
|
|
66
|
+
// ③ The dry run: the fee, or the contract's own refusal.
|
|
67
|
+
const verdict = await reads.dryRun(shape, address);
|
|
68
|
+
if (verdict.refusal !== null) {
|
|
69
|
+
throw new NmtsError(`The chain would refuse this: ${verdict.refusal}`, {
|
|
70
|
+
exitCode: 4,
|
|
71
|
+
nextStep: "Nothing was signed and no fee was spent.",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const feeMist = verdict.feeMist;
|
|
75
|
+
const facts = { op, network, address, shape, feeSui: feeMist === null ? null : coinAmount(feeMist), currentEpoch: read.currentEpoch };
|
|
76
|
+
// ④ The review.
|
|
77
|
+
if (!options.json) {
|
|
78
|
+
say(`Would ${lines.what}`);
|
|
79
|
+
for (const l of lines.detail)
|
|
80
|
+
say(` ${l}`);
|
|
81
|
+
say(feeMist === null
|
|
82
|
+
? ` Chain fee (SUI): could not be measured just now; it is charged with the signature.`
|
|
83
|
+
: ` Chain fee about ${facts.feeSui} SUI, measured by a dry run just now.`);
|
|
84
|
+
if (op === "transfer") {
|
|
85
|
+
say(` ⛔ No file goes with it. What goes is size and remaining time; the files stay sealed with this`);
|
|
86
|
+
say(` account's keys. It cannot be undone, and NMTS cannot recall it. Check the address once more.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ⑤ Without --yes, that is the end.
|
|
90
|
+
if (options.dryRun === true || options.yes !== true) {
|
|
91
|
+
if (options.json) {
|
|
92
|
+
say(JSON.stringify({ ...facts, signed: false, dryRun: options.dryRun === true }));
|
|
93
|
+
return options.dryRun === true ? 0 : 4;
|
|
94
|
+
}
|
|
95
|
+
say(``);
|
|
96
|
+
if (options.dryRun === true) {
|
|
97
|
+
say(` Nothing was signed. Run the same command with --yes to do it.`);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
throw new NmtsError(`Nothing was signed: this needs --yes.`, {
|
|
101
|
+
exitCode: 4,
|
|
102
|
+
nextStep: `Read the review above, then: \`${command} --yes\``,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
// ⑥ The unlock, held against the fee. ⑦ The signature.
|
|
106
|
+
const spend = { walFrost: 0n, suiMist: feeMist ?? 0n };
|
|
107
|
+
requireWalletGrant(action, spend, new Date(options.now ?? Date.now()));
|
|
108
|
+
const sign = options.signStorage ?? (await import("../wallet-sign.js")).signStorageOp;
|
|
109
|
+
const digest = await sign({ network, code: resolved.code, shape, walrusPackageId });
|
|
110
|
+
recordWalletSpend(spend);
|
|
111
|
+
if (options.json) {
|
|
112
|
+
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
say(``);
|
|
116
|
+
say(` Done. Transaction ${digest}`);
|
|
117
|
+
say(` ${explorerTxUrl(digest, network)}`);
|
|
118
|
+
say(` \`${BINARY_NAME} wallet storage\` lists what the wallet holds now.`);
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
/** The shape one request is, the unlock action it needs, and the review's words. */
|
|
122
|
+
function plan(op, rest, options, held) {
|
|
123
|
+
const [a, b] = rest;
|
|
124
|
+
const resource = (r) => `${formatBytes(r.sizeBytes)} · epoch ${r.startEpoch} to ${r.endEpoch} (${r.objectId})`;
|
|
125
|
+
if (op === "split") {
|
|
126
|
+
if (a === undefined)
|
|
127
|
+
throw usage("split <resource id> --size <bytes> | --epochs <n>");
|
|
128
|
+
const r = held(a);
|
|
129
|
+
if (options.size !== undefined && options.epochs !== undefined)
|
|
130
|
+
throw usage("split takes --size OR --epochs, not both");
|
|
131
|
+
if (options.size !== undefined) {
|
|
132
|
+
const keep = parseBytes(options.size);
|
|
133
|
+
if (keep <= 0 || keep >= r.sizeBytes) {
|
|
134
|
+
throw new NmtsError(`--size must be above 0 and below the resource's ${formatBytes(r.sizeBytes)}.`, { exitCode: 2 });
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
shape: { kind: "splitSize", objectId: a, keepBytes: keep },
|
|
138
|
+
action: "reshape",
|
|
139
|
+
lines: {
|
|
140
|
+
what: `cut ${resource(r)} by size.`,
|
|
141
|
+
detail: [`It keeps ${formatBytes(keep)}; a new resource of ${formatBytes(r.sizeBytes - keep)} over the same period appears in this wallet.`],
|
|
142
|
+
},
|
|
143
|
+
command: `${BINARY_NAME} wallet storage split ${a} --size ${options.size}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (options.epochs !== undefined) {
|
|
147
|
+
const n = Number(options.epochs);
|
|
148
|
+
const span = r.endEpoch - r.startEpoch;
|
|
149
|
+
if (!Number.isSafeInteger(n) || n <= 0 || n >= span) {
|
|
150
|
+
throw new NmtsError(`--epochs must be a whole number above 0 and below the resource's ${span} epochs.`, { exitCode: 2 });
|
|
151
|
+
}
|
|
152
|
+
const at = r.startEpoch + n;
|
|
153
|
+
return {
|
|
154
|
+
shape: { kind: "splitEpoch", objectId: a, splitEpoch: at },
|
|
155
|
+
action: "reshape",
|
|
156
|
+
lines: {
|
|
157
|
+
what: `cut ${resource(r)} by period.`,
|
|
158
|
+
detail: [`It keeps epoch ${r.startEpoch} to ${at}; a new resource of the same size over epoch ${at} to ${r.endEpoch} appears in this wallet.`],
|
|
159
|
+
},
|
|
160
|
+
command: `${BINARY_NAME} wallet storage split ${a} --epochs ${options.epochs}`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
throw usage("split <resource id> --size <bytes> | --epochs <n>");
|
|
164
|
+
}
|
|
165
|
+
if (op === "merge") {
|
|
166
|
+
if (a === undefined || b === undefined)
|
|
167
|
+
throw usage("merge <resource id> <resource id>");
|
|
168
|
+
const first = held(a);
|
|
169
|
+
const second = held(b);
|
|
170
|
+
const verdict = canFuse(first, second);
|
|
171
|
+
if (!verdict.can) {
|
|
172
|
+
throw new NmtsError(`These two cannot be joined: ${FUSE_WHY[verdict.why]}`, {
|
|
173
|
+
exitCode: 4,
|
|
174
|
+
nextStep: `Nothing was signed. \`${BINARY_NAME} wallet storage\` shows each one's size and period.`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
shape: { kind: "fuse", first: a, second: b, how: verdict.kind },
|
|
179
|
+
action: "reshape",
|
|
180
|
+
lines: {
|
|
181
|
+
what: `join ${resource(second)} into ${resource(first)}.`,
|
|
182
|
+
detail: [
|
|
183
|
+
verdict.kind === "amount"
|
|
184
|
+
? `Same period, so the sizes add: the first becomes ${formatBytes(first.sizeBytes + second.sizeBytes)} and the second is gone.`
|
|
185
|
+
: `Same size and touching periods, so the periods join: the first spans epoch ${Math.min(first.startEpoch, second.startEpoch)} to ${Math.max(first.endEpoch, second.endEpoch)} and the second is gone.`,
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
command: `${BINARY_NAME} wallet storage merge ${a} ${b}`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (a === undefined || b === undefined)
|
|
192
|
+
throw usage("transfer <resource id> <address>");
|
|
193
|
+
const r = held(a);
|
|
194
|
+
if (!isValidSuiAddress(b)) {
|
|
195
|
+
throw new NmtsError("That is not a Sui address: it is 0x followed by 64 hexadecimal characters.", { exitCode: 2 });
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
shape: { kind: "transfer", objectId: a, to: b },
|
|
199
|
+
action: "give",
|
|
200
|
+
lines: { what: `hand ${resource(r)} to ${b}.`, detail: [`Afterwards this wallet no longer holds it.`] },
|
|
201
|
+
command: `${BINARY_NAME} wallet storage transfer ${a} ${b}`,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function usage(form) {
|
|
205
|
+
return new NmtsError(`\`${BINARY_NAME} wallet storage ${form}\``, { exitCode: 2 });
|
|
206
|
+
}
|
|
207
|
+
/** "500MiB", "1GiB", "4096" — bytes, in binary units, the way the listing prints them. */
|
|
208
|
+
export function parseBytes(text) {
|
|
209
|
+
const m = /^(\d+(?:\.\d+)?)\s*(B|KiB|MiB|GiB|TiB)?$/i.exec(text.trim());
|
|
210
|
+
if (m === null || m[1] === undefined)
|
|
211
|
+
throw new NmtsError("--size is a number of bytes, or one with KiB, MiB or GiB.", { exitCode: 2 });
|
|
212
|
+
const unit = (m[2] ?? "B").toLowerCase();
|
|
213
|
+
const scale = { b: 1, kib: 1024, mib: 1024 ** 2, gib: 1024 ** 3, tib: 1024 ** 4 }[unit] ?? 1;
|
|
214
|
+
return Math.floor(Number(m[1]) * scale);
|
|
215
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Network } from "../network.ts";
|
|
2
|
+
import type { StorageResource } from "../shared/lib/storage-control/chain.ts";
|
|
3
|
+
/** What the chain said: the resources, and which epoch it is (null when the clock could not be read). */
|
|
4
|
+
export interface StorageRead {
|
|
5
|
+
items: readonly StorageResource[];
|
|
6
|
+
currentEpoch: number | null;
|
|
7
|
+
}
|
|
8
|
+
export interface WalletStorageOptions {
|
|
9
|
+
server?: string | undefined;
|
|
10
|
+
network?: string | undefined;
|
|
11
|
+
json?: boolean;
|
|
12
|
+
write?: (line: string) => void;
|
|
13
|
+
/** ⚠ A SEAM, NOT AN OPTION — no flag reaches it. Rejects when the chain could not be read. */
|
|
14
|
+
readStorage?: (network: Network, address: string) => Promise<StorageRead>;
|
|
15
|
+
}
|
|
16
|
+
export declare function walletStorage(options?: WalletStorageOptions): Promise<number>;
|
|
17
|
+
/** Bytes for a person: binary units, two decimals, whole bytes below a KiB. */
|
|
18
|
+
export declare function formatBytes(bytes: number): string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// `nmts wallet storage` — the storage resources the wallet holds: size × time, bought and not
|
|
2
|
+
// bound inside a file.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ IT READS. What the storage network sells is size and time, not a file; deleting a file from the
|
|
5
|
+
// network returns the remaining time to this wallet, and that is what is listed. Splitting,
|
|
6
|
+
// fusing and spending a resource need a signature and are not here.
|
|
7
|
+
//
|
|
8
|
+
// ⛔ "NONE" AND "COULD NOT READ" ARE DIFFERENT SENTENCES — holding no resource is normal; failing to
|
|
9
|
+
// read is a reason to look again. The reader (`shared/lib/storage-control/chain.ts`, the
|
|
10
|
+
// browser's own, copied byte-for-byte) throws rather than answering an empty list.
|
|
11
|
+
import { requireAccountCode } from "../code-access.js";
|
|
12
|
+
import { readCredentialsFile } from "../credentials.js";
|
|
13
|
+
import { NmtsError } from "../errors.js";
|
|
14
|
+
import { resolveNetwork } from "../network.js";
|
|
15
|
+
import { BINARY_NAME } from "../product.js";
|
|
16
|
+
import { resolveServer } from "../server.js";
|
|
17
|
+
import { statusOf, totalUsableBytes, usableFirst } from "../shared/lib/storage-control/plan.js";
|
|
18
|
+
import { walletAddress } from "../wallet.js";
|
|
19
|
+
export async function walletStorage(options = {}) {
|
|
20
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
21
|
+
const resolved = await requireAccountCode();
|
|
22
|
+
const address = await walletAddress(resolved.code);
|
|
23
|
+
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
24
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
25
|
+
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
26
|
+
const read = options.readStorage ??
|
|
27
|
+
(async (net, addr) => (await import("../wallet-storage-chain.js")).readWalletStorage(net, addr));
|
|
28
|
+
let got;
|
|
29
|
+
try {
|
|
30
|
+
got = await read(network, address);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new NmtsError("The storage resources could not be read from the chain.", {
|
|
34
|
+
exitCode: 1,
|
|
35
|
+
nextStep: `That is not the same as holding none. \`${BINARY_NAME} env\` says which network was asked. ` +
|
|
36
|
+
`Cause: ${error instanceof Error ? error.message : String(error)}`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const epoch = got.currentEpoch;
|
|
40
|
+
const items = epoch === null ? [...got.items] : usableFirst(got.items, epoch);
|
|
41
|
+
if (options.json) {
|
|
42
|
+
say(JSON.stringify({
|
|
43
|
+
address,
|
|
44
|
+
network,
|
|
45
|
+
currentEpoch: epoch,
|
|
46
|
+
usableBytes: epoch === null ? null : totalUsableBytes(items, epoch),
|
|
47
|
+
resources: items.map((r) => ({
|
|
48
|
+
objectId: r.objectId,
|
|
49
|
+
sizeBytes: r.sizeBytes,
|
|
50
|
+
startEpoch: r.startEpoch,
|
|
51
|
+
endEpoch: r.endEpoch,
|
|
52
|
+
status: epoch === null ? null : statusOf(r, epoch),
|
|
53
|
+
})),
|
|
54
|
+
}));
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
say(`Address ${address}`);
|
|
58
|
+
say(`Network ${network}`);
|
|
59
|
+
say(``);
|
|
60
|
+
if (items.length === 0) {
|
|
61
|
+
say(` This wallet holds no free storage resource. Anything bound inside a file is not listed here.`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
if (epoch !== null)
|
|
65
|
+
say(` ${formatBytes(totalUsableBytes(items, epoch))} usable now, in ${items.length} resource${items.length === 1 ? "" : "s"}.`);
|
|
66
|
+
for (const r of items) {
|
|
67
|
+
const status = epoch === null ? "" : ` ${statusWord(statusOf(r, epoch))}`;
|
|
68
|
+
say(` ${formatBytes(r.sizeBytes).padStart(11)} · epoch ${r.startEpoch} to ${r.endEpoch}${status}`);
|
|
69
|
+
say(` ${r.objectId}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
say(``);
|
|
73
|
+
say(` What the storage network sells is size and time, not a file. Deleting a file from the`);
|
|
74
|
+
say(` network returns its remaining time to this wallet, and that is what is listed. Sizes are`);
|
|
75
|
+
say(` what a resource holds after the network's encoding, not file sizes. Nothing here signs.`);
|
|
76
|
+
if (epoch === null)
|
|
77
|
+
say(` The current epoch could not be read, so no line says whether a resource can be used yet.`);
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
function statusWord(status) {
|
|
81
|
+
return status === "usable" ? "usable now" : status === "notYet" ? "not started" : "ended";
|
|
82
|
+
}
|
|
83
|
+
/** Bytes for a person: binary units, two decimals, whole bytes below a KiB. */
|
|
84
|
+
export function formatBytes(bytes) {
|
|
85
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
86
|
+
let value = bytes;
|
|
87
|
+
let unit = "B";
|
|
88
|
+
for (const next of units) {
|
|
89
|
+
if (value < 1024)
|
|
90
|
+
break;
|
|
91
|
+
value /= 1024;
|
|
92
|
+
unit = next;
|
|
93
|
+
}
|
|
94
|
+
return unit === "B" ? `${bytes} B` : `${value.toFixed(2)} ${unit}`;
|
|
95
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type Network } from "../network.ts";
|
|
2
|
+
import { type SwapReads } from "../wallet-swap-chain.ts";
|
|
3
|
+
import type { SignSwap } from "../wallet-sign.ts";
|
|
4
|
+
/** What the site's price route says, when it says anything. Null = no reference price just now. */
|
|
5
|
+
export interface MarketPrices {
|
|
6
|
+
suiUsd: number | null;
|
|
7
|
+
walUsd: number | null;
|
|
8
|
+
}
|
|
9
|
+
export interface WalletSwapOptions {
|
|
10
|
+
server?: string | undefined;
|
|
11
|
+
network?: string | undefined;
|
|
12
|
+
json?: boolean;
|
|
13
|
+
write?: (line: string) => void;
|
|
14
|
+
yes?: boolean;
|
|
15
|
+
dryRun?: boolean;
|
|
16
|
+
feeCap?: string | undefined;
|
|
17
|
+
/** `--to`: the coin to receive. Only ever the other coin; said so a typo cannot flip the trade. */
|
|
18
|
+
to?: string | undefined;
|
|
19
|
+
/** `--venue`: deepbook or bluefin. Without it, both are quoted and the run stops. */
|
|
20
|
+
venue?: string | undefined;
|
|
21
|
+
/** `--slippage-bps`: whole bps, clamped to the browser's range. Default 50. */
|
|
22
|
+
slippageBps?: string | undefined;
|
|
23
|
+
/** `--accept-extremes`: go on past the extremes gate. A person's act — refused outside mode off. */
|
|
24
|
+
acceptExtremes?: boolean;
|
|
25
|
+
now?: number;
|
|
26
|
+
/** ⚠ SEAMS, NOT OPTIONS — no flag reaches them. */
|
|
27
|
+
readChain?: (network: Network) => SwapReads | Promise<SwapReads>;
|
|
28
|
+
readPrices?: (server: string) => Promise<MarketPrices | null>;
|
|
29
|
+
sign?: SignSwap;
|
|
30
|
+
}
|
|
31
|
+
export declare function asMarketPrices(value: unknown): MarketPrices | null;
|
|
32
|
+
export declare function walletSwap(operands: readonly string[], options?: WalletSwapOptions): Promise<number>;
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// `nmts wallet swap <SUI|WAL> <amount|max>` — turn one coin into the other, from the wallet this
|
|
2
|
+
// NMTS key derives, on the venue the person names. ⛔ SIGNS AND SPENDS.
|
|
3
|
+
//
|
|
4
|
+
// ⛔ THE ORDER IS THE SAFETY. ① both balances are READ, and an unread one stops the run ② the
|
|
5
|
+
// amount and the slippage are judged by the browser's own rules (`shared/lib/wallet/swap-rules.ts`,
|
|
6
|
+
// copied byte-for-byte) ③ the venue's quote is read from the chain — on mainnet BOTH venues when
|
|
7
|
+
// none was named, printed side by side, and the run stops there: neither is a default and nobody
|
|
8
|
+
// recommends one ④ the exact swap is dry-run for its fee ⑤ the quote is compared with the site's
|
|
9
|
+
// reference price when one can be read, and said to be uncompared when none can ⑥ the review is
|
|
10
|
+
// printed ⑦ anything outside the ordinary bands — slippage, fee cap, market distance — is refused;
|
|
11
|
+
// only a person, in mode off, may say `--accept-extremes` ⑧ without `--yes` that is the end ⑨ the
|
|
12
|
+
// wallet agreement is held against the amount and the fee, scope `all` ⑩ only then the signature.
|
|
13
|
+
//
|
|
14
|
+
// ⛔ NOT ON THE MCP SURFACE. A model can ask a person to run this; it cannot run it through a tool call.
|
|
15
|
+
import { request } from "../api.js";
|
|
16
|
+
import { currentMode } from "../autonomy.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 { explorerTxUrl } from "../shared/lib/wallet/activity.js";
|
|
25
|
+
import { clampGasBudgetMist, parseTokenAmountToBaseUnits, SUI_GAS_RESERVE_MIST } from "../shared/lib/wallet/send-rules.js";
|
|
26
|
+
import { clampSlippageBps, impliedRate, marketRate, maxSwappableSuiMist, minOutFromQuote, priceDeviationBps, SLIPPAGE_BPS_DEFAULT, SLIPPAGE_BPS_MAX, SLIPPAGE_BPS_MIN, swapExtremes, } from "../shared/lib/wallet/swap-rules.js";
|
|
27
|
+
import { coinAmount, walletAddress } from "../wallet.js";
|
|
28
|
+
import { recordWalletSpend, requireWalletGrant } from "../wallet-grant.js";
|
|
29
|
+
import { railsFor } from "../wallet-swap-chain.js";
|
|
30
|
+
const EXTREME_WORDS = {
|
|
31
|
+
lowSlippage: "the slippage is below 10 bps: the smallest price movement fails the swap, and the chain fee is spent for nothing",
|
|
32
|
+
highSlippage: "the slippage is above 200 bps: a thin order book may keep that much of what goes in",
|
|
33
|
+
lowFee: "the fee cap is under 1.2 times the measured fee: the swap may fail for want of gas, and that gas is gone",
|
|
34
|
+
highFee: "the fee cap is over ten times the measured fee and over 0.05 SUI",
|
|
35
|
+
deviation: "the quote is more than 3% from the site's reference price: the book may be thin or moved",
|
|
36
|
+
};
|
|
37
|
+
function coinOf(raw, what) {
|
|
38
|
+
const up = (raw ?? "").toUpperCase();
|
|
39
|
+
if (up === "SUI" || up === "WAL")
|
|
40
|
+
return up;
|
|
41
|
+
throw new NmtsError(`Say which coin ${what}: SUI or WAL.`, {
|
|
42
|
+
exitCode: 2,
|
|
43
|
+
nextStep: `\`${BINARY_NAME} wallet swap <SUI|WAL> <amount|max> --venue deepbook|bluefin\``,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function pct(bps) {
|
|
47
|
+
return `${(bps / 100).toFixed(2).replace(/0+$/, "").replace(/\.$/, "")}%`;
|
|
48
|
+
}
|
|
49
|
+
export function asMarketPrices(value) {
|
|
50
|
+
if (!isRecord(value) || value["unavailable"] === true)
|
|
51
|
+
return null;
|
|
52
|
+
const num = (v) => (typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null);
|
|
53
|
+
return { suiUsd: num(value["suiUsd"]), walUsd: num(value["walUsd"]) };
|
|
54
|
+
}
|
|
55
|
+
function venueName(venue) {
|
|
56
|
+
return venue === "deepbook" ? "DeepBook" : venue === "bluefin" ? "Bluefin" : "the official Walrus testnet exchange";
|
|
57
|
+
}
|
|
58
|
+
function quoteLine(q, outCoin, inCoin) {
|
|
59
|
+
const fee = q.feeRateBps === null ? "venue fee could not be measured" : `venue fee about ${pct(q.feeRateBps)}`;
|
|
60
|
+
const left = q.leftoverInUnits > 0n ? ` (${coinAmount(q.leftoverInUnits)} ${inCoin} would come back unused)` : "";
|
|
61
|
+
return `${q.venue.padEnd(9)} ${coinAmount(q.outUnits)} ${outCoin} — ${fee}${left}`;
|
|
62
|
+
}
|
|
63
|
+
export async function walletSwap(operands, options = {}) {
|
|
64
|
+
const say = options.write ?? ((line) => process.stdout.write(`${line}\n`));
|
|
65
|
+
const [coinRaw, amountRaw] = operands;
|
|
66
|
+
const coinIn = coinOf(coinRaw, "goes in");
|
|
67
|
+
const coinOut = coinIn === "SUI" ? "WAL" : "SUI";
|
|
68
|
+
if (options.to !== undefined && coinOf(options.to, "comes out") !== coinOut) {
|
|
69
|
+
throw new NmtsError(`${coinIn} can only be swapped for ${coinOut}.`, { exitCode: 2, nextStep: `Drop --to, or say --to ${coinOut}.` });
|
|
70
|
+
}
|
|
71
|
+
const direction = coinIn === "SUI" ? "SUI_TO_WAL" : "WAL_TO_SUI";
|
|
72
|
+
if (amountRaw === undefined) {
|
|
73
|
+
throw new NmtsError("Say how much.", { exitCode: 2, nextStep: `\`${BINARY_NAME} wallet swap ${coinIn} <amount|max>\` — "max" swaps everything that can be swapped.` });
|
|
74
|
+
}
|
|
75
|
+
const resolved = await requireAccountCode();
|
|
76
|
+
const address = await walletAddress(resolved.code);
|
|
77
|
+
const stored = resolved.source === "file" || resolved.source === "file-locked" ? readCredentialsFile() : null;
|
|
78
|
+
const server = resolveServer(options.server ?? stored?.server);
|
|
79
|
+
const network = resolveNetwork(server, options.network ?? stored?.network);
|
|
80
|
+
const rails = railsFor(network);
|
|
81
|
+
const reads = await (options.readChain ?? (async (net) => (await import("../wallet-swap-chain.js")).swapReads(net)))(network);
|
|
82
|
+
// ① The balances — read, and refused as unread rather than treated as zero.
|
|
83
|
+
const purse = await reads.readWallet(address);
|
|
84
|
+
if (!purse.sui.read || !purse.wal.read) {
|
|
85
|
+
const which = [!purse.sui.read ? `SUI: ${purse.sui.why}` : null, !purse.wal.read ? `WAL: ${purse.wal.why}` : null].filter((w) => w !== null);
|
|
86
|
+
throw new NmtsError("A balance could not be read, so this tool cannot tell what can be swapped.", {
|
|
87
|
+
exitCode: 1,
|
|
88
|
+
nextStep: `Nothing was signed. That is not an empty wallet — ${which.join("; ")}.`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const sui = purse.sui.baseUnits;
|
|
92
|
+
const wal = purse.wal.baseUnits;
|
|
93
|
+
// ② The numbers — the browser's rules, unchanged. The gas kept back is the cap the person set,
|
|
94
|
+
// or the same reserve `wallet send` keeps when none was set.
|
|
95
|
+
const gasBudgetMist = options.feeCap === undefined ? undefined : clampGasBudgetMist(parseTokenAmountToBaseUnits(options.feeCap) ?? 0n);
|
|
96
|
+
const keptForGas = gasBudgetMist ?? SUI_GAS_RESERVE_MIST;
|
|
97
|
+
const maxIn = direction === "SUI_TO_WAL" ? maxSwappableSuiMist(sui, keptForGas) : wal;
|
|
98
|
+
const amountInUnits = amountRaw.toLowerCase() === "max" ? maxIn : parseTokenAmountToBaseUnits(amountRaw);
|
|
99
|
+
if (amountInUnits === null || amountInUnits <= 0n) {
|
|
100
|
+
throw new NmtsError("The amount must be a number above zero, with at most nine decimals.", { exitCode: 2 });
|
|
101
|
+
}
|
|
102
|
+
if (amountInUnits > maxIn || (direction === "WAL_TO_SUI" && sui < keptForGas)) {
|
|
103
|
+
throw new NmtsError(direction === "WAL_TO_SUI" && sui < keptForGas
|
|
104
|
+
? "A WAL swap pays its fee in SUI, and the wallet holds less SUI than is kept back for that."
|
|
105
|
+
: "The wallet does not hold that much.", {
|
|
106
|
+
exitCode: 4,
|
|
107
|
+
nextStep: `Nothing was signed. The wallet holds ${coinAmount(sui)} SUI and ${coinAmount(wal)} WAL; the most that can go in is ${coinAmount(maxIn)} ${coinIn} (SUI keeps ${coinAmount(keptForGas)} back for the fee).`,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
let slippageBps = SLIPPAGE_BPS_DEFAULT;
|
|
111
|
+
if (options.slippageBps !== undefined) {
|
|
112
|
+
if (!/^\d+$/.test(options.slippageBps.trim())) {
|
|
113
|
+
throw new NmtsError(`--slippage-bps must be a whole number of bps, ${SLIPPAGE_BPS_MIN} to ${SLIPPAGE_BPS_MAX} (1 bps = 0.01%).`, { exitCode: 2 });
|
|
114
|
+
}
|
|
115
|
+
slippageBps = clampSlippageBps(Number(options.slippageBps));
|
|
116
|
+
}
|
|
117
|
+
// ③ The venue and its quote. Testnet has one facility and no choice; mainnet has two and no default.
|
|
118
|
+
let venue;
|
|
119
|
+
let quote;
|
|
120
|
+
let bluefin;
|
|
121
|
+
let exchange;
|
|
122
|
+
if (rails[0] === "exchange") {
|
|
123
|
+
if (options.venue !== undefined)
|
|
124
|
+
throw new NmtsError(`On testnet the only rail is ${venueName("exchange")}; --venue does not apply.`, { exitCode: 2 });
|
|
125
|
+
if (direction === "WAL_TO_SUI")
|
|
126
|
+
throw new NmtsError(`${venueName("exchange")} only turns SUI into WAL.`, { exitCode: 2 });
|
|
127
|
+
if (options.slippageBps !== undefined)
|
|
128
|
+
throw new NmtsError("The testnet facility takes no minimum; --slippage-bps does not apply.", { exitCode: 2 });
|
|
129
|
+
venue = "exchange";
|
|
130
|
+
exchange = await reads.readExchange().catch((error) => {
|
|
131
|
+
throw new NmtsError("The testnet exchange could not be read, so what it would give is unknown.", { exitCode: 1, nextStep: `Nothing was signed. ${error instanceof Error ? error.message : String(error)}` });
|
|
132
|
+
});
|
|
133
|
+
// The facility's rate is a fraction read off its object; there is no fee to measure and no lot to leave over.
|
|
134
|
+
quote = { outUnits: (amountInUnits * exchange.rateWal) / exchange.rateSui, feeRateBps: null, leftoverInUnits: 0n };
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
if (options.venue !== undefined && options.venue !== "deepbook" && options.venue !== "bluefin") {
|
|
138
|
+
throw new NmtsError("--venue must be deepbook or bluefin.", { exitCode: 2 });
|
|
139
|
+
}
|
|
140
|
+
const wanted = options.venue === undefined ? ["deepbook", "bluefin"] : [options.venue];
|
|
141
|
+
const answers = await Promise.all(wanted.map(async (v) => {
|
|
142
|
+
try {
|
|
143
|
+
const binding = v === "bluefin" ? await reads.resolveBluefin() : null;
|
|
144
|
+
return { venue: v, quote: await reads.quote(v, direction, amountInUnits, binding), why: null, binding };
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return { venue: v, quote: null, why: error instanceof Error ? error.message : String(error), binding: null };
|
|
148
|
+
}
|
|
149
|
+
}));
|
|
150
|
+
if (options.venue === undefined) {
|
|
151
|
+
const lines = answers.map((a) => (a.quote === null ? `${a.venue.padEnd(9)} did not answer — ${a.why}` : quoteLine(a.quote, coinOut, coinIn)));
|
|
152
|
+
if (options.json) {
|
|
153
|
+
say(JSON.stringify({ direction, amountInUnits: amountInUnits.toString(), network, quotes: answers.map((a) => (a.quote === null ? { venue: a.venue, answered: false, why: a.why } : { venue: a.venue, answered: true, outUnits: a.quote.outUnits.toString(), feeRateBps: a.quote.feeRateBps, leftoverInUnits: a.quote.leftoverInUnits.toString() })), signed: false }));
|
|
154
|
+
return 4;
|
|
155
|
+
}
|
|
156
|
+
say(`Quotes for ${coinAmount(amountInUnits)} ${coinIn} → ${coinOut}, read from the ${network} chain just now`);
|
|
157
|
+
for (const line of lines)
|
|
158
|
+
say(` ${line}`);
|
|
159
|
+
say(``);
|
|
160
|
+
throw new NmtsError("Neither venue is a default, and this tool recommends neither. Choose one.", {
|
|
161
|
+
exitCode: 4,
|
|
162
|
+
nextStep: `Nothing was signed. Run it again with --venue deepbook or --venue bluefin. Any other exchange may be used instead.`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const only = answers[0];
|
|
166
|
+
if (only === undefined || only.quote === null) {
|
|
167
|
+
throw new NmtsError(`${venueName(wanted[0] ?? "deepbook")} did not answer just now.`, { exitCode: 1, nextStep: `Nothing was signed. ${only?.why ?? ""}`.trim() });
|
|
168
|
+
}
|
|
169
|
+
venue = only.venue;
|
|
170
|
+
quote = only.quote;
|
|
171
|
+
bluefin = only.binding ?? undefined;
|
|
172
|
+
}
|
|
173
|
+
if (quote.outUnits <= 0n) {
|
|
174
|
+
throw new NmtsError(`${venueName(venue)} would give nothing for ${coinAmount(amountInUnits)} ${coinIn}: the amount is below one lot, or the book is empty.`, { exitCode: 4, nextStep: "Nothing was signed." });
|
|
175
|
+
}
|
|
176
|
+
const minOutUnits = venue === "exchange" ? 0n : minOutFromQuote(quote.outUnits, slippageBps);
|
|
177
|
+
const shape = { venue, direction, amountInUnits, minOutUnits, gasBudgetMist, bluefin, exchange };
|
|
178
|
+
// ④ The fee of this exact swap. ⑤ The market check, said as unknown when it is.
|
|
179
|
+
const feeMist = await reads.estimateFee(shape, address);
|
|
180
|
+
const prices = await (options.readPrices ?? (async (base) => asMarketPrices(await request(base, "/api/prices").catch(() => null))))(server).catch(() => null);
|
|
181
|
+
const market = marketRate(direction, prices?.suiUsd, prices?.walUsd);
|
|
182
|
+
const implied = impliedRate(quote.outUnits, amountInUnits);
|
|
183
|
+
const deviationBps = market !== null && implied !== null ? priceDeviationBps(implied, market) : null;
|
|
184
|
+
const extremes = venue === "exchange" ? [] : swapExtremes({ slippageBps, budgetMist: gasBudgetMist ?? null, estimateMist: feeMist, deviationBps });
|
|
185
|
+
const suiAfter = sui - (direction === "SUI_TO_WAL" ? amountInUnits - quote.leftoverInUnits : -quote.outUnits) - (feeMist ?? 0n);
|
|
186
|
+
const walAfter = wal - (direction === "WAL_TO_SUI" ? amountInUnits - quote.leftoverInUnits : -quote.outUnits);
|
|
187
|
+
const facts = {
|
|
188
|
+
from: address,
|
|
189
|
+
venue,
|
|
190
|
+
direction,
|
|
191
|
+
amountInUnits: amountInUnits.toString(),
|
|
192
|
+
amountIn: coinAmount(amountInUnits),
|
|
193
|
+
coinIn,
|
|
194
|
+
coinOut,
|
|
195
|
+
quotedOutUnits: quote.outUnits.toString(),
|
|
196
|
+
quotedOut: coinAmount(quote.outUnits),
|
|
197
|
+
minOutUnits: minOutUnits.toString(),
|
|
198
|
+
minOut: coinAmount(minOutUnits),
|
|
199
|
+
slippageBps: venue === "exchange" ? null : slippageBps,
|
|
200
|
+
venueFeeBps: quote.feeRateBps,
|
|
201
|
+
leftoverInUnits: quote.leftoverInUnits.toString(),
|
|
202
|
+
feeMist: feeMist === null ? null : feeMist.toString(),
|
|
203
|
+
feeSui: feeMist === null ? null : coinAmount(feeMist),
|
|
204
|
+
feeCapMist: gasBudgetMist === undefined ? null : gasBudgetMist.toString(),
|
|
205
|
+
deviationBps: deviationBps === null ? null : Math.round(deviationBps * 100) / 100,
|
|
206
|
+
extremes,
|
|
207
|
+
network,
|
|
208
|
+
suiAfter: coinAmount(suiAfter < 0n ? 0n : suiAfter),
|
|
209
|
+
walAfter: coinAmount(walAfter < 0n ? 0n : walAfter),
|
|
210
|
+
};
|
|
211
|
+
// ⑥ The review, every time.
|
|
212
|
+
if (!options.json) {
|
|
213
|
+
say(`Swapping ${facts.amountIn} ${coinIn} for ${coinOut} on ${venueName(venue)}`);
|
|
214
|
+
say(` from ${address}`);
|
|
215
|
+
say(` Quoted ${facts.quotedOut} ${coinOut} — the chain's answer just now, not a promise${quote.leftoverInUnits > 0n ? `; ${coinAmount(quote.leftoverInUnits)} ${coinIn} would come back unused` : ""}`);
|
|
216
|
+
if (venue === "exchange")
|
|
217
|
+
say(` Rate ${exchange?.rateWal} WAL per ${exchange?.rateSui} SUI, read off the facility; it takes no minimum`);
|
|
218
|
+
else
|
|
219
|
+
say(` At least ${facts.minOut} ${coinOut}, or the swap fails on chain — slippage ${slippageBps} bps (${pct(slippageBps)})`);
|
|
220
|
+
say(quote.feeRateBps === null ? ` Venue fee: could not be measured from the quote just now` : ` Venue fee about ${pct(quote.feeRateBps)} of what goes in, measured from the quote`);
|
|
221
|
+
say(feeMist === null ? ` Chain fee (SUI): could not be measured just now; it is charged with the signature` : ` Chain fee about ${facts.feeSui} SUI, measured by a dry run just now — the amount charged is fixed when it executes`);
|
|
222
|
+
if (gasBudgetMist !== undefined)
|
|
223
|
+
say(` Fee cap ${coinAmount(gasBudgetMist)} SUI — only up to this is used; the rest stays`);
|
|
224
|
+
say(deviationBps === null ? ` Market no reference price could be read just now, so the quote was not compared with the market` : ` Market ${pct(deviationBps)} from the site's reference price (1 ${coinIn} ≈ ${market?.toFixed(4)} ${coinOut})`);
|
|
225
|
+
say(` Afterwards, if the chain gives exactly the quote: about ${facts.suiAfter} SUI and ${facts.walAfter} WAL`);
|
|
226
|
+
say(``);
|
|
227
|
+
say(` NMTS is not a party to this trade and takes nothing from it. Any other exchange may be used instead.`);
|
|
228
|
+
if (extremes.length > 0) {
|
|
229
|
+
say(``);
|
|
230
|
+
say(` ⛔ Outside the ordinary bands:`);
|
|
231
|
+
for (const e of extremes)
|
|
232
|
+
say(` · ${EXTREME_WORDS[e]}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// ⑦ The extremes gate: refused, unless a person said so.
|
|
236
|
+
if (extremes.length > 0 && options.dryRun !== true) {
|
|
237
|
+
if (options.acceptExtremes !== true) {
|
|
238
|
+
if (options.json) {
|
|
239
|
+
say(JSON.stringify({ ...facts, signed: false, refused: "extremes" }));
|
|
240
|
+
return 4;
|
|
241
|
+
}
|
|
242
|
+
throw new NmtsError("Nothing was signed: this swap is outside the ordinary bands.", {
|
|
243
|
+
exitCode: 4,
|
|
244
|
+
nextStep: `Choose again (--slippage-bps 50, a different --fee-cap, the other venue, or later), or — as a person — add --accept-extremes to go on anyway.`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
const mode = currentMode();
|
|
248
|
+
if (mode === "auto-low" || mode === "auto-high") {
|
|
249
|
+
throw new NmtsError("Going past the extremes gate is a person's act.", { exitCode: 5, nextStep: `Nothing was signed. --accept-extremes is refused in mode auto and with --skip-permissions.` });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// ⑧ Without --yes, that is the end.
|
|
253
|
+
if (options.dryRun === true || options.yes !== true) {
|
|
254
|
+
if (options.json) {
|
|
255
|
+
say(JSON.stringify({ ...facts, signed: false, dryRun: options.dryRun === true }));
|
|
256
|
+
return options.dryRun === true ? 0 : 4;
|
|
257
|
+
}
|
|
258
|
+
say(``);
|
|
259
|
+
if (options.dryRun === true) {
|
|
260
|
+
say(` Nothing was signed. Run the same command with --yes to swap.`);
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
throw new NmtsError("Nothing was signed: swapping needs --yes.", {
|
|
264
|
+
exitCode: 4,
|
|
265
|
+
nextStep: `Read the review above, then run the same command with --yes. The quote will be read again; the chain gives what it gives, never less than the minimum.`,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
// ⑨ The agreement — scope `all`, the amount and the fee held against its ceiling.
|
|
269
|
+
const spend = {
|
|
270
|
+
walFrost: direction === "WAL_TO_SUI" ? amountInUnits : 0n,
|
|
271
|
+
suiMist: (direction === "SUI_TO_WAL" ? amountInUnits : 0n) + (feeMist ?? 0n),
|
|
272
|
+
};
|
|
273
|
+
requireWalletGrant("exchange", spend, new Date(options.now ?? Date.now()));
|
|
274
|
+
// ⑩ The signature.
|
|
275
|
+
const sign = options.sign ?? (await import("../wallet-sign.js")).signSwap;
|
|
276
|
+
const digest = await sign({ network, code: resolved.code, shape });
|
|
277
|
+
recordWalletSpend(spend);
|
|
278
|
+
if (options.json) {
|
|
279
|
+
say(JSON.stringify({ ...facts, signed: true, digest, explorerUrl: explorerTxUrl(digest, network) }));
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
say(``);
|
|
283
|
+
say(` Swapped. Transaction ${digest}`);
|
|
284
|
+
say(` ${explorerTxUrl(digest, network)}`);
|
|
285
|
+
say(` What arrived is what the chain gave — at least the minimum above. \`${BINARY_NAME} wallet\` shows both balances now.`);
|
|
286
|
+
return 0;
|
|
287
|
+
}
|