@ghostgate/sdk 0.1.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.
@@ -0,0 +1,422 @@
1
+ import { randomBytes, randomUUID } from "node:crypto";
2
+ import { privateKeyToAccount } from "viem/accounts";
3
+ import { recoverTypedDataAddress, verifyTypedData } from "viem";
4
+ import { buildFulfillmentDeliveryProofEnvelope, buildFulfillmentDeliveryProofTypedData, buildFulfillmentTicketHeaders, buildFulfillmentTicketRequestAuthTypedData, buildFulfillmentTicketTypedData, encodeTypedPayloadBase64Url, parseFulfillmentTicketHeaders, parseWireFulfillmentTicketMessage, hashFulfillmentDeliveryProofTypedData, normalizeFulfillmentDeliveryProofMessage, normalizeFulfillmentTicketRequestAuthMessage, redactFulfillmentDeliveryProofEnvelopeDebug, redactFulfillmentTicketEnvelopeDebug, } from "./fulfillment-eip712.js";
5
+ import { FULFILLMENT_DEFAULT_CHAIN_ID, } from "./fulfillment-types.js";
6
+ import { FULFILLMENT_ZERO_HASH_32, hashCanonicalFulfillmentBodyJson, hashCanonicalFulfillmentQuery, sha256HexUtf8, } from "./fulfillment-hash.js";
7
+ const DEFAULT_BASE_URL = "https://ghostprotocol.cc";
8
+ const DEFAULT_TICKET_COST = 1;
9
+ const normalizeBaseUrl = (value) => value.replace(/\/+$/, "");
10
+ const parseJsonSafe = async (response) => {
11
+ try {
12
+ return await response.json();
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ };
18
+ const parseTextSafe = async (response) => {
19
+ try {
20
+ return await response.text();
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ };
26
+ const assertPrivateKey = (value, name) => {
27
+ if (!value || !/^0x[a-fA-F0-9]{64}$/.test(value)) {
28
+ throw new Error(`${name} must be a 0x-prefixed 32-byte hex private key.`);
29
+ }
30
+ return value;
31
+ };
32
+ const normalizeChainId = (value) => Number.isFinite(value) && (value ?? 0) > 0 ? Math.trunc(value) : FULFILLMENT_DEFAULT_CHAIN_ID;
33
+ const normalizeCost = (value) => Number.isFinite(value) && (value ?? 0) > 0 ? Math.trunc(value) : DEFAULT_TICKET_COST;
34
+ const normalizeMethod = (value) => (value ?? "POST").trim().toUpperCase() || "POST";
35
+ const normalizePath = (value) => {
36
+ const trimmed = value.trim();
37
+ if (!trimmed.startsWith("/"))
38
+ throw new Error(`Fulfillment path must start with '/'. Received: ${value}`);
39
+ if (trimmed.includes("?"))
40
+ throw new Error(`Fulfillment path must not include a query string. Received: ${value}`);
41
+ return trimmed;
42
+ };
43
+ const normalizeServiceSlug = (value, fallback = "agent-18755") => {
44
+ const trimmed = value?.trim();
45
+ if (!trimmed)
46
+ return fallback;
47
+ return trimmed;
48
+ };
49
+ const stringifyQuery = (query) => {
50
+ if (query == null)
51
+ return "";
52
+ if (typeof query === "string")
53
+ return query;
54
+ if (query instanceof URLSearchParams)
55
+ return query.toString();
56
+ const params = new URLSearchParams();
57
+ for (const [key, rawValue] of Object.entries(query)) {
58
+ if (rawValue == null)
59
+ continue;
60
+ params.set(key, String(rawValue));
61
+ }
62
+ return params.toString();
63
+ };
64
+ const buildMerchantTargetUrl = (endpointUrl, path, query) => {
65
+ const base = new URL(`${normalizeBaseUrl(endpointUrl)}/`);
66
+ const basePath = base.pathname === "/" ? "" : base.pathname.replace(/\/+$/, "");
67
+ const targetPath = path.trim();
68
+ const normalizedTargetPath = targetPath.startsWith("/") ? targetPath : `/${targetPath}`;
69
+ base.pathname = `${basePath}${normalizedTargetPath}` || "/";
70
+ if (query) {
71
+ const source = query.startsWith("?") ? query.slice(1) : query;
72
+ base.search = source;
73
+ }
74
+ return base.toString();
75
+ };
76
+ const extractTicketEnvelope = (payload) => {
77
+ if (typeof payload !== "object" || payload == null || Array.isArray(payload))
78
+ return null;
79
+ const record = payload;
80
+ const ticketId = typeof record.ticketId === "string" ? record.ticketId.trim().toLowerCase() : null;
81
+ if (!ticketId || !/^0x[a-f0-9]{64}$/.test(ticketId))
82
+ return null;
83
+ const ticketObj = record.ticket;
84
+ if (typeof ticketObj !== "object" || ticketObj == null || Array.isArray(ticketObj))
85
+ return null;
86
+ const ticketRecord = ticketObj;
87
+ const version = ticketRecord.version;
88
+ const payloadRaw = typeof ticketRecord.payload === "string" ? ticketRecord.payload.trim() : "";
89
+ const signature = typeof ticketRecord.signature === "string" ? ticketRecord.signature.trim().toLowerCase() : "";
90
+ if (version !== 1 || !payloadRaw || !/^0x[a-f0-9]+$/.test(signature))
91
+ return null;
92
+ let merchantTargetUrl = null;
93
+ const merchantTarget = record.merchantTarget;
94
+ if (typeof merchantTarget === "object" && merchantTarget != null && !Array.isArray(merchantTarget)) {
95
+ const mt = merchantTarget;
96
+ const endpointUrl = typeof mt.endpointUrl === "string" ? mt.endpointUrl : "";
97
+ const path = typeof mt.path === "string" ? mt.path : "";
98
+ if (endpointUrl && path) {
99
+ merchantTargetUrl = buildMerchantTargetUrl(endpointUrl, path, "");
100
+ }
101
+ }
102
+ const validated = typeof record.validated === "object" && record.validated != null && !Array.isArray(record.validated)
103
+ ? record.validated
104
+ : null;
105
+ const clientRequestId = validated && typeof validated.clientRequestId === "string" ? validated.clientRequestId : null;
106
+ return {
107
+ ticketId,
108
+ ticket: { version: 1, payload: payloadRaw, signature: signature },
109
+ merchantTargetUrl,
110
+ clientRequestId,
111
+ };
112
+ };
113
+ const redactHeaders = (headers) => {
114
+ const out = {};
115
+ for (const [key, value] of Object.entries(headers)) {
116
+ if (key.toLowerCase().includes("fulfillment-ticket")) {
117
+ out[key] = value.length > 20 ? `${value.slice(0, 20)}...` : value;
118
+ continue;
119
+ }
120
+ out[key] = value;
121
+ }
122
+ return out;
123
+ };
124
+ export class GhostFulfillmentConsumer {
125
+ constructor(config = {}) {
126
+ this.baseUrl = normalizeBaseUrl(config.baseUrl ?? DEFAULT_BASE_URL);
127
+ this.privateKey = assertPrivateKey(config.privateKey ?? null, "GhostFulfillmentConsumer.privateKey");
128
+ this.chainId = normalizeChainId(config.chainId);
129
+ this.defaultServiceSlug = normalizeServiceSlug(config.defaultServiceSlug, "agent-18755");
130
+ }
131
+ async requestTicket(input) {
132
+ const serviceSlug = normalizeServiceSlug(input.serviceSlug, this.defaultServiceSlug);
133
+ const method = normalizeMethod(input.method);
134
+ const path = normalizePath(input.path);
135
+ const query = stringifyQuery(input.query);
136
+ const cost = normalizeCost(input.cost);
137
+ const bodyHash = hashCanonicalFulfillmentBodyJson(input.body ?? {});
138
+ const queryHash = hashCanonicalFulfillmentQuery(query);
139
+ const issuedAt = Math.floor(Date.now() / 1000);
140
+ const nonce = randomUUID().replace(/-/g, "");
141
+ const authMessage = normalizeFulfillmentTicketRequestAuthMessage({
142
+ action: "fulfillment_ticket",
143
+ serviceSlug,
144
+ method,
145
+ path,
146
+ queryHash,
147
+ bodyHash,
148
+ cost,
149
+ issuedAt,
150
+ nonce,
151
+ });
152
+ const account = privateKeyToAccount(this.privateKey);
153
+ const authSignature = await account.signTypedData(buildFulfillmentTicketRequestAuthTypedData(authMessage, { chainId: this.chainId }));
154
+ const clientRequestId = input.clientRequestId?.trim() || `fx-${Date.now()}`;
155
+ const endpoint = `${this.baseUrl}/api/fulfillment/ticket`;
156
+ const response = await fetch(endpoint, {
157
+ method: "POST",
158
+ headers: {
159
+ "content-type": "application/json",
160
+ accept: "application/json, text/plain;q=0.9, */*;q=0.8",
161
+ },
162
+ body: JSON.stringify({
163
+ serviceSlug,
164
+ method,
165
+ path,
166
+ cost,
167
+ query,
168
+ clientRequestId,
169
+ ticketRequestAuth: {
170
+ payload: encodeTypedPayloadBase64Url(authMessage),
171
+ signature: String(authSignature).toLowerCase(),
172
+ },
173
+ }),
174
+ cache: "no-store",
175
+ });
176
+ const payload = await parseJsonSafe(response);
177
+ const envelope = extractTicketEnvelope(payload);
178
+ const merchantTargetUrl = envelope?.merchantTargetUrl
179
+ ? (() => {
180
+ const parsed = payload;
181
+ const mt = parsed.merchantTarget;
182
+ const endpointUrl = String(mt.endpointUrl ?? "");
183
+ const merchantPath = String(mt.path ?? path);
184
+ return endpointUrl ? buildMerchantTargetUrl(endpointUrl, merchantPath, query) : envelope.merchantTargetUrl;
185
+ })()
186
+ : null;
187
+ return {
188
+ status: response.status,
189
+ endpoint,
190
+ payload,
191
+ ok: response.ok,
192
+ ticketId: envelope?.ticketId ?? null,
193
+ ticket: envelope?.ticket ?? null,
194
+ merchantTargetUrl,
195
+ clientRequestId,
196
+ };
197
+ }
198
+ async execute(input) {
199
+ const ticket = await this.requestTicket(input);
200
+ if (!ticket.ok || !ticket.ticketId || !ticket.ticket || !ticket.merchantTargetUrl) {
201
+ return {
202
+ ticket,
203
+ merchant: {
204
+ attempted: false,
205
+ status: null,
206
+ url: null,
207
+ bodyText: null,
208
+ bodyJson: null,
209
+ headers: null,
210
+ },
211
+ };
212
+ }
213
+ const merchantHeaders = buildFulfillmentTicketHeaders({
214
+ ticketId: ticket.ticketId,
215
+ ticket: ticket.ticket,
216
+ clientRequestId: ticket.clientRequestId,
217
+ });
218
+ const method = normalizeMethod(input.method);
219
+ const customHeaders = input.headers ?? {};
220
+ const headers = {
221
+ ...merchantHeaders,
222
+ accept: customHeaders.accept ?? "application/json, text/plain;q=0.9, */*;q=0.8",
223
+ ...customHeaders,
224
+ };
225
+ let bodyInit;
226
+ if (input.body != null) {
227
+ if (typeof input.body === "string") {
228
+ bodyInit = input.body;
229
+ }
230
+ else {
231
+ bodyInit = JSON.stringify(input.body);
232
+ if (!Object.keys(headers).some((key) => key.toLowerCase() === "content-type")) {
233
+ headers["content-type"] = "application/json";
234
+ }
235
+ }
236
+ }
237
+ const merchantResponse = await fetch(ticket.merchantTargetUrl, {
238
+ method,
239
+ headers,
240
+ body: bodyInit,
241
+ cache: "no-store",
242
+ });
243
+ const bodyText = await parseTextSafe(merchantResponse);
244
+ let bodyJson = null;
245
+ if (bodyText != null) {
246
+ try {
247
+ bodyJson = JSON.parse(bodyText);
248
+ }
249
+ catch {
250
+ bodyJson = null;
251
+ }
252
+ }
253
+ return {
254
+ ticket,
255
+ merchant: {
256
+ attempted: true,
257
+ status: merchantResponse.status,
258
+ url: ticket.merchantTargetUrl,
259
+ bodyText,
260
+ bodyJson,
261
+ headers: redactHeaders(headers),
262
+ },
263
+ };
264
+ }
265
+ }
266
+ const normalizeProtocolSignerAddressSet = (addresses) => {
267
+ const normalized = new Set();
268
+ for (const raw of addresses) {
269
+ const trimmed = raw.trim().toLowerCase();
270
+ if (/^0x[a-f0-9]{40}$/.test(trimmed)) {
271
+ normalized.add(trimmed);
272
+ }
273
+ }
274
+ if (normalized.size === 0) {
275
+ throw new Error("GhostFulfillmentMerchant requires at least one valid protocolSignerAddress.");
276
+ }
277
+ return normalized;
278
+ };
279
+ export class GhostFulfillmentMerchant {
280
+ constructor(config) {
281
+ this.baseUrl = normalizeBaseUrl(config.baseUrl ?? DEFAULT_BASE_URL);
282
+ this.delegatedPrivateKey = config.delegatedPrivateKey ? assertPrivateKey(config.delegatedPrivateKey, "delegatedPrivateKey") : null;
283
+ this.protocolSignerAddresses = normalizeProtocolSignerAddressSet(config.protocolSignerAddresses);
284
+ this.chainId = normalizeChainId(config.chainId);
285
+ }
286
+ parseTicketHeaders(headers) {
287
+ return parseFulfillmentTicketHeaders(headers);
288
+ }
289
+ async requireFulfillmentTicket(input) {
290
+ const parsedHeaders = parseFulfillmentTicketHeaders(input.headers);
291
+ if (!parsedHeaders) {
292
+ throw new Error("Missing or invalid fulfillment ticket headers.");
293
+ }
294
+ const payload = parseWireFulfillmentTicketMessage(parsedHeaders.ticket.payload);
295
+ if (payload.ticketId !== parsedHeaders.ticketId) {
296
+ throw new Error("Ticket header ticketId does not match ticket payload ticketId.");
297
+ }
298
+ const typedData = buildFulfillmentTicketTypedData(payload, { chainId: this.chainId });
299
+ const recoveredSigner = (await recoverTypedDataAddress({
300
+ ...typedData,
301
+ signature: parsedHeaders.ticket.signature,
302
+ })).toLowerCase();
303
+ if (!this.protocolSignerAddresses.has(recoveredSigner)) {
304
+ throw new Error("Fulfillment ticket signer is not an allowed protocol signer.");
305
+ }
306
+ const valid = await verifyTypedData({
307
+ address: recoveredSigner,
308
+ ...typedData,
309
+ signature: parsedHeaders.ticket.signature,
310
+ });
311
+ if (!valid) {
312
+ throw new Error("Invalid fulfillment ticket signature.");
313
+ }
314
+ const nowSeconds = BigInt(Math.floor((input.nowMs ?? Date.now()) / 1000));
315
+ if (nowSeconds > payload.expiresAt) {
316
+ throw new Error("Fulfillment ticket has expired.");
317
+ }
318
+ const expected = input.expected;
319
+ if (expected) {
320
+ if (expected.serviceSlug && expected.serviceSlug.trim() !== payload.serviceSlug) {
321
+ throw new Error("Fulfillment ticket serviceSlug does not match expected service.");
322
+ }
323
+ if (expected.method && normalizeMethod(expected.method) !== payload.method) {
324
+ throw new Error("Fulfillment ticket method does not match request method.");
325
+ }
326
+ if (expected.path && normalizePath(expected.path) !== payload.path) {
327
+ throw new Error("Fulfillment ticket path does not match request path.");
328
+ }
329
+ if (expected.query !== undefined) {
330
+ const queryHash = hashCanonicalFulfillmentQuery(stringifyQuery(expected.query));
331
+ if (queryHash !== payload.queryHash) {
332
+ throw new Error("Fulfillment ticket queryHash does not match request query.");
333
+ }
334
+ }
335
+ if (expected.body !== undefined) {
336
+ const bodyHash = hashCanonicalFulfillmentBodyJson(expected.body);
337
+ if (bodyHash !== payload.bodyHash) {
338
+ throw new Error("Fulfillment ticket bodyHash does not match request body.");
339
+ }
340
+ }
341
+ }
342
+ return {
343
+ ticketId: parsedHeaders.ticketId,
344
+ ticket: parsedHeaders.ticket,
345
+ payload,
346
+ signer: recoveredSigner,
347
+ clientRequestId: parsedHeaders.clientRequestId,
348
+ };
349
+ }
350
+ async captureCompletion(input) {
351
+ if (!this.delegatedPrivateKey) {
352
+ throw new Error("GhostFulfillmentMerchant.captureCompletion requires delegatedPrivateKey.");
353
+ }
354
+ const ticketId = String(input.ticketId).trim().toLowerCase();
355
+ if (!/^0x[a-f0-9]{64}$/.test(ticketId))
356
+ throw new Error("ticketId must be a bytes32 hex string.");
357
+ const serviceSlug = normalizeServiceSlug(input.serviceSlug);
358
+ if (!Number.isInteger(input.statusCode) || input.statusCode < 100 || input.statusCode > 599) {
359
+ throw new Error("statusCode must be an integer in the HTTP status range.");
360
+ }
361
+ if (!Number.isInteger(input.latencyMs) || input.latencyMs < 0) {
362
+ throw new Error("latencyMs must be a non-negative integer.");
363
+ }
364
+ const merchant = privateKeyToAccount(this.delegatedPrivateKey);
365
+ const completedAtSeconds = Math.floor((input.completedAtMs ?? Date.now()) / 1000);
366
+ const responseHash = input.responseBodyJson !== undefined
367
+ ? hashCanonicalFulfillmentBodyJson(input.responseBodyJson)
368
+ : typeof input.responseBodyText === "string"
369
+ ? sha256HexUtf8(input.responseBodyText)
370
+ : null;
371
+ const proofMessage = normalizeFulfillmentDeliveryProofMessage({
372
+ ticketId,
373
+ deliveryProofId: input.deliveryProofId ?? `0x${randomBytes(32).toString("hex")}`,
374
+ merchantSigner: merchant.address.toLowerCase(),
375
+ serviceSlug,
376
+ completedAt: completedAtSeconds,
377
+ statusCode: input.statusCode,
378
+ latencyMs: input.latencyMs,
379
+ responseHash: responseHash ?? FULFILLMENT_ZERO_HASH_32,
380
+ });
381
+ const proofSignature = await merchant.signTypedData(buildFulfillmentDeliveryProofTypedData(proofMessage, { chainId: this.chainId }));
382
+ const envelope = buildFulfillmentDeliveryProofEnvelope(proofMessage, proofSignature);
383
+ const proofTypedHash = hashFulfillmentDeliveryProofTypedData(proofMessage, { chainId: this.chainId });
384
+ const endpoint = `${this.baseUrl}/api/fulfillment/capture`;
385
+ const response = await fetch(endpoint, {
386
+ method: "POST",
387
+ headers: {
388
+ "content-type": "application/json",
389
+ accept: "application/json, text/plain;q=0.9, */*;q=0.8",
390
+ },
391
+ body: JSON.stringify({
392
+ ticketId,
393
+ deliveryProof: envelope,
394
+ completionMeta: {
395
+ statusCode: input.statusCode,
396
+ latencyMs: input.latencyMs,
397
+ ...(responseHash ? { responseHash } : {}),
398
+ },
399
+ }),
400
+ cache: "no-store",
401
+ });
402
+ const payload = await parseJsonSafe(response);
403
+ return {
404
+ status: response.status,
405
+ endpoint,
406
+ payload,
407
+ ok: response.ok,
408
+ deliveryProof: envelope,
409
+ debug: {
410
+ proofTypedHash,
411
+ ticketId,
412
+ deliveryProofId: proofMessage.deliveryProofId,
413
+ envelope: redactFulfillmentDeliveryProofEnvelopeDebug(envelope),
414
+ },
415
+ };
416
+ }
417
+ }
418
+ export const fulfillmentTicketHeadersToRecord = (input) => buildFulfillmentTicketHeaders(input);
419
+ export const parseFulfillmentTicketHeadersFromRecord = parseFulfillmentTicketHeaders;
420
+ export const debugFulfillmentTicketEnvelope = redactFulfillmentTicketEnvelopeDebug;
421
+ export const debugFulfillmentDeliveryProofEnvelope = redactFulfillmentDeliveryProofEnvelopeDebug;
422
+ export default GhostFulfillmentConsumer;
@@ -0,0 +1,78 @@
1
+ import type { GhostFulfillmentMerchantConfig } from "./fulfillment.js";
2
+ import { GhostFulfillmentMerchant } from "./fulfillment.js";
3
+ export * from "./fulfillment.js";
4
+ export type GhostAgentConfig = {
5
+ apiKey?: string;
6
+ agentId?: string;
7
+ baseUrl?: string;
8
+ privateKey?: `0x${string}`;
9
+ chainId?: number;
10
+ serviceSlug?: string;
11
+ creditCost?: number;
12
+ };
13
+ export type ConnectResult = {
14
+ connected: boolean;
15
+ apiKeyPrefix: string;
16
+ endpoint: string;
17
+ status: number;
18
+ payload: unknown;
19
+ };
20
+ export type TelemetryResult = {
21
+ ok: boolean;
22
+ endpoint: string;
23
+ status: number;
24
+ payload: unknown;
25
+ };
26
+ export type PulseInput = {
27
+ apiKey?: string;
28
+ agentId?: string | null;
29
+ serviceSlug?: string | null;
30
+ metadata?: Record<string, unknown>;
31
+ };
32
+ export type OutcomeInput = PulseInput & {
33
+ success: boolean;
34
+ statusCode?: number | null;
35
+ };
36
+ export type HeartbeatOptions = PulseInput & {
37
+ intervalMs?: number;
38
+ immediate?: boolean;
39
+ onResult?: (result: TelemetryResult) => void;
40
+ onError?: (error: unknown) => void;
41
+ };
42
+ export type HeartbeatController = {
43
+ stop: () => void;
44
+ };
45
+ export type CanaryPayload = {
46
+ ghostgate: "ready";
47
+ service: string;
48
+ };
49
+ export type GhostMerchantConfig = GhostFulfillmentMerchantConfig & {
50
+ serviceSlug: string;
51
+ };
52
+ export declare const buildCanaryPayload: (serviceSlug: string) => CanaryPayload;
53
+ export declare const createCanaryHandler: (serviceSlug: string) => (_req?: unknown, res?: unknown) => unknown;
54
+ export declare class GhostAgent {
55
+ private apiKey;
56
+ private readonly agentId;
57
+ private readonly baseUrl;
58
+ private readonly privateKey;
59
+ private readonly chainId;
60
+ private readonly telemetryServiceSlug;
61
+ private readonly serviceSlug;
62
+ private readonly creditCost;
63
+ constructor(config?: GhostAgentConfig);
64
+ connect(apiKey?: string): Promise<ConnectResult>;
65
+ pulse(input?: PulseInput): Promise<TelemetryResult>;
66
+ outcome(input: OutcomeInput): Promise<TelemetryResult>;
67
+ startHeartbeat(options?: HeartbeatOptions): HeartbeatController;
68
+ get isConnected(): boolean;
69
+ get endpoint(): string;
70
+ }
71
+ export declare class GhostMerchant extends GhostFulfillmentMerchant {
72
+ private readonly merchantServiceSlug;
73
+ constructor(config: GhostMerchantConfig);
74
+ canaryPayload(): CanaryPayload;
75
+ canaryHandler(): (_req?: unknown, res?: unknown) => unknown;
76
+ }
77
+ export default GhostAgent;
78
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}