@paybond/kit 0.6.1 → 0.7.1
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 -34
- package/dist/index.d.ts +60 -69
- package/dist/index.js +256 -164
- package/dist/mcp-server.d.ts +1 -4
- package/dist/mcp-server.js +4 -79
- 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,29 +17,18 @@ npm install @paybond/kit
|
|
|
17
17
|
## Requirements
|
|
18
18
|
|
|
19
19
|
- Node.js 22+
|
|
20
|
-
- A `
|
|
21
|
-
- Reachable Gateway and Harbor base URLs
|
|
22
|
-
- For capability verification: a funded intent id and a capability token minted for that intent
|
|
20
|
+
- A `paybond_sk_sandbox_...` or `paybond_sk_live_...` service-account API key
|
|
23
21
|
- For intent creation or evidence submission: 32-byte Ed25519 signing seeds owned by your application
|
|
24
22
|
|
|
25
23
|
Minimal environment for the quick start:
|
|
26
24
|
|
|
27
25
|
```bash
|
|
28
|
-
export
|
|
29
|
-
export PAYBOND_HARBOR_URL="https://harbor.example.com"
|
|
30
|
-
export PAYBOND_API_KEY="paybond_sk_..."
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Optional, if you want the quick start to verify a capability:
|
|
34
|
-
|
|
35
|
-
```bash
|
|
36
|
-
export PAYBOND_INTENT_ID="00000000-0000-0000-0000-000000000000"
|
|
37
|
-
export PAYBOND_CAPABILITY="base64-biscuit-token"
|
|
26
|
+
export PAYBOND_API_KEY="paybond_sk_sandbox_..."
|
|
38
27
|
```
|
|
39
28
|
|
|
40
29
|
## Tenant isolation
|
|
41
30
|
|
|
42
|
-
Every session is bound to the tenant realm echoed by gateway-authenticated service-account introspection
|
|
31
|
+
Every session is bound to the tenant realm echoed by gateway-authenticated service-account introspection.
|
|
43
32
|
|
|
44
33
|
- Do not pass tenant ids by hand for normal SDK usage.
|
|
45
34
|
- Construct one `Paybond` session per tenant/service account.
|
|
@@ -59,29 +48,12 @@ function requiredEnv(name: string): string {
|
|
|
59
48
|
}
|
|
60
49
|
|
|
61
50
|
const paybond = await Paybond.open({
|
|
62
|
-
gatewayBaseUrl: requiredEnv("PAYBOND_GATEWAY_URL"),
|
|
63
51
|
apiKey: requiredEnv("PAYBOND_API_KEY"),
|
|
64
|
-
|
|
52
|
+
expectedEnvironment: "sandbox",
|
|
65
53
|
});
|
|
66
54
|
|
|
67
55
|
try {
|
|
68
56
|
console.log("tenant realm:", paybond.harbor.tenantId);
|
|
69
|
-
|
|
70
|
-
const intentId = process.env.PAYBOND_INTENT_ID;
|
|
71
|
-
const capability = process.env.PAYBOND_CAPABILITY;
|
|
72
|
-
if (intentId && capability) {
|
|
73
|
-
const verified = await paybond.harbor.verifyCapability({
|
|
74
|
-
intentId,
|
|
75
|
-
token: capability,
|
|
76
|
-
operation: "payments.capture",
|
|
77
|
-
requestedSpendCents: 18_700,
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
if (!verified.allow) {
|
|
81
|
-
throw new Error(`verify denied: ${verified.code ?? "deny"} ${verified.message ?? ""}`);
|
|
82
|
-
}
|
|
83
|
-
console.log("capability verified:", verified.auditId);
|
|
84
|
-
}
|
|
85
57
|
} finally {
|
|
86
58
|
await paybond.aclose();
|
|
87
59
|
}
|
|
@@ -91,7 +63,7 @@ try {
|
|
|
91
63
|
|
|
92
64
|
Core SDK:
|
|
93
65
|
|
|
94
|
-
- `Paybond.open(...)` for
|
|
66
|
+
- `Paybond.open(...)` for API-key-only, tenant-derived hosted sessions
|
|
95
67
|
- `HarborClient` for capability verification, intent creation, x402 funding, evidence submission, and ledger reads
|
|
96
68
|
- `paybond.signal` and `paybond.fraud` on `Paybond` sessions opened from one service-account API key
|
|
97
69
|
- `PaybondIntents` helpers for principal-signed intent creation, x402 funding, and payee-signed evidence submission
|
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;
|
|
@@ -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,18 +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
947
|
export type PaybondSubmitEvidenceParams = Omit<SignPayeeEvidenceParams, "tenantId" | "artifactsBlake3Hex" | "submittedAtRfc3339"> & {
|
|
957
948
|
artifactsBlake3Hex?: string[];
|
|
958
949
|
submittedAtRfc3339?: string;
|
|
950
|
+
recognitionProof: AgentRecognitionProofV1 | Record<string, unknown>;
|
|
959
951
|
};
|
|
960
952
|
/**
|
|
961
953
|
* Ergonomic intent helpers: principal-signed intent create, x402 funding, and payee-signed evidence.
|
|
962
954
|
*/
|
|
963
955
|
export declare class PaybondIntents {
|
|
964
956
|
private readonly harbor;
|
|
965
|
-
constructor(harbor: HarborClient);
|
|
957
|
+
constructor(harbor: HarborClient | GatewayHarborClient);
|
|
966
958
|
/**
|
|
967
959
|
* Build a principal-signed `POST /intents` body and submit it. `principalSigningSeed` must be
|
|
968
960
|
* 32 bytes. `settlementRail` is signed as the requested rail; destinations stay server-owned.
|
|
@@ -975,6 +967,7 @@ export declare class PaybondIntents {
|
|
|
975
967
|
*/
|
|
976
968
|
fund(params: {
|
|
977
969
|
intentId: string;
|
|
970
|
+
recognitionProof: AgentRecognitionProofV1 | Record<string, unknown>;
|
|
978
971
|
paymentSignature?: string;
|
|
979
972
|
idempotencyKey?: string;
|
|
980
973
|
}): Promise<FundIntentResult>;
|
|
@@ -986,21 +979,19 @@ export declare class PaybondIntents {
|
|
|
986
979
|
}): Promise<SubmitEvidenceResult>;
|
|
987
980
|
}
|
|
988
981
|
/**
|
|
989
|
-
* High-level Kit entrypoint:
|
|
982
|
+
* High-level Kit entrypoint: tenant-bound Gateway clients plus ergonomic intent helpers.
|
|
990
983
|
*/
|
|
991
984
|
export declare class Paybond {
|
|
992
|
-
readonly harbor:
|
|
985
|
+
readonly harbor: GatewayHarborClient;
|
|
993
986
|
readonly signal: GatewaySignalClient;
|
|
994
987
|
readonly fraud: GatewayFraudClient;
|
|
995
988
|
readonly a2a: GatewayA2AClient;
|
|
996
989
|
readonly protocol: GatewayProtocolClient;
|
|
997
990
|
readonly intents: PaybondIntents;
|
|
998
|
-
private readonly session;
|
|
999
991
|
private constructor();
|
|
1000
|
-
/** Open a tenant-bound session
|
|
1001
|
-
static open(init:
|
|
1002
|
-
|
|
1003
|
-
/** 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. */
|
|
1004
995
|
aclose(): Promise<void>;
|
|
1005
996
|
}
|
|
1006
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).
|
|
@@ -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
|
}));
|
|
@@ -1543,14 +1633,14 @@ export class PaybondIntents {
|
|
|
1543
1633
|
* 32 bytes. `settlementRail` is signed as the requested rail; destinations stay server-owned.
|
|
1544
1634
|
*/
|
|
1545
1635
|
async create(params) {
|
|
1546
|
-
const { idempotencyKey, intentId: maybeIntentId, ...fields } = params;
|
|
1636
|
+
const { idempotencyKey, intentId: maybeIntentId, recognitionProof, ...fields } = params;
|
|
1547
1637
|
const intentId = maybeIntentId ?? globalThis.crypto.randomUUID();
|
|
1548
1638
|
const body = buildSignedCreateIntentBody({
|
|
1549
1639
|
tenantId: this.harbor.tenantId,
|
|
1550
1640
|
intentId,
|
|
1551
1641
|
...fields,
|
|
1552
1642
|
});
|
|
1553
|
-
return this.harbor.createIntent(body, { idempotencyKey });
|
|
1643
|
+
return this.harbor.createIntent(body, { idempotencyKey, recognitionProof });
|
|
1554
1644
|
}
|
|
1555
1645
|
/**
|
|
1556
1646
|
* Advance Harbor `/intents/{id}/fund` for x402 / USDC-on-Base intents.
|
|
@@ -1559,24 +1649,25 @@ export class PaybondIntents {
|
|
|
1559
1649
|
return this.harbor.fundIntent(params.intentId, {
|
|
1560
1650
|
paymentSignature: params.paymentSignature,
|
|
1561
1651
|
idempotencyKey: params.idempotencyKey,
|
|
1652
|
+
recognitionProof: params.recognitionProof,
|
|
1562
1653
|
});
|
|
1563
1654
|
}
|
|
1564
1655
|
/**
|
|
1565
1656
|
* Sign payee evidence and POST it. `payeeSigningSeed` must be 32 bytes.
|
|
1566
1657
|
*/
|
|
1567
1658
|
async submitEvidence(params) {
|
|
1568
|
-
const { idempotencyKey, artifactsBlake3Hex = [], submittedAtRfc3339 = nowRfc3339Seconds(), ...rest } = params;
|
|
1659
|
+
const { idempotencyKey, artifactsBlake3Hex = [], submittedAtRfc3339 = nowRfc3339Seconds(), recognitionProof, ...rest } = params;
|
|
1569
1660
|
const wire = signPayeeEvidenceBinding({
|
|
1570
1661
|
tenantId: this.harbor.tenantId,
|
|
1571
1662
|
artifactsBlake3Hex,
|
|
1572
1663
|
submittedAtRfc3339,
|
|
1573
1664
|
...rest,
|
|
1574
1665
|
});
|
|
1575
|
-
return this.harbor.submitEvidence(rest.intentId, wire, { idempotencyKey });
|
|
1666
|
+
return this.harbor.submitEvidence(rest.intentId, wire, { idempotencyKey, recognitionProof });
|
|
1576
1667
|
}
|
|
1577
1668
|
}
|
|
1578
1669
|
/**
|
|
1579
|
-
* High-level Kit entrypoint:
|
|
1670
|
+
* High-level Kit entrypoint: tenant-bound Gateway clients plus ergonomic intent helpers.
|
|
1580
1671
|
*/
|
|
1581
1672
|
export class Paybond {
|
|
1582
1673
|
harbor;
|
|
@@ -1585,43 +1676,44 @@ export class Paybond {
|
|
|
1585
1676
|
a2a;
|
|
1586
1677
|
protocol;
|
|
1587
1678
|
intents;
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
this.session = session;
|
|
1591
|
-
this.harbor = session.harbor;
|
|
1679
|
+
constructor(harbor, signal, fraud, a2a, protocol) {
|
|
1680
|
+
this.harbor = harbor;
|
|
1592
1681
|
this.signal = signal;
|
|
1593
1682
|
this.fraud = fraud;
|
|
1594
1683
|
this.a2a = a2a;
|
|
1595
1684
|
this.protocol = protocol;
|
|
1596
|
-
this.intents = new PaybondIntents(
|
|
1685
|
+
this.intents = new PaybondIntents(harbor);
|
|
1597
1686
|
}
|
|
1598
|
-
/** Open a tenant-bound session
|
|
1687
|
+
/** Open a tenant-bound hosted Paybond session from a service-account API key. */
|
|
1599
1688
|
static async open(init) {
|
|
1600
|
-
const
|
|
1601
|
-
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, {
|
|
1602
1693
|
staticGatewayBearerToken: init.apiKey,
|
|
1603
|
-
maxRetries
|
|
1694
|
+
maxRetries,
|
|
1604
1695
|
});
|
|
1605
|
-
const
|
|
1696
|
+
const signal = new GatewaySignalClient(gatewayBaseUrl, tenantId, {
|
|
1606
1697
|
staticGatewayBearerToken: init.apiKey,
|
|
1607
|
-
maxRetries
|
|
1698
|
+
maxRetries,
|
|
1608
1699
|
});
|
|
1609
|
-
const
|
|
1700
|
+
const fraud = new GatewayFraudClient(gatewayBaseUrl, tenantId, {
|
|
1610
1701
|
staticGatewayBearerToken: init.apiKey,
|
|
1611
|
-
maxRetries
|
|
1702
|
+
maxRetries,
|
|
1612
1703
|
});
|
|
1613
|
-
const
|
|
1704
|
+
const a2a = new GatewayA2AClient(gatewayBaseUrl, {
|
|
1614
1705
|
staticGatewayBearerToken: init.apiKey,
|
|
1615
|
-
maxRetries
|
|
1706
|
+
maxRetries,
|
|
1616
1707
|
});
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1708
|
+
const protocol = new GatewayProtocolClient(gatewayBaseUrl, tenantId, {
|
|
1709
|
+
staticGatewayBearerToken: init.apiKey,
|
|
1710
|
+
maxRetries,
|
|
1711
|
+
});
|
|
1712
|
+
return new Paybond(harbor, signal, fraud, a2a, protocol);
|
|
1621
1713
|
}
|
|
1622
|
-
/**
|
|
1714
|
+
/** Reserved for future HTTP client cleanup; safe to call after work completes. */
|
|
1623
1715
|
async aclose() {
|
|
1624
|
-
await
|
|
1716
|
+
await Promise.resolve();
|
|
1625
1717
|
}
|
|
1626
1718
|
}
|
|
1627
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/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.1",
|
|
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",
|