viveworker 0.8.5 → 0.8.6

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "viveworker",
3
- "version": "0.8.5",
4
- "description": "Open mobile control surface for Codex, Claude, Thread Sharing, File Share, Moltbook, and A2A tasks on your trusted LAN.",
3
+ "version": "0.8.6",
4
+ "description": "Mobile control plane for AI agents. Approve, answer, and hand off work for Codex, Claude, MCP-ready tools, and A2A from one phone.",
5
5
  "author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -9,6 +9,15 @@
9
9
  "codex-desktop",
10
10
  "claude",
11
11
  "claude-desktop",
12
+ "mcp",
13
+ "model-context-protocol",
14
+ "mcp-server",
15
+ "ai-agents",
16
+ "agent-control-plane",
17
+ "agent-handoff",
18
+ "a2a",
19
+ "agent-to-agent",
20
+ "a2a-relay",
12
21
  "macos",
13
22
  "mobile",
14
23
  "ios",
@@ -22,17 +31,15 @@
22
31
  "moltbook",
23
32
  "file-share",
24
33
  "thread-sharing",
25
- "ai-agents",
26
- "a2a",
27
- "agent-to-agent",
28
- "a2a-relay",
29
- "agent-control-plane",
30
34
  "lan",
31
- "companion-app",
32
- "vivecoding"
35
+ "remote-pairing",
36
+ "companion-app"
33
37
  ],
34
38
  "homepage": "https://viveworker.com/",
35
39
  "repository": "viveworker-dev/viveworker",
40
+ "bugs": {
41
+ "url": "https://github.com/viveworker-dev/viveworker/issues"
42
+ },
36
43
  "type": "module",
37
44
  "bin": {
38
45
  "viveworker": "./scripts/viveworker.mjs"
@@ -67,7 +74,8 @@
67
74
  "ntfy/docker-compose.yml.example"
68
75
  ],
69
76
  "dependencies": {
70
- "@hazbase/auth": "^0.5.1",
77
+ "@hazbase/auth": "^0.5.3",
78
+ "@hazbase/simplicity": "^0.3.0",
71
79
  "@noble/ciphers": "^1.0.0",
72
80
  "@noble/curves": "^1.2.0",
73
81
  "@noble/hashes": "^1.3.2",
@@ -7,22 +7,19 @@
7
7
  * .html .htm .pdf .png .jpg .jpeg .gif .webp .csv
8
8
  *
9
9
  * Commands:
10
- * viveworker share upload <file> [--password <pw>] [--price <usd> --pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]
10
+ * viveworker share upload <file> [--password <pw>] [--price <usd> (--pay-to <0x…>|--accept wallet-defaults)] [--expires-days <n>] [--no-optimize] [--json]
11
11
  * viveworker share list [--json]
12
12
  * viveworker share update <slug> [--file <file>] [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]
13
13
  * viveworker share replace <slug> <file> [--expires-days <n>] [--no-optimize] [--json]
14
14
  * viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]
15
- * viveworker share pay <url> [--output <file>] [--dry-run] [--wallet eoa|hazbase] [--no-approval] [--json]
15
+ * viveworker share pay <url> [--output <file>] [--dry-run] [--wallet eoa|hazbase|liquid] [--no-approval] [--json]
16
16
  * viveworker share delete <slug>
17
17
  *
18
- * Paid shares: `--price 0.10 --pay-to 0x…` attaches an x402 payment gate.
19
- * Buyers hit HTTP 402 on first view, pay USDC on Base (testnet or mainnet
20
- * depending on worker config), and the worker serves the content. Any
21
- * x402-compatible clients can pay. This CLI ships a minimal buyer flow for
22
- * Base/Base Sepolia using VIVEWORKER_BUYER_PRIVATE_KEY or BUYER_PK. Before
23
- * signing, non-dry-run EOA payments are sent to the paired viveworker device for
24
- * human approval; --wallet hazbase asks the paired device to reauth with passkey
25
- * and pay from the configured hazBase Smart Wallet.
18
+ * Paid shares: `--price 0.10 --accept wallet-defaults` attaches x402 payment
19
+ * options from Wallet settings. EVM payments use @hazbase/auth, while Liquid
20
+ * PSET payments use @hazbase/simplicity from the Node CLI. Before signing,
21
+ * non-dry-run buyer payments are sent to the paired viveworker device for human
22
+ * approval.
26
23
  *
27
24
  * Environment overrides:
28
25
  * VIVEWORKER_SHARE_URL — share worker base URL (default: https://share.viveworker.com)
@@ -50,6 +47,21 @@ const DEFAULT_SHARE_URL = "https://share.viveworker.com";
50
47
  const MAX_FILE_SIZE = 5 * 1024 * 1024; // mirror worker
51
48
  const PAYMENT_APPROVAL_TIMEOUT_MS = 10 * 60 * 1000;
52
49
  const SUPPORTED_BUYER_NETWORKS = SUPPORTED_X402_BUYER_NETWORKS;
50
+ const LIQUID_X402_SCHEME = "exact-liquid-pset";
51
+ const PAYMENT_NETWORKS = {
52
+ base: { family: "evm", chainId: 8453, asset: "usdc", assets: ["usdc"], scheme: "exact", label: "Base", releaseStatus: "comingSoon" },
53
+ "base-sepolia": { family: "evm", chainId: 84532, asset: "usdc", assets: ["usdc"], scheme: "exact", label: "Base Sepolia" },
54
+ polygon: { family: "evm", chainId: 137, asset: "usdc", assets: ["usdc", "jpyc"], scheme: "exact", label: "Polygon", releaseStatus: "comingSoon" },
55
+ "polygon-amoy": { family: "evm", chainId: 80002, asset: "usdc", assets: ["usdc", "jpyc"], scheme: "exact", label: "Polygon Amoy" },
56
+ liquidtestnet: { family: "liquid", chainId: null, asset: "usdt", assets: ["usdt", "lbtc"], scheme: LIQUID_X402_SCHEME, label: "Liquid Testnet" },
57
+ liquidv1: { family: "liquid", chainId: null, asset: "usdt", assets: ["usdt", "lbtc"], scheme: LIQUID_X402_SCHEME, label: "Liquid", releaseStatus: "comingSoon" },
58
+ };
59
+ const PAYMENT_ASSETS = {
60
+ usdc: { decimals: 6, label: "USDC" },
61
+ jpyc: { decimals: 18, label: "JPYC" },
62
+ usdt: { decimals: 8, label: "USDt" },
63
+ lbtc: { decimals: 8, label: "L-BTC" },
64
+ };
53
65
 
54
66
  // Mirror share-worker/worker.js SHARE_TYPES. Keep in sync by inspection —
55
67
  // scripts/ and share-worker/ don't share a module. Adding a new type here
@@ -100,20 +112,22 @@ export async function runShareCli(args) {
100
112
 
101
113
  function printHelp() {
102
114
  console.log("Commands:");
103
- console.log(" viveworker share upload <file> [--password <pw>] [--price <usd> --pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]");
115
+ console.log(" viveworker share upload <file> [--password <pw>] [--price <amount> (--pay-to <0x…>|--accept wallet-defaults|--accept net:asset,...)] [--expires-days <n>] [--no-optimize] [--json]");
104
116
  console.log(" viveworker share list [--metrics] [--json]");
105
- console.log(" viveworker share update <slug> [--file <file>] [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>] [--expires-days <n>] [--no-optimize] [--json]");
117
+ console.log(" viveworker share update <slug> [--file <file>] [--password <pw>] [--no-password] [--price <usd>] [--no-price] [--pay-to <0x…>|--accept wallet-defaults] [--expires-days <n>] [--no-optimize] [--json]");
106
118
  console.log(" viveworker share replace <slug> <file> [--expires-days <n>] [--no-optimize] [--json]");
107
119
  console.log(" viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]");
108
- console.log(" viveworker share pay <url> [--output <file>] [--dry-run] [--no-approval] [--json]");
120
+ console.log(" viveworker share pay <url> [--output <file>] [--dry-run] [--wallet eoa|hazbase|liquid] [--no-approval] [--json]");
109
121
  console.log(" viveworker share delete <slug>");
110
122
  console.log("");
111
123
  console.log(`Accepted file types: ${ALLOWED_EXTENSIONS.join(" / ")}`);
112
124
  console.log("CSV files are rendered as an HTML table on view; append ?raw=1 for bytes.");
113
125
  console.log("HTML uploads are optimized by default when possible (use --no-optimize to disable).");
114
126
  console.log("");
115
- console.log("Paid shares (x402 / USDC on Base — CLOSED BETA, testnet only): --price 0.10 --pay-to 0x…");
116
- console.log(" Buyers can use: VIVEWORKER_BUYER_PRIVATE_KEY=0x… viveworker share pay <url>");
127
+ console.log("Paid shares (x402 — CLOSED BETA): --price 0.10 --pay-to 0x… or --price 0.10 --accept wallet-defaults");
128
+ console.log(" Explicit multi-network accepts: --accept base-sepolia:usdc,polygon-amoy:usdc,polygon-amoy:jpyc,liquidtestnet:usdt");
129
+ console.log(" EVM buyers can use: VIVEWORKER_BUYER_PRIVATE_KEY=0x… viveworker share pay <url>");
130
+ console.log(" Liquid buyers can use: viveworker share pay <url> --wallet liquid with VIVEWORKER_LIQUID_* RPC env.");
117
131
  console.log(" Non-dry-run payments require paired-device approval before signing.");
118
132
  console.log(" Use --no-approval / --yes only for trusted test automation.");
119
133
  console.log(" To use a hazbase wallet as payTo, resolve it first via the local /api/hazbase/payout-address endpoint.");
@@ -123,7 +137,7 @@ function printHelp() {
123
137
  console.log("Credentials are read from ~/.viveworker/a2a.env (same as `viveworker a2a`).");
124
138
  }
125
139
 
126
- const PRICE_REGEX = /^\d+(\.\d{1,6})?$/;
140
+ const PRICE_REGEX = /^\d+(\.\d{1,8})?$/;
127
141
  const ETH_ADDR_REGEX = /^0x[0-9a-fA-F]{40}$/;
128
142
 
129
143
  // ---------------------------------------------------------------------------
@@ -141,6 +155,7 @@ async function handleUpload(args) {
141
155
  const expiresDays = flags["expires-days"] || flags["expiresDays"] || "";
142
156
  const price = flags["price"] || "";
143
157
  const payTo = flags["pay-to"] || flags["payTo"] || "";
158
+ const accept = flags.accept || flags.accepts || "";
144
159
 
145
160
  if (password && password.length > 256) {
146
161
  throw new Error("Password too long (max 256 chars)");
@@ -152,17 +167,25 @@ async function handleUpload(args) {
152
167
  }
153
168
  }
154
169
 
155
- // --price / --pay-to are both required or both absent.
170
+ // --price must be paired with either legacy --pay-to or capability-based
171
+ // --accept. The latter lets a share advertise multiple x402 payment rails.
156
172
  const anyPrice = Boolean(price);
157
173
  const anyPayTo = Boolean(payTo);
158
- if (anyPrice !== anyPayTo) {
159
- throw new Error("--price and --pay-to must be supplied together");
174
+ const anyAccept = Boolean(accept);
175
+ if (anyPayTo && anyAccept) {
176
+ throw new Error("Use either --pay-to or --accept, not both");
177
+ }
178
+ if (anyPrice && !anyPayTo && !anyAccept) {
179
+ throw new Error("--price requires --pay-to or --accept");
180
+ }
181
+ if (!anyPrice && (anyPayTo || anyAccept)) {
182
+ throw new Error("--pay-to / --accept require --price");
160
183
  }
161
184
  if (anyPrice) {
162
185
  if (!PRICE_REGEX.test(price)) {
163
- throw new Error("--price must be a decimal with ≤6 fractional digits (e.g. `0.10`)");
186
+ throw new Error("--price must be a decimal with ≤8 fractional digits (e.g. `0.10`)");
164
187
  }
165
- if (!ETH_ADDR_REGEX.test(payTo)) {
188
+ if (anyPayTo && !ETH_ADDR_REGEX.test(payTo)) {
166
189
  throw new Error("--pay-to must be a 0x-prefixed 40-hex-char EVM address");
167
190
  }
168
191
  if (password) {
@@ -181,7 +204,12 @@ async function handleUpload(args) {
181
204
  if (expiresDays) form.set("expiresDays", String(expiresDays));
182
205
  if (anyPrice) {
183
206
  form.set("price", price);
184
- form.set("payTo", payTo);
207
+ if (anyAccept) {
208
+ const paymentOptions = await resolveAcceptedPaymentOptions(accept);
209
+ form.set("paymentOptions", JSON.stringify(paymentOptions));
210
+ } else {
211
+ form.set("payTo", payTo);
212
+ }
185
213
  }
186
214
 
187
215
  const res = await fetchWithTimeout(`${shareUrl}/api/upload`, {
@@ -219,7 +247,7 @@ async function handleUpload(args) {
219
247
  console.log("");
220
248
  if (body.hasPassword) console.log(` 🔒 Password-protected`);
221
249
  if (body.price && body.payTo) {
222
- console.log(` 💰 Paid — ${formatUsdc(body.price)} USDC on ${body.network || "?"} → ${body.payTo}`);
250
+ printPaidShareLine(body, " ");
223
251
  }
224
252
  if (body.expiresAtMs) console.log(` ⏱ Expires ${new Date(body.expiresAtMs).toISOString()}`);
225
253
  if (body.hasPassword || body.price || body.expiresAtMs) console.log("");
@@ -446,7 +474,7 @@ async function handleList(args) {
446
474
  console.log(` ${item.url}`);
447
475
  if (item.originalName) console.log(` ${item.originalName}`);
448
476
  if (item.price && item.payTo) {
449
- console.log(` 💰 ${formatUsdc(item.price)} USDC on ${item.network || "?"} → ${item.payTo}`);
477
+ printPaidShareLine(item, " ");
450
478
  }
451
479
  console.log("");
452
480
  }
@@ -553,6 +581,8 @@ async function handleUpdateWithFlags(flags, mode = "update") {
553
581
  Object.prototype.hasOwnProperty.call(flags, "noPrice");
554
582
  const hasPayTo = Object.prototype.hasOwnProperty.call(flags, "pay-to") ||
555
583
  Object.prototype.hasOwnProperty.call(flags, "payTo");
584
+ const hasAccept = Object.prototype.hasOwnProperty.call(flags, "accept") ||
585
+ Object.prototype.hasOwnProperty.call(flags, "accepts");
556
586
  const hasFile = Object.prototype.hasOwnProperty.call(flags, "file") ||
557
587
  Object.prototype.hasOwnProperty.call(flags, "f");
558
588
  const filePath = flags.file || flags.f || "";
@@ -569,9 +599,15 @@ async function handleUpdateWithFlags(flags, mode = "update") {
569
599
  if (hasFile && (typeof filePath !== "string" || filePath.length === 0)) {
570
600
  throw new Error("--file requires a path");
571
601
  }
572
- if (!hasPassword && !hasNoPassword && !hasExpires && !hasPrice && !hasNoPrice && !hasPayTo && !hasFile) {
602
+ if (hasAccept && hasPayTo) {
603
+ throw new Error("Use either --pay-to or --accept, not both");
604
+ }
605
+ if (hasAccept && !hasPrice) {
606
+ throw new Error("--accept currently requires --price so all accepted assets can be repriced together");
607
+ }
608
+ if (!hasPassword && !hasNoPassword && !hasExpires && !hasPrice && !hasNoPrice && !hasPayTo && !hasAccept && !hasFile) {
573
609
  throw new Error(
574
- "Nothing to update — specify at least one of --file <file>, --password <pw>, --no-password, --price <usd>, --no-price, --pay-to <0x…>, --expires-days <n>"
610
+ "Nothing to update — specify at least one of --file <file>, --password <pw>, --no-password, --price <usd>, --no-price, --pay-to <0x…>, --accept <list>, --expires-days <n>"
575
611
  );
576
612
  }
577
613
 
@@ -596,9 +632,13 @@ async function handleUpdateWithFlags(flags, mode = "update") {
596
632
  throw new Error("--price requires a value (use --no-price to clear)");
597
633
  }
598
634
  if (!PRICE_REGEX.test(pv)) {
599
- throw new Error("--price must be a decimal with ≤6 fractional digits (e.g. `0.10`)");
635
+ throw new Error("--price must be a decimal with ≤8 fractional digits (e.g. `0.10`)");
600
636
  }
601
637
  body.price = pv;
638
+ if (hasAccept) {
639
+ const accept = flags.accept || flags.accepts || "";
640
+ body.paymentOptions = JSON.stringify(await resolveAcceptedPaymentOptions(accept));
641
+ }
602
642
  // --pay-to is only required on first set; the worker rejects with
603
643
  // `payTo-required-on-first-price` if the share had no payTo before, so
604
644
  // let it do the policy enforcement. But if the user passed --pay-to
@@ -702,9 +742,7 @@ async function handleUpdateWithFlags(flags, mode = "update") {
702
742
  }
703
743
  if (respBody.price && respBody.payTo) {
704
744
  const rotated = hasPrice || respBody.paymentSessionsInvalidated ? " (paid sessions invalidated)" : "";
705
- console.log(
706
- ` 💰 Paid — ${formatUsdc(respBody.price)} USDC on ${respBody.network || "?"} → ${respBody.payTo}${rotated}`
707
- );
745
+ printPaidShareLine(respBody, " ", rotated);
708
746
  } else if (hasNoPrice) {
709
747
  console.log(` 💸 Payment gate removed`);
710
748
  }
@@ -834,7 +872,7 @@ async function handlePay(args) {
834
872
  }
835
873
 
836
874
  const x402 = parseX402ResponseBody(initialText);
837
- const requirement = selectX402PaymentRequirement(x402);
875
+ const requirement = selectSupportedPaymentRequirement(x402, flags);
838
876
  const paymentSummary = summarizeRequirement(requirement);
839
877
  if (flags["dry-run"] || flags.dryRun) {
840
878
  const dryRun = {
@@ -853,7 +891,9 @@ async function handlePay(args) {
853
891
  }
854
892
 
855
893
  const walletMode = resolveBuyerWalletMode(flags);
856
- const payment = walletMode === "hazbase"
894
+ const payment = String(requirement.scheme || "") === LIQUID_X402_SCHEME
895
+ ? await requestLiquidPayment({ url, requirement, paymentSummary, flags })
896
+ : walletMode === "hazbase"
857
897
  ? await requestHazbaseWalletPayment({ url, x402, requirement, paymentSummary, flags })
858
898
  : await requestEoaPayment({ url, requirement, paymentSummary, flags });
859
899
  const paid = await fetchWithTimeout(url, {
@@ -946,24 +986,50 @@ async function handleDelete(args) {
946
986
  console.log(`✅ Deleted ${slug}`);
947
987
  }
948
988
 
989
+ function selectSupportedPaymentRequirement(x402, flags = {}) {
990
+ const accepts = Array.isArray(x402?.accepts) ? x402.accepts : [];
991
+ const preferredNetwork = normalizePaymentNetworkKey(flags.network || flags["payment-network"] || flags.paymentNetwork || "");
992
+ if (preferredNetwork) {
993
+ const match = accepts.find((item) => String(item?.network || "") === preferredNetwork);
994
+ if (match) return match;
995
+ }
996
+ const liquid = accepts.find((item) => String(item?.scheme || "") === LIQUID_X402_SCHEME);
997
+ if (liquid && resolveBuyerWalletMode(flags) === "liquid") return liquid;
998
+ const evm = accepts.find((item) => (
999
+ String(item?.scheme || "exact") === "exact" &&
1000
+ SUPPORTED_BUYER_NETWORKS[String(item?.network || "")]
1001
+ ));
1002
+ if (evm) return selectX402PaymentRequirement({ ...x402, accepts: [evm] });
1003
+ if (liquid) return liquid;
1004
+ const offered = accepts.map((item) => `${item?.scheme || "?"}:${item?.network || "?"}`).join(", ") || "none";
1005
+ throw new Error(`No supported x402 payment option found (offered: ${offered})`);
1006
+ }
1007
+
949
1008
  function summarizeRequirement(requirement) {
950
1009
  const network = SUPPORTED_BUYER_NETWORKS[String(requirement.network)];
1010
+ const assetKey = String(requirement.extra?.asset || "").toLowerCase();
1011
+ const decimals = Number(requirement.extra?.decimals ?? (String(requirement.scheme || "") === LIQUID_X402_SCHEME ? 8 : 6));
1012
+ const assetLabel = PAYMENT_ASSETS[assetKey]?.label || (String(requirement.scheme || "") === LIQUID_X402_SCHEME ? "USDt" : "USDC");
951
1013
  return {
952
1014
  network: String(requirement.network),
953
- chainId: network.chainId,
1015
+ chainId: network?.chainId ?? null,
1016
+ scheme: String(requirement.scheme || "exact"),
954
1017
  amountAtomic: String(requirement.maxAmountRequired),
955
- amountUsdc: formatUsdc(requirement.maxAmountRequired),
1018
+ amount: formatAtomicAmount(requirement.maxAmountRequired, decimals),
1019
+ amountUsdc: formatAtomicAmount(requirement.maxAmountRequired, decimals),
1020
+ assetLabel,
956
1021
  payTo: String(requirement.payTo),
957
1022
  asset: String(requirement.asset),
1023
+ assetKey,
958
1024
  resource: String(requirement.resource || ""),
959
1025
  description: String(requirement.description || ""),
960
1026
  };
961
1027
  }
962
1028
 
963
1029
  function printPaymentSummary(summary) {
964
- const network = SUPPORTED_BUYER_NETWORKS[summary.network];
1030
+ const network = SUPPORTED_BUYER_NETWORKS[summary.network] || PAYMENT_NETWORKS[summary.network];
965
1031
  console.log("");
966
- console.log(`${summary.paid ? "Paid" : "Payment required"} — ${summary.amountUsdc} USDC on ${network?.label || summary.network}`);
1032
+ console.log(`${summary.paid ? "Paid" : "Payment required"} — ${summary.amount} ${summary.assetLabel} on ${network?.label || summary.network}`);
967
1033
  console.log(` to: ${summary.payTo}`);
968
1034
  if (summary.payer) console.log(` from: ${summary.payer}`);
969
1035
  if (summary.resource) console.log(` resource: ${summary.resource}`);
@@ -975,7 +1041,8 @@ function resolveBuyerWalletMode(flags) {
975
1041
  const raw = String(flags.wallet || flags["buyer-wallet"] || flags.buyerWallet || flags["payment-wallet"] || "eoa").trim().toLowerCase();
976
1042
  if (!raw || raw === "eoa" || raw === "private-key" || raw === "private_key") return "eoa";
977
1043
  if (raw === "hazbase" || raw === "hazbase-wallet" || raw === "smart-wallet" || raw === "smart_wallet") return "hazbase";
978
- throw new Error("--wallet must be either eoa or hazbase");
1044
+ if (raw === "liquid" || raw === "elements" || raw === "liquid-wallet" || raw === "liquid_wallet") return "liquid";
1045
+ throw new Error("--wallet must be eoa, hazbase, or liquid");
979
1046
  }
980
1047
 
981
1048
  async function requestEoaPayment({ url, requirement, paymentSummary, flags }) {
@@ -1028,6 +1095,55 @@ async function requestHazbaseWalletPayment({ url, x402, requirement, paymentSumm
1028
1095
  };
1029
1096
  }
1030
1097
 
1098
+ async function requestLiquidPayment({ url, requirement, paymentSummary, flags }) {
1099
+ await requirePaymentApproval({ url, paymentSummary, flags });
1100
+ const configText = await readOptionalEnvFile(CONFIG_ENV_FILE);
1101
+ const rpcUrl = String(process.env.VIVEWORKER_LIQUID_RPC_URL || envValue(configText, "VIVEWORKER_LIQUID_RPC_URL") || "").trim();
1102
+ const username = String(process.env.VIVEWORKER_LIQUID_RPC_USER || envValue(configText, "VIVEWORKER_LIQUID_RPC_USER") || "").trim();
1103
+ const password = String(process.env.VIVEWORKER_LIQUID_RPC_PASSWORD || envValue(configText, "VIVEWORKER_LIQUID_RPC_PASSWORD") || "").trim();
1104
+ const wallet = String(
1105
+ flags["liquid-wallet"] ||
1106
+ flags.liquidWallet ||
1107
+ process.env.VIVEWORKER_LIQUID_WALLET ||
1108
+ envValue(configText, "VIVEWORKER_LIQUID_WALLET") ||
1109
+ "",
1110
+ ).trim();
1111
+ const payer = String(process.env.VIVEWORKER_LIQUID_PAYER || envValue(configText, "VIVEWORKER_LIQUID_PAYER") || "").trim();
1112
+ if (!rpcUrl || !username || !password || !wallet) {
1113
+ throw new Error(
1114
+ "Liquid x402 payment requires VIVEWORKER_LIQUID_RPC_URL, VIVEWORKER_LIQUID_RPC_USER, " +
1115
+ "VIVEWORKER_LIQUID_RPC_PASSWORD, and VIVEWORKER_LIQUID_WALLET."
1116
+ );
1117
+ }
1118
+ const simplicity = await loadSimplicitySdk();
1119
+ const rpc = new simplicity.ElementsRpcClient({ url: rpcUrl, username, password, wallet });
1120
+ const prepared = await simplicity.prepareLiquidX402PsetPayment({
1121
+ call: (method, params = [], selectedWallet) => rpc.call(method, params, selectedWallet),
1122
+ }, {
1123
+ requirements: requirement,
1124
+ wallet,
1125
+ payer: payer || undefined,
1126
+ });
1127
+ return {
1128
+ header: prepared.xPayment,
1129
+ payer: prepared.paymentPayload?.payer || payer || "",
1130
+ payload: prepared.paymentPayload,
1131
+ walletMode: "liquid",
1132
+ };
1133
+ }
1134
+
1135
+ async function loadSimplicitySdk() {
1136
+ try {
1137
+ return await import("@hazbase/simplicity");
1138
+ } catch (error) {
1139
+ throw new Error(
1140
+ "@hazbase/simplicity is required for Liquid x402 payments. " +
1141
+ "For local testing, symlink node_modules/@hazbase/simplicity to the local simplicity-sdk build. " +
1142
+ `Import failed: ${error?.message || error}`
1143
+ );
1144
+ }
1145
+ }
1146
+
1031
1147
  async function requirePaymentApproval({ url, paymentSummary, flags }) {
1032
1148
  const config = await resolveApprovalBridgeConfig();
1033
1149
  if (shouldSkipPaymentApproval(flags, config.envText)) {
@@ -1211,7 +1327,7 @@ function resolveBuyerPrivateKey() {
1211
1327
  if (!raw) {
1212
1328
  throw new Error(
1213
1329
  "Missing buyer wallet private key. Set VIVEWORKER_BUYER_PRIVATE_KEY=0x... " +
1214
- "or BUYER_PK=0x... for Base/Base Sepolia x402 payments."
1330
+ "or BUYER_PK=0x... for EVM x402 payments."
1215
1331
  );
1216
1332
  }
1217
1333
  return raw.startsWith("0x") ? raw : `0x${raw}`;
@@ -1237,6 +1353,145 @@ function previewPaidBody(contentType, bytes) {
1237
1353
  console.log(` received ${formatSize(bytes.length)} (${contentType || "unknown content type"}). Pass --output <file> to save bytes.`);
1238
1354
  }
1239
1355
 
1356
+ function normalizePaymentNetworkKey(raw) {
1357
+ const value = String(raw || "").trim().toLowerCase();
1358
+ if (value === "basesepolia") return "base-sepolia";
1359
+ if (value === "polygonamoy" || value === "amoy") return "polygon-amoy";
1360
+ if (value === "matic") return "polygon";
1361
+ if (value === "liquid" || value === "liquid-mainnet") return "liquidv1";
1362
+ return PAYMENT_NETWORKS[value] ? value : "";
1363
+ }
1364
+
1365
+ function isPaymentNetworkAvailable(network) {
1366
+ return PAYMENT_NETWORKS[network]?.releaseStatus !== "comingSoon";
1367
+ }
1368
+
1369
+ function normalizePaymentAsset(raw, network) {
1370
+ const networkInfo = PAYMENT_NETWORKS[network] || null;
1371
+ const value = String(raw || PAYMENT_NETWORKS[network]?.asset || "").trim().toLowerCase();
1372
+ if (value === "usd-t" || value === "tether") return "usdt";
1373
+ if (value === "jpy" || value === "jpy-coin") return "jpyc";
1374
+ if (value === "bitcoin" || value === "btc") return "lbtc";
1375
+ const allowed = Array.isArray(networkInfo?.assets) ? networkInfo.assets : [networkInfo?.asset].filter(Boolean);
1376
+ return PAYMENT_ASSETS[value] && allowed.includes(value) ? value : "";
1377
+ }
1378
+
1379
+ async function resolveAcceptedPaymentOptions(rawAccept) {
1380
+ const accept = String(rawAccept || "").trim();
1381
+ if (!accept) throw new Error("--accept requires wallet-defaults or a comma-separated network:asset list");
1382
+ const status = await fetchHazbaseWalletStatusForCli();
1383
+ const capabilities = Array.isArray(status.paymentCapabilities) ? status.paymentCapabilities : [];
1384
+ const defaults = status.agentPaymentDefaults && typeof status.agentPaymentDefaults === "object"
1385
+ ? status.agentPaymentDefaults
1386
+ : { mode: "configured", accepts: [] };
1387
+ const requested = accept === "wallet-defaults"
1388
+ ? String(defaults.mode || "") === "custom"
1389
+ ? (Array.isArray(defaults.effectiveAccepts) ? defaults.effectiveAccepts : defaults.accepts || [])
1390
+ .filter((entry) => isPaymentNetworkAvailable(normalizePaymentNetworkKey(entry?.network)))
1391
+ : capabilities
1392
+ .filter((entry) => entry?.configured && entry?.enabled !== false && isPaymentNetworkAvailable(normalizePaymentNetworkKey(entry.network)))
1393
+ .map((entry) => ({ network: entry.network, asset: entry.asset || PAYMENT_NETWORKS[entry.network]?.asset }))
1394
+ : accept.split(",").map((part) => {
1395
+ const [networkRaw, assetRaw = ""] = part.trim().split(":");
1396
+ const network = normalizePaymentNetworkKey(networkRaw);
1397
+ const asset = normalizePaymentAsset(assetRaw, network);
1398
+ if (!network || !asset) throw new Error(`Invalid --accept entry: ${part}`);
1399
+ if (!isPaymentNetworkAvailable(network)) {
1400
+ throw new Error(`${PAYMENT_NETWORKS[network].label} payment capabilities are coming soon and cannot be used before the formal release.`);
1401
+ }
1402
+ return { network, asset };
1403
+ });
1404
+ const resolved = [];
1405
+ for (const item of requested) {
1406
+ const network = normalizePaymentNetworkKey(item.network);
1407
+ const asset = normalizePaymentAsset(item.asset, network);
1408
+ const capability = capabilities.find((entry) => (
1409
+ normalizePaymentNetworkKey(entry.network) === network &&
1410
+ normalizePaymentAsset(entry.asset, network) === asset &&
1411
+ entry.configured
1412
+ ));
1413
+ if (!capability?.payTo && !capability?.payoutAddress) {
1414
+ throw new Error(`No configured payout capability for ${network}:${asset}. Open Wallet settings and issue/register it first.`);
1415
+ }
1416
+ resolved.push({
1417
+ network,
1418
+ asset,
1419
+ scheme: capability.scheme || PAYMENT_NETWORKS[network]?.scheme || "exact",
1420
+ payTo: capability.payTo || capability.payoutAddress,
1421
+ payoutMethod: capability.payoutMethod || (PAYMENT_NETWORKS[network]?.family === "liquid" ? "external_liquid" : "hazbase_wallet"),
1422
+ ...(capability.assetId ? { assetId: capability.assetId } : {}),
1423
+ });
1424
+ }
1425
+ if (resolved.length === 0) {
1426
+ throw new Error("No configured payment capabilities found. Open Wallet settings and issue/register a payout option first.");
1427
+ }
1428
+ return resolved;
1429
+ }
1430
+
1431
+ async function fetchHazbaseWalletStatusForCli() {
1432
+ const config = await resolveApprovalBridgeConfig();
1433
+ if (!config.baseUrl || !config.sessionSecret) {
1434
+ throw new Error(`--accept requires the local viveworker bridge. Start viveworker or check ${CONFIG_ENV_FILE}.`);
1435
+ }
1436
+ const response = await getBridgeJson(config, "/api/hazbase/status", 10_000);
1437
+ if (!response.ok) {
1438
+ throw new Error(`Failed to read Wallet settings from bridge (${response.body?.error || `http-${response.status}`}).`);
1439
+ }
1440
+ return response.body || {};
1441
+ }
1442
+
1443
+ function getBridgeJson(config, pathname, timeoutMs) {
1444
+ const endpoint = `${config.baseUrl}${pathname}`;
1445
+ return new Promise((resolve) => {
1446
+ let resolved = false;
1447
+ const done = (value) => {
1448
+ if (resolved) return;
1449
+ resolved = true;
1450
+ resolve(value);
1451
+ };
1452
+ const timer = setTimeout(() => done({ ok: false, status: 0, body: { error: "bridge-request-timeout" } }), timeoutMs);
1453
+ let parsedUrl;
1454
+ try {
1455
+ parsedUrl = new URL(endpoint);
1456
+ } catch {
1457
+ clearTimeout(timer);
1458
+ done({ ok: false, status: 0, body: { error: "invalid-approval-server-url" } });
1459
+ return;
1460
+ }
1461
+ const isHttps = parsedUrl.protocol === "https:";
1462
+ const port = parsedUrl.port ? Number(parsedUrl.port) : isHttps ? 443 : 80;
1463
+ const req = (isHttps ? https : http).request({
1464
+ hostname: parsedUrl.hostname,
1465
+ port,
1466
+ path: parsedUrl.pathname + parsedUrl.search,
1467
+ method: "GET",
1468
+ headers: {
1469
+ accept: "application/json",
1470
+ "x-viveworker-hook-secret": config.sessionSecret,
1471
+ },
1472
+ rejectUnauthorized: false,
1473
+ }, (res) => {
1474
+ let text = "";
1475
+ res.on("data", (chunk) => { text += chunk; });
1476
+ res.on("end", () => {
1477
+ clearTimeout(timer);
1478
+ let body = {};
1479
+ try {
1480
+ body = text ? JSON.parse(text) : {};
1481
+ } catch {
1482
+ body = { error: text.slice(0, 200) };
1483
+ }
1484
+ done({ ok: res.statusCode >= 200 && res.statusCode < 300, status: res.statusCode || 0, body });
1485
+ });
1486
+ });
1487
+ req.on("error", (error) => {
1488
+ clearTimeout(timer);
1489
+ done({ ok: false, status: 0, body: { error: error.message || "bridge-request-failed" } });
1490
+ });
1491
+ req.end();
1492
+ });
1493
+ }
1494
+
1240
1495
  function formatApiError(op, status, body) {
1241
1496
  const code = body?.error || "";
1242
1497
  switch (code) {
@@ -1288,7 +1543,7 @@ function formatApiError(op, status, body) {
1288
1543
  case "payTo-cannot-be-cleared-alone":
1289
1544
  return `${op} failed (${status}): --no-price clears both price and payTo; clearing payTo alone is not supported`;
1290
1545
  case "payment-network-not-configured":
1291
- return `${op} failed (${status}): the worker's X402_NETWORK var is not set to a supported chain (base / base-sepolia)`;
1546
+ return `${op} failed (${status}): the worker's X402_NETWORK var is not set to a supported chain (base / base-sepolia / polygon / polygon-amoy)`;
1292
1547
  case "payment-required":
1293
1548
  return `${op} failed (${status}): this share requires payment (x402). Use an x402-compatible client (e.g. \`x402-fetch\` on npm) to pay.`;
1294
1549
  case "payment-verification-failed":
@@ -1408,6 +1663,10 @@ function formatSize(bytes) {
1408
1663
  // back to "0.00" on anything unparseable so we never crash the CLI output
1409
1664
  // on a malformed server response.
1410
1665
  function formatUsdc(atomic) {
1666
+ return formatAtomicAmount(atomic, 6);
1667
+ }
1668
+
1669
+ function formatAtomicAmount(atomic, decimals = 6) {
1411
1670
  let n;
1412
1671
  try {
1413
1672
  n = BigInt(String(atomic ?? "0"));
@@ -1415,12 +1674,34 @@ function formatUsdc(atomic) {
1415
1674
  return "0.00";
1416
1675
  }
1417
1676
  if (n < 0n) n = -n;
1418
- const whole = n / 1_000_000n;
1419
- const frac = (n % 1_000_000n).toString().padStart(6, "0").replace(/0+$/u, "");
1677
+ const places = Math.max(0, Math.min(18, Number(decimals) || 0));
1678
+ const scale = 10n ** BigInt(places);
1679
+ const whole = scale > 0n ? n / scale : n;
1680
+ const frac = scale > 0n ? (n % scale).toString().padStart(places, "0").replace(/0+$/u, "") : "";
1420
1681
  if (!frac) return `${whole}.00`;
1421
1682
  return `${whole}.${frac.padEnd(2, "0")}`;
1422
1683
  }
1423
1684
 
1685
+ function printPaidShareLine(item, indent = "", suffix = "") {
1686
+ const options = Array.isArray(item.paymentOptions) && item.paymentOptions.length > 0
1687
+ ? item.paymentOptions
1688
+ : [{
1689
+ network: item.network || item.paymentNetwork || "?",
1690
+ asset: "usdc",
1691
+ priceAtomic: item.price,
1692
+ payTo: item.payTo,
1693
+ }];
1694
+ const rendered = options.map((option) => {
1695
+ const network = option.network || item.network || item.paymentNetwork || "?";
1696
+ const asset = normalizePaymentAsset(option.asset || "usdc", network) || "usdc";
1697
+ const label = PAYMENT_ASSETS[asset]?.label || asset.toUpperCase();
1698
+ const decimals = Number(option.decimals || PAYMENT_ASSETS[asset]?.decimals || 6);
1699
+ const amount = formatAtomicAmount(option.priceAtomic || item.price, decimals);
1700
+ return `${amount} ${label} on ${network} → ${option.payTo || item.payTo || "?"}`;
1701
+ });
1702
+ console.log(`${indent}💰 Paid — ${rendered.join(" / ")}${suffix}`);
1703
+ }
1704
+
1424
1705
  function formatRelative(ms) {
1425
1706
  const sec = Math.floor(ms / 1000);
1426
1707
  if (sec < 60) return `${sec}s ago`;