paytaca-cli 0.8.0 → 0.8.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/README.md +37 -4
- package/dist/web/page.d.ts.map +1 -1
- package/dist/web/page.js +89 -54
- package/dist/web/page.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A command-line interface for the Paytaca [Bitcoin Cash](https://bitcoincash.org)
|
|
|
4
4
|
|
|
5
5
|
Bitcoin Cash is peer-to-peer electronic cash, enabling fast, low-fee transactions for everyday use. Paytaca CLI brings the full capabilities of the Paytaca wallet to the terminal — create wallets, derive addresses, send and receive BCH, manage CashTokens (fungible tokens and NFTs), swap tokens on the Cauldron DEX, pay x402 HTTP APIs, chat over Nostr, and view transaction history, all from the command line.
|
|
6
6
|
|
|
7
|
-
Designed to be AI agent-friendly and useful for automation by power users. It ships an MCP server
|
|
7
|
+
Designed to be AI agent-friendly and useful for automation by power users. It ships an MCP server, a one-step installer for the Paytaca AI provider, and a local web UI (`paytaca web`).
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
|
|
@@ -36,11 +36,15 @@ Most commands accept `--json` for machine-readable output and `--chipnet` to tar
|
|
|
36
36
|
```bash
|
|
37
37
|
paytaca wallet create # Generate a new 12-word seed phrase
|
|
38
38
|
paytaca wallet create --chipnet # Create on chipnet (testnet)
|
|
39
|
-
paytaca wallet import # Import
|
|
39
|
+
paytaca wallet import # Import a seed phrase (12/15/18/21/24 words)
|
|
40
40
|
paytaca wallet info # Show wallet hash, address, and balance
|
|
41
41
|
paytaca wallet export # Display the stored seed phrase (interactive terminal + biometrics)
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
> `wallet import` accepts 12/15/18/21/24-word phrases separated by spaces or
|
|
45
|
+
> commas. Unknown words are rejected with the closest BIP39 suggestion, and the
|
|
46
|
+
> checksum is validated before the wallet is stored.
|
|
47
|
+
|
|
44
48
|
> `wallet export` requires an interactive terminal and prompts for biometric
|
|
45
49
|
> authentication (Touch ID, fingerprint, or Windows Hello) when available. It
|
|
46
50
|
> cannot be used from scripts, pipes, or other non-interactive processes.
|
|
@@ -185,7 +189,7 @@ paytaca ai auto-refill --status # Inspect; also --disable, --delete
|
|
|
185
189
|
|
|
186
190
|
`ai configure` accepts `--backend`, `--path`, `--api-key`, `--chipnet`, and `-y/--yes`. `ai purchase` and `ai auto-refill` accept `--lift` to pay with LIFT tokens at a discount. Add `--json` to any `ai` subcommand for machine-readable output.
|
|
187
191
|
|
|
188
|
-
Armed auto-refill state lives at `~/.paytaca/auto-refill.json`.
|
|
192
|
+
Armed auto-refill state lives at `~/.paytaca/auto-refill.json`. Arming performs one immediate check (buying right away if the armed model has no credits), and the `paytaca mcp` server keeps a background backstop (every 60s) while a session is running. The `get_credits` MCP tool is read-only and never triggers a purchase. Each execution is appended to `~/.paytaca/auto-refill-events.jsonl` and the latest is reported as `lastEvent`. It disarms when the budget is spent, funds are short, or the plan is no longer offered. Use `--disable` to pause and `--delete` to remove the configuration (history is kept).
|
|
189
193
|
|
|
190
194
|
### Image Generation
|
|
191
195
|
|
|
@@ -232,7 +236,7 @@ paytaca ai configure opencode --api-key sk-pytc-… # read-only wallet
|
|
|
232
236
|
|
|
233
237
|
Read-only wallets report credits from the shared wallet hash but can't buy plans — fund credits from the full wallet.
|
|
234
238
|
|
|
235
|
-
**MCP tools:** wallet
|
|
239
|
+
**MCP tools:** wallet (`get_balance`, `get_transactions`, `get_receiving_address`, `get_tokens`, `send`) plus Paytaca AI (`get_models`, `get_plans`, `get_credits`, `buy_plan`, `auto_refill`, `get_help`, `generate_image`, `get_image_status`, `get_image_models`, `get_image_history`).
|
|
236
240
|
|
|
237
241
|
Spending tools (`send`, `buy_plan`, `auto_refill`, `generate_image`) require host-level approval. By default MCP operates on your **main wallet** and can spend real funds.
|
|
238
242
|
|
|
@@ -271,6 +275,27 @@ paytaca chat remove-display-name # Remove published name
|
|
|
271
275
|
paytaca chat remove-bch-address # Remove published address
|
|
272
276
|
```
|
|
273
277
|
|
|
278
|
+
### Web UI
|
|
279
|
+
|
|
280
|
+
Launch a local browser UI for the wallet, Cauldron swaps, and Paytaca AI:
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
paytaca web # Serve on http://127.0.0.1:7474 and open the browser
|
|
284
|
+
paytaca web --port 8080 # Use a custom port
|
|
285
|
+
paytaca web --chipnet # Mainnet by default; --chipnet targets testnet
|
|
286
|
+
paytaca web --backend <url> # Override the Paytaca AI backend URL
|
|
287
|
+
paytaca web --no-open # Do not auto-open the browser
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The server binds to `127.0.0.1` only. Access is gated by a random per-session token sent in the `X-Paytaca-Token` header; the browser is auto-opened with a session link that carries the token, and the page strips it from the address bar after loading. With `--no-open`, use the printed URL alongside the session link to open it manually.
|
|
291
|
+
|
|
292
|
+
The UI is organized into two tabs:
|
|
293
|
+
|
|
294
|
+
- **Wallet** — send and receive BCH, send CashTokens, swap on the Cauldron DEX, and review transaction history (date, type, counterparty, amount, USD value, fee, and explorer links), with token-category selection in the send and swap forms.
|
|
295
|
+
- **AI** — buy plans (BCH or a live LIFT quote), manage auto-refill (enable / pause / delete, with budget-cap validation), and browse generated images (gallery with lightbox, order history, and delete).
|
|
296
|
+
|
|
297
|
+
Incoming transactions are streamed live over a Watchtower WebSocket and surface as in-page notifications; a light/dark theme toggle is persisted in the browser.
|
|
298
|
+
|
|
274
299
|
## Network
|
|
275
300
|
|
|
276
301
|
All commands default to **mainnet**. Pass `--chipnet` for testnet:
|
|
@@ -293,6 +318,8 @@ Powered by [@napi-rs/keyring](https://github.com/Brooooooklyn/keyring-node) (pre
|
|
|
293
318
|
|
|
294
319
|
Nostr chat keys are **not** persisted — they are re-derived from the wallet mnemonic at runtime via HD path `m/44'/1237'/0'/0/0` and held only in memory for the duration of a session. Non-secret chat metadata (contacts, rooms, messages) is written to `~/.paytaca/chat-state.json` with mode `0600`; no key material is ever written there.
|
|
295
320
|
|
|
321
|
+
The `paytaca web` server listens on `127.0.0.1` only and requires a random per-session token in the `X-Paytaca-Token` header; requests whose `Host` header is not `127.0.0.1`/`localhost` are rejected, request bodies are capped at 1 MB, and internal error details are logged to stderr rather than returned to the client.
|
|
322
|
+
|
|
296
323
|
## Architecture
|
|
297
324
|
|
|
298
325
|
```
|
|
@@ -310,6 +337,7 @@ src/
|
|
|
310
337
|
check.ts Check if URL requires x402 payment
|
|
311
338
|
ai.ts Paytaca AI (configure, api-key, models, plans, credits, purchase, auto-refill, image)
|
|
312
339
|
chat.ts Nostr chat (contacts, conversations, identity, listen)
|
|
340
|
+
web.ts `paytaca web` — local browser UI launcher
|
|
313
341
|
mcp.ts `paytaca mcp` stdio entry + per-harness config templates
|
|
314
342
|
update.ts Self-update from npm registry
|
|
315
343
|
core/
|
|
@@ -318,6 +346,10 @@ src/
|
|
|
318
346
|
mcp/
|
|
319
347
|
server.ts MCP server construction and stdio lifecycle
|
|
320
348
|
tools.ts MCP tool registration (wallet reads, send, Paytaca AI)
|
|
349
|
+
web/
|
|
350
|
+
server.ts Local HTTP server (token auth, JSON API routes)
|
|
351
|
+
page.ts Self-contained Alpine.js UI (wallet, swap, AI tabs)
|
|
352
|
+
assets.ts Inline asset loading (vendored Alpine.js, styles)
|
|
321
353
|
wallet/
|
|
322
354
|
index.ts Wallet class, mnemonic gen/import/load
|
|
323
355
|
bch.ts BchWallet (balance, send, history, CashTokens)
|
|
@@ -364,6 +396,7 @@ src/
|
|
|
364
396
|
| `@cashlab/cauldron` | Cauldron DEX swap building |
|
|
365
397
|
| `@napi-rs/keyring` | OS-native keychain storage |
|
|
366
398
|
| `@modelcontextprotocol/sdk` | MCP server (stdio) |
|
|
399
|
+
| `alpinejs` | Reactive web UI (`paytaca web`), vendored inline |
|
|
367
400
|
| `bip39` | Mnemonic generation and validation |
|
|
368
401
|
| `nostr-tools` | Nostr chat events, keys, and relay transport |
|
|
369
402
|
| `js-sha256` | Hashing for key derivation and payment headers |
|
package/dist/web/page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/web/page.ts"],"names":[],"mappings":"AAWA,wBAAgB,UAAU,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../src/web/page.ts"],"names":[],"mappings":"AAWA,wBAAgB,UAAU,IAAI,MAAM,CAwpBnC"}
|
package/dist/web/page.js
CHANGED
|
@@ -18,6 +18,7 @@ export function renderPage() {
|
|
|
18
18
|
<style>
|
|
19
19
|
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
|
|
20
20
|
:root{
|
|
21
|
+
color-scheme:light;
|
|
21
22
|
--sans:'Rubik',system-ui,-apple-system,'Segoe UI',sans-serif;
|
|
22
23
|
--mono:'JetBrains Mono',ui-monospace,'SF Mono','Cascadia Code',monospace;
|
|
23
24
|
--bg:#ecf3f3;
|
|
@@ -45,6 +46,7 @@ export function renderPage() {
|
|
|
45
46
|
--on-ink:#fff;
|
|
46
47
|
}
|
|
47
48
|
[data-theme="dark"]{
|
|
49
|
+
color-scheme:dark;
|
|
48
50
|
--bg:#273746;
|
|
49
51
|
--surface:rgba(28,40,51,0.7);
|
|
50
52
|
--surface-2:#1c2833;
|
|
@@ -102,9 +104,10 @@ body{font-family:var(--sans);background:var(--bg);color:var(--ink);font-size:14p
|
|
|
102
104
|
.tx-toast a{font-family:var(--mono);font-size:10.5px;color:var(--accent);text-decoration:underline;text-underline-offset:2px}
|
|
103
105
|
.tx-toast a:hover{color:var(--accent-strong)}
|
|
104
106
|
.content{width:100%;max-width:1400px;margin:0 auto;padding:26px 30px 56px}
|
|
105
|
-
.grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}
|
|
107
|
+
.grid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:16px}
|
|
106
108
|
.span-4{grid-column:span 4}.span-5{grid-column:span 5}.span-6{grid-column:span 6}.span-7{grid-column:span 7}.span-8{grid-column:span 8}.span-12{grid-column:span 12}
|
|
107
|
-
.grid>div{display:flex;flex-direction:column}.grid>div>.card{flex:1}
|
|
109
|
+
.grid>div{display:flex;flex-direction:column;min-width:0}.grid>div>.card{flex:1;min-width:0}
|
|
110
|
+
.tbl-scroll{overflow-x:auto}
|
|
108
111
|
.card{background:var(--surface);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid var(--line);border-radius:var(--r);padding:20px;box-shadow:var(--shadow),var(--glass-hi)}
|
|
109
112
|
.card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:18px}
|
|
110
113
|
.card-title{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:0.13em;text-transform:uppercase;color:var(--ink-2)}
|
|
@@ -172,7 +175,8 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
172
175
|
.tbl tbody tr:hover{background:var(--surface-2)}
|
|
173
176
|
.tbl tr:last-child td{border-bottom:none}
|
|
174
177
|
.tbl-num{font-family:var(--mono);font-variant-numeric:tabular-nums;font-size:12px}
|
|
175
|
-
.hist-date
|
|
178
|
+
.hist-date-text{font-family:var(--mono);font-size:11px;color:var(--ink-2)}
|
|
179
|
+
.hist-date a{color:var(--ink-3);text-decoration:none;border-bottom:1px dotted var(--line-2);font-family:var(--mono);font-size:10px}
|
|
176
180
|
.hist-date a:hover{color:var(--accent-strong);border-bottom-color:var(--accent)}.hist-addr{max-width:210px}
|
|
177
181
|
.hist-sub{font-family:var(--mono);font-size:10px;color:var(--ink-3);margin-top:2px}
|
|
178
182
|
.hist-amt{font-family:var(--mono);font-size:12px;font-variant-numeric:tabular-nums}
|
|
@@ -187,6 +191,11 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
187
191
|
.pill-wait{background:rgba(255,193,7,0.12);color:#d4a017}
|
|
188
192
|
[data-theme="dark"] .pill-ok{background:rgba(40,167,69,0.18);color:#4ade80}
|
|
189
193
|
[data-theme="dark"] .pill-wait{background:rgba(255,193,7,0.18);color:#fbbf24}
|
|
194
|
+
[data-theme="dark"] .field-input{background:rgba(255,255,255,0.06);border-color:rgba(255,255,255,0.2)}
|
|
195
|
+
[data-theme="dark"] .field-input:focus{background:rgba(255,255,255,0.09)}
|
|
196
|
+
[data-theme="dark"] .segmented,[data-theme="dark"] .seg{border-color:rgba(255,255,255,0.2)}
|
|
197
|
+
[data-theme="dark"] .segmented-opt.active{background:rgba(255,255,255,0.12)}
|
|
198
|
+
[data-theme="dark"] .seg button{background:rgba(255,255,255,0.08)}
|
|
190
199
|
.pagination{display:flex;align-items:center;justify-content:center;gap:14px;margin-top:18px}
|
|
191
200
|
.pagination .page-info{font-family:var(--mono);font-size:11px;color:var(--ink-3)}
|
|
192
201
|
.empty{padding:30px 12px;text-align:center;font-family:var(--mono);font-size:11.5px;letter-spacing:0.02em;color:var(--ink-3)}
|
|
@@ -200,11 +209,23 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
200
209
|
.plan-group:first-child{padding-top:2px}
|
|
201
210
|
.plan-group:last-child{border-bottom:0;padding-bottom:2px}
|
|
202
211
|
.plan-model{font-size:13.5px;font-weight:600;margin-bottom:12px}
|
|
203
|
-
.plan-
|
|
204
|
-
.plan-
|
|
205
|
-
.plan-
|
|
212
|
+
.plan-slider{margin:2px 0 0}
|
|
213
|
+
.plan-slider input[type="range"]{-webkit-appearance:none;appearance:none;display:block;width:100%;height:4px;border-radius:2px;background:var(--line);outline:none;cursor:pointer;margin:0}
|
|
214
|
+
.plan-slider input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border-radius:50%;background:var(--accent);border:2px solid var(--surface);box-shadow:0 1px 4px rgba(0,0,0,0.25);cursor:pointer}
|
|
215
|
+
.plan-slider input[type="range"]::-moz-range-thumb{width:12px;height:12px;border-radius:50%;background:var(--accent);border:2px solid var(--surface);box-shadow:0 1px 4px rgba(0,0,0,0.25);cursor:pointer}
|
|
216
|
+
.plan-slider input[type="range"]::-moz-range-track{background:transparent}
|
|
217
|
+
.plan-ticks{display:flex;justify-content:space-between;padding:7px 8px 0}
|
|
218
|
+
.plan-ticks span{width:0;display:flex;flex-direction:column;align-items:center;white-space:nowrap;font-family:var(--mono);font-size:9.5px;letter-spacing:0.04em;color:var(--ink-3)}
|
|
219
|
+
.plan-ticks span::before{content:'';width:1px;height:4px;background:var(--line);margin-bottom:3px}
|
|
220
|
+
.plan-selection{display:flex;align-items:center;gap:12px;margin-top:12px;min-height:29px}
|
|
221
|
+
.plan-selection .grow{flex:1}
|
|
222
|
+
.plan-hint{font-family:var(--mono);font-size:10.5px;letter-spacing:0.04em;color:var(--ink-3)}
|
|
223
|
+
.plan-insufficient{font-family:var(--mono);font-size:10px;letter-spacing:0.04em;color:var(--err)}
|
|
206
224
|
.plan-duration{font-family:var(--mono);font-size:10.5px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;color:var(--ink-2)}
|
|
207
225
|
.plan-price{font-size:13px;font-weight:600}
|
|
226
|
+
.refill-model{margin-bottom:14px}
|
|
227
|
+
.refill-pay{display:flex;align-items:center;gap:12px;margin-top:14px}
|
|
228
|
+
.refill-pay .field-label{margin:0}
|
|
208
229
|
.seg{display:inline-flex;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
|
|
209
230
|
.seg button{appearance:none;background:var(--surface-3);border:0;padding:8px 20px;font-family:var(--mono);font-size:11px;font-weight:600;letter-spacing:0.08em;color:var(--ink-3);cursor:pointer}
|
|
210
231
|
.seg button+.seg button{border-left:1px solid var(--line)}
|
|
@@ -319,7 +340,7 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
319
340
|
<button class="action-btn" :class="{active: walletAction==='receive'}" @click="walletAction = walletAction==='receive' ? null : 'receive'"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 7 7 17"/><path d="M16 17H7V8"/></svg>Receive</button>
|
|
320
341
|
<button class="action-btn" :class="{active: walletAction==='swap'}" @click="walletAction = walletAction==='swap' ? null : 'swap'"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m16 3 4 4-4 4"/><path d="M20 7H4"/><path d="m8 21-4-4 4-4"/><path d="M4 17h16"/></svg>Swap</button>
|
|
321
342
|
</div>
|
|
322
|
-
<div class="grid" x-show="walletAction">
|
|
343
|
+
<div class="grid" x-show="walletAction" style="margin-top:16px">
|
|
323
344
|
<div class="span-12">
|
|
324
345
|
<div class="card">
|
|
325
346
|
<div class="card-head"><div class="card-title" x-text="walletAction==='send' ? 'Send' : walletAction==='receive' ? 'Receive' : 'Cauldron Swap'"></div><button class="btn-close" @click="walletAction=null" title="Close">✕</button></div>
|
|
@@ -343,14 +364,10 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
343
364
|
<div class="segmented-opt" :class="{active: sendType==='bch'}" @click="sendType='bch'; sendError=''; sendSuccess=''">BCH</div>
|
|
344
365
|
<div class="segmented-opt" :class="{active: sendType==='token'}" @click="sendType='token'; sendError=''; sendSuccess=''">CashToken</div>
|
|
345
366
|
</div>
|
|
346
|
-
<div x-show="sendType==='token'">
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
</div>
|
|
351
|
-
<div x-show="sendType==='bch'">
|
|
352
|
-
<div class="field"><label class="field-label">Amount</label><div class="inline-row"><input class="field-input" type="number" x-model="sendAmount" placeholder="0.0" step="any" min="0"><select class="field-input shrink" x-model="sendCurrency" style="width:104px"><option value="bch">BCH</option><option value="sats">sats</option><option value="usd">USD</option></select></div></div>
|
|
353
|
-
</div>
|
|
367
|
+
<div class="field" x-show="sendType==='token'"><label class="field-label">Token Category ID</label><select class="field-input" x-model="sendTokenPreset" @change="if(sendTokenPreset==='__custom__')sendCategory='';else sendCategory=sendTokenPreset"><option value="__custom__">Enter token category</option><option value="5932b2fd4915d6a75d3ec53282cd49118149a2176ee67ed68b1111ff0786f7fc">LIFT</option><option value="2469acc5afa4b10cb5b5c04afb89c3a3ffd61c5da9c01e26d00951cae2a02544">PUSD</option></select></div>
|
|
368
|
+
<div class="field" x-show="sendType==='token' && sendTokenPreset === '__custom__'"><input class="field-input" type="text" x-model="sendCategory" placeholder="Paste 64-char hex category ID"></div>
|
|
369
|
+
<div class="field" x-show="sendType==='token'"><label class="field-label">Token Amount (base units)</label><input class="field-input" type="text" x-model="sendTokenAmount" placeholder="e.g. 1000"></div>
|
|
370
|
+
<div class="field" x-show="sendType==='bch'"><div class="field-label" style="display:flex;justify-content:space-between;align-items:baseline">Amount<a href="#" @click.prevent="fillMaxSend()" style="font-family:var(--mono);font-size:9.5px;font-weight:500;letter-spacing:0.12em;text-transform:uppercase;color:var(--accent);text-decoration:underline;text-underline-offset:2px;cursor:pointer">Max</a></div><div class="inline-row"><input class="field-input" type="number" x-model="sendAmount" placeholder="0.0" step="any" min="0"><select class="field-input shrink" x-model="sendCurrency" style="width:104px"><option value="bch">BCH</option><option value="sats">sats</option><option value="usd">USD</option></select></div></div>
|
|
354
371
|
<div class="field"><label class="field-label">Recipient Address</label><input class="field-input" type="text" x-model="sendAddress" placeholder="bitcoincash:q..."></div>
|
|
355
372
|
<template x-if="sendType==='token' && sendAddress && !isTokenAddr(sendAddress)"><div class="warning-bar">Address is not token-aware (z-prefix). Tokens may be lost.</div></template>
|
|
356
373
|
<div x-show="sendError" class="field-error" x-text="sendError"></div>
|
|
@@ -362,10 +379,10 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
362
379
|
<div class="field"><label class="field-label">Token</label><select class="field-input" x-model="swapToken"><option value="__custom__">Enter token category</option><template x-for="t in swapTokens" :key="t.category"><option :value="t.category" x-text="t.symbol + ' · ' + t.category.slice(0,10) + '…' + t.category.slice(-6)"></option></template></select></div>
|
|
363
380
|
<div class="field" x-show="swapToken === '__custom__'"><input class="field-input" type="text" x-model="swapCustomCategory" placeholder="Paste 64-char hex category ID"></div>
|
|
364
381
|
<div class="segmented" style="margin-bottom:14px">
|
|
365
|
-
<div class="segmented-opt" :class="{active: swapDir==='sell'}" @click="swapDir='sell'; swapQuoteText=''; pendingSwap=null">Sell Token → BCH</div>
|
|
366
382
|
<div class="segmented-opt" :class="{active: swapDir==='buy'}" @click="swapDir='buy'; swapQuoteText=''; pendingSwap=null">Buy Token ← BCH</div>
|
|
383
|
+
<div class="segmented-opt" :class="{active: swapDir==='sell'}" @click="swapDir='sell'; swapQuoteText=''; pendingSwap=null">Sell Token → BCH</div>
|
|
367
384
|
</div>
|
|
368
|
-
<div class="field"><label class="field-label"
|
|
385
|
+
<div class="field"><label class="field-label">Token Amount (human units)</label><input class="field-input" type="number" x-model="swapAmount" placeholder="0.0" step="any" min="0"></div>
|
|
369
386
|
<div x-show="swapError" class="field-error" x-text="swapError"></div>
|
|
370
387
|
<button class="btn btn-primary" style="margin-top:14px;min-width:180px" @click="doSwapQuote()" :disabled="swapBusy" x-text="swapBusy ? 'Fetching quote…' : 'Get Quote'"></button>
|
|
371
388
|
<template x-if="swapQuoteText"><div style="margin-top:16px;padding-top:16px;border-top:1px solid var(--line-2)">
|
|
@@ -389,8 +406,8 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
389
406
|
<div class="card-head"><div class="card-title">History</div><select class="field-input" x-model="histType" @change="loadHistory(1)" style="width:auto;padding:5px 9px;font-size:11.5px"><option value="all">All</option><option value="incoming">Incoming</option><option value="outgoing">Outgoing</option></select></div>
|
|
390
407
|
<template x-if="!histRecords.length && histLoading"><div class="empty">Loading…</div></template>
|
|
391
408
|
<template x-if="!histRecords.length && !histLoading"><div class="empty">No transactions yet</div></template>
|
|
392
|
-
<template x-if="histRecords.length"><div><table class="tbl"><thead><tr><th>Date</th><th>Type</th><th>Counterparty</th><th style="text-align:right">Amount</th></tr></thead><tbody>
|
|
393
|
-
<template x-for="(r, ri) in histRecords" :key="ri"><tr><td class="hist-date" style="white-space:nowrap"><a :href="r.explorer" target="_blank" rel="noopener" :title="'View '+r.txid+' on explorer'" x-text="r.
|
|
409
|
+
<template x-if="histRecords.length"><div class="tbl-scroll"><table class="tbl"><thead><tr><th>Date</th><th>Type</th><th>Counterparty</th><th style="text-align:right">Amount</th></tr></thead><tbody>
|
|
410
|
+
<template x-for="(r, ri) in histRecords" :key="ri"><tr><td class="hist-date" style="white-space:nowrap"><div class="hist-date-text" x-text="r.dateText"></div><a x-show="r.txidShort" :href="r.explorer" target="_blank" rel="noopener" :title="'View '+r.txid+' on explorer'" x-text="r.txidShort"></a></td><td><span class="pill" :class="r.type==='incoming'?'pill-in':'pill-out'" x-text="r.type==='incoming'?'IN':'OUT'"></span></td><td class="hist-addr"><span class="mono-break" :title="r.counterparty" x-text="r.partyShort"></span><div class="hist-sub" x-show="r.type==='outgoing' && r.fee" x-text="'fee '+r.fee+' BCH'"></div></td><td class="tbl-num" style="text-align:right"><div class="hist-amt" :class="r.type==='incoming'?'hist-in':'hist-out'" x-text="(r.type==='incoming'?'+':'-')+r.amountText+' BCH'"></div><div class="hist-sub" x-show="r.usd!=null" x-text="'≈ $'+r.usd+' USD'"></div></td></tr></template>
|
|
394
411
|
</tbody></table><div class="pagination"><button class="btn btn-sm btn-ghost" @click="loadHistory(histPage - 1)" x-show="histPage > 1">← Prev</button><span class="page-info">Page <span x-text="histPage"></span> / <span x-text="histNumPages"></span></span><button class="btn btn-sm btn-ghost" @click="loadHistory(histPage + 1)" x-show="histHasNext">Next →</button></div></div></template>
|
|
395
412
|
</div>
|
|
396
413
|
</div>
|
|
@@ -414,9 +431,9 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
414
431
|
<div class="span-8">
|
|
415
432
|
<div class="card">
|
|
416
433
|
<div class="card-head"><div class="card-title">Usage</div></div>
|
|
417
|
-
<template x-if="state?.usage?.length"><table class="tbl"><thead><tr><th>Model</th><th>Status</th><th>Remaining</th><th>Used</th></tr></thead><tbody>
|
|
434
|
+
<template x-if="state?.usage?.length"><div class="tbl-scroll"><table class="tbl"><thead><tr><th>Model</th><th>Status</th><th>Remaining</th><th>Used</th></tr></thead><tbody>
|
|
418
435
|
<template x-for="(s, i) in state?.usage || []" :key="i"><tr><td style="font-size:12.5px" x-text="s.displayName || s.model || 'Unknown'"></td><td><span class="pill pill-active" x-show="s.active">ACTIVE</span><span class="pill pill-idle" x-show="!s.active">IDLE</span></td><td class="tbl-num" x-text="fmtDuration(s.remainingSeconds)"></td><td class="tbl-num" x-text="fmtDuration(s.usedSeconds)"></td></tr></template>
|
|
419
|
-
</tbody></table></template>
|
|
436
|
+
</tbody></table></div></template>
|
|
420
437
|
<template x-if="state && !state?.usage?.length"><div class="empty">No usage data</div></template>
|
|
421
438
|
<template x-if="!state"><div class="empty">Loading…</div></template>
|
|
422
439
|
</div>
|
|
@@ -427,7 +444,7 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
427
444
|
<button class="action-btn" :class="{active: aiAction==='refill'}" @click="aiAction = aiAction==='refill' ? null : 'refill'"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/><path d="M3 21v-5h5"/></svg>Auto-Refill</button>
|
|
428
445
|
<button class="action-btn" :class="{active: aiAction==='images'}" @click="aiAction = aiAction==='images' ? null : 'images'"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>Generate Images</button>
|
|
429
446
|
</div>
|
|
430
|
-
<div class="grid" x-show="aiAction==='buy' || aiAction==='refill'">
|
|
447
|
+
<div class="grid" x-show="aiAction==='buy' || aiAction==='refill'" style="margin-top:16px">
|
|
431
448
|
<div class="span-12">
|
|
432
449
|
<div class="card">
|
|
433
450
|
<div class="card-head"><div class="card-title" x-text="aiAction==='buy' ? 'Buy Credits' : 'Auto-Refill'"></div><button class="btn-close" @click="aiAction=null" title="Close">✕</button></div>
|
|
@@ -437,14 +454,15 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
437
454
|
<template x-for="plan in state?.plans || []" :key="plan.modelId">
|
|
438
455
|
<div class="plan-group">
|
|
439
456
|
<div class="plan-model" x-text="plan.displayName || plan.modelId"></div>
|
|
440
|
-
<div class="plan-
|
|
441
|
-
<
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
457
|
+
<div class="plan-slider">
|
|
458
|
+
<input type="range" min="0" max="3" step="1" :value="planSliderIdx[plan.modelId] || 0" @input="setPlanSlider(plan.modelId, parseInt($event.target.value))" :aria-label="'Duration for ' + (plan.displayName || plan.modelId)" :style="'background:linear-gradient(to right,var(--accent) 0%,var(--accent) ' + ((planSliderIdx[plan.modelId] || 0) / 3 * 100) + '%,var(--line) ' + ((planSliderIdx[plan.modelId] || 0) / 3 * 100) + '%,var(--line) 100%)'">
|
|
459
|
+
<div class="plan-ticks"><span>0</span><span>15</span><span>30</span><span>60</span></div>
|
|
460
|
+
</div>
|
|
461
|
+
<div class="plan-selection">
|
|
462
|
+
<template x-if="planMinutes(plan.modelId) > 0"><span style="display:flex;align-items:center;gap:12px"><span class="plan-duration" x-text="planTier(plan)?.durationDisplay || ''"></span><span class="plan-price" x-text="tierPriceLabel(planTier(plan))"></span><template x-if="tierInsufficient(planTier(plan))"><span class="plan-insufficient">insufficient balance</span></template></span></template>
|
|
463
|
+
<template x-if="!planMinutes(plan.modelId)"><span class="plan-hint">Slide to select duration</span></template>
|
|
464
|
+
<span class="grow"></span>
|
|
465
|
+
<button class="btn btn-sm btn-primary" :disabled="!planMinutes(plan.modelId) || tierInsufficient(planTier(plan))" @click="buyPlan(plan)">Buy</button>
|
|
448
466
|
</div>
|
|
449
467
|
</div>
|
|
450
468
|
</template>
|
|
@@ -454,20 +472,26 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
454
472
|
</div></template>
|
|
455
473
|
<template x-if="aiAction==='refill'"><div>
|
|
456
474
|
<template x-if="state && (!state.autoRefill || refillEditing)"><div>
|
|
457
|
-
<div style="font-size:12.5px;color:var(--ink-3);margin-bottom:
|
|
458
|
-
<div class="
|
|
459
|
-
<div class="
|
|
460
|
-
<div class="
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
<div class="
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
<
|
|
470
|
-
|
|
475
|
+
<div style="font-size:12.5px;color:var(--ink-3);margin-bottom:14px">Auto-refill buys a new plan when credits run out. It tops up immediately if this model has no active credits.</div>
|
|
476
|
+
<div class="plan-group" style="padding-top:0">
|
|
477
|
+
<div class="refill-model"><label class="field-label">Model</label><select class="field-input" x-model="refillModel"><option value="">Select model</option><template x-for="p in state?.plans || []" :key="p.modelId"><option :value="p.modelId" x-text="p.displayName || p.modelId"></option></template></select></div>
|
|
478
|
+
<div class="plan-slider">
|
|
479
|
+
<input type="range" min="0" max="2" step="1" :value="refillMinutesIdx" @input="refillMinutesIdx = parseInt($event.target.value)" aria-label="Top-up duration" :style="'background:linear-gradient(to right,var(--accent) 0%,var(--accent) ' + (refillMinutesIdx / 2 * 100) + '%,var(--line) ' + (refillMinutesIdx / 2 * 100) + '%,var(--line) 100%)'">
|
|
480
|
+
<div class="plan-ticks"><span>15m</span><span>30m</span><span>1h</span></div>
|
|
481
|
+
</div>
|
|
482
|
+
<div class="plan-slider" style="margin-top:10px">
|
|
483
|
+
<input type="range" min="0" max="3" step="1" :value="refillCapIdx" @input="refillCapIdx = parseInt($event.target.value)" aria-label="Budget cap multiplier" :style="'background:linear-gradient(to right,var(--accent) 0%,var(--accent) ' + (refillCapIdx / 3 * 100) + '%,var(--line) ' + (refillCapIdx / 3 * 100) + '%,var(--line) 100%)'">
|
|
484
|
+
<div class="plan-ticks"><span>2×</span><span>4×</span><span>8×</span><span>∞</span></div>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="refill-pay"><span class="field-label">Pay with</span><div class="seg"><button :class="{active: refillPayMethod==='bch'}" @click="refillPayMethod='bch'">BCH</button><button :class="{active: refillPayMethod==='lift'}" @click="refillPayMethod='lift'">LIFT</button></div></div>
|
|
487
|
+
<div class="plan-selection">
|
|
488
|
+
<template x-if="refillModel"><span style="display:flex;align-items:center;gap:12px"><span class="plan-duration" x-text="refillTopUpMinutes() + 'm top-up'"></span><span class="plan-price" style="font-size:11.5px;color:var(--ink-3)" x-text="refillCapLabel()"></span></span></template>
|
|
489
|
+
<template x-if="!refillModel"><span class="plan-hint">Select a model to configure auto-refill</span></template>
|
|
490
|
+
<span class="grow"></span>
|
|
491
|
+
<button class="btn btn-sm btn-primary" :disabled="!refillModel" @click="enableRefill()" x-text="refillEditing ? 'Save & Resume' : 'Enable'"></button>
|
|
492
|
+
<template x-if="refillEditing"><button class="btn btn-sm" @click="cancelRefillEdit()">Cancel</button></template>
|
|
493
|
+
</div>
|
|
494
|
+
<template x-if="refillNotice"><div style="margin-top:10px;font-size:12px;color:var(--ink-3)" x-text="refillNotice"></div></template>
|
|
471
495
|
</div>
|
|
472
496
|
</div></template>
|
|
473
497
|
<template x-if="state?.autoRefill && !refillEditing"><div>
|
|
@@ -495,7 +519,7 @@ input[type="number"]{-moz-appearance:textfield}input::-webkit-outer-spin-button,
|
|
|
495
519
|
</div>
|
|
496
520
|
</div>
|
|
497
521
|
</div>
|
|
498
|
-
<div x-show="aiAction==='images'" class="grid">
|
|
522
|
+
<div x-show="aiAction==='images'" class="grid" style="margin-top:16px">
|
|
499
523
|
<div class="span-5">
|
|
500
524
|
<div class="card">
|
|
501
525
|
<div class="card-head"><div class="card-title">Image Generation</div></div>
|
|
@@ -579,20 +603,30 @@ token:(function(){var t=new URLSearchParams(window.location.search).get('token')
|
|
|
579
603
|
rcvType:'bch',rcvCategory:'',rcvAmount:'',rcvView:null,rcvQrSvg:'',
|
|
580
604
|
sendType:'bch',sendAmount:'',sendCurrency:'bch',sendAddress:'',sendCategory:'',sendTokenPreset:'__custom__',sendTokenAmount:'',sendError:'',sendSuccess:'',pendingSend:null,
|
|
581
605
|
histType:'all',histRecords:[],histPage:1,histNumPages:1,histHasNext:false,histLoading:false,histNetwork:'mainnet',
|
|
582
|
-
swapTokens:[{symbol:'LIFT',category:'5932b2fd4915d6a75d3ec53282cd49118149a2176ee67ed68b1111ff0786f7fc'},{symbol:'PUSD',category:'2469acc5afa4b10cb5b5c04afb89c3a3ffd61c5da9c01e26d00951cae2a02544'}],swapToken:'5932b2fd4915d6a75d3ec53282cd49118149a2176ee67ed68b1111ff0786f7fc',swapCustomCategory:'',swapDir:'
|
|
583
|
-
pendingPurchase:null,purchaseBusy:false,purchaseMethod:'bch',purchaseError:'',liftQuote:null,liftQuoteBusy:false,liftQuoteError:'',showPurchaseModal:false,showSendModal:false,sendConfirmBusy:false,
|
|
606
|
+
swapTokens:[{symbol:'LIFT',category:'5932b2fd4915d6a75d3ec53282cd49118149a2176ee67ed68b1111ff0786f7fc'},{symbol:'PUSD',category:'2469acc5afa4b10cb5b5c04afb89c3a3ffd61c5da9c01e26d00951cae2a02544'}],swapToken:'5932b2fd4915d6a75d3ec53282cd49118149a2176ee67ed68b1111ff0786f7fc',swapCustomCategory:'',swapDir:'buy',swapAmount:'',swapError:'',swapQuoteText:'',swapBusy:false,swapExecBusy:false,pendingSwap:null,
|
|
607
|
+
pendingPurchase:null,purchaseBusy:false,purchaseMethod:'bch',purchaseError:'',liftQuote:null,liftQuoteBusy:false,liftQuoteError:'',showPurchaseModal:false,showSendModal:false,sendConfirmBusy:false,planSliderIdx:{},PLAN_STEPS:[0,15,30,60],
|
|
584
608
|
pendingImageQuote:null,showImageQuoteModal:false,imgGenBusy:false,imgQuoteBusy:false,walletAction:null,aiAction:null,lightbox:null,lightboxConfirmDelete:false,imgUrls:{},
|
|
585
|
-
refillModel:'',
|
|
609
|
+
refillModel:'',refillMinutesIdx:1,refillCapIdx:0,REFILL_MINUTES:[15,30,60],REFILL_CAPS:[2,4,8,-1],refillPayMethod:'bch',refillEditing:false,refillNotice:'',
|
|
586
610
|
imgPrompt:'',imgModel:'bytedance-seed/seedream-5-0-pro',imgAspect:'1:1',imgQuality:'auto',
|
|
587
611
|
toastVisible:false,toastMsg:'',toastOk:true,
|
|
588
612
|
ws:null,wsStatus:'connecting',wsAddress:null,wsSeen:{},wsReconnectTimer:null,wsPingTimer:null,wsRefreshTimer:null,wsLateTimer:null,notify:null,_ntid:0,
|
|
589
|
-
async init(){document.documentElement.setAttribute('data-theme',this.theme);try{this.state=await this.api('GET','/api/wallet/state');this.loadHistory(1);await this.onRcvChange();this.connectWatch()}catch(e){this.toast(e.message,true)}var self=this;document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible'&&self.state){if(!self.ws||self.ws.readyState!==1){if(self.wsReconnectTimer)clearTimeout(self.wsReconnectTimer);self.connectWatch()}}});window.addEventListener('online',function(){if(self.state){if(self.wsReconnectTimer)clearTimeout(self.wsReconnectTimer);self.connectWatch()}})},
|
|
590
|
-
async load(){try{this.state=await this.api('GET','/api/wallet/state');this.onRcvChange();this.loadHistory(this.histPage);
|
|
613
|
+
async init(){document.documentElement.setAttribute('data-theme',this.theme);try{this.state=await this.api('GET','/api/wallet/state');this.loadHistory(1);this.loadImages();await this.onRcvChange();this.connectWatch()}catch(e){this.toast(e.message,true)}var self=this;document.addEventListener('visibilitychange',function(){if(document.visibilityState==='visible'&&self.state){if(!self.ws||self.ws.readyState!==1){if(self.wsReconnectTimer)clearTimeout(self.wsReconnectTimer);self.connectWatch()}}});window.addEventListener('online',function(){if(self.state){if(self.wsReconnectTimer)clearTimeout(self.wsReconnectTimer);self.connectWatch()}})},
|
|
614
|
+
async load(){try{this.state=await this.api('GET','/api/wallet/state');this.onRcvChange();this.loadHistory(this.histPage);this.loadImages()}catch(e){this.toast(e.message,true)}},
|
|
615
|
+
loadImages(){var s=this;(this.state&&this.state.imageHistory||[]).forEach(function(h){if(h.status==='completed'&&h.filepath)s.loadImage(h.id)})},
|
|
591
616
|
async loadImage(id){if(!id||this.imgUrls[id])return;try{var res=await fetch('/api/ai/images/'+id+'/file',{headers:{'X-Paytaca-Token':this.token}});if(!res.ok)return;var blob=await res.blob();this.imgUrls[id]=URL.createObjectURL(blob)}catch(e){}},
|
|
592
617
|
async api(method,apipath,body){var opts={method:method,headers:{'X-Paytaca-Token':this.token,'Accept':'application/json'}};if(body!==undefined){opts.headers['Content-Type']='application/json';opts.body=JSON.stringify(body)}var res=await fetch(apipath,opts);if(!res.ok){var msg='Request failed ('+res.status+')';try{var j=await res.json();if(j.error)msg=j.error}catch(ex){}throw new Error(msg)}return res.json()},
|
|
593
618
|
fmtDuration(s){if(!s||s<=0)return '0m';var h=Math.floor(s/3600),m=Math.floor((s%3600)/60);return h?h+'h '+m+'m':m+'m'},
|
|
594
619
|
bchPriceLabel(p){if(!p)return '';return p.priceUsd!=null?'$'+p.priceUsd.toFixed(2)+' USD':((p.priceSats||0)/1e8).toFixed(8)+' BCH'},
|
|
595
620
|
liftPriceLabel(p){if(!p)return '';var pct=(this.state&&this.state.liftDiscountPercent)||0;var f=1-pct/100;return p.priceUsd!=null?'$'+(p.priceUsd*f).toFixed(2)+' USD':(((p.priceSats||0)*f)/1e8).toFixed(8)+' BCH'},
|
|
621
|
+
planMinutes(modelId){return this.PLAN_STEPS[this.planSliderIdx[modelId]||0]||0},
|
|
622
|
+
setPlanSlider(modelId,val){var next={};var keys=Object.keys(this.planSliderIdx);for(var i=0;i<keys.length;i++){next[keys[i]]=keys[i]===modelId?val:0}next[modelId]=val;this.planSliderIdx=next},
|
|
623
|
+
planTier(plan){var m=this.planMinutes(plan.modelId);if(!m)return null;var tiers=(plan&&plan.tiers)||[],i;for(i=0;i<tiers.length;i++)if(tiers[i].minutes===m)return tiers[i];var sorted=tiers.slice().sort(function(a,b){return a.minutes-b.minutes});for(i=0;i<sorted.length;i++)if(sorted[i].minutes>=m)return sorted[i];return sorted[sorted.length-1]||null},
|
|
624
|
+
tierPriceLabel(tier){if(!tier)return '';return tier.priceUsd!=null?'$'+tier.priceUsd.toFixed(2):(tier.priceSats/1e8).toFixed(8)+' BCH'},
|
|
625
|
+
tierInsufficient(tier){if(!tier)return false;var have=this.state&&this.state.balance?Number(this.state.balance.spendableSats):0;return have<Number(tier.priceSats||0)},
|
|
626
|
+
buyPlan(plan){var t=this.planTier(plan);if(!t)return;this.startPurchase(plan.modelId,t.minutes,t.priceUsd,t.priceSats,t.durationDisplay,plan.displayName||plan.modelId)},
|
|
627
|
+
refillTopUpMinutes(){return this.REFILL_MINUTES[this.refillMinutesIdx]||30},
|
|
628
|
+
refillCapMinutes(){var cap=this.REFILL_CAPS[this.refillCapIdx];return cap>0?cap*this.refillTopUpMinutes():null},
|
|
629
|
+
refillCapLabel(){var cap=this.refillCapMinutes();if(!cap)return 'No cap';return 'Max '+cap+' min ('+this.REFILL_CAPS[this.refillCapIdx]+'×)'},
|
|
596
630
|
|
|
597
631
|
async loadLiftQuote(){var p=this.pendingPurchase;if(!p||!p.priceSats){this.liftQuote=null;this.liftQuoteError='';return}var pct=(this.state&&this.state.liftDiscountPercent)||0;var sats=Math.round(p.priceSats*(1-pct/100));if(sats<1)sats=1;this.liftQuoteBusy=true;this.liftQuoteError='';try{this.liftQuote=await this.api('GET','/api/ai/lift-quote?sats='+sats)}catch(e){this.liftQuote=null;this.liftQuoteError=e.message||'Failed to estimate LIFT needed'}this.liftQuoteBusy=false},
|
|
598
632
|
|
|
@@ -609,17 +643,18 @@ startWsPing(){var self=this;if(this.wsPingTimer)clearTimeout(this.wsPingTimer);t
|
|
|
609
643
|
handleWsUpdate(m){if(!m||!m.txid)return;var key=m.txid+'|'+(m.address||'')+'|'+(m.index==null?'':m.index);if(this.wsSeen[key])return;this.wsSeen[key]=1;if(Object.keys(this.wsSeen).length>400)this.wsSeen={};this.showTxToast(m,Array.isArray(m.senders)&&m.senders.length>0)},
|
|
610
644
|
showTxToast(m,incoming){var self=this;var sym=(m.token_symbol||'').toLowerCase();var tname=(m.token_name||'').toLowerCase();var isBch=!m.token_symbol||sym==='bch'||tname==='bch'||tname==='bitcoin cash'||m.token==='bch';var amountText;if(isBch){var sats=m.value!=null?Number(m.value):null;var bch=sats!=null&&isFinite(sats)?sats/1e8:null;amountText=bch!=null?bch.toFixed(8).replace(/\.?0+$/,'')+' BCH':'New activity';if(bch!=null&&this.state&&this.state.bchPriceUsd){var usd=bch*this.state.bchPriceUsd;if(usd>0.0001){var usdStr=usd.toFixed(2);if(usdStr.slice(-3)==='.00')usdStr=usdStr.slice(0,-3);amountText+=' ('+usdStr+' USD)'}}}else{amountText=m.amount!=null?(m.amount+' '+m.token_symbol):(m.token_symbol||'Token')+' transfer'}var base=this.state&&this.state.network==='chipnet'?'https://chipnet.bchexplorer.info/tx/':'https://bchexplorer.info/tx/';var id=m.txid||'';var tid=++self._ntid;var n={title:incoming?'Incoming transaction':'Wallet activity',amountText:amountText,txidShort:id.slice(0,10)+'…'+id.slice(-6),explorer:base+id};this.notify=n;if(this.wsRefreshTimer)clearTimeout(this.wsRefreshTimer);this.wsRefreshTimer=setTimeout(function(){self.load()},1200);if(this.wsLateTimer)clearTimeout(this.wsLateTimer);this.wsLateTimer=setTimeout(function(){self.load()},5000);setTimeout(function(){if(self._ntid===tid)self.notify=null},6000)},
|
|
611
645
|
async onRcvChange(){this.rcvView=null;this.rcvQrSvg='';var params=new URLSearchParams();if(this.rcvType==='token'&&this.rcvCategory){params.set('category',this.rcvCategory);params.set('token','1')}if(this.rcvAmount)params.set('amount',this.rcvAmount);try{var view=await this.api('GET','/api/wallet/receive?'+params.toString());this.rcvView=view;if(view.address){try{this.rcvQrSvg=QR.toSVG(QR.encode(view.address))}catch(ex){}}if(view&&view.address&&this.wsAddress!==view.address)this.connectWatch(view.address)}catch(e){this.rcvView={address:'Error: '+e.message}}},
|
|
646
|
+
fillMaxSend(){var bal=this.state&&this.state.balance;if(!bal)return;if(this.sendCurrency==='bch'){this.sendAmount=String(bal.spendableBch||'')}else if(this.sendCurrency==='sats'){this.sendAmount=String(bal.spendableSats||0)}else{var bch=parseFloat(bal.spendableBch)||0;var price=this.state.bchPriceUsd||0;this.sendAmount=price?(bch*price).toFixed(2):''}},
|
|
612
647
|
doSend(){this.sendError='';this.sendSuccess='';var address=(this.sendAddress||'').trim();if(!address){this.sendError='Enter a recipient address';return}if(this.sendType==='bch'){var amount=parseFloat(this.sendAmount);var currency=this.sendCurrency;if(!amount||amount<=0){this.sendError='Enter a valid amount';return}this.pendingSend={type:'bch',address:address,amount:amount,currency:currency};this.showSendModal=true}else{var category=(this.sendCategory||'').trim();var tokenAmount=(this.sendTokenAmount||'').trim();if(!category||!/^[a-fA-F0-9]{64}$/.test(category)){this.sendError='Enter a valid 64-char hex category ID';return}if(!tokenAmount){this.sendError='Enter a token amount';return}this.pendingSend={type:'token',category:category,tokenAmount:tokenAmount,address:address};this.showSendModal=true}},
|
|
613
648
|
async confirmSend(){if(!this.pendingSend)return;this.sendConfirmBusy=true;try{var result;if(this.pendingSend.type==='bch'){result=await this.api('POST','/api/wallet/send',this.pendingSend)}else{result=await this.api('POST','/api/wallet/send-token',{category:this.pendingSend.category,amount:this.pendingSend.tokenAmount,address:this.pendingSend.address})}this.showSendModal=false;if(result.success){this.sendSuccess='Sent! TX: '+(result.txid||'').slice(0,16)+'...';this.toast('Transaction sent');this.load()}else{this.sendError=result.error||'Send failed'}}catch(e){this.sendError=e.message}this.sendConfirmBusy=false;this.pendingSend=null},
|
|
614
|
-
async loadHistory(page){this.histPage=page||1;this.histLoading=true;this.histRecords=[];try{var data=await this.api('GET','/api/wallet/history?page='+this.histPage+'&type='+this.histType);var net=data.network||'mainnet';this.histNetwork=net;var base=net==='chipnet'?'https://chipnet.bchexplorer.info/tx/':'https://bchexplorer.info/tx/';this.histRecords=(data.records||[]).map(function(r){var isIn=r.record_type==='incoming';var party=isIn?((r.senders&&r.senders[0]&&r.senders[0][0])||''):((r.recipients&&r.recipients[0]&&r.recipients[0][0])||'');var amt=Number(r.amount)||0;var when=r.tx_timestamp||r.date_created||'';var dt=when?new Date(when):null;return{txid:r.txid,type:isIn?'incoming':'outgoing',dateText:dt?dt.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}):'',counterparty:party,partyShort:party?(party.slice(0,16)+'…'+party.slice(-6)):'—',amount:amt,amountText:amt.toFixed(8),usd:r.usd_price!=null?(amt*Number(r.usd_price)).toFixed(2):null,fee:r.tx_fee!=null?(Number(r.tx_fee)/1e8).toFixed(8):null,explorer:r.txid?base+r.txid:''}});this.histNumPages=data.numPages||1;this.histHasNext=!!data.hasNext}catch(e){this.histRecords=[]}this.histLoading=false},
|
|
649
|
+
async loadHistory(page){this.histPage=page||1;this.histLoading=true;this.histRecords=[];try{var data=await this.api('GET','/api/wallet/history?page='+this.histPage+'&type='+this.histType);var net=data.network||'mainnet';this.histNetwork=net;var base=net==='chipnet'?'https://chipnet.bchexplorer.info/tx/':'https://bchexplorer.info/tx/';this.histRecords=(data.records||[]).map(function(r){var isIn=r.record_type==='incoming';var party=isIn?((r.senders&&r.senders[0]&&r.senders[0][0])||''):((r.recipients&&r.recipients[0]&&r.recipients[0][0])||'');var amt=Number(r.amount)||0;var when=r.tx_timestamp||r.date_created||'';var dt=when?new Date(when):null;return{txid:r.txid,type:isIn?'incoming':'outgoing',dateText:dt?dt.toLocaleString(undefined,{year:'numeric',month:'short',day:'numeric',hour:'2-digit',minute:'2-digit'}):'',txidShort:r.txid?(r.txid.slice(0,10)+'…'+r.txid.slice(-6)):'',counterparty:party,partyShort:party?(party.slice(0,16)+'…'+party.slice(-6)):'—',amount:amt,amountText:amt.toFixed(8),usd:r.usd_price!=null?(amt*Number(r.usd_price)).toFixed(2):null,fee:r.tx_fee!=null?(Number(r.tx_fee)/1e8).toFixed(8):null,explorer:r.txid?base+r.txid:''}});this.histNumPages=data.numPages||1;this.histHasNext=!!data.hasNext}catch(e){this.histRecords=[]}this.histLoading=false},
|
|
615
650
|
async doSwapQuote(){var tokenId=this.swapToken==='__custom__'?(this.swapCustomCategory||'').trim():(this.swapToken||'').trim();var amount=(this.swapAmount||'').trim();this.swapError='';if(!tokenId||!/^[a-fA-F0-9]{64}$/.test(tokenId)){this.swapError='Enter a valid 64-char hex token ID';return}if(!amount||isNaN(parseFloat(amount))||parseFloat(amount)<=0){this.swapError='Enter a valid amount';return}this.swapBusy=true;try{var q=await this.api('POST','/api/swap/quote',{tokenId:tokenId,direction:this.swapDir,amount:amount});this.pendingSwap={tokenId:tokenId,direction:this.swapDir,amount:amount};var text=q.formatted||'';if(!text){text='Rate: '+(q.rate||'?')+'\\nToken: '+q.tokenAmount+' '+(q.symbol||'')+'\\nBCH: '+(Number(q.bchAmountSats)/1e8).toFixed(8)+' BCH\\nTrade fee: '+(Number(q.tradeFeeSats)/1e8).toFixed(8)+' BCH';if(q.platformFeeSats)text+='\\nPlatform fee: '+(Number(q.platformFeeSats)/1e8).toFixed(8)+' BCH'}this.swapQuoteText=text}catch(e){this.swapError=e.message}this.swapBusy=false},
|
|
616
651
|
async doSwapExecute(){if(!this.pendingSwap)return;this.swapExecBusy=true;try{var result=await this.api('POST','/api/swap/execute',this.pendingSwap);if(result.success){this.toast('Swap executed! TX: '+(result.txid||'').slice(0,16)+'...');this.swapQuoteText='';this.pendingSwap=null;this.load()}else{this.swapError=result.error||'Swap failed'}}catch(e){this.swapError=e.message}this.swapExecBusy=false},
|
|
617
652
|
startPurchase(model,minutes,priceUsd,priceSats,durationDisplay,displayName){this.pendingPurchase={model:model,minutes:minutes,priceUsd:priceUsd,priceSats:priceSats,durationDisplay:durationDisplay,displayName:displayName};this.purchaseMethod='bch';this.purchaseError='';this.liftQuote=null;this.liftQuoteError='';this.showPurchaseModal=true},
|
|
618
|
-
async confirmPurchase(){if(!this.pendingPurchase)return;this.purchaseBusy=true;this.purchaseError='';try{var r=await this.api('POST','/api/ai/purchase',{model:this.pendingPurchase.model,minutes:this.pendingPurchase.minutes,method:this.purchaseMethod});if(r&&r.success&&r.paid){this.showPurchaseModal=false;this.pendingPurchase=null;this.toast('Plan purchased!');this.load()}else if(r&&r.success&&!r.paid){this.showPurchaseModal=false;this.pendingPurchase=null;this.toast(r.error||'Payment submitted — credits will update shortly',true);this.load()}else{this.purchaseError=(r&&r.error)||'Purchase failed';this.toast(this.purchaseError,true)}}catch(e){this.purchaseError=e.message||'Purchase failed';this.toast(e.message,true)}this.purchaseBusy=false},
|
|
619
|
-
async enableRefill(){var model=this.refillModel;var minutes=
|
|
653
|
+
async confirmPurchase(){if(!this.pendingPurchase)return;this.purchaseBusy=true;this.purchaseError='';try{var r=await this.api('POST','/api/ai/purchase',{model:this.pendingPurchase.model,minutes:this.pendingPurchase.minutes,method:this.purchaseMethod});if(r&&r.success&&r.paid){this.showPurchaseModal=false;this.pendingPurchase=null;this.planSliderIdx={};this.toast('Plan purchased!');this.load()}else if(r&&r.success&&!r.paid){this.showPurchaseModal=false;this.pendingPurchase=null;this.toast(r.error||'Payment submitted — credits will update shortly',true);this.load()}else{this.purchaseError=(r&&r.error)||'Purchase failed';this.toast(this.purchaseError,true)}}catch(e){this.purchaseError=e.message||'Purchase failed';this.toast(e.message,true)}this.purchaseBusy=false},
|
|
654
|
+
async enableRefill(){var model=this.refillModel;var minutes=this.refillTopUpMinutes();var maxMinutes=this.refillCapMinutes();var paymentMethod=this.refillPayMethod||'bch';if(!model){this.toast('Select a model',true);return}try{var r=await this.api('POST','/api/ai/auto-refill',{enabled:true,model:model,minutes:minutes,maxMinutes:maxMinutes,paymentMethod:paymentMethod});this.refillNotice=this.refillNoticeText(r&&r.initialTick);this.refillEditing=false;this.toast('Auto-refill enabled');this.load()}catch(e){this.toast(e.message,true)}},
|
|
620
655
|
async resumeRefill(){try{var r=await this.api('POST','/api/ai/auto-refill',{enabled:true});this.refillNotice=this.refillNoticeText(r&&r.initialTick);this.toast('Auto-refill resumed');this.load()}catch(e){this.toast(e.message,true)}},
|
|
621
656
|
async pauseRefill(){try{await this.api('POST','/api/ai/auto-refill',{enabled:false});this.refillNotice='';this.toast('Auto-refill paused');this.load()}catch(e){this.toast(e.message,true)}},
|
|
622
|
-
startRefillEdit(){if(!this.state||!this.state.autoRefill)return;
|
|
657
|
+
startRefillEdit(){if(!this.state||!this.state.autoRefill)return;var ar=this.state.autoRefill;this.refillModel=ar.model||'';var mins=ar.minutes||30;var mi=this.REFILL_MINUTES.indexOf(mins);this.refillMinutesIdx=mi>=0?mi:1;this.refillCapIdx=ar.maxMinutes?Math.round(ar.maxMinutes/mins)<=2?0:Math.round(ar.maxMinutes/mins)<=4?1:2:3;this.refillPayMethod=ar.paymentMethod||'bch';this.refillNotice='';this.refillEditing=true},
|
|
623
658
|
cancelRefillEdit(){this.refillEditing=false;this.refillNotice=''},
|
|
624
659
|
refillNoticeText(tick){if(!tick)return '';if(tick.action==='refilled'){var m=(tick.state&&tick.state.minutes)||'';var tx=tick.state&&tick.state.lastRefillTxid;return 'Topped up '+m+' min now'+(tx?' (tx '+String(tx).slice(0,10)+'…)':'')+'.'}if(tick.action==='skipped')return 'Refill skipped: '+tick.reason+'.';if(tick.action==='disarmed')return 'Auto-refill stopped: '+tick.reason+'.';return 'Credits still active — no refill needed.'},
|
|
625
660
|
async deleteRefill(){if(!confirm('Delete auto-refill configuration?'))return;try{await this.api('POST','/api/ai/auto-refill',{delete:true});this.refillEditing=false;this.refillNotice='';this.toast('Auto-refill deleted');this.load()}catch(e){this.toast(e.message,true)}},
|
package/dist/web/page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/web/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE3D,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;AACjE,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EACvF,MAAM,CACP,CAAA;AAED,MAAM,UAAU,UAAU;IACxB,OAAO;;;;;;0CAMiC,YAAY
|
|
1
|
+
{"version":3,"file":"page.js","sourceRoot":"","sources":["../../src/web/page.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAE3D,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAA;AACjE,MAAM,SAAS,GAAG,EAAE,CAAC,YAAY,CAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,EACvF,MAAM,CACP,CAAA;AAED,MAAM,UAAU,UAAU;IACxB,OAAO;;;;;;0CAMiC,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCA2RjB,YAAY;;;;;;wGAMuD,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;sCAmHjF,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBA0PrC,SAAS;;QAEjB,CAAC;AACT,CAAC"}
|