Monorepo for the Pontmore custodial escrow service and operator dashboard.
escrow/- NestJS escrow API serviceapps/dashboard/- Next.js Hexclave dashboardpackages/core/- shared config types and connection-string parsing
The service owns escrow reference issuance, funding verification, release/refund execution, idempotency, audit history, and PIP-01 escrow descriptor publication.
npm install
npm run build
npm startUseful workspace commands:
npm run dev:escrow
npm run dev:dashboard
npm run dev
npm run build:escrow
npm run build:dashboard
npm testnpm start runs Docker Compose with Postgres, the escrow service, and the Next.js
dashboard. The dashboard is served as
its own app on port 4001; the Nest service no longer bundles or serves static
dashboard assets.
Use npm run dev for hot reload. It runs the shared packages in TypeScript watch
mode, the Nest service in watch mode, and the Next.js dashboard dev server.
The escrow API still uses Hexclave credentials for protected service/admin requests. Requests must include either a Hexclave session authorization header or a Hexclave-issued API key:
Authorization: Bearer <hexclave-api-key-or-session-authorization-header>
Team-scoped escrow operations require team credentials. User credentials are not allowed to operate tenant escrow state directly.
The escrow service uses server-side Hexclave env names:
HEXCLAVE_PROJECT_ID=...
HEXCLAVE_PUBLISHABLE_CLIENT_KEY=...
HEXCLAVE_SECRET_SERVER_KEY=...The dashboard uses Next.js public names for browser-exposed values:
NEXT_PUBLIC_HEXCLAVE_PROJECT_ID=...
NEXT_PUBLIC_HEXCLAVE_PUBLISHABLE_CLIENT_KEY=...
HEXCLAVE_SECRET_SERVER_KEY=...Breez SDK - Spark is the only production escrow wallet backend. The hosted
wallet service uses the service-level BREEZE_API_KEY; teams do not configure
or store their own Breez API key. Configure team-owned wallets under:
team.serverMetadata.features.wallets.config.wallets[]
team.serverMetadata.features.escrow.config.serviceConfig.backendWalletId
Wallet shape:
{
"wallets": [
{
"id": "main-escrow-wallet",
"name": "Main escrow wallet",
"provider": "breez_spark",
"isActive": true,
"providerConfig": {
"network": "mainnet",
"mnemonic": "<team-visible-bip39-seed-phrase>",
"usdtTokenIdentifier": "<spark-usdt-token-id>",
"usdcTokenIdentifier": "<spark-usdc-token-id>"
}
}
]
}Escrow feature shape:
{
"serviceConfig": {
"backendWalletId": "main-escrow-wallet",
"isActive": true,
"fundingInvoiceExpirySeconds": 172800,
"allowOnchainPayouts": true
}
}The dashboard generates a BIP-39 seed phrase for each Breez Spark wallet and stores it in team config. The team can view the phrase for recovery, while the escrow service uses it at runtime through the Breez SDK mnemonic seed path.
Public:
GET /health
Protected team-scoped:
POST /v1/escrowsGET /v1/escrows/:referencePOST /v1/escrows/:reference/funding/verifyPOST /v1/escrows/:reference/releasePOST /v1/escrows/:reference/refundPOST /v1/escrows/:reference/dispute-resolutionGET /v1/team/descriptorsPOST /v1/team/descriptors/publishGET /v1/walletsPOST /v1/walletsGET /v1/wallets/:walletIdPOST /v1/wallets/:walletId/seed/revealDELETE /v1/wallets/:walletId
Escrow capability discovery happens through team-published Nostr descriptors, not through a public service descriptor endpoint.
Admin team-scoped:
GET /v1/admin/escrows?limit=25&offset=0GET /v1/admin/escrows/:referenceGET /v1/admin/escrows/:reference/auditGET /v1/admin/walletsGET /v1/admin/wallets/:walletId
Set DATABASE_URL before starting the service:
createdb pontmore_escrow
export DATABASE_URL=postgres://postgres:postgres@localhost:5432/pontmore_escrow
export POSTGRES_SYNCHRONIZE=true # local development only
npm run dev:escrowEscrow records are tenant-scoped by teamId. swapReference uniqueness is per
team, and each funding invoice stores the backend ID/type used to create it so
later backend rotation does not affect funding verification or payouts.