@paybond/kit 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # `@paybond/kit`
2
2
 
3
- Paybond Kit for TypeScript provides a tenant-bound Harbor client, gateway-backed service-account sessions, capability verification, canonical signing helpers for intent creation and evidence submission, tenant-scoped ledger provenance reads, and tenant-scoped Signal analytics and reputation reads.
3
+ Paybond Kit for TypeScript provides a tenant-bound Harbor client, gateway-backed service-account sessions, capability verification, canonical signing helpers for intent creation and evidence submission, x402 / USDC-on-Base intent funding helpers, tenant-scoped ledger provenance reads, and tenant-scoped Signal analytics and reputation reads.
4
4
 
5
5
  Install the public package with:
6
6
 
@@ -56,14 +56,16 @@ try {
56
56
  ## What the package includes
57
57
 
58
58
  - `Paybond.open(...)` for gateway-authenticated, tenant-derived Harbor sessions
59
- - `HarborClient` for capability verification, intent creation, evidence submission, and ledger reads
59
+ - `HarborClient` for capability verification, intent creation, x402 funding, evidence submission, and ledger reads
60
60
  - `GatewaySignalClient` and `ServiceAccountSignalSession` for tenant-scoped Signal reads
61
61
  - `paybond.signal` on `Paybond` sessions opened from one service-account API key
62
- - `PaybondIntents` helpers for principal-signed intent creation and payee-signed evidence submission
62
+ - `PaybondIntents` helpers for principal-signed intent creation, x402 funding, and payee-signed evidence submission
63
63
  - Low-level signing helpers exported for advanced callers
64
64
 
65
65
  `allowedTools` values are your own tool or operation names, not a Paybond-owned catalog. Harbor enforces string matching against whatever names you chose when creating the intent.
66
66
 
67
+ `settlementRail` on intent creation is only a rail request. Stripe destinations and x402 receive addresses stay tenant-owned server-side config and are never supplied by the SDK caller.
68
+
67
69
  ## What it does not include
68
70
 
69
71
  - No operator-tier settlement or console workflows
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Paybond Kit — TypeScript Harbor client with tenant binding, retries, optional upstream JWT,
3
3
  * and gateway service-account token exchange (`POST /v1/auth/harbor-access`).
4
4
  */
5
- import { type BuildSignedCreateIntentParams } from "./principal-intent.js";
5
+ import { type BuildSignedCreateIntentParams, type SettlementRail } from "./principal-intent.js";
6
6
  import { type SignPayeeEvidenceParams } from "./payee-evidence.js";
7
7
  export type VerifyCapabilityResult = {
8
8
  allow: boolean;
@@ -18,6 +18,44 @@ export type SubmitEvidenceResult = {
18
18
  state: string;
19
19
  predicatePassed?: boolean;
20
20
  };
21
+ export type IntentFundingResult = {
22
+ settlementRail: SettlementRail;
23
+ harborFundEndpoint?: string;
24
+ status?: string;
25
+ paymentSessionId?: string;
26
+ paymentUrl?: string;
27
+ asset?: string;
28
+ network?: string;
29
+ authorizationId?: string;
30
+ captureId?: string;
31
+ voidId?: string;
32
+ refundId?: string;
33
+ sourceAddress?: string;
34
+ targetAddress?: string;
35
+ authorizationExpiresAt?: string;
36
+ captureExpiresAt?: string;
37
+ refundExpiresAt?: string;
38
+ onchainTransactionHashes?: {
39
+ authorizations?: string[];
40
+ captures?: string[];
41
+ voids?: string[];
42
+ refunds?: string[];
43
+ };
44
+ };
45
+ export type FundIntentResult = {
46
+ statusCode: 200 | 202 | 402;
47
+ paymentRequired?: string;
48
+ paymentResponse?: string;
49
+ intentId: string;
50
+ tenant: string;
51
+ state: string;
52
+ settlementRail: SettlementRail;
53
+ currency: string;
54
+ amountCents: number;
55
+ funded: boolean;
56
+ capabilityToken?: string;
57
+ funding?: IntentFundingResult;
58
+ };
21
59
  /** Async supplier for short-lived Harbor JWTs minted by the Paybond gateway. */
22
60
  export type HarborBearerSupplier = () => Promise<string | null | undefined>;
23
61
  /**
@@ -172,6 +210,21 @@ export declare class HarborClient {
172
210
  createIntent(body: Record<string, unknown>, options?: {
173
211
  idempotencyKey?: string;
174
212
  }): Promise<Record<string, unknown>>;
213
+ /**
214
+ * POST `/intents/{intentId}/fund` for x402 / USDC-on-Base funding.
215
+ *
216
+ * Harbor returns:
217
+ * - `402` with `paymentRequired` details when a facilitator or wallet must sign
218
+ * - `202` while authorization is pending
219
+ * - `200` once the intent is funded and any capability token is available
220
+ *
221
+ * @throws HarborHttpError for non-funding HTTP errors
222
+ * @throws Error when Harbor echoes a different tenant or intent than requested
223
+ */
224
+ fundIntent(intentId: string, options?: {
225
+ paymentSignature?: string;
226
+ idempotencyKey?: string;
227
+ }): Promise<FundIntentResult>;
175
228
  /**
176
229
  * POST `/intents/{intentId}/evidence` with a signed evidence JSON body.
177
230
  *
@@ -240,24 +293,37 @@ export declare class ServiceAccountSignalSession {
240
293
  static open(init: ServiceAccountSignalSessionInit): Promise<ServiceAccountSignalSession>;
241
294
  aclose(): Promise<void>;
242
295
  }
243
- /** Parameters for {@link PaybondIntents.create} (tenant is taken from the bound Harbor client). */
296
+ /**
297
+ * Parameters for {@link PaybondIntents.create} (tenant is taken from the bound Harbor client).
298
+ * `settlementRail`, when set, requests one allowed rail; Harbor still resolves the destination
299
+ * from tenant-owned settlement config.
300
+ */
244
301
  export type PaybondCreateIntentParams = Omit<BuildSignedCreateIntentParams, "tenantId" | "intentId"> & {
245
302
  intentId?: string;
246
303
  };
247
304
  /** Parameters for {@link PaybondIntents.submitEvidence} (tenant is taken from the bound Harbor client). */
248
305
  export type PaybondSubmitEvidenceParams = Omit<SignPayeeEvidenceParams, "tenantId">;
249
306
  /**
250
- * Ergonomic intent helpers: principal-signed intent create and payee-signed evidence.
307
+ * Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
251
308
  */
252
309
  export declare class PaybondIntents {
253
310
  private readonly harbor;
254
311
  constructor(harbor: HarborClient);
255
312
  /**
256
- * Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be 32 bytes.
313
+ * Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be
314
+ * 32 bytes. `settlementRail` only requests an allowed rail; destinations stay server-owned.
257
315
  */
258
316
  create(params: PaybondCreateIntentParams & {
259
317
  idempotencyKey?: string;
260
318
  }): Promise<Record<string, unknown>>;
319
+ /**
320
+ * Advance Harbor `/intents/{id}/fund` for x402 / USDC-on-Base intents.
321
+ */
322
+ fund(params: {
323
+ intentId: string;
324
+ paymentSignature?: string;
325
+ idempotencyKey?: string;
326
+ }): Promise<FundIntentResult>;
261
327
  /**
262
328
  * Sign payee evidence and POST it. `payeeSigningSeed` must be 32 bytes.
263
329
  */
@@ -281,5 +347,5 @@ export declare class Paybond {
281
347
  aclose(): Promise<void>;
282
348
  }
283
349
  export { normalizeJson, jsonValueDigest } from "./json-digest.js";
284
- export { buildSignedCreateIntentBody, intentCreationSignBytesRaw, type BuildSignedCreateIntentParams, } from "./principal-intent.js";
350
+ export { buildSignedCreateIntentBody, intentCreationSignBytesRaw, type BuildSignedCreateIntentParams, type SettlementRail, } from "./principal-intent.js";
285
351
  export { artifactsDigest, signPayeeEvidenceBinding, type SignPayeeEvidenceParams } from "./payee-evidence.js";
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Paybond Kit — TypeScript Harbor client with tenant binding, retries, optional upstream JWT,
3
3
  * and gateway service-account token exchange (`POST /v1/auth/harbor-access`).
4
4
  */
5
- import { buildSignedCreateIntentBody } from "./principal-intent.js";
5
+ import { buildSignedCreateIntentBody, } from "./principal-intent.js";
6
6
  import { signPayeeEvidenceBinding } from "./payee-evidence.js";
7
7
  /**
8
8
  * Structured HTTP failure from Harbor with operator-facing diagnostics.
@@ -292,7 +292,7 @@ export class HarborClient {
292
292
  }
293
293
  throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
294
294
  }
295
- async fetchWithRetries(url, init, { retryBody }) {
295
+ async fetchWithRetries(url, init, { retryBody, retryBodyText, }) {
296
296
  let lastErr;
297
297
  for (let attempt = 0; attempt < this.maxRetries; attempt++) {
298
298
  let res;
@@ -318,10 +318,18 @@ export class HarborClient {
318
318
  const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
319
319
  const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
320
320
  await new Promise((r) => setTimeout(r, delayMs));
321
- init = {
322
- ...init,
323
- body: JSON.stringify(retryBody),
324
- };
321
+ if (retryBodyText !== undefined) {
322
+ init = {
323
+ ...init,
324
+ body: retryBodyText,
325
+ };
326
+ }
327
+ else if (retryBody !== undefined) {
328
+ init = {
329
+ ...init,
330
+ body: JSON.stringify(retryBody),
331
+ };
332
+ }
325
333
  continue;
326
334
  }
327
335
  return res;
@@ -403,6 +411,79 @@ export class HarborClient {
403
411
  }
404
412
  return JSON.parse(text);
405
413
  }
414
+ /**
415
+ * POST `/intents/{intentId}/fund` for x402 / USDC-on-Base funding.
416
+ *
417
+ * Harbor returns:
418
+ * - `402` with `paymentRequired` details when a facilitator or wallet must sign
419
+ * - `202` while authorization is pending
420
+ * - `200` once the intent is funded and any capability token is available
421
+ *
422
+ * @throws HarborHttpError for non-funding HTTP errors
423
+ * @throws Error when Harbor echoes a different tenant or intent than requested
424
+ */
425
+ async fundIntent(intentId, options) {
426
+ const url = `${this.base}intents/${intentId}/fund`;
427
+ const headers = {
428
+ "x-tenant-id": this.tenantId,
429
+ };
430
+ if (options?.idempotencyKey?.trim()) {
431
+ headers["idempotency-key"] = options.idempotencyKey.trim();
432
+ }
433
+ if (options?.paymentSignature?.trim()) {
434
+ headers["payment-signature"] = options.paymentSignature.trim();
435
+ }
436
+ const res = await this.fetchWithRetries(url, {
437
+ method: "POST",
438
+ headers,
439
+ body: "",
440
+ }, { retryBodyText: "" });
441
+ const text = await res.text();
442
+ if (![200, 202, 402].includes(res.status)) {
443
+ throw new HarborHttpError(`Harbor fund intent HTTP ${res.status}: ${text}`, {
444
+ statusCode: res.status,
445
+ url,
446
+ bodyText: text,
447
+ });
448
+ }
449
+ const body = assertJSONObject(JSON.parse(text));
450
+ const tenant = String(body.tenant ?? "");
451
+ if (tenant !== this.tenantId) {
452
+ throw new Error(`fund tenant mismatch: client=${this.tenantId} harbor=${tenant}`);
453
+ }
454
+ const echoedIntentId = String(body.intent_id ?? "");
455
+ if (echoedIntentId !== intentId) {
456
+ throw new Error(`fund intent mismatch: requested=${intentId} harbor=${echoedIntentId}`);
457
+ }
458
+ if (typeof body.state !== "string" || !body.state.trim()) {
459
+ throw new Error("fund response missing state");
460
+ }
461
+ if (typeof body.currency !== "string" || !body.currency.trim()) {
462
+ throw new Error("fund response missing currency");
463
+ }
464
+ const amountCents = Number(body.amount_cents);
465
+ if (!Number.isFinite(amountCents)) {
466
+ throw new Error("fund response missing amount_cents");
467
+ }
468
+ return {
469
+ statusCode: res.status,
470
+ paymentRequired: res.headers.get("payment-required") ?? undefined,
471
+ paymentResponse: res.headers.get("payment-response") ?? undefined,
472
+ intentId: echoedIntentId,
473
+ tenant,
474
+ state: body.state,
475
+ settlementRail: readSettlementRailValue(body.settlement_rail, "fund settlement_rail"),
476
+ currency: body.currency,
477
+ amountCents,
478
+ funded: Boolean(body.funded),
479
+ capabilityToken: typeof body.capability_token === "string" && body.capability_token.trim()
480
+ ? body.capability_token
481
+ : undefined,
482
+ funding: body.funding === undefined || body.funding === null
483
+ ? undefined
484
+ : parseIntentFundingResult(body.funding),
485
+ };
486
+ }
406
487
  /**
407
488
  * POST `/intents/{intentId}/evidence` with a signed evidence JSON body.
408
489
  *
@@ -523,12 +604,71 @@ export class HarborClient {
523
604
  }
524
605
  }
525
606
  const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
607
+ const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "x402_usdc_base"]);
526
608
  function assertJSONObject(value) {
527
609
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
528
610
  throw new Error("expected JSON object");
529
611
  }
530
612
  return value;
531
613
  }
614
+ function readSettlementRailValue(value, field) {
615
+ if (typeof value !== "string" || !SETTLEMENT_RAIL_VALUES.has(value)) {
616
+ throw new Error(`invalid ${field}`);
617
+ }
618
+ return value;
619
+ }
620
+ function readStringArrayValue(value, field) {
621
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) {
622
+ throw new Error(`invalid ${field}`);
623
+ }
624
+ return [...value];
625
+ }
626
+ function parseIntentFundingResult(value) {
627
+ const body = assertJSONObject(value);
628
+ const onchainRaw = body.onchain_transaction_hashes;
629
+ const onchain = onchainRaw === undefined || onchainRaw === null
630
+ ? undefined
631
+ : (() => {
632
+ const parsed = assertJSONObject(onchainRaw);
633
+ return {
634
+ authorizations: parsed.authorizations === undefined
635
+ ? undefined
636
+ : readStringArrayValue(parsed.authorizations, "funding.onchain_transaction_hashes.authorizations"),
637
+ captures: parsed.captures === undefined
638
+ ? undefined
639
+ : readStringArrayValue(parsed.captures, "funding.onchain_transaction_hashes.captures"),
640
+ voids: parsed.voids === undefined
641
+ ? undefined
642
+ : readStringArrayValue(parsed.voids, "funding.onchain_transaction_hashes.voids"),
643
+ refunds: parsed.refunds === undefined
644
+ ? undefined
645
+ : readStringArrayValue(parsed.refunds, "funding.onchain_transaction_hashes.refunds"),
646
+ };
647
+ })();
648
+ const readOptionalString = (field) => {
649
+ const raw = body[field];
650
+ return typeof raw === "string" && raw.trim() ? raw : undefined;
651
+ };
652
+ return {
653
+ settlementRail: readSettlementRailValue(body.settlement_rail, "funding.settlement_rail"),
654
+ harborFundEndpoint: readOptionalString("harbor_fund_endpoint"),
655
+ status: readOptionalString("status"),
656
+ paymentSessionId: readOptionalString("payment_session_id"),
657
+ paymentUrl: readOptionalString("payment_url"),
658
+ asset: readOptionalString("asset"),
659
+ network: readOptionalString("network"),
660
+ authorizationId: readOptionalString("authorization_id"),
661
+ captureId: readOptionalString("capture_id"),
662
+ voidId: readOptionalString("void_id"),
663
+ refundId: readOptionalString("refund_id"),
664
+ sourceAddress: readOptionalString("source_address"),
665
+ targetAddress: readOptionalString("target_address"),
666
+ authorizationExpiresAt: readOptionalString("authorization_expires_at"),
667
+ captureExpiresAt: readOptionalString("capture_expires_at"),
668
+ refundExpiresAt: readOptionalString("refund_expires_at"),
669
+ onchainTransactionHashes: onchain,
670
+ };
671
+ }
532
672
  /**
533
673
  * Tenant-bound reader for gateway Signal routes.
534
674
  */
@@ -743,7 +883,7 @@ export class ServiceAccountSignalSession {
743
883
  }
744
884
  }
745
885
  /**
746
- * Ergonomic intent helpers: principal-signed intent create and payee-signed evidence.
886
+ * Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
747
887
  */
748
888
  export class PaybondIntents {
749
889
  harbor;
@@ -751,7 +891,8 @@ export class PaybondIntents {
751
891
  this.harbor = harbor;
752
892
  }
753
893
  /**
754
- * Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be 32 bytes.
894
+ * Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be
895
+ * 32 bytes. `settlementRail` only requests an allowed rail; destinations stay server-owned.
755
896
  */
756
897
  async create(params) {
757
898
  const { idempotencyKey, intentId: maybeIntentId, ...fields } = params;
@@ -763,6 +904,15 @@ export class PaybondIntents {
763
904
  });
764
905
  return this.harbor.createIntent(body, { idempotencyKey });
765
906
  }
907
+ /**
908
+ * Advance Harbor `/intents/{id}/fund` for x402 / USDC-on-Base intents.
909
+ */
910
+ async fund(params) {
911
+ return this.harbor.fundIntent(params.intentId, {
912
+ paymentSignature: params.paymentSignature,
913
+ idempotencyKey: params.idempotencyKey,
914
+ });
915
+ }
766
916
  /**
767
917
  * Sign payee evidence and POST it. `payeeSigningSeed` must be 32 bytes.
768
918
  */
@@ -2,6 +2,8 @@
2
2
  * Principal intent creation signing for raw `predicate_dsl` (no managed-policy binding).
3
3
  * Matches `crates/harbor-intent-escrow/src/signing.rs` (`intent_creation_sign_bytes_raw`).
4
4
  */
5
+ /** Tenant-configured settlement rail names. Clients may request a rail, not a destination. */
6
+ export type SettlementRail = "stripe_connect" | "x402_usdc_base";
5
7
  export declare function intentCreationSignBytesRaw(input: {
6
8
  tenantId: string;
7
9
  intentId: string;
@@ -30,8 +32,11 @@ export type BuildSignedCreateIntentParams = {
30
32
  deadlineRfc3339: string;
31
33
  allowedTools: string[];
32
34
  predicateRef?: string;
35
+ /** Optional rail request for the new intent. Harbor resolves the destination server-side. */
36
+ settlementRail?: SettlementRail;
33
37
  };
34
38
  /**
35
39
  * Build a Harbor `POST /intents` JSON body with principal Ed25519 detached signature.
40
+ * `settlementRail` only requests an allowed rail; destinations remain tenant-owned server-side.
36
41
  */
37
42
  export declare function buildSignedCreateIntentBody(params: BuildSignedCreateIntentParams): Record<string, unknown>;
@@ -5,6 +5,13 @@
5
5
  import { sign, getPublicKey } from "@noble/ed25519";
6
6
  import { parse as parseUuid } from "uuid";
7
7
  import { jsonValueDigest } from "./json-digest.js";
8
+ const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "x402_usdc_base"]);
9
+ function validateSettlementRail(value) {
10
+ if (!SETTLEMENT_RAIL_VALUES.has(value)) {
11
+ throw new Error(`settlementRail must be one of ${[...SETTLEMENT_RAIL_VALUES].join(", ")}`);
12
+ }
13
+ return value;
14
+ }
8
15
  function concatBytes(...parts) {
9
16
  const n = parts.reduce((a, p) => a + p.length, 0);
10
17
  const out = new Uint8Array(n);
@@ -80,6 +87,7 @@ function bytesToBase64(bytes) {
80
87
  }
81
88
  /**
82
89
  * Build a Harbor `POST /intents` JSON body with principal Ed25519 detached signature.
90
+ * `settlementRail` only requests an allowed rail; destinations remain tenant-owned server-side.
83
91
  */
84
92
  export function buildSignedCreateIntentBody(params) {
85
93
  if (params.principalSigningSeed.length !== 32) {
@@ -88,6 +96,9 @@ export function buildSignedCreateIntentBody(params) {
88
96
  if (params.allowedTools.length === 0) {
89
97
  throw new Error("allowedTools must be non-empty");
90
98
  }
99
+ const settlementRail = params.settlementRail
100
+ ? validateSettlementRail(params.settlementRail)
101
+ : undefined;
91
102
  const predicateRef = params.predicateRef ?? "";
92
103
  const msg = intentCreationSignBytesRaw({
93
104
  tenantId: params.tenantId,
@@ -124,5 +135,8 @@ export function buildSignedCreateIntentBody(params) {
124
135
  if (predicateRef.trim() !== "") {
125
136
  body.predicate_ref = predicateRef;
126
137
  }
138
+ if (settlementRail) {
139
+ body.settlement_rail = settlementRail;
140
+ }
127
141
  return body;
128
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paybond/kit",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Paybond Kit for TypeScript: tenant-bound Harbor sessions, capability verification, and signed intent/evidence flows.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",