Repository navigation
Remote public apps: any strut UI hosts another server's public app, one-tap payment in Peel - #625
Open
Evanfeenstra wants to merge 3 commits into
Open
Evanfeenstra wants to merge 3 commits into
Evanfeenstra wants to merge 3 commits into
Conversation
…nd pays with one tap in Peel A public app's page is two layers: the host page (the purse, any native wallet bridge) and the app in a sandboxed frame. Opened on the seller's own server, the host is the seller's copy of strut's UI, which in Peel's web view would sit beside the wallet bridge. Now any strut UI hosts it: <your strut>/?app=<name>&public=1&from=<seller base> - web/src/remote-app.ts: the link (from read like a seller's: https, or http to loopback), where the page's links lead (its own server), and the stranger's document with the build's CSP written ahead of it. - AppHost / SoloApp / page.ts: the remote mode, alone (solo implied), its info, document and /public/mcp on the seller, a stranger's frame (no microphone, nothing its version declares), a refused link saying why. - app-host.ts: one purse per server (store-purse's, keyed by the seller), every challenge checked before it is shown or paid (web/src/challenge.ts: one payment hash in the invoice and the macaroon, sats agreeing three ways, credit equal to the dollars), and in a native host the wallet asked first. - The host's own purchase (specs/WALLET.md §1.2, kit/src/wallet): host_capability, buy_credit, buy_credit_cancel; CreditPurchase, creditPurchaseProblems, readCreditReply, probeHostWallet, buyCreditNatively; credit_purchases and credit_replies vectors. - PaymentSheet: the wallet stage (with Show the invoice), a paying stage with no QR to pay twice from, the QR with why on any fallback. - Store: Open in your strut beside Open full page. - src/public-routes.ts: POST /public/mcp answers with a Response of its own, so the CORS headers never reached it; set again after the handler. The preflight also allows mcp-method and mcp-name. Docs: specs/API.md §17.6 and §17.9, specs/WALLET.md §1.2 and §7, AGENTS.md, kit/README.md.
…yment after a tap, an older seller named
Money:
- The sheet's price is the invoice's SATS; dollars only at the hosting strut's
own rate (new GET /rate, any credential, a member's session included); the
seller's usd only as the credit it says the payment buys, flagged when worth
under 90% of what is paid (visitor-pay priceView). Peel is specced the same.
- Once the native wallet SENT the payment (pending, or settled unproven), the
invoice is never shown again: a seller holding the first HTLC could settle a
second. Peel answers pending as soon as the person taps; buy_credit_status
(new op, polled every 3 s) says where it stands, a failure brings the
invoice back; after 60 s the sheet says a server may hold it, and Close tells
the page the credit is kept. A late "sent" answer takes a shown QR down.
- A call pays at most once (paidAgainResult).
Normal use:
- GET /public says host: { remote: 1 }; an older seller is named instead of
"Failed to fetch", and the store's Open in your strut shows only for a
seller that says it.
- hostWalletCapabilityOf drops an op it does not know instead of reading none;
host_capabilities vectors. host_capability is asked once per page, warmed at
connect (createHostWalletProbe), so no sheet waits on it.
- A refusal (no fresh tap) leaves the QR and Pay with <wallet>, a fresh tap.
- A remote page's link to another app on its server is hosted here too.
Edges:
- The claim keeper watches until the invoice's decoded end.
- A stranger's frame: no download, no fullscreen (nor orientation lock).
- Every page's ui/open-link opens http(s) and mailto alone.
- remote-app's comment no longer claims typed text stays in the frame.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Strut's half of "Peel opens strangers' public app pages", option A: the HOST page is the person's own strut UI (Peel's local strut), never the stranger's copy of it. Only the sandboxed frame is the stranger's. Peel's half follows in stakwork/peel, built against specs/WALLET.md §1.2.
The link (specs/API.md §17.9)
fromis read like a seller's (sellerOf): the origin and path, no trailing/nor/public, no credentials.solo=1is implied: the app shows alone, for anyone.host: { remote: 1 }on itsGET /public, new here. An older seller gets a page naming it ("… runs an older strut, whose public apps cannot be hosted from another strut yet"), never a bare "Failed to fetch". The store shows Open in your strut only for such a seller.fromthe UI does not open is a page that says why. It never shows this strut's app of the same name.POST /public/mcpall come from the seller, straight from the browser. Nothing is proxied through this strut.What changed
The remote mode in AppHost and SoloApp.
?app=<other>&public=1&from=<seller>), never by the seller's own UI.ui/open-linkopens http(s) and mailto alone, never ajavascript:URL.The frame is a stranger's. It runs on this strut's origin, so it gets:
strangerDocument).It can still navigate itself away (a known limit, below).
The price is the sats. Every challenge is checked before it is shown or paid (
web/src/challenge.ts):strut_satsequals the challenge'ssats, which equals the invoice's amount;strut_creditequals the challenge'susd.The seller writes both
usdandstrut_credit, so they agree with each other, not with the world. So:GET /rate(any credential, a member's session included);One tap in Peel (specs/WALLET.md §1.2,
kit/src/wallet). This is a HOST-initiated purchase, distinct from an app'sstrut/wallet/*calls: no app identity, no connection, no budget.host_capabilityis asked once per page, warmed when the relay connects. Ops it does not know are left out, never fatal.walletis the name the sheet uses ("Pay with Peel").buy_credit { purchase }→ aPayment. Peel answers as soon as the person acts:pendingonce the payment is sent.buy_credit_status, polled every 3 s. Afailedstatus brings the invoice back. After 60 s the sheet says the server may be holding the payment, and Close tells the page the credit is kept.buy_credit_canceltakes Peel's sheet down when the page stops waiting. A late "sent" answer takes a QR code already shown down.host_capabilities(8),credit_purchases(16),credit_replies(15).Store: Open in your strut beside Open full page, for a seller that says
host.remote.Server fix.
POST /public/mcpanswers with a Response of its own, so the CORS headers the public middleware set never reached it. A host on another origin passed the preflight and then could not read the answer. The headers are now set again after the handler, and the preflight also allowsmcp-methodandmcp-name.Tests
npm test: 4418 tests, 0 failures, 54 skipped (the live suites).src/l402.ts's minter, Go's macaroon fixtures and the BOLT11 encoder;/public/mcp's answer carries CORS.buy_credit_cancel.from: refused.buy_credit_statussettles it and the run succeeds;Not tested: Peel itself, real sats, and the store's button in a browser (it needs an index with a live row; its link is unit-tested).
Known limit
A stranger's frame can navigate itself away, carrying whatever was typed into it. A browser cannot stop that; Peel's web view can, and WALLET.md §1.2 recommends it.