Skip to content

minmoto/escrow

Repository files navigation

Pontmore Escrow

Monorepo for the Pontmore custodial escrow service and operator dashboard.

  • escrow/ - NestJS escrow API service
  • apps/dashboard/ - Next.js Hexclave dashboard
  • packages/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.

Development

npm install
npm run build
npm start

Useful workspace commands:

npm run dev:escrow
npm run dev:dashboard
npm run dev
npm run build:escrow
npm run build:dashboard
npm test

npm 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.

Authentication

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=...

Runtime Backend

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.

API

Public:

  • GET /health

Protected team-scoped:

  • POST /v1/escrows
  • GET /v1/escrows/:reference
  • POST /v1/escrows/:reference/funding/verify
  • POST /v1/escrows/:reference/release
  • POST /v1/escrows/:reference/refund
  • POST /v1/escrows/:reference/dispute-resolution
  • GET /v1/team/descriptors
  • POST /v1/team/descriptors/publish
  • GET /v1/wallets
  • POST /v1/wallets
  • GET /v1/wallets/:walletId
  • POST /v1/wallets/:walletId/seed/reveal
  • DELETE /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=0
  • GET /v1/admin/escrows/:reference
  • GET /v1/admin/escrows/:reference/audit
  • GET /v1/admin/wallets
  • GET /v1/admin/wallets/:walletId

Database

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:escrow

Escrow 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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages