@paybond/kit 0.6.0 → 0.7.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 +6 -10
- package/dist/ed25519-sync.d.ts +1 -0
- package/dist/ed25519-sync.js +14 -0
- package/dist/index.d.ts +66 -72
- package/dist/index.js +263 -166
- package/dist/mcp-server.d.ts +1 -4
- package/dist/mcp-server.js +4 -79
- package/dist/payee-evidence.js +2 -0
- package/dist/principal-intent.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# `@paybond/kit`
|
|
2
2
|
|
|
3
|
-
Paybond Kit for TypeScript is the npm package for tenant-bound Paybond integrations. It opens
|
|
3
|
+
Paybond Kit for TypeScript is the npm package for tenant-bound Paybond integrations. It opens hosted Gateway sessions, verifies capability tokens, signs intent and evidence payloads, uses Stripe Connect or x402 / USDC-on-Base settlement rails, and reads tenant-scoped Signal, fraud, ledger, protocol, and A2A data.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -17,17 +17,14 @@ npm install @paybond/kit
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
19
|
- Node.js 22+
|
|
20
|
-
- A `
|
|
21
|
-
- Reachable Gateway and Harbor base URLs
|
|
20
|
+
- A `paybond_sk_sandbox_...` or `paybond_sk_live_...` service-account API key
|
|
22
21
|
- For capability verification: a funded intent id and a capability token minted for that intent
|
|
23
22
|
- For intent creation or evidence submission: 32-byte Ed25519 signing seeds owned by your application
|
|
24
23
|
|
|
25
24
|
Minimal environment for the quick start:
|
|
26
25
|
|
|
27
26
|
```bash
|
|
28
|
-
export
|
|
29
|
-
export PAYBOND_HARBOR_URL="https://harbor.example.com"
|
|
30
|
-
export PAYBOND_API_KEY="paybond_sk_..."
|
|
27
|
+
export PAYBOND_API_KEY="paybond_sk_sandbox_..."
|
|
31
28
|
```
|
|
32
29
|
|
|
33
30
|
Optional, if you want the quick start to verify a capability:
|
|
@@ -39,7 +36,7 @@ export PAYBOND_CAPABILITY="base64-biscuit-token"
|
|
|
39
36
|
|
|
40
37
|
## Tenant isolation
|
|
41
38
|
|
|
42
|
-
Every session is bound to the tenant realm echoed by gateway-authenticated service-account introspection
|
|
39
|
+
Every session is bound to the tenant realm echoed by gateway-authenticated service-account introspection.
|
|
43
40
|
|
|
44
41
|
- Do not pass tenant ids by hand for normal SDK usage.
|
|
45
42
|
- Construct one `Paybond` session per tenant/service account.
|
|
@@ -59,9 +56,8 @@ function requiredEnv(name: string): string {
|
|
|
59
56
|
}
|
|
60
57
|
|
|
61
58
|
const paybond = await Paybond.open({
|
|
62
|
-
gatewayBaseUrl: requiredEnv("PAYBOND_GATEWAY_URL"),
|
|
63
59
|
apiKey: requiredEnv("PAYBOND_API_KEY"),
|
|
64
|
-
|
|
60
|
+
expectedEnvironment: "sandbox",
|
|
65
61
|
});
|
|
66
62
|
|
|
67
63
|
try {
|
|
@@ -91,7 +87,7 @@ try {
|
|
|
91
87
|
|
|
92
88
|
Core SDK:
|
|
93
89
|
|
|
94
|
-
- `Paybond.open(...)` for
|
|
90
|
+
- `Paybond.open(...)` for API-key-only, tenant-derived hosted sessions
|
|
95
91
|
- `HarborClient` for capability verification, intent creation, x402 funding, evidence submission, and ledger reads
|
|
96
92
|
- `paybond.signal` and `paybond.fraud` on `Paybond` sessions opened from one service-account API key
|
|
97
93
|
- `PaybondIntents` helpers for principal-signed intent creation, x402 funding, and payee-signed evidence submission
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function ensureEd25519Sha512Sync(): void;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { etc } from "@noble/ed25519";
|
|
3
|
+
export function ensureEd25519Sha512Sync() {
|
|
4
|
+
if (etc.sha512Sync) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
etc.sha512Sync = (...messages) => {
|
|
8
|
+
const hash = createHash("sha512");
|
|
9
|
+
for (const message of messages) {
|
|
10
|
+
hash.update(message);
|
|
11
|
+
}
|
|
12
|
+
return new Uint8Array(hash.digest());
|
|
13
|
+
};
|
|
14
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Paybond Kit — TypeScript
|
|
3
|
-
* and
|
|
2
|
+
* Paybond Kit — TypeScript Gateway client with tenant binding, retries, capability verification,
|
|
3
|
+
* and signed intent/evidence helpers.
|
|
4
4
|
*/
|
|
5
5
|
import { type BuildSignedCreateIntentParams, type SettlementRail } from "./principal-intent.js";
|
|
6
6
|
import { type SignPayeeEvidenceParams } from "./payee-evidence.js";
|
|
@@ -56,7 +56,8 @@ export type FundIntentResult = {
|
|
|
56
56
|
capabilityToken?: string;
|
|
57
57
|
funding?: IntentFundingResult;
|
|
58
58
|
};
|
|
59
|
-
|
|
59
|
+
export declare const DEFAULT_PAYBOND_GATEWAY_BASE_URL = "https://api.paybond.ai";
|
|
60
|
+
/** Async supplier for upstream Harbor bearer tokens on low-level direct clients. */
|
|
60
61
|
export type HarborBearerSupplier = () => Promise<string | null | undefined>;
|
|
61
62
|
/**
|
|
62
63
|
* Structured HTTP failure from Harbor with operator-facing diagnostics.
|
|
@@ -72,7 +73,7 @@ export declare class HarborHttpError extends Error {
|
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
|
-
* Gateway rejected
|
|
76
|
+
* Gateway rejected service-account credentials or returned an unusable tenant-principal payload.
|
|
76
77
|
*/
|
|
77
78
|
export declare class GatewayAuthError extends Error {
|
|
78
79
|
readonly statusCode: number | undefined;
|
|
@@ -642,72 +643,23 @@ export declare class ProtocolHttpError extends Error {
|
|
|
642
643
|
errorMessage?: string;
|
|
643
644
|
});
|
|
644
645
|
}
|
|
645
|
-
|
|
646
|
-
* Exchanges a `paybond_sk_` API key for short-lived Harbor JWTs and caches tenant realm from the
|
|
647
|
-
* gateway response (no separate tenant env var for the default path).
|
|
648
|
-
*/
|
|
649
|
-
export declare class GatewayHarborTokenProvider {
|
|
650
|
-
private readonly gatewayBase;
|
|
651
|
-
private readonly apiKey;
|
|
652
|
-
private readonly path;
|
|
653
|
-
private readonly skewMs;
|
|
654
|
-
private readonly clock;
|
|
655
|
-
private token;
|
|
656
|
-
private tenantIdValue;
|
|
657
|
-
private notAfterMonotonic;
|
|
658
|
-
private refreshTail;
|
|
659
|
-
constructor(init: {
|
|
660
|
-
gatewayBaseUrl: string;
|
|
661
|
-
apiKey: string;
|
|
662
|
-
harborAccessPath?: string;
|
|
663
|
-
clockSkewSeconds?: number;
|
|
664
|
-
/** Injectable monotonic clock (milliseconds) for tests. */
|
|
665
|
-
clock?: () => number;
|
|
666
|
-
});
|
|
667
|
-
get tenantId(): string | null;
|
|
668
|
-
/**
|
|
669
|
-
* First exchange; returns tenant realm echoed by the gateway.
|
|
670
|
-
*/
|
|
671
|
-
ensureInitial(): Promise<string>;
|
|
672
|
-
/** Return a valid Harbor JWT, refreshing when near expiry. */
|
|
673
|
-
bearer(): Promise<string>;
|
|
674
|
-
/** Force rotation (credential rotation drills). */
|
|
675
|
-
forceRotate(): Promise<void>;
|
|
676
|
-
private refresh;
|
|
677
|
-
private refreshInner;
|
|
678
|
-
}
|
|
646
|
+
export type PaybondEnvironment = "live" | "sandbox";
|
|
679
647
|
/**
|
|
680
648
|
* Tenant-scoped Harbor binding for one funded intent and one Biscuit capability token.
|
|
681
649
|
*/
|
|
682
650
|
export declare class PaybondCapabilityBinding {
|
|
683
|
-
readonly harbor: HarborClient
|
|
651
|
+
readonly harbor: Pick<HarborClient | GatewayHarborClient, "tenantId" | "verifyCapability">;
|
|
684
652
|
readonly intentId: string;
|
|
685
653
|
readonly capabilityToken: string;
|
|
686
|
-
constructor(harbor: HarborClient, intentId: string, capabilityToken: string);
|
|
654
|
+
constructor(harbor: Pick<HarborClient | GatewayHarborClient, "tenantId" | "verifyCapability">, intentId: string, capabilityToken: string);
|
|
687
655
|
}
|
|
688
|
-
export type
|
|
689
|
-
gatewayBaseUrl: string;
|
|
656
|
+
export type PaybondOpenOptions = {
|
|
690
657
|
apiKey: string;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
658
|
+
gatewayBaseUrl?: string;
|
|
659
|
+
principalPath?: string;
|
|
660
|
+
expectedEnvironment?: PaybondEnvironment;
|
|
694
661
|
maxRetries?: number;
|
|
695
662
|
};
|
|
696
|
-
/**
|
|
697
|
-
* Harbor client plus gateway token lifecycle for one service account.
|
|
698
|
-
*/
|
|
699
|
-
export declare class ServiceAccountHarborSession {
|
|
700
|
-
readonly harbor: HarborClient;
|
|
701
|
-
private readonly tokens;
|
|
702
|
-
private constructor();
|
|
703
|
-
/**
|
|
704
|
-
* Build a tenant-bound {@link HarborClient} using gateway-derived tenant id and JWT supplier.
|
|
705
|
-
*/
|
|
706
|
-
static open(init: ServiceAccountHarborSessionInit): Promise<ServiceAccountHarborSession>;
|
|
707
|
-
rotateHarborToken(): Promise<void>;
|
|
708
|
-
/** Reserved for future HTTP client cleanup; safe to call after work completes. */
|
|
709
|
-
aclose(): Promise<void>;
|
|
710
|
-
}
|
|
711
663
|
type HarborClientOptions = {
|
|
712
664
|
harborBearerSupplier?: HarborBearerSupplier;
|
|
713
665
|
staticHarborBearerToken?: string;
|
|
@@ -792,8 +744,8 @@ export declare class HarborClient {
|
|
|
792
744
|
*/
|
|
793
745
|
getLedgerAuthority(): Promise<Record<string, unknown>>;
|
|
794
746
|
/**
|
|
795
|
-
* `GET /ledger/v1/events`
|
|
796
|
-
* `limit` is clamped to 1
|
|
747
|
+
* `GET /ledger/v1/events` - protected Harbor append-only history for trusted clients.
|
|
748
|
+
* `afterSeq` is an exclusive cursor; `limit` is clamped to 1..256 to match Harbor.
|
|
797
749
|
*/
|
|
798
750
|
getLedgerEvents(options?: {
|
|
799
751
|
afterSeq?: number;
|
|
@@ -804,6 +756,43 @@ export declare class HarborClient {
|
|
|
804
756
|
*/
|
|
805
757
|
getLedgerMerkleLatest(): Promise<Record<string, unknown>>;
|
|
806
758
|
}
|
|
759
|
+
type GatewayHarborClientOptions = {
|
|
760
|
+
staticGatewayBearerToken: string;
|
|
761
|
+
maxRetries?: number;
|
|
762
|
+
};
|
|
763
|
+
type GatewayHarborMutationOptions = {
|
|
764
|
+
idempotencyKey?: string;
|
|
765
|
+
recognitionProof?: AgentRecognitionProofV1 | Record<string, unknown>;
|
|
766
|
+
};
|
|
767
|
+
/**
|
|
768
|
+
* Gateway-backed Harbor surface for hosted Paybond integrations.
|
|
769
|
+
*
|
|
770
|
+
* This client sends the service-account API key to the public Gateway. Gateway derives tenant,
|
|
771
|
+
* mints upstream Harbor credentials internally, and applies recognition/guardrail checks before
|
|
772
|
+
* forwarding state-changing Harbor requests.
|
|
773
|
+
*/
|
|
774
|
+
export declare class GatewayHarborClient {
|
|
775
|
+
private readonly base;
|
|
776
|
+
readonly tenantId: string;
|
|
777
|
+
private readonly staticGatewayBearerToken;
|
|
778
|
+
private readonly maxRetries;
|
|
779
|
+
constructor(gatewayBaseUrl: string, tenantId: string, options: GatewayHarborClientOptions);
|
|
780
|
+
private headers;
|
|
781
|
+
private fetchWithRetries;
|
|
782
|
+
private postJSON;
|
|
783
|
+
private mutationHeaders;
|
|
784
|
+
verifyCapability(input: {
|
|
785
|
+
intentId: string;
|
|
786
|
+
token: string;
|
|
787
|
+
operation: string;
|
|
788
|
+
requestedSpendCents?: number;
|
|
789
|
+
}): Promise<VerifyCapabilityResult>;
|
|
790
|
+
createIntent(body: Record<string, unknown>, options: GatewayHarborMutationOptions): Promise<Record<string, unknown>>;
|
|
791
|
+
fundIntent(intentId: string, options: GatewayHarborMutationOptions & {
|
|
792
|
+
paymentSignature?: string;
|
|
793
|
+
}): Promise<FundIntentResult>;
|
|
794
|
+
submitEvidence(intentId: string, evidenceBody: Record<string, unknown>, options: GatewayHarborMutationOptions): Promise<SubmitEvidenceResult>;
|
|
795
|
+
}
|
|
807
796
|
type GatewaySignalClientOptions = {
|
|
808
797
|
staticGatewayBearerToken: string;
|
|
809
798
|
maxRetries?: number;
|
|
@@ -920,9 +909,10 @@ export declare class GatewayProtocolClient {
|
|
|
920
909
|
}): Promise<Record<string, unknown>>;
|
|
921
910
|
}
|
|
922
911
|
export type ServiceAccountSignalSessionInit = {
|
|
923
|
-
gatewayBaseUrl: string;
|
|
924
912
|
apiKey: string;
|
|
913
|
+
gatewayBaseUrl?: string;
|
|
925
914
|
principalPath?: string;
|
|
915
|
+
expectedEnvironment?: PaybondEnvironment;
|
|
926
916
|
maxRetries?: number;
|
|
927
917
|
};
|
|
928
918
|
export type ServiceAccountFraudSessionInit = ServiceAccountSignalSessionInit;
|
|
@@ -951,15 +941,20 @@ export declare class ServiceAccountFraudSession {
|
|
|
951
941
|
*/
|
|
952
942
|
export type PaybondCreateIntentParams = Omit<BuildSignedCreateIntentParams, "tenantId" | "intentId"> & {
|
|
953
943
|
intentId?: string;
|
|
944
|
+
recognitionProof: AgentRecognitionProofV1 | Record<string, unknown>;
|
|
954
945
|
};
|
|
955
946
|
/** Parameters for {@link PaybondIntents.submitEvidence} (tenant is taken from the bound Harbor client). */
|
|
956
|
-
export type PaybondSubmitEvidenceParams = Omit<SignPayeeEvidenceParams, "tenantId"
|
|
947
|
+
export type PaybondSubmitEvidenceParams = Omit<SignPayeeEvidenceParams, "tenantId" | "artifactsBlake3Hex" | "submittedAtRfc3339"> & {
|
|
948
|
+
artifactsBlake3Hex?: string[];
|
|
949
|
+
submittedAtRfc3339?: string;
|
|
950
|
+
recognitionProof: AgentRecognitionProofV1 | Record<string, unknown>;
|
|
951
|
+
};
|
|
957
952
|
/**
|
|
958
953
|
* Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
|
|
959
954
|
*/
|
|
960
955
|
export declare class PaybondIntents {
|
|
961
956
|
private readonly harbor;
|
|
962
|
-
constructor(harbor: HarborClient);
|
|
957
|
+
constructor(harbor: HarborClient | GatewayHarborClient);
|
|
963
958
|
/**
|
|
964
959
|
* Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be
|
|
965
960
|
* 32 bytes. `settlementRail` is signed as the requested rail; destinations stay server-owned.
|
|
@@ -972,6 +967,7 @@ export declare class PaybondIntents {
|
|
|
972
967
|
*/
|
|
973
968
|
fund(params: {
|
|
974
969
|
intentId: string;
|
|
970
|
+
recognitionProof: AgentRecognitionProofV1 | Record<string, unknown>;
|
|
975
971
|
paymentSignature?: string;
|
|
976
972
|
idempotencyKey?: string;
|
|
977
973
|
}): Promise<FundIntentResult>;
|
|
@@ -983,21 +979,19 @@ export declare class PaybondIntents {
|
|
|
983
979
|
}): Promise<SubmitEvidenceResult>;
|
|
984
980
|
}
|
|
985
981
|
/**
|
|
986
|
-
* High-level Kit entrypoint:
|
|
982
|
+
* High-level Kit entrypoint: tenant-bound Gateway clients plus ergonomic intent helpers.
|
|
987
983
|
*/
|
|
988
984
|
export declare class Paybond {
|
|
989
|
-
readonly harbor:
|
|
985
|
+
readonly harbor: GatewayHarborClient;
|
|
990
986
|
readonly signal: GatewaySignalClient;
|
|
991
987
|
readonly fraud: GatewayFraudClient;
|
|
992
988
|
readonly a2a: GatewayA2AClient;
|
|
993
989
|
readonly protocol: GatewayProtocolClient;
|
|
994
990
|
readonly intents: PaybondIntents;
|
|
995
|
-
private readonly session;
|
|
996
991
|
private constructor();
|
|
997
|
-
/** Open a tenant-bound session
|
|
998
|
-
static open(init:
|
|
999
|
-
|
|
1000
|
-
/** Release HTTP resources (Harbor client + gateway token provider). */
|
|
992
|
+
/** Open a tenant-bound hosted Paybond session from a service-account API key. */
|
|
993
|
+
static open(init: PaybondOpenOptions): Promise<Paybond>;
|
|
994
|
+
/** Reserved for future HTTP client cleanup; safe to call after work completes. */
|
|
1001
995
|
aclose(): Promise<void>;
|
|
1002
996
|
}
|
|
1003
997
|
export { normalizeJson, jsonValueDigest } from "./json-digest.js";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Paybond Kit — TypeScript
|
|
3
|
-
* and
|
|
2
|
+
* Paybond Kit — TypeScript Gateway client with tenant binding, retries, capability verification,
|
|
3
|
+
* and signed intent/evidence helpers.
|
|
4
4
|
*/
|
|
5
5
|
import { buildSignedCreateIntentBody, } from "./principal-intent.js";
|
|
6
6
|
import { signPayeeEvidenceBinding } from "./payee-evidence.js";
|
|
7
|
+
export const DEFAULT_PAYBOND_GATEWAY_BASE_URL = "https://api.paybond.ai";
|
|
8
|
+
function defaultGatewayBaseUrl(value) {
|
|
9
|
+
const trimmed = value?.trim();
|
|
10
|
+
return trimmed ? trimmed : DEFAULT_PAYBOND_GATEWAY_BASE_URL;
|
|
11
|
+
}
|
|
7
12
|
/**
|
|
8
13
|
* Structured HTTP failure from Harbor with operator-facing diagnostics.
|
|
9
14
|
*/
|
|
@@ -20,7 +25,7 @@ export class HarborHttpError extends Error {
|
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
|
-
* Gateway rejected
|
|
28
|
+
* Gateway rejected service-account credentials or returned an unusable tenant-principal payload.
|
|
24
29
|
*/
|
|
25
30
|
export class GatewayAuthError extends Error {
|
|
26
31
|
statusCode;
|
|
@@ -117,103 +122,26 @@ function parseRetryAfterSeconds(v) {
|
|
|
117
122
|
return null;
|
|
118
123
|
return Math.min(n, 30);
|
|
119
124
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
gatewayBase;
|
|
127
|
-
apiKey;
|
|
128
|
-
path;
|
|
129
|
-
skewMs;
|
|
130
|
-
clock;
|
|
131
|
-
token = null;
|
|
132
|
-
tenantIdValue = null;
|
|
133
|
-
notAfterMonotonic = 0;
|
|
134
|
-
refreshTail = Promise.resolve();
|
|
135
|
-
constructor(init) {
|
|
136
|
-
this.gatewayBase = normalizeBase(init.gatewayBaseUrl);
|
|
137
|
-
this.apiKey = init.apiKey.trim();
|
|
138
|
-
const rawPath = (init.harborAccessPath ?? DEFAULT_HARBOR_ACCESS_PATH).trim();
|
|
139
|
-
this.path = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
140
|
-
this.skewMs = Math.max(0, (init.clockSkewSeconds ?? 90) * 1000);
|
|
141
|
-
this.clock = init.clock ?? (() => performance.now());
|
|
142
|
-
}
|
|
143
|
-
get tenantId() {
|
|
144
|
-
return this.tenantIdValue;
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* First exchange; returns tenant realm echoed by the gateway.
|
|
148
|
-
*/
|
|
149
|
-
async ensureInitial() {
|
|
150
|
-
await this.refresh(true);
|
|
151
|
-
if (!this.tenantIdValue) {
|
|
152
|
-
throw new GatewayAuthError("harbor-access response missing tenant_id; upgrade gateway (PAYBOND-V1-008)");
|
|
153
|
-
}
|
|
154
|
-
return this.tenantIdValue;
|
|
155
|
-
}
|
|
156
|
-
/** Return a valid Harbor JWT, refreshing when near expiry. */
|
|
157
|
-
async bearer() {
|
|
158
|
-
await this.refresh(false);
|
|
159
|
-
if (!this.token) {
|
|
160
|
-
throw new GatewayAuthError("harbor-access did not return access_token");
|
|
161
|
-
}
|
|
162
|
-
return this.token;
|
|
163
|
-
}
|
|
164
|
-
/** Force rotation (credential rotation drills). */
|
|
165
|
-
async forceRotate() {
|
|
166
|
-
await this.refresh(true);
|
|
167
|
-
}
|
|
168
|
-
async refresh(force) {
|
|
169
|
-
const job = this.refreshTail.then(() => this.refreshInner(force));
|
|
170
|
-
this.refreshTail = job.then(() => undefined, () => undefined);
|
|
171
|
-
await job;
|
|
125
|
+
function normalizeExpectedEnvironment(value) {
|
|
126
|
+
if (value === undefined)
|
|
127
|
+
return null;
|
|
128
|
+
const env = String(value).trim();
|
|
129
|
+
if (env === "live" || env === "sandbox") {
|
|
130
|
+
return env;
|
|
172
131
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
132
|
+
throw new GatewayAuthError(`expectedEnvironment must be "live" or "sandbox", got ${JSON.stringify(value)}`);
|
|
133
|
+
}
|
|
134
|
+
function assertExpectedEnvironment(source, actualRaw, expected, bodyText) {
|
|
135
|
+
if (!expected)
|
|
136
|
+
return;
|
|
137
|
+
const actual = typeof actualRaw === "string" ? actualRaw.trim() : "";
|
|
138
|
+
if (!actual) {
|
|
139
|
+
throw new GatewayAuthError(`${source} response missing environment`, { bodyText });
|
|
140
|
+
}
|
|
141
|
+
if (actual !== expected) {
|
|
142
|
+
throw new GatewayAuthError(`${source} environment mismatch: expected=${expected} gateway=${actual}`, {
|
|
143
|
+
bodyText,
|
|
185
144
|
});
|
|
186
|
-
const text = await res.text();
|
|
187
|
-
if (!res.ok) {
|
|
188
|
-
throw new GatewayAuthError(`harbor-access HTTP ${res.status}`, {
|
|
189
|
-
statusCode: res.status,
|
|
190
|
-
bodyText: text,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
let body;
|
|
194
|
-
try {
|
|
195
|
-
body = JSON.parse(text);
|
|
196
|
-
}
|
|
197
|
-
catch {
|
|
198
|
-
throw new GatewayAuthError("harbor-access response was not JSON", { bodyText: text });
|
|
199
|
-
}
|
|
200
|
-
const access = String(body.access_token ?? "").trim();
|
|
201
|
-
if (!access) {
|
|
202
|
-
throw new GatewayAuthError("harbor-access JSON missing access_token", { bodyText: text });
|
|
203
|
-
}
|
|
204
|
-
const expIn = Number(body.expires_in ?? 0);
|
|
205
|
-
if (!Number.isFinite(expIn) || expIn <= 0) {
|
|
206
|
-
throw new GatewayAuthError("harbor-access JSON missing expires_in", { bodyText: text });
|
|
207
|
-
}
|
|
208
|
-
const tidRaw = body.tenant_id;
|
|
209
|
-
if (typeof tidRaw === "string" && tidRaw.trim()) {
|
|
210
|
-
this.tenantIdValue = tidRaw.trim();
|
|
211
|
-
}
|
|
212
|
-
if (!this.tenantIdValue) {
|
|
213
|
-
throw new GatewayAuthError("harbor-access response missing tenant_id; upgrade gateway (PAYBOND-V1-008)", { bodyText: text });
|
|
214
|
-
}
|
|
215
|
-
this.token = access;
|
|
216
|
-
this.notAfterMonotonic = now + Math.max(1000, expIn * 1000 - this.skewMs);
|
|
217
145
|
}
|
|
218
146
|
}
|
|
219
147
|
/**
|
|
@@ -229,41 +157,6 @@ export class PaybondCapabilityBinding {
|
|
|
229
157
|
this.capabilityToken = capabilityToken;
|
|
230
158
|
}
|
|
231
159
|
}
|
|
232
|
-
/**
|
|
233
|
-
* Harbor client plus gateway token lifecycle for one service account.
|
|
234
|
-
*/
|
|
235
|
-
export class ServiceAccountHarborSession {
|
|
236
|
-
harbor;
|
|
237
|
-
tokens;
|
|
238
|
-
constructor(harbor, tokens) {
|
|
239
|
-
this.harbor = harbor;
|
|
240
|
-
this.tokens = tokens;
|
|
241
|
-
}
|
|
242
|
-
/**
|
|
243
|
-
* Build a tenant-bound {@link HarborClient} using gateway-derived tenant id and JWT supplier.
|
|
244
|
-
*/
|
|
245
|
-
static async open(init) {
|
|
246
|
-
const tokens = new GatewayHarborTokenProvider({
|
|
247
|
-
gatewayBaseUrl: init.gatewayBaseUrl,
|
|
248
|
-
apiKey: init.apiKey,
|
|
249
|
-
harborAccessPath: init.harborAccessPath,
|
|
250
|
-
clockSkewSeconds: init.clockSkewSeconds,
|
|
251
|
-
});
|
|
252
|
-
const tenant = await tokens.ensureInitial();
|
|
253
|
-
const harbor = new HarborClient(init.harborBaseUrl, tenant, {
|
|
254
|
-
harborBearerSupplier: () => tokens.bearer(),
|
|
255
|
-
maxRetries: init.maxRetries ?? 3,
|
|
256
|
-
});
|
|
257
|
-
return new ServiceAccountHarborSession(harbor, tokens);
|
|
258
|
-
}
|
|
259
|
-
async rotateHarborToken() {
|
|
260
|
-
await this.tokens.forceRotate();
|
|
261
|
-
}
|
|
262
|
-
/** Reserved for future HTTP client cleanup; safe to call after work completes. */
|
|
263
|
-
async aclose() {
|
|
264
|
-
await Promise.resolve();
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
160
|
/**
|
|
268
161
|
* HTTP client for Harbor: capability verify, intents, evidence, and tenant-scoped ledger reads
|
|
269
162
|
* (`GET /ledger/v1/*`, PAYBOND-007).
|
|
@@ -613,8 +506,8 @@ export class HarborClient {
|
|
|
613
506
|
return body;
|
|
614
507
|
}
|
|
615
508
|
/**
|
|
616
|
-
* `GET /ledger/v1/events`
|
|
617
|
-
* `limit` is clamped to 1
|
|
509
|
+
* `GET /ledger/v1/events` - protected Harbor append-only history for trusted clients.
|
|
510
|
+
* `afterSeq` is an exclusive cursor; `limit` is clamped to 1..256 to match Harbor.
|
|
618
511
|
*/
|
|
619
512
|
async getLedgerEvents(options) {
|
|
620
513
|
const afterSeq = Math.max(0, Math.floor(options?.afterSeq ?? 0));
|
|
@@ -657,6 +550,152 @@ export class HarborClient {
|
|
|
657
550
|
return body;
|
|
658
551
|
}
|
|
659
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Gateway-backed Harbor surface for hosted Paybond integrations.
|
|
555
|
+
*
|
|
556
|
+
* This client sends the service-account API key to the public Gateway. Gateway derives tenant,
|
|
557
|
+
* mints upstream Harbor credentials internally, and applies recognition/guardrail checks before
|
|
558
|
+
* forwarding state-changing Harbor requests.
|
|
559
|
+
*/
|
|
560
|
+
export class GatewayHarborClient {
|
|
561
|
+
base;
|
|
562
|
+
tenantId;
|
|
563
|
+
staticGatewayBearerToken;
|
|
564
|
+
maxRetries;
|
|
565
|
+
constructor(gatewayBaseUrl, tenantId, options) {
|
|
566
|
+
this.base = `${normalizeBase(gatewayBaseUrl)}/`;
|
|
567
|
+
this.tenantId = tenantId.trim();
|
|
568
|
+
this.staticGatewayBearerToken = options.staticGatewayBearerToken.trim();
|
|
569
|
+
this.maxRetries = Math.max(1, options.maxRetries ?? 3);
|
|
570
|
+
}
|
|
571
|
+
headers(extra) {
|
|
572
|
+
const headers = new Headers(extra);
|
|
573
|
+
headers.set("accept", "application/json");
|
|
574
|
+
headers.set("x-tenant-id", this.tenantId);
|
|
575
|
+
headers.set("authorization", `Bearer ${this.staticGatewayBearerToken}`);
|
|
576
|
+
return headers;
|
|
577
|
+
}
|
|
578
|
+
async fetchWithRetries(url, init) {
|
|
579
|
+
let lastErr;
|
|
580
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
581
|
+
let res;
|
|
582
|
+
try {
|
|
583
|
+
res = await fetch(url, init);
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
lastErr = e;
|
|
587
|
+
if (attempt + 1 >= this.maxRetries)
|
|
588
|
+
throw e;
|
|
589
|
+
await new Promise((r) => setTimeout(r, backoffMs(attempt)));
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if ([429, 500, 502, 503, 504].includes(res.status) && attempt + 1 < this.maxRetries) {
|
|
593
|
+
const raSec = parseRetryAfterSeconds(res.headers.get("retry-after"));
|
|
594
|
+
const delayMs = raSec != null ? raSec * 1000 : backoffMs(attempt);
|
|
595
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
return res;
|
|
599
|
+
}
|
|
600
|
+
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
601
|
+
}
|
|
602
|
+
async postJSON(path, payload, extraHeaders) {
|
|
603
|
+
const url = `${this.base}${path.replace(/^\/+/, "")}`;
|
|
604
|
+
const res = await this.fetchWithRetries(url, {
|
|
605
|
+
method: "POST",
|
|
606
|
+
headers: this.headers({
|
|
607
|
+
"content-type": "application/json",
|
|
608
|
+
...(extraHeaders ?? {}),
|
|
609
|
+
}),
|
|
610
|
+
body: JSON.stringify(payload),
|
|
611
|
+
});
|
|
612
|
+
const text = await res.text();
|
|
613
|
+
return { res, text, url };
|
|
614
|
+
}
|
|
615
|
+
mutationHeaders(operation, options, headers) {
|
|
616
|
+
const proof = options?.recognitionProof;
|
|
617
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)) {
|
|
618
|
+
throw new Error(`${operation} requires recognitionProof`);
|
|
619
|
+
}
|
|
620
|
+
return gatewayMutationHeaders(proof, {
|
|
621
|
+
...(headers ?? {}),
|
|
622
|
+
...(options?.idempotencyKey?.trim() ? { "idempotency-key": options.idempotencyKey.trim() } : {}),
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
async verifyCapability(input) {
|
|
626
|
+
const payload = {
|
|
627
|
+
intent_id: input.intentId,
|
|
628
|
+
token: input.token,
|
|
629
|
+
operation: input.operation,
|
|
630
|
+
requested_spend_cents: input.requestedSpendCents ?? 0,
|
|
631
|
+
};
|
|
632
|
+
const { res, text, url } = await this.postJSON("/verify", payload);
|
|
633
|
+
if (!res.ok) {
|
|
634
|
+
throw new HarborHttpError(`Gateway verify HTTP ${res.status}: ${text}`, {
|
|
635
|
+
statusCode: res.status,
|
|
636
|
+
url,
|
|
637
|
+
bodyText: text,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
const body = JSON.parse(text);
|
|
641
|
+
if (body.tenant !== this.tenantId) {
|
|
642
|
+
throw new Error(`verify tenant mismatch: client=${this.tenantId} gateway=${body.tenant}`);
|
|
643
|
+
}
|
|
644
|
+
if (body.intent_id !== input.intentId) {
|
|
645
|
+
throw new Error(`verify intent mismatch: requested=${input.intentId} gateway=${body.intent_id}`);
|
|
646
|
+
}
|
|
647
|
+
return {
|
|
648
|
+
allow: body.allow,
|
|
649
|
+
auditId: body.audit_id,
|
|
650
|
+
tenant: body.tenant,
|
|
651
|
+
intentId: body.intent_id,
|
|
652
|
+
code: body.code,
|
|
653
|
+
message: body.message,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
async createIntent(body, options) {
|
|
657
|
+
const { res, text, url } = await this.postJSON("/harbor/intents", body, this.mutationHeaders("createIntent", options));
|
|
658
|
+
if (!res.ok) {
|
|
659
|
+
throw new HarborHttpError(`Gateway Harbor create intent HTTP ${res.status}: ${text}`, {
|
|
660
|
+
statusCode: res.status,
|
|
661
|
+
url,
|
|
662
|
+
bodyText: text,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
return JSON.parse(text);
|
|
666
|
+
}
|
|
667
|
+
async fundIntent(intentId, options) {
|
|
668
|
+
const { res, text, url } = await this.postJSON(`/harbor/intents/${encodeURIComponent(intentId)}/fund`, {}, this.mutationHeaders("fundIntent", options, {
|
|
669
|
+
...(options.paymentSignature?.trim() ? { "payment-signature": options.paymentSignature.trim() } : {}),
|
|
670
|
+
}));
|
|
671
|
+
if (![200, 202, 402].includes(res.status)) {
|
|
672
|
+
throw new HarborHttpError(`Gateway Harbor fund intent HTTP ${res.status}: ${text}`, {
|
|
673
|
+
statusCode: res.status,
|
|
674
|
+
url,
|
|
675
|
+
bodyText: text,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
return parseFundIntentResponse(assertJSONObject(JSON.parse(text)), {
|
|
679
|
+
tenantId: this.tenantId,
|
|
680
|
+
intentId,
|
|
681
|
+
statusCode: res.status,
|
|
682
|
+
paymentRequired: res.headers.get("payment-required") ?? undefined,
|
|
683
|
+
paymentResponse: res.headers.get("payment-response") ?? undefined,
|
|
684
|
+
source: "gateway",
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
async submitEvidence(intentId, evidenceBody, options) {
|
|
688
|
+
const { res, text, url } = await this.postJSON(`/harbor/intents/${encodeURIComponent(intentId)}/evidence`, evidenceBody, this.mutationHeaders("submitEvidence", options));
|
|
689
|
+
if (!res.ok) {
|
|
690
|
+
throw new HarborHttpError(`Gateway Harbor evidence HTTP ${res.status}: ${text}`, {
|
|
691
|
+
statusCode: res.status,
|
|
692
|
+
url,
|
|
693
|
+
bodyText: text,
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
return parseSubmitEvidenceResponse(JSON.parse(text));
|
|
697
|
+
}
|
|
698
|
+
}
|
|
660
699
|
const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
|
|
661
700
|
const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "x402_usdc_base"]);
|
|
662
701
|
const FRAUD_REVIEW_EVENT_TYPES = new Set([
|
|
@@ -687,6 +726,53 @@ function readStringArrayValue(value, field) {
|
|
|
687
726
|
}
|
|
688
727
|
return [...value];
|
|
689
728
|
}
|
|
729
|
+
function parseFundIntentResponse(body, init) {
|
|
730
|
+
const tenant = String(body.tenant ?? "");
|
|
731
|
+
if (tenant !== init.tenantId) {
|
|
732
|
+
throw new Error(`fund tenant mismatch: client=${init.tenantId} ${init.source}=${tenant}`);
|
|
733
|
+
}
|
|
734
|
+
const echoedIntentId = String(body.intent_id ?? "");
|
|
735
|
+
if (echoedIntentId !== init.intentId) {
|
|
736
|
+
throw new Error(`fund intent mismatch: requested=${init.intentId} ${init.source}=${echoedIntentId}`);
|
|
737
|
+
}
|
|
738
|
+
if (typeof body.state !== "string" || !body.state.trim()) {
|
|
739
|
+
throw new Error("fund response missing state");
|
|
740
|
+
}
|
|
741
|
+
if (typeof body.currency !== "string" || !body.currency.trim()) {
|
|
742
|
+
throw new Error("fund response missing currency");
|
|
743
|
+
}
|
|
744
|
+
const amountCents = Number(body.amount_cents);
|
|
745
|
+
if (!Number.isFinite(amountCents)) {
|
|
746
|
+
throw new Error("fund response missing amount_cents");
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
statusCode: init.statusCode,
|
|
750
|
+
paymentRequired: init.paymentRequired,
|
|
751
|
+
paymentResponse: init.paymentResponse,
|
|
752
|
+
intentId: echoedIntentId,
|
|
753
|
+
tenant,
|
|
754
|
+
state: body.state,
|
|
755
|
+
settlementRail: readSettlementRailValue(body.settlement_rail, "fund settlement_rail"),
|
|
756
|
+
currency: body.currency,
|
|
757
|
+
amountCents,
|
|
758
|
+
funded: Boolean(body.funded),
|
|
759
|
+
capabilityToken: typeof body.capability_token === "string" && body.capability_token.trim()
|
|
760
|
+
? body.capability_token
|
|
761
|
+
: undefined,
|
|
762
|
+
funding: body.funding === undefined || body.funding === null
|
|
763
|
+
? undefined
|
|
764
|
+
: parseIntentFundingResult(body.funding),
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
function parseSubmitEvidenceResponse(value) {
|
|
768
|
+
const body = assertJSONObject(value);
|
|
769
|
+
return {
|
|
770
|
+
intentId: String(body.intent_id ?? ""),
|
|
771
|
+
tenant: String(body.tenant ?? ""),
|
|
772
|
+
state: String(body.state ?? ""),
|
|
773
|
+
predicatePassed: typeof body.predicate_passed === "boolean" ? body.predicate_passed : undefined,
|
|
774
|
+
};
|
|
775
|
+
}
|
|
690
776
|
function parseIntentFundingResult(value) {
|
|
691
777
|
const body = assertJSONObject(value);
|
|
692
778
|
const onchainRaw = body.onchain_transaction_hashes;
|
|
@@ -1441,10 +1527,11 @@ function gatewayMutationHeaders(recognitionProof, headers) {
|
|
|
1441
1527
|
function encodeRecognitionProofHeader(proof) {
|
|
1442
1528
|
return Buffer.from(JSON.stringify(proof), "utf8").toString("base64url");
|
|
1443
1529
|
}
|
|
1444
|
-
async function resolveGatewayTenantId(gatewayBaseUrl, apiKey, principalPath, maxRetries) {
|
|
1530
|
+
async function resolveGatewayTenantId(gatewayBaseUrl, apiKey, principalPath, maxRetries, expectedEnvironment) {
|
|
1445
1531
|
const base = normalizeBase(gatewayBaseUrl);
|
|
1446
1532
|
const path = principalPath.startsWith("/") ? principalPath : `/${principalPath}`;
|
|
1447
1533
|
const url = `${base}${path}`;
|
|
1534
|
+
const expected = normalizeExpectedEnvironment(expectedEnvironment);
|
|
1448
1535
|
let lastErr;
|
|
1449
1536
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
1450
1537
|
let res;
|
|
@@ -1485,6 +1572,7 @@ async function resolveGatewayTenantId(gatewayBaseUrl, apiKey, principalPath, max
|
|
|
1485
1572
|
bodyText: text,
|
|
1486
1573
|
});
|
|
1487
1574
|
}
|
|
1575
|
+
assertExpectedEnvironment("gateway principal", body.environment, expected, text);
|
|
1488
1576
|
return tenant;
|
|
1489
1577
|
}
|
|
1490
1578
|
throw lastErr instanceof Error ? lastErr : new Error(String(lastErr));
|
|
@@ -1498,8 +1586,9 @@ export class ServiceAccountSignalSession {
|
|
|
1498
1586
|
this.signal = signal;
|
|
1499
1587
|
}
|
|
1500
1588
|
static async open(init) {
|
|
1501
|
-
const
|
|
1502
|
-
|
|
1589
|
+
const gatewayBaseUrl = defaultGatewayBaseUrl(init.gatewayBaseUrl);
|
|
1590
|
+
const tenantId = await resolveGatewayTenantId(gatewayBaseUrl, init.apiKey, init.principalPath ?? DEFAULT_PRINCIPAL_PATH, Math.max(1, init.maxRetries ?? 3), init.expectedEnvironment);
|
|
1591
|
+
return new ServiceAccountSignalSession(new GatewaySignalClient(gatewayBaseUrl, tenantId, {
|
|
1503
1592
|
staticGatewayBearerToken: init.apiKey,
|
|
1504
1593
|
maxRetries: init.maxRetries ?? 3,
|
|
1505
1594
|
}));
|
|
@@ -1517,8 +1606,9 @@ export class ServiceAccountFraudSession {
|
|
|
1517
1606
|
this.fraud = fraud;
|
|
1518
1607
|
}
|
|
1519
1608
|
static async open(init) {
|
|
1520
|
-
const
|
|
1521
|
-
|
|
1609
|
+
const gatewayBaseUrl = defaultGatewayBaseUrl(init.gatewayBaseUrl);
|
|
1610
|
+
const tenantId = await resolveGatewayTenantId(gatewayBaseUrl, init.apiKey, init.principalPath ?? DEFAULT_PRINCIPAL_PATH, Math.max(1, init.maxRetries ?? 3), init.expectedEnvironment);
|
|
1611
|
+
return new ServiceAccountFraudSession(new GatewayFraudClient(gatewayBaseUrl, tenantId, {
|
|
1522
1612
|
staticGatewayBearerToken: init.apiKey,
|
|
1523
1613
|
maxRetries: init.maxRetries ?? 3,
|
|
1524
1614
|
}));
|
|
@@ -1527,6 +1617,9 @@ export class ServiceAccountFraudSession {
|
|
|
1527
1617
|
await Promise.resolve();
|
|
1528
1618
|
}
|
|
1529
1619
|
}
|
|
1620
|
+
function nowRfc3339Seconds() {
|
|
1621
|
+
return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1622
|
+
}
|
|
1530
1623
|
/**
|
|
1531
1624
|
* Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
|
|
1532
1625
|
*/
|
|
@@ -1540,14 +1633,14 @@ export class PaybondIntents {
|
|
|
1540
1633
|
* 32 bytes. `settlementRail` is signed as the requested rail; destinations stay server-owned.
|
|
1541
1634
|
*/
|
|
1542
1635
|
async create(params) {
|
|
1543
|
-
const { idempotencyKey, intentId: maybeIntentId, ...fields } = params;
|
|
1636
|
+
const { idempotencyKey, intentId: maybeIntentId, recognitionProof, ...fields } = params;
|
|
1544
1637
|
const intentId = maybeIntentId ?? globalThis.crypto.randomUUID();
|
|
1545
1638
|
const body = buildSignedCreateIntentBody({
|
|
1546
1639
|
tenantId: this.harbor.tenantId,
|
|
1547
1640
|
intentId,
|
|
1548
1641
|
...fields,
|
|
1549
1642
|
});
|
|
1550
|
-
return this.harbor.createIntent(body, { idempotencyKey });
|
|
1643
|
+
return this.harbor.createIntent(body, { idempotencyKey, recognitionProof });
|
|
1551
1644
|
}
|
|
1552
1645
|
/**
|
|
1553
1646
|
* Advance Harbor `/intents/{id}/fund` for x402 / USDC-on-Base intents.
|
|
@@ -1556,22 +1649,25 @@ export class PaybondIntents {
|
|
|
1556
1649
|
return this.harbor.fundIntent(params.intentId, {
|
|
1557
1650
|
paymentSignature: params.paymentSignature,
|
|
1558
1651
|
idempotencyKey: params.idempotencyKey,
|
|
1652
|
+
recognitionProof: params.recognitionProof,
|
|
1559
1653
|
});
|
|
1560
1654
|
}
|
|
1561
1655
|
/**
|
|
1562
1656
|
* Sign payee evidence and POST it. `payeeSigningSeed` must be 32 bytes.
|
|
1563
1657
|
*/
|
|
1564
1658
|
async submitEvidence(params) {
|
|
1565
|
-
const { idempotencyKey, ...rest } = params;
|
|
1659
|
+
const { idempotencyKey, artifactsBlake3Hex = [], submittedAtRfc3339 = nowRfc3339Seconds(), recognitionProof, ...rest } = params;
|
|
1566
1660
|
const wire = signPayeeEvidenceBinding({
|
|
1567
1661
|
tenantId: this.harbor.tenantId,
|
|
1662
|
+
artifactsBlake3Hex,
|
|
1663
|
+
submittedAtRfc3339,
|
|
1568
1664
|
...rest,
|
|
1569
1665
|
});
|
|
1570
|
-
return this.harbor.submitEvidence(rest.intentId, wire, { idempotencyKey });
|
|
1666
|
+
return this.harbor.submitEvidence(rest.intentId, wire, { idempotencyKey, recognitionProof });
|
|
1571
1667
|
}
|
|
1572
1668
|
}
|
|
1573
1669
|
/**
|
|
1574
|
-
* High-level Kit entrypoint:
|
|
1670
|
+
* High-level Kit entrypoint: tenant-bound Gateway clients plus ergonomic intent helpers.
|
|
1575
1671
|
*/
|
|
1576
1672
|
export class Paybond {
|
|
1577
1673
|
harbor;
|
|
@@ -1580,43 +1676,44 @@ export class Paybond {
|
|
|
1580
1676
|
a2a;
|
|
1581
1677
|
protocol;
|
|
1582
1678
|
intents;
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
this.session = session;
|
|
1586
|
-
this.harbor = session.harbor;
|
|
1679
|
+
constructor(harbor, signal, fraud, a2a, protocol) {
|
|
1680
|
+
this.harbor = harbor;
|
|
1587
1681
|
this.signal = signal;
|
|
1588
1682
|
this.fraud = fraud;
|
|
1589
1683
|
this.a2a = a2a;
|
|
1590
1684
|
this.protocol = protocol;
|
|
1591
|
-
this.intents = new PaybondIntents(
|
|
1685
|
+
this.intents = new PaybondIntents(harbor);
|
|
1592
1686
|
}
|
|
1593
|
-
/** Open a tenant-bound session
|
|
1687
|
+
/** Open a tenant-bound hosted Paybond session from a service-account API key. */
|
|
1594
1688
|
static async open(init) {
|
|
1595
|
-
const
|
|
1596
|
-
const
|
|
1689
|
+
const gatewayBaseUrl = defaultGatewayBaseUrl(init.gatewayBaseUrl);
|
|
1690
|
+
const maxRetries = Math.max(1, init.maxRetries ?? 3);
|
|
1691
|
+
const tenantId = await resolveGatewayTenantId(gatewayBaseUrl, init.apiKey, init.principalPath ?? DEFAULT_PRINCIPAL_PATH, maxRetries, init.expectedEnvironment);
|
|
1692
|
+
const harbor = new GatewayHarborClient(gatewayBaseUrl, tenantId, {
|
|
1597
1693
|
staticGatewayBearerToken: init.apiKey,
|
|
1598
|
-
maxRetries
|
|
1694
|
+
maxRetries,
|
|
1599
1695
|
});
|
|
1600
|
-
const
|
|
1696
|
+
const signal = new GatewaySignalClient(gatewayBaseUrl, tenantId, {
|
|
1601
1697
|
staticGatewayBearerToken: init.apiKey,
|
|
1602
|
-
maxRetries
|
|
1698
|
+
maxRetries,
|
|
1603
1699
|
});
|
|
1604
|
-
const
|
|
1700
|
+
const fraud = new GatewayFraudClient(gatewayBaseUrl, tenantId, {
|
|
1605
1701
|
staticGatewayBearerToken: init.apiKey,
|
|
1606
|
-
maxRetries
|
|
1702
|
+
maxRetries,
|
|
1607
1703
|
});
|
|
1608
|
-
const
|
|
1704
|
+
const a2a = new GatewayA2AClient(gatewayBaseUrl, {
|
|
1609
1705
|
staticGatewayBearerToken: init.apiKey,
|
|
1610
|
-
maxRetries
|
|
1706
|
+
maxRetries,
|
|
1611
1707
|
});
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1708
|
+
const protocol = new GatewayProtocolClient(gatewayBaseUrl, tenantId, {
|
|
1709
|
+
staticGatewayBearerToken: init.apiKey,
|
|
1710
|
+
maxRetries,
|
|
1711
|
+
});
|
|
1712
|
+
return new Paybond(harbor, signal, fraud, a2a, protocol);
|
|
1616
1713
|
}
|
|
1617
|
-
/**
|
|
1714
|
+
/** Reserved for future HTTP client cleanup; safe to call after work completes. */
|
|
1618
1715
|
async aclose() {
|
|
1619
|
-
await
|
|
1716
|
+
await Promise.resolve();
|
|
1620
1717
|
}
|
|
1621
1718
|
}
|
|
1622
1719
|
export { normalizeJson, jsonValueDigest } from "./json-digest.js";
|
package/dist/mcp-server.d.ts
CHANGED
|
@@ -24,13 +24,10 @@ type MCPCallToolResult = {
|
|
|
24
24
|
isError?: boolean;
|
|
25
25
|
};
|
|
26
26
|
export type PaybondMCPSettings = {
|
|
27
|
-
gatewayBaseUrl: string;
|
|
28
27
|
apiKey: string;
|
|
29
|
-
|
|
30
|
-
harborAccessPath?: string;
|
|
28
|
+
gatewayBaseUrl?: string;
|
|
31
29
|
principalPath?: string;
|
|
32
30
|
maxRetries?: number;
|
|
33
|
-
clockSkewSeconds?: number;
|
|
34
31
|
};
|
|
35
32
|
export declare class PaybondMCPServer {
|
|
36
33
|
private readonly runtime;
|
package/dist/mcp-server.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { GatewayAuthError, GatewayFraudClient, GatewaySignalClient,
|
|
2
|
+
import { GatewayAuthError, GatewayFraudClient, GatewaySignalClient, SignalHttpError, DEFAULT_PAYBOND_GATEWAY_BASE_URL, } from "./index.js";
|
|
3
3
|
const SERVER_NAME = "Paybond MCP";
|
|
4
4
|
const SERVER_VERSION = "0.6.0";
|
|
5
5
|
const MCP_PROTOCOL_VERSION = "2025-11-25";
|
|
6
|
-
const DEFAULT_HARBOR_ACCESS_PATH = "/v1/auth/harbor-access";
|
|
7
6
|
const DEFAULT_PRINCIPAL_PATH = "/v1/auth/principal";
|
|
8
7
|
const DEFAULT_RECOGNITION_VERIFIER_ID = "paybond-gateway";
|
|
9
8
|
const agentRecognitionProofHeader = "x-paybond-agent-recognition-proof";
|
|
@@ -115,16 +114,12 @@ class PaybondMCPRuntime {
|
|
|
115
114
|
principalValue = null;
|
|
116
115
|
signalValue = null;
|
|
117
116
|
fraudValue = null;
|
|
118
|
-
harborValue = null;
|
|
119
117
|
constructor(settings) {
|
|
120
118
|
this.settings = {
|
|
121
|
-
gatewayBaseUrl: settings.gatewayBaseUrl,
|
|
119
|
+
gatewayBaseUrl: settings.gatewayBaseUrl ?? DEFAULT_PAYBOND_GATEWAY_BASE_URL,
|
|
122
120
|
apiKey: settings.apiKey,
|
|
123
|
-
harborBaseUrl: settings.harborBaseUrl,
|
|
124
|
-
harborAccessPath: settings.harborAccessPath ?? DEFAULT_HARBOR_ACCESS_PATH,
|
|
125
121
|
principalPath: settings.principalPath ?? DEFAULT_PRINCIPAL_PATH,
|
|
126
122
|
maxRetries: Math.max(1, settings.maxRetries ?? 3),
|
|
127
|
-
clockSkewSeconds: Math.max(0, settings.clockSkewSeconds ?? 90),
|
|
128
123
|
};
|
|
129
124
|
this.gateway = new GatewayAPIClient({
|
|
130
125
|
gatewayBaseUrl: this.settings.gatewayBaseUrl,
|
|
@@ -158,20 +153,6 @@ class PaybondMCPRuntime {
|
|
|
158
153
|
}))();
|
|
159
154
|
return this.fraudValue;
|
|
160
155
|
}
|
|
161
|
-
async harbor() {
|
|
162
|
-
if (!this.settings.harborBaseUrl) {
|
|
163
|
-
throw new Error("PAYBOND_HARBOR_URL is required for direct Harbor mutation tools");
|
|
164
|
-
}
|
|
165
|
-
this.harborValue ??= ServiceAccountHarborSession.open({
|
|
166
|
-
gatewayBaseUrl: this.settings.gatewayBaseUrl,
|
|
167
|
-
apiKey: this.settings.apiKey,
|
|
168
|
-
harborBaseUrl: this.settings.harborBaseUrl,
|
|
169
|
-
harborAccessPath: this.settings.harborAccessPath,
|
|
170
|
-
clockSkewSeconds: this.settings.clockSkewSeconds,
|
|
171
|
-
maxRetries: this.settings.maxRetries,
|
|
172
|
-
});
|
|
173
|
-
return this.harborValue;
|
|
174
|
-
}
|
|
175
156
|
async listIntents(init) {
|
|
176
157
|
const params = new URLSearchParams({
|
|
177
158
|
limit: String(Math.max(1, Math.min(intArg(init.limit ?? 20, "limit"), 200))),
|
|
@@ -319,9 +300,6 @@ export class PaybondMCPServer {
|
|
|
319
300
|
tools;
|
|
320
301
|
initialized = false;
|
|
321
302
|
constructor(settings) {
|
|
322
|
-
if (!settings.gatewayBaseUrl.trim()) {
|
|
323
|
-
throw new Error("PAYBOND_GATEWAY_URL is required");
|
|
324
|
-
}
|
|
325
303
|
if (!settings.apiKey.trim()) {
|
|
326
304
|
throw new Error("PAYBOND_API_KEY is required");
|
|
327
305
|
}
|
|
@@ -701,68 +679,27 @@ export class PaybondMCPServer {
|
|
|
701
679
|
}),
|
|
702
680
|
},
|
|
703
681
|
];
|
|
704
|
-
if (settings.harborBaseUrl?.trim()) {
|
|
705
|
-
tools.push({
|
|
706
|
-
name: "paybond_create_intent_legacy",
|
|
707
|
-
description: "Legacy direct-Harbor fallback for POST /intents. Prefer paybond_create_intent unless you explicitly need PAYBOND_HARBOR_URL direct mode.",
|
|
708
|
-
inputSchema: objectSchema({
|
|
709
|
-
body: { type: "object", additionalProperties: true },
|
|
710
|
-
idempotency_key: { type: "string" },
|
|
711
|
-
}, ["body"]),
|
|
712
|
-
call: async (args) => (await this.runtime.harbor()).harbor.createIntent(ensureObject(args.body, "body"), { idempotencyKey: optionalString(args.idempotency_key) }),
|
|
713
|
-
}, {
|
|
714
|
-
name: "paybond_fund_intent_legacy",
|
|
715
|
-
description: "Legacy direct-Harbor fallback for POST /intents/{intent_id}/fund.",
|
|
716
|
-
inputSchema: objectSchema({
|
|
717
|
-
intent_id: { type: "string" },
|
|
718
|
-
payment_signature: { type: "string" },
|
|
719
|
-
idempotency_key: { type: "string" },
|
|
720
|
-
}, ["intent_id"]),
|
|
721
|
-
call: async (args) => jsonObjectFromValue(await (await this.runtime.harbor()).harbor.fundIntent(uuidArg(args.intent_id, "intent_id"), {
|
|
722
|
-
paymentSignature: optionalString(args.payment_signature),
|
|
723
|
-
idempotencyKey: optionalString(args.idempotency_key),
|
|
724
|
-
})),
|
|
725
|
-
}, {
|
|
726
|
-
name: "paybond_submit_evidence_legacy",
|
|
727
|
-
description: "Legacy direct-Harbor fallback for POST /intents/{intent_id}/evidence.",
|
|
728
|
-
inputSchema: objectSchema({
|
|
729
|
-
intent_id: { type: "string" },
|
|
730
|
-
body: { type: "object", additionalProperties: true },
|
|
731
|
-
idempotency_key: { type: "string" },
|
|
732
|
-
}, ["intent_id", "body"]),
|
|
733
|
-
call: async (args) => (await this.runtime.harbor()).harbor.submitEvidence(uuidArg(args.intent_id, "intent_id"), ensureObject(args.body, "body"), { idempotencyKey: optionalString(args.idempotency_key) }),
|
|
734
|
-
});
|
|
735
|
-
}
|
|
736
682
|
return tools;
|
|
737
683
|
}
|
|
738
684
|
}
|
|
739
685
|
export function settingsFromEnv(env = process.env) {
|
|
740
|
-
const gatewayBaseUrl = String(env.PAYBOND_GATEWAY_URL ?? "").trim();
|
|
741
686
|
const apiKey = String(env.PAYBOND_API_KEY ?? "").trim();
|
|
742
|
-
if (!gatewayBaseUrl) {
|
|
743
|
-
throw new Error("PAYBOND_GATEWAY_URL is required");
|
|
744
|
-
}
|
|
745
687
|
if (!apiKey) {
|
|
746
688
|
throw new Error("PAYBOND_API_KEY is required");
|
|
747
689
|
}
|
|
748
690
|
return {
|
|
749
|
-
gatewayBaseUrl,
|
|
691
|
+
gatewayBaseUrl: DEFAULT_PAYBOND_GATEWAY_BASE_URL,
|
|
750
692
|
apiKey,
|
|
751
|
-
harborBaseUrl: optionalEnv(env.PAYBOND_HARBOR_URL),
|
|
752
|
-
harborAccessPath: optionalEnv(env.PAYBOND_HARBOR_ACCESS_PATH) ?? DEFAULT_HARBOR_ACCESS_PATH,
|
|
753
693
|
principalPath: optionalEnv(env.PAYBOND_PRINCIPAL_PATH) ?? DEFAULT_PRINCIPAL_PATH,
|
|
754
694
|
maxRetries: optionalEnv(env.PAYBOND_MCP_MAX_RETRIES)
|
|
755
695
|
? intArg(optionalEnv(env.PAYBOND_MCP_MAX_RETRIES), "PAYBOND_MCP_MAX_RETRIES")
|
|
756
696
|
: 3,
|
|
757
|
-
clockSkewSeconds: optionalEnv(env.PAYBOND_MCP_CLOCK_SKEW_SECONDS)
|
|
758
|
-
? numberArg(optionalEnv(env.PAYBOND_MCP_CLOCK_SKEW_SECONDS), "PAYBOND_MCP_CLOCK_SKEW_SECONDS")
|
|
759
|
-
: 90,
|
|
760
697
|
};
|
|
761
698
|
}
|
|
762
699
|
export function main(argv = process.argv.slice(2)) {
|
|
763
700
|
if (argv.includes("--help")) {
|
|
764
701
|
process.stderr.write("Usage: paybond-mcp-server\n\n" +
|
|
765
|
-
"Runs the tenant-bound Paybond MCP server over stdio using
|
|
702
|
+
"Runs the tenant-bound Paybond MCP server over stdio using PAYBOND_API_KEY.\n");
|
|
766
703
|
return 0;
|
|
767
704
|
}
|
|
768
705
|
if (argv.length > 0) {
|
|
@@ -817,17 +754,6 @@ function intArg(value, field) {
|
|
|
817
754
|
}
|
|
818
755
|
return parsed;
|
|
819
756
|
}
|
|
820
|
-
function numberArg(value, field) {
|
|
821
|
-
const parsed = typeof value === "number"
|
|
822
|
-
? value
|
|
823
|
-
: typeof value === "string" && value.trim()
|
|
824
|
-
? Number(value)
|
|
825
|
-
: Number.NaN;
|
|
826
|
-
if (!Number.isFinite(parsed)) {
|
|
827
|
-
throw new Error(`${field} must be numeric`);
|
|
828
|
-
}
|
|
829
|
-
return parsed;
|
|
830
|
-
}
|
|
831
757
|
function uuidArg(value, field) {
|
|
832
758
|
const raw = stringArg(value, field);
|
|
833
759
|
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(raw)) {
|
|
@@ -909,7 +835,6 @@ function formatError(err) {
|
|
|
909
835
|
if (err instanceof Error ||
|
|
910
836
|
err instanceof GatewayAuthError ||
|
|
911
837
|
err instanceof GatewayHTTPError ||
|
|
912
|
-
err instanceof HarborHttpError ||
|
|
913
838
|
err instanceof SignalHttpError) {
|
|
914
839
|
return err.message;
|
|
915
840
|
}
|
package/dist/payee-evidence.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import { sign, getPublicKey } from "@noble/ed25519";
|
|
5
5
|
import { createHash } from "blake3";
|
|
6
6
|
import { parse as parseUuid } from "uuid";
|
|
7
|
+
import { ensureEd25519Sha512Sync } from "./ed25519-sync.js";
|
|
7
8
|
import { jsonValueDigest } from "./json-digest.js";
|
|
8
9
|
function concatBytes(...parts) {
|
|
9
10
|
const n = parts.reduce((a, p) => a + p.length, 0);
|
|
@@ -65,6 +66,7 @@ export function signPayeeEvidenceBinding(params) {
|
|
|
65
66
|
if (params.payeeSigningSeed.length !== 32) {
|
|
66
67
|
throw new Error("payeeSigningSeed must be 32 bytes");
|
|
67
68
|
}
|
|
69
|
+
ensureEd25519Sha512Sync();
|
|
68
70
|
const artifactBin = params.artifactsBlake3Hex.map((h) => hexToBytes(h));
|
|
69
71
|
const payloadDigest = jsonValueDigest(params.payload);
|
|
70
72
|
const artDigest = artifactsDigest(artifactBin);
|
package/dist/principal-intent.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { sign, getPublicKey } from "@noble/ed25519";
|
|
6
6
|
import { parse as parseUuid } from "uuid";
|
|
7
|
+
import { ensureEd25519Sha512Sync } from "./ed25519-sync.js";
|
|
7
8
|
import { jsonValueDigest } from "./json-digest.js";
|
|
8
9
|
const SETTLEMENT_RAIL_VALUES = new Set(["stripe_connect", "x402_usdc_base"]);
|
|
9
10
|
function validateSettlementRail(value) {
|
|
@@ -99,6 +100,7 @@ export function buildSignedCreateIntentBody(params) {
|
|
|
99
100
|
}
|
|
100
101
|
const settlementRail = validateSettlementRail(params.settlementRail);
|
|
101
102
|
const predicateRef = params.predicateRef ?? "";
|
|
103
|
+
ensureEd25519Sha512Sync();
|
|
102
104
|
const msg = intentCreationSignBytesRaw({
|
|
103
105
|
tenantId: params.tenantId,
|
|
104
106
|
intentId: params.intentId,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paybond/kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Paybond Kit for TypeScript:
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Paybond Kit for TypeScript: hosted Gateway sessions, capability verification, signed intent/evidence flows, and Stripe Connect or x402 / USDC-on-Base settlement.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|