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
22 changes: 1 addition & 21 deletions governance/repository-health/contract-baseline.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schemaVersion": 1,
"recordedAt": "2026-07-26",
"findingCount": 59,
"findingCount": 54,
"findings": [
{
"id": ".::missing-script::format:check",
Expand Down Expand Up @@ -51,26 +51,6 @@
"id": "packages/messaging/message-box-client::missing-script::typecheck",
"message": "Profile dual-runtime-library requires script typecheck"
},
{
"id": "packages/messaging/ts-paymail::missing-script::format:check",
"message": "Profile dual-runtime-library requires script format:check"
},
{
"id": "packages/messaging/ts-paymail::missing-script::pack:check",
"message": "Profile dual-runtime-library requires script pack:check"
},
{
"id": "packages/messaging/ts-paymail::missing-script::typecheck",
"message": "Profile dual-runtime-library requires script typecheck"
},
{
"id": "packages/messaging/ts-paymail/docs/examples::missing-script::format:check",
"message": "Profile examples requires script format:check"
},
{
"id": "packages/messaging/ts-paymail/docs/examples::missing-script::typecheck",
"message": "Profile examples requires script typecheck"
},
{
"id": "packages/middleware/auth-express-middleware::missing-script::format:check",
"message": "Profile node-library requires script format:check"
Expand Down
227 changes: 78 additions & 149 deletions packages/messaging/ts-paymail/AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,170 +1,99 @@
# CLAUDE.md — @bsv/paymail
# AGENTS.md — `@bsv/paymail`

## Purpose
TypeScript SDK for BSV Paymail (BRC-121 capability discovery and routing). Provides both client-side capability discovery and server-side router with built-in support for PKI, P2P destinations, and public profiles.

## Public API surface

### Client (@bsv/paymail/client or default export)
- **PaymailClient** (class): `new PaymailClient(httpClient, validateDomain, validateEmail)`
- `getPublicProfile(paymail)` — retrieve name, avatar, avatar_hash for user@domain
- `getCapabilities(domain)` — discover all capabilities (PKI, P2P Destinations, etc.) for domain
- `getPki(paymail)` — get user's public key (BRC-121 PKI capability)
- `getP2pDestinations(paymail, amount, purpose)` — get payment output scripts (BRC-121 P2P capability)
- Internal: auto-discovers capabilities, validates paymail format, caches results

### Server (@bsv/paymail/router)
- **PaymailRouter** (class): `new PaymailRouter({ baseUrl, routes })`
- Constructor takes Express baseUrl (e.g., 'https://myDomain.com') and array of `PaymailRoute` handlers
- `.getRouter()` — returns Express router configured for `/.well-known/bsvalias/*` endpoints
- Built-in routes: `PublicProfileRoute`, `PublicKeyInfrastructureRoute`, `ReceiveRawTransactionRoute`

### Built-in Route Classes
- **PublicProfileRoute** — responds to `GET /.well-known/bsvalias/public-profile/{name}@{domain}`
- Requires `domainLogicHandler` callback for looking up user profile
- Returns: `{ name, domain, avatar?, avatar_hash? }`

- **PublicKeyInfrastructureRoute** — responds to `GET /.well-known/bsvalias/pki/{name}@{domain}`
- Requires `domainLogicHandler` callback returning user's public key
- Returns: `{ bsvalias, handle, pubkey }`

- **ReceiveRawTransactionRoute** — responds to `POST /.well-known/bsvalias/receive-transaction/{name}@{domain}`
- Requires `domainLogicHandler` callback for transaction handling
- Receives hex-encoded raw transaction, returns: `{ txid }`

- **P2PDestinationsRoute** — responds to `GET /.well-known/bsvalias/p2p-payment-destination/{name}@{domain}?sats={amount}&purpose={purpose}`
- Returns payment derivation instructions per BRC-29
- Returns: `{ outputs: [ { derivationSuffix, script, satoshis } ] }`

## Real usage patterns

From README:
```ts
import express from 'express'
import { PaymailRouter, PublicKeyInfrastructureRoute, PublicProfileRoute } from '@bsv/paymail'

const app = express()
const baseUrl = 'https://myDomain.com'

const publicProfileRoute = new PublicProfileRoute({
domainLogicHandler: async (name, domain) => {
const user = await fetchUser(name, domain)
return {
name: user.getAlias(),
domain,
avatar: user.getAvatarUrl()
}
}
})

const pkiRoute = new PublicKeyInfrastructureRoute({
domainLogicHandler: async (name, domain) => {
const user = await fetchUser(name, domain)
return {
bsvalias: '1.0',
handle: `${name}@${domain}`,
pubkey: user.getIdentityKey()
}
}
})

const routes = [publicProfileRoute, pkiRoute]
const paymailRouter = new PaymailRouter({ baseUrl, routes })
app.use(paymailRouter.getRouter())

app.listen(3000, () => {
console.log(`Paymail server running on ${baseUrl}:3000`)
})
```

Client:
```ts
import { PaymailClient } from '@bsv/paymail'
`@bsv/paymail` is the TypeScript stack's Paymail client and Express routing
library. It implements capability discovery and typed flows for profiles, PKI,
P2P transaction delivery, transaction negotiation, and simple ordinals.

const client = new PaymailClient()
## Supported surfaces

const publicProfile = await client.getPublicProfile('satoshi@myDomain.com')
console.log(publicProfile.name, publicProfile.avatar)
- Root: `@bsv/paymail`
- Client: `@bsv/paymail/client`
- Capabilities: `@bsv/paymail/capability`
- Server router: `@bsv/paymail/router`
- Errors: `@bsv/paymail/errors`

const capabilities = await client.getCapabilities('myDomain.com')
console.log(capabilities) // lists all supported BRC-121 capabilities
The root browser condition intentionally exports the client, capabilities, and
errors without importing Express or server routes. Preserve every declared
subpath and its ESM/CommonJS declaration pairing when changing the build.

const pkiResult = await client.getPki('satoshi@myDomain.com')
console.log(pkiResult.pubkey) // user's identity key
```
## Client contract

From tests:
```ts
const paymailClient = new PaymailClient(new HttpClient())
Construct `PaymailClient` with optional `HttpClient`, `DNSResolverOptions`, and
localhost port arguments. `getCapabilities` is the public alias for
`getDomainCapabilities`; capabilities are cached per client instance.

const publicProfile = await paymailClient.getPublicProfile('brandonbryant@handcash.io')
expect(publicProfile).toHaveProperty('name')
expect(publicProfile).toHaveProperty('avatar')
```
The default HTTP transport serializes JSON requests and enforces a 30-second
timeout. Unit tests must use injected HTTP and DNS fakes. Do not add tests that
depend on public DNS, Paymail providers, ARC, or other live services.

## Key concepts
P2P signatures use compact Base64 Bitcoin Signed Message encoding. The raw,
BEEF, and ordinal receive routes must accept the same encoding produced by
`PaymailClient.createP2PSignature`. Keep malformed length/header rejection and
transaction-ID signature checks covered. Verify the signature locally before
performing an outbound Paymail ownership lookup.

- **BRC-121 capabilities**: Discovery of Paymail services (PKI, P2P destinations, receive transaction, public profile)
- **Well-known endpoints**: All Paymail services exposed via `/.well-known/bsvalias/*` paths
- **Capability negotiation**: Client discovers what a domain supports before making requests
- **PKI (Public Key Infrastructure)**: BRC-121 PKI capability maps paymail to public key
- **P2P Destinations**: BRC-121 P2P capability returns payment output derivation per BRC-29
- **Domain logic**: Routes delegate user lookup to your `domainLogicHandler` callback
- **Route extensibility**: Create custom `PaymailRoute` subclasses for custom capabilities
- **Paymail format**: `name@domain` validated per BRC-121 spec
## Router contract

## Dependencies
`domainLogicHandler` receives:

- `@bsv/sdk` ^2.0.14 — crypto, HTTP utilities
- `express` ^5.1.0 — web framework (server only)
- `joi` ^18.0.1 — schema validation
- `cross-fetch` or `node-fetch` ^3 — HTTP client
- Dev: jest, ts-jest, TypeScript, Oxlint
1. a route-parameter object containing `paymail` and any other path parameters;
2. the validated and unknown-field-stripped request body, when present; and
3. an optional public key for route implementations that supply one.

## Common pitfalls / gotchas
Use `PaymailRoute.getNameAndDomain(params)` to parse the handle. A router's
`baseUrl` is the externally reachable origin advertised by
`/.well-known/bsvalias`; `basePath` is optional.

1. **Domain logic is async** — all `domainLogicHandler` callbacks are async; implement accordingly
2. **Paymail format validation** — library validates `name@domain` format; invalid paymail raises error
3. **Capability caching** — client caches discovered capabilities; use options to control TTL
4. **BaseUrl must match DNS** — PaymailRouter's baseUrl should match the domain's actual DNS (e.g., 'https://myDomain.com')
5. **No trailing slashes** — baseUrl should not have trailing slash
6. **HTTP-only PKI discovery** — Paymail service discovery happens via standard HTTP DNS lookup + well-known paths
7. **Error handling** — 404 if user not found, 403 if user found but capability not offered
The package deliberately does not impose CORS or CSP. These endpoints are often
public services called by applications, WUI clients, mobile devices, webviews,
and unknown future origins. Host applications and edge infrastructure must keep
credential-free public access broadly available by default where required,
while allowing explicit origin allowlists or credentialed modes as operator
configuration. Never treat CORS as authorization or combine wildcard origins
with credentials.

## Spec conformance
## Build and publication

- **BRC-121** (Paymail Service Discovery): Full capability discovery, service routing, profile/PKI/P2P capability exposure
- **BRC-29** (Payment Derivation): P2P payment destination uses BRC-29 key derivation
- **BRC-100** (Wallet interface): Public key infrastructure integrates with wallet identity keys
`tsdown.config.ts` produces unbundled ESM and CommonJS files, module-specific
declarations, source maps, and a client-only browser root. Do not restore the
legacy project-reference build or publish source/test/example files.

## File map
The package is published only by the repository's coordinated release workflow.
Do not run `npm publish` or `pnpm publish` from a workstation. Preserve Open BSV
License Version 6 through the repository's license synchronization and packed
artifact controls.

```
/Users/personal/git/ts-stack/packages/messaging/ts-paymail/
src/
index.ts — main exports
paymailClient/
paymailClient.ts — client for capability discovery and profile retrieval
httpClient.ts — HTTP transport
resolver/ — DNS resolution for Paymail domains
paymailRouter/
paymailRouter.ts — Express router configuration
routes/
PublicProfileRoute.ts
PublicKeyInfrastructureRoute.ts
ReceiveRawTransactionRoute.ts
P2PDestinationsRoute.ts
capability/ — capability definitions and handlers
errors/ — PaymailError, ValidationError
tests/
**/tests/ — unit & integration tests
docs/
examples/ — example server & client code
```
## Required verification

Run from the repository root:

## Integration points
```sh
pnpm --filter @bsv/paymail format:check
pnpm --filter @bsv/paymail lint
pnpm --filter @bsv/paymail typecheck
pnpm --filter @bsv/paymail test:coverage
pnpm --filter @bsv/paymail pack:check
pnpm --filter @bsv/paymail test:browser
pnpm --filter example-paymail format:check
pnpm --filter example-paymail lint
pnpm --filter example-paymail test
pnpm --filter example-paymail build
```

- **@bsv/sdk** — Wallet identity keys, public key operations, HTTP utilities
- **express** — Web framework for server-side router
- **auth-express-middleware** — Can be combined for authenticated Paymail routes (BRC-103 + BRC-121)
- **message-box-client** — Can use Paymail for peer discovery before messaging
Coverage thresholds are enforced in `jest.config.js`. `pack:check` validates
the exact dry-packed tarball in clean ESM and CommonJS consumers. `test:browser`
validates the same tarball with Vite and esbuild against
`browser-budget.json`. Coverage collection must continue to include every
production file under `src`, including modules that no test imports.

## Examples and secrets

`docs/examples` is a private compiled fixture, not a deployable or published
package. External examples accept only operator-supplied
`ARC_API_KEY`, `PAYMAIL_EXAMPLE_SATOSHI_XPRV`,
`PAYMAIL_EXAMPLE_HAL_XPRV`, and `PAYMAIL_EXAMPLE_JWT_SECRET` environment
variables. Never commit credentials, private keys, live-user data, or fallback
secret values. `DOMAIN`, `PORT`, and `PAYMAIL_BASE_URL` configure the manual
server's advertised endpoint.
Loading
Loading