@pbjpro/relay

中文文档
1.3.0发布于 2026/11/02MITTypeScriptESM + CJS零依赖

TypeScript SDK for Relay's USDC milestone escrow protocol

原始使用文档
文档目录25 节
  1. Install
  2. Requirements
  3. Quickstart
  4. Environment
  5. Create an escrow project
  6. Fund escrow with USDC
  7. Managed funding workflow
  8. Submit milestone evidence
  9. Verify delivery evidence
  10. Approve and release
  11. Resolve a dispute
  12. Webhooks
  13. Devnet and mainnet
  14. Integrations
  15. Pagination
  16. Transaction monitoring
  17. Typed errors
  18. Public services
  19. Testing utilities
  20. Security checklist
  21. Examples
  22. Development
  23. Documentation and support
  24. Publishing
  25. License

Relay TypeScript SDK

npm version license Node.js Solana

Relay — your work deserves a funded commitment

Relay is PBJ Protocol's production TypeScript SDK for USDC milestone escrow on Solana. It helps applications, agencies, clients, and independent teams define deliverables, secure project budgets before work begins, preserve delivery evidence, approve releases, and resolve disputes through explicit rules.

  • Typed end to end — strict TypeScript models, errors, and service APIs
  • Non-custodial signing — Relay prepares transactions; your wallet signs them
  • Devnet and mainnet — explicit network selection with mainnet safeguards
  • Evidence ready — GitHub, Figma, Google Drive, URLs, and checksums
  • Safe transport — timeouts, abort signals, idempotency, retry controls, and redaction

[!WARNING] Relay enforces funding, evidence, approval, and release rules. It cannot independently determine whether subjective creative or software work meets expectations. Define measurable milestone criteria and select a dispute resolver before funding.

Install

npm i @pbjpro/relay

Current version: v1.3.0

Requirements

  • Node.js 20 or newer, or a modern browser with fetch and AbortController
  • A Relay API key for authenticated server operations
  • A compatible Solana wallet for signing funding and release transactions
  • Decimal strings for all token amounts—never JavaScript floating-point numbers

The package includes ESM, CommonJS, declaration files, and source maps.

Quickstart

import { RelayClient } from "@pbjpro/relay";

const relay = new RelayClient({
  apiKey: process.env.RELAY_API_KEY!,
  baseUrl: process.env.RELAY_API_URL,
  network: "devnet",
});

Keep API keys on trusted servers. Do not include privileged Relay or integration credentials in browser bundles.

Environment

RELAY_API_KEY=relay_test_replace_me
RELAY_API_URL=https://api.0xpbj.pro
RELAY_NETWORK=devnet
RELAY_CONFIRM_MAINNET=false

See .env.example for the complete configuration template.

Create an escrow project

const project = await relay.escrows.create({
  name: "Brand redesign",
  clientWallet: "CLIENT_SOLANA_ADDRESS",
  freelancerWallet: "FREELANCER_SOLANA_ADDRESS",
  resolverWallet: "RESOLVER_SOLANA_ADDRESS",
  currency: "USDC",
  milestones: [
    {
      title: "Creative direction",
      amount: "1500.00",
      description: "Approved research and creative direction",
      dueAt: "2027-01-31T00:00:00.000Z",
    },
    {
      title: "Final delivery",
      amount: "3500.00",
      description: "Approved final files and documented handoff",
      dueAt: "2027-02-28T00:00:00.000Z",
    },
  ],
  disputePolicy: {
    resolverType: "human",
    inactivityTimeoutHours: 168,
    evidenceWindowHours: 72,
  },
  idempotencyKey: "brand-redesign-001",
});

Fund escrow with USDC

Relay prepares the transaction. The client wallet reviews and signs it locally.

const funding = await relay.escrows.prepareFunding({
  projectId: project.id,
  amount: "5000.00",
});

const signature = await wallet.sendTransaction(funding.transaction);

await relay.escrows.confirmFunding({
  projectId: project.id,
  signature,
});

Relay never needs a wallet seed phrase or private key.

Managed funding workflow

The high-level workflow prepares the transaction, delegates signing to your wallet, and confirms the submitted signature. Private keys never enter the SDK.

const confirmation = await relay.escrows.fund({
  projectId: project.id,
  amount: "5000.00",
  signTransaction: (transaction) => wallet.sendTransaction(transaction),
  onProgress: ({ stage, signature }) => {
    console.log(stage, signature ?? "");
  },
});

Submit milestone evidence

Pin specific, immutable versions whenever possible. A live branch, design file, or Drive link may change after submission.

const submission = await relay.milestones.submit({
  milestoneId: "milestone_id",
  summary: "Final files and handoff documentation",
  evidence: [
    {
      type: "github",
      repository: "pbjprotocol/client-project",
      commitSha: "FULL_COMMIT_SHA",
    },
    {
      type: "figma",
      fileKey: "FIGMA_FILE_KEY",
      versionId: "FIGMA_VERSION_ID",
    },
    {
      type: "google_drive",
      fileId: "GOOGLE_DRIVE_FILE_ID",
      checksum: "SHA256_CHECKSUM",
    },
  ],
  idempotencyKey: "delivery-001",
});

Verify delivery evidence

const report = await relay.evidence.verify("evidence_id");

if (!report.valid) {
  console.error("Evidence verification failed", report.checks);
}

Approve and release

const prepared = await relay.milestones.prepareRelease({
  milestoneId: submission.milestoneId,
});

const signature = await wallet.sendTransaction(prepared.transaction);

await relay.milestones.confirmRelease({
  milestoneId: submission.milestoneId,
  signature,
});

Signed blockchain transaction confirmations are never automatically retried.

The equivalent managed release workflow is available as relay.milestones.release():

await relay.milestones.release({
  milestoneId: submission.milestoneId,
  signTransaction: (transaction) => wallet.sendTransaction(transaction),
});

Resolve a dispute

const dispute = await relay.disputes.create({
  milestoneId: "milestone_id",
  reason: "delivery_does_not_match_scope",
  statement: "The submitted package omits the agreed mobile layouts.",
  evidenceIds: ["evidence_1", "evidence_2"],
  idempotencyKey: "dispute-001",
});

await relay.disputes.resolve({
  disputeId: dispute.id,
  outcome: "split",
  statement: "Desktop scope delivered; mobile scope remains incomplete.",
  clientAmount: "1800.00",
  freelancerAmount: "1200.00",
});

Webhooks

Always verify the exact, unmodified request body before processing a webhook. Verification includes HMAC authentication and timestamp replay protection.

const event = await relay.webhooks.verify({
  payload: rawRequestBody,
  signature: request.headers["x-relay-signature"],
  secret: process.env.RELAY_WEBHOOK_SECRET!,
});

switch (event.type) {
  case "milestone.submitted":
    console.log(event.data.submission.id);
    break;
  case "dispute.opened":
    console.log(event.data.id);
    break;
}

Use relay.webhooks.idempotent(store, handler) with your database or cache to prevent duplicate delivery processing. Events are recorded only after the handler succeeds.

Devnet and mainnet

Devnet is the default and recommended environment during development.

const relay = new RelayClient({
  apiKey: process.env.RELAY_API_KEY!,
  network: "devnet",
});

Mainnet requires explicit confirmation and never silently falls back to devnet.

const relay = new RelayClient({
  apiKey: process.env.RELAY_API_KEY!,
  network: "mainnet",
  confirmMainnet: true,
});

Integrations

IntegrationDelivery evidence
GitHubRepositories, immutable commit SHAs, delivery activity
FigmaFile versions, frozen previews, review comments
Google DriveFiles, folders, version metadata, checksums

Integration tokens should use the narrowest permissions your workflow supports.

Pagination

const page = await relay.milestones.list({
  projectId: project.id,
  status: ["submitted", "in_dispute"],
  page: 1,
  limit: 20,
});

for await (const milestone of relay.milestones.iterate({
  projectId: project.id,
})) {
  console.log(milestone.id, milestone.status);
}

Transaction monitoring

for await (const update of relay.solana.watchTransaction(signature, {
  timeoutMs: 60_000,
  signal: abortController.signal,
})) {
  console.log(update.status);
}

The iterator emits status changes and stops at confirmation. Failed transactions and timeouts produce typed errors.

Typed errors

import { RelayAuthenticationError, RelayTransactionError } from "@pbjpro/relay";

try {
  await createFundedProject();
} catch (error) {
  if (error instanceof RelayAuthenticationError) {
    console.error("Check RELAY_API_KEY");
  } else if (error instanceof RelayTransactionError) {
    console.error("Transaction failed", error.signature);
  } else {
    throw error;
  }
}

All Relay errors expose code, message, and retryable, plus optional statusCode, requestId, cause, and structured details.

Public services

ServiceMethods
relay.escrowscreate, get, list, prepareFunding, confirmFunding, cancel, getBalance, getActivity
relay.milestonescreate, get, list, iterate, submit, prepareRelease, confirmRelease, requestChanges
relay.evidencecreate, get, list, refresh, getFrozenPreview, verifyChecksum
relay.disputescreate, get, list, addEvidence, addStatement, resolve, getTimeline
relay.integrationsGitHub, Figma, and Google Drive evidence services
relay.solanaNetwork, RPC, USDC mint, transaction status, confirmation, and wallet validation
relay.webhooksHMAC verification, replay protection, duplicate detection, and idempotent handlers

Testing utilities

The optional testing entry point provides deterministic fixtures and a mock Relay transport without network access.

import { createRelayMock, projectFixture } from "@pbjpro/relay/testing";

const project = projectFixture();
const mock = createRelayMock([
  { path: `/v1/escrows/${project.id}`, body: project },
]);

const result = await mock.client.escrows.get(project.id);

It also exports milestoneFixture, evidenceFixture, webhookFixture, and signWebhookEvent.

Security checklist

  • Keep API and integration credentials server-side.
  • Never store wallet private keys in Relay configuration.
  • Validate the network, wallet addresses, USDC mint, milestone totals, and resolver before signing.
  • Use full commit SHAs, frozen design versions, and checksums for immutable evidence.
  • Use unique idempotency keys for create and submission workflows.
  • Redact authorization headers and sensitive bodies from application logs.
  • Review mainnet transactions before signing.

Examples

Runnable integrations are available in examples:

  • Node.js project creation, funding, submission, release, and dispute flows
  • Next.js route handler integration that keeps the API key server-side
  • Express server integration with typed error handling

Development

npm ci
npm run typecheck
npm run lint
npm test
npm run build

Other scripts include test:coverage, format, and format:check.

Documentation and support

Publishing

  1. Update the package version and CHANGELOG.md.
  2. Run npm ci and npm run prepublishOnly.
  3. Inspect the package with npm pack --dry-run.
  4. Publish with npm publish --access public from an authorized npm account.

License

MIT © PBJ Protocol. See LICENSE.

版本与发布记录

@pbjpro/relay 共发布 3 个版本,首次发布于 2026/10/28;过去 12 个月发布了 3 个版本。

版本发布日期距今
1.3.0latest2026/11/021 天前
1.2.12026/10/286 天前
0.0.0-stage2026/10/286 天前

关于 @pbjpro/relay 的常见问题

@pbjpro/relay 是什么?

Relay的USDC里程碑托管协议的TypeScript SDK

如何安装 @pbjpro/relay?

在项目目录运行 npm install @pbjpro/relay,或使用 pnpm add / yarn add / bun add。

@pbjpro/relay 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@pbjpro/relay 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@pbjpro/relay 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@pbjpro/relay 可以商用吗?

@pbjpro/relay 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@pbjpro/relay 还在维护吗?

最新版本 1.3.0 发布于 2026/11/02(1 天前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/10/28。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。