Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Run the Shop's marketplace on Synonym's staging with our own Paykit Server v0.1.0-rc11 behind a Cloudflare quick tunnel (`./shop-mixed`, profile `shop-mixed`)
- Rebuild the Paykit fixtures on the paykit-rs version a Bitkit pull request pins with `scripts/follow-app-paykit`
- Withhold and restore the payment request issuer's endpoints with `POST /endpoints`, so a journey can make the app's request resolution fail and recover
- Route the apps' homeserver traffic through `homeserver-proxy`, whose control port (23298) delays or fails one identity's homeserver requests by path
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Every host port is 1024 or above. The ports Bitkit, the Pubky SDK or Bitkit's UI
| homeserver-proxy control, homeserver admin (`marketplace`) | 23298, 23288 (`MARKETPLACE_HOMESERVER_ADMIN_PORT`) | 6298, 6288 |
| Paykit Server (`marketplace`) | 23101 (`MARKETPLACE_PAYKIT_PORT`) | 3001 |
| `fixture-issuer`, `rc56-peer` (`payment-requests`) | 23012, 23013 | 3012, 3013 |
| Paykit Server, quick tunnel metrics (`shop-mixed`) | 23110, 23111 | 3001, 23111 |

## API Examples

Expand Down Expand Up @@ -610,6 +611,33 @@ The fixture cannot check a Bitkit seller's payout address against the wallet's x

Remove the fixture with `./pubky-marketplace down`, or start over with `./pubky-marketplace reset`. The Pubky testnet keeps its accounts in memory, so the fixture cannot restart with its state and `down` deletes it (the regtest chain stays). `./pubky-marketplace --help` lists every command. See [docs/pubky-marketplace.md](docs/pubky-marketplace.md) for the pins, ports, roles and what the fixture does not cover.

#### Shop on Staging with Our Own Paykit Server

The `shop-mixed` profile runs the marketplace half of the Shop ourselves and everything else on Synonym's staging: Paykit Server
v0.1.0-rc11 (`662dca06`, paykit-rs `ad3c7224` = v0.1.0-rc72, the version the Bitkit send-fix builds pin) on the staging homeserver
`ufibwbmed6jeq9k4p583go95wofakh9fwpp4k734trq79pd9u1uy` (`homeserver.staging.pubky.app`), watching Blocktank's staging regtest
Electrum (`ssl://electrs.bitkit.stag0.blocktank.to:9999`, the one Bitkit's staging builds use), behind a Cloudflare quick tunnel
(`https://<words>.trycloudflare.com`, a new name at every start). Bitkit staging builds (Android `devDebug`, iOS `Debug`) are the
seller and the buyer as they are; no local build, `adb reverse` or local chain is needed. Use it when the staging Shop cannot serve a
test, for example when the app pins a Paykit version the staging Paykit Server does not run. Needs Docker, `curl` and `jq`, and
outbound internet.

```bash
./shop-mixed up # builds on first use (a Rust build), starts, waits until /health/ready answers through the tunnel
./shop-mixed health # pinned revisions, loopback and tunnel /health/ready
./shop-mixed setup-url # seller wallet: open `android` / `ios_url`, or `setup_page` in its browser, approve, then:
./shop-mixed setup-wait <flow>
./shop-mixed seller-auth # the marketplace grant on /pub/app.locks/ through httprelay.staging.pubky.app
./shop-mixed purchase --buyer <buyer pubky> # the Payment Request appears in the buyer wallet
./shop-mixed wait <bundle> detected # after the buyer pays in the app
./shop-mixed mine # one block on Blocktank's staging regtest chain
./shop-mixed wait <bundle> confirmed
./shop-mixed down # removes the stack and its state
```

The headless seller and buyer of the `marketplace` profile need the local testnet and chain, so `seed`, `fund`, `receive`, `pay`,
`peers` and `verify` refuse here. See [docs/shop-mixed.md](docs/shop-mixed.md) for what runs where and how the pins are checked.

#### Bech32 LNURL Pay

- in `Env.{kt,swift}`, use for REGTEST electrum server: `"tcp://localhost:60001"`
Expand Down
95 changes: 95 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,99 @@ services:
FIXTURE_PORT: 3013
BITCOIN_RPC_URL: http://bitcoind:43782

# ---------------------------------------------------------------------------
# Shop on staging with our own Paykit Server (profile "shop-mixed"). Paykit Server v0.1.0-rc11 (662dca06, paykit-rs ad3c7224 =
# v0.1.0-rc72, the version the Bitkit send-fix builds pin) resolves Pubky on mainnet, so it serves Synonym's staging homeserver,
# and watches Blocktank's staging regtest Electrum, the chain of Bitkit's staging builds. A Cloudflare quick tunnel gives it a
# public https URL. The driver plays the marketplace (Locks and the issuer); Bitkit wallets are the seller and the buyer.
# Drive it with ./shop-mixed; see docs/shop-mixed.md. Nothing of the local chain or Pubky testnet runs for it.
# ---------------------------------------------------------------------------
shop-mixed-postgres:
profiles: [shop-mixed]
image: postgres:16-alpine
restart: "no"
environment:
POSTGRES_DB: paykit
POSTGRES_USER: paykit
POSTGRES_PASSWORD: paykit
volumes:
- shop_mixed_postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paykit -d paykit"]
interval: 2s
timeout: 5s
retries: 30

shop-mixed-paykit:
profiles: [shop-mixed]
image: bitkit-docker/paykit-server:${SHOP_MIXED_PAYKIT_SERVER_TAG:-v0.1.0-rc11}
build:
context: ./marketplace/shop-mixed
dockerfile: paykit-server.Dockerfile
args:
PAYKIT_SERVER_TAG: ${SHOP_MIXED_PAYKIT_SERVER_TAG:-v0.1.0-rc11}
PAYKIT_SERVER_REV: ${SHOP_MIXED_PAYKIT_SERVER_REV:-662dca0619a9aa2962bcd677bd5ddd4563cd2784}
PAYKIT_RS_REV: ${SHOP_MIXED_PAYKIT_RS_REV:-ad3c72248d18587bb5b6ef3c99b063fa9bf31551}
restart: "no"
depends_on:
shop-mixed-postgres:
condition: service_healthy
shop-mixed-driver:
condition: service_started
environment:
PAYKIT_CONFIG: /state/paykit/paykit-server.toml
PAYKIT_DATABASE_URL: postgres://paykit:paykit@shop-mixed-postgres:5432/paykit
volumes:
- shop_mixed_state:/state:ro
ports:
- "127.0.0.1:23110:3001"
# The config and master key are written by `shop-mixed-driver init`, which `up` runs first, as in the marketplace profile.
entrypoint:
- /bin/sh
- -c
- |
until [ -s /state/paykit/paykit-server.toml ] && [ -s /state/paykit/master-key ]; do
echo '[shop-mixed] waiting for shop-mixed-driver init'
sleep 1
done
PAYKIT_MASTER_KEY="$$(cat /state/paykit/master-key)"
export PAYKIT_MASTER_KEY
exec /usr/local/bin/paykit-server

# Cloudflare quick tunnel: https://<random words>.trycloudflare.com, a new name at every start, no account. Its metrics server
# answers /quicktunnel with the hostname (./shop-mixed url). Built from the pinned cloudflared image, so a project whose
# images are never pulled (the QA lanes) fetches it with `up --build`.
shop-mixed-tunnel:
profiles: [shop-mixed]
image: bitkit-docker/shop-mixed-tunnel:2026.9.3
build:
context: ./marketplace/shop-mixed
dockerfile: tunnel.Dockerfile
restart: "no"
depends_on:
- shop-mixed-paykit
command: ["tunnel", "--metrics", "0.0.0.0:23111", "--url", "http://shop-mixed-paykit:3001"]
ports:
- "127.0.0.1:23111:23111"

# The driver's service writes the Paykit config, issuer and master keys (`init`) and stays up, so `up --wait` sees no exited
# one-shot container; ./shop-mixed runs the driver's other commands with `compose run`.
shop-mixed-driver:
profiles: [shop-mixed]
image: bitkit-docker/shop-mixed-driver:local
build:
context: ./marketplace/driver
dockerfile: Dockerfile.staging
environment:
MARKETPLACE_BACKEND: staging
PAYKIT_URL: http://shop-mixed-paykit:3001
MARKETPLACE_TUNNEL_METRICS: http://shop-mixed-tunnel:23111
volumes:
- shop_mixed_state:/state
- ./.marketplace/evidence:/evidence
entrypoint: ["node", "/app/driver.mjs"]
command: ["init-and-stay"]

volumes:
trezor_emulator_state:
trezor_emulator_logs:
Expand All @@ -629,5 +722,7 @@ volumes:
homegate_data:
marketplace_postgres_data:
marketplace_state:
shop_mixed_postgres_data:
shop_mixed_state:

networks: {}
42 changes: 42 additions & 0 deletions docs/shop-mixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Shop on staging with our own Paykit Server

The `shop-mixed` profile (`./shop-mixed`) is the second Shop route for Bitkit tests. The first is Synonym's staging Shop
(`shop.staging.pubky.app`) as it is deployed; this one replaces only its Paykit Server and marketplace with ours, so a test can run a
Paykit Server version the staging Shop does not. The README section "Shop on Staging with Our Own Paykit Server" has the commands.

## What runs where

| Piece | Where | Pin |
| --- | --- | --- |
| Paykit Server | `shop-mixed-paykit`, built from the release tag by `marketplace/shop-mixed/paykit-server.Dockerfile` | `v0.1.0-rc11` = `662dca06`, paykit-rs `ad3c7224` (v0.1.0-rc72), locks-core v0.1.0-rc9, Pubky 0.15.0 |
| Its database | `shop-mixed-postgres` | `postgres:16-alpine` |
| Public URL | `shop-mixed-tunnel`, a Cloudflare quick tunnel to `shop-mixed-paykit:3001` | cloudflared 2026.9.3 |
| Marketplace (Locks and the trusted issuer) | `shop-mixed-driver`, `marketplace/driver/driver.mjs` with `MARKETPLACE_BACKEND=staging` | `@synonymdev/pubky` 0.14.0 |
| Homeserver, HTTP relay | Synonym's staging: `homeserver.staging.pubky.app` (`ufibwbmed6jeq9k4p583go95wofakh9fwpp4k734trq79pd9u1uy`), `httprelay.staging.pubky.app` | staging |
| Chain | Blocktank's staging regtest: Electrum `ssl://electrs.bitkit.stag0.blocktank.to:9999`, mining through `api.stag0.blocktank.to/blocktank/api/v2/regtest/chain/mine` | staging |

The homeserver key and Electrum endpoint are the staging constants of bitkit-android `Env.kt` and bitkit-ios `Env.swift`, so a
staging Bitkit build and this Paykit Server see the same identities and the same chain. Paykit Server resolves Pubky on mainnet
(`[paykit] network = "mainnet"`), where the staging homeserver is published, and uses `bitcoin.network = "regtest"`, which makes its
Android setup link open `to.bitkit.dev`.

## Pins

The Paykit Server image is built from `git clone --branch v0.1.0-rc11`, and the build fails when the tag is not at
`SHOP_MIXED_PAYKIT_SERVER_REV` or its `Cargo.lock` does not lock paykit-rs at `SHOP_MIXED_PAYKIT_RS_REV`. The image labels
(`tech.masivo.paykit-server`, `tech.masivo.paykit-server-tag`, `tech.masivo.paykit-rs`, `org.opencontainers.image.revision`) name what
it was built from; `./shop-mixed health` prints them. Move the three `SHOP_MIXED_*` defaults in `docker-compose.yml` together.

## Config

`shop-mixed-driver init` (run by the driver service, which then stays up; Paykit Server waits for its files) writes the issuer key,
the master key and the Paykit Server config into the `shop_mixed_state` volume. The config trusts the driver's issuer key under
`[signed_services]`, accepts the setup page from any origin (`allowed_origins = ["*"]`, the page is opened through the tunnel), and
counts one proxy hop (`trusted_proxy_hops = 1`, Cloudflare's `X-Forwarded-For`).

## Limits

- Bitkit wallets are the seller and the buyer. The headless roles need the local testnet and bitcoind, so `seed`, `fund`, `receive`,
`pay`, `peers`, `verify` and `verify-bitkit-seller` refuse.
- The quick tunnel gets a new hostname at every start and carries no uptime guarantee; read it with `./shop-mixed url`.
- A staging identity needs a staging invite code at signup, as for any staging test.
8 changes: 8 additions & 0 deletions marketplace/driver/Dockerfile.staging
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# The marketplace driver for the shop-mixed profile (MARKETPLACE_BACKEND=staging). Bitkit wallets play the seller and the buyer on
# staging, so it carries none of the headless helpers the local image copies from the Paykit Server and payment request images.
FROM node:22-bookworm-slim@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund
COPY driver.mjs ./
ENV MARKETPLACE_BACKEND=staging
Loading