@ghostgate/sdk 0.1.2 → 0.1.3
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 +3 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,9 @@ const sdk = new GhostAgent({
|
|
|
38
38
|
privateKey: process.env.GHOST_SIGNER_PRIVATE_KEY as `0x${string}`,
|
|
39
39
|
baseUrl: process.env.GHOST_BASE_URL,
|
|
40
40
|
serviceSlug: "agent-18755",
|
|
41
|
+
// Optional x402 compatibility mode:
|
|
42
|
+
// authMode: "x402",
|
|
43
|
+
// x402Scheme: "ghost-eip712-credit-v1",
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
await sdk.connect();
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export type GhostAgentConfig = {
|
|
|
9
9
|
chainId?: number;
|
|
10
10
|
serviceSlug?: string;
|
|
11
11
|
creditCost?: number;
|
|
12
|
+
authMode?: "ghost-eip712" | "x402";
|
|
13
|
+
x402Scheme?: string;
|
|
12
14
|
};
|
|
13
15
|
export type ConnectResult = {
|
|
14
16
|
connected: boolean;
|
|
@@ -16,6 +18,10 @@ export type ConnectResult = {
|
|
|
16
18
|
endpoint: string;
|
|
17
19
|
status: number;
|
|
18
20
|
payload: unknown;
|
|
21
|
+
x402?: {
|
|
22
|
+
paymentRequired: unknown | null;
|
|
23
|
+
paymentResponse: unknown | null;
|
|
24
|
+
};
|
|
19
25
|
};
|
|
20
26
|
export type TelemetryResult = {
|
|
21
27
|
ok: boolean;
|
|
@@ -48,6 +54,44 @@ export type CanaryPayload = {
|
|
|
48
54
|
};
|
|
49
55
|
export type GhostMerchantConfig = GhostFulfillmentMerchantConfig & {
|
|
50
56
|
serviceSlug: string;
|
|
57
|
+
ownerPrivateKey?: `0x${string}`;
|
|
58
|
+
};
|
|
59
|
+
type MerchantGatewayConfigResponse = {
|
|
60
|
+
configured: boolean;
|
|
61
|
+
config: {
|
|
62
|
+
ownerAddress: string;
|
|
63
|
+
readinessStatus: "UNCONFIGURED" | "CONFIGURED" | "LIVE" | "DEGRADED";
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
type MerchantGatewayVerifyResponse = {
|
|
67
|
+
verified?: boolean;
|
|
68
|
+
readinessStatus?: "UNCONFIGURED" | "CONFIGURED" | "LIVE" | "DEGRADED";
|
|
69
|
+
error?: string;
|
|
70
|
+
canaryUrl?: string;
|
|
71
|
+
statusCode?: number | null;
|
|
72
|
+
latencyMs?: number | null;
|
|
73
|
+
};
|
|
74
|
+
type MerchantGatewayDelegatedSignerRegisterResponse = {
|
|
75
|
+
ok?: boolean;
|
|
76
|
+
created?: boolean;
|
|
77
|
+
alreadyActive?: boolean;
|
|
78
|
+
error?: string;
|
|
79
|
+
};
|
|
80
|
+
export type MerchantActivateOptions = {
|
|
81
|
+
agentId: string;
|
|
82
|
+
serviceSlug: string;
|
|
83
|
+
endpointUrl: string;
|
|
84
|
+
canaryPath?: string;
|
|
85
|
+
canaryMethod?: string;
|
|
86
|
+
signerLabel?: string;
|
|
87
|
+
};
|
|
88
|
+
export type ActivateResult = {
|
|
89
|
+
status: "LIVE";
|
|
90
|
+
readiness: "LIVE";
|
|
91
|
+
config: MerchantGatewayConfigResponse["config"];
|
|
92
|
+
verify: MerchantGatewayVerifyResponse;
|
|
93
|
+
signerRegistration: MerchantGatewayDelegatedSignerRegisterResponse;
|
|
94
|
+
heartbeat: HeartbeatController;
|
|
51
95
|
};
|
|
52
96
|
export declare const buildCanaryPayload: (serviceSlug: string) => CanaryPayload;
|
|
53
97
|
export declare const createCanaryHandler: (serviceSlug: string) => (_req?: unknown, res?: unknown) => unknown;
|
|
@@ -60,6 +104,8 @@ export declare class GhostAgent {
|
|
|
60
104
|
private readonly telemetryServiceSlug;
|
|
61
105
|
private readonly serviceSlug;
|
|
62
106
|
private readonly creditCost;
|
|
107
|
+
private readonly authMode;
|
|
108
|
+
private readonly x402Scheme;
|
|
63
109
|
constructor(config?: GhostAgentConfig);
|
|
64
110
|
connect(apiKey?: string): Promise<ConnectResult>;
|
|
65
111
|
pulse(input?: PulseInput): Promise<TelemetryResult>;
|
|
@@ -70,9 +116,18 @@ export declare class GhostAgent {
|
|
|
70
116
|
}
|
|
71
117
|
export declare class GhostMerchant extends GhostFulfillmentMerchant {
|
|
72
118
|
private readonly merchantServiceSlug;
|
|
119
|
+
private readonly merchantBaseUrl;
|
|
120
|
+
private readonly ownerPrivateKey;
|
|
121
|
+
private readonly ownerAddress;
|
|
122
|
+
private readonly delegatedSignerAddress;
|
|
123
|
+
private heartbeatController;
|
|
73
124
|
constructor(config: GhostMerchantConfig);
|
|
74
125
|
canaryPayload(): CanaryPayload;
|
|
75
126
|
canaryHandler(): (_req?: unknown, res?: unknown) => unknown;
|
|
127
|
+
activate(options: MerchantActivateOptions): Promise<ActivateResult>;
|
|
128
|
+
private fetchGatewayOwnerConfig;
|
|
129
|
+
private postMerchantSignedWrite;
|
|
130
|
+
private extractApiErrorMessage;
|
|
76
131
|
}
|
|
77
132
|
export default GhostAgent;
|
|
78
133
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAE5D,cAAc,kBAAkB,CAAC;AAEjC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,13 @@ const DEFAULT_CHAIN_ID = 8453;
|
|
|
7
7
|
const DEFAULT_SERVICE_SLUG = "connect";
|
|
8
8
|
const DEFAULT_CREDIT_COST = 1;
|
|
9
9
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 60000;
|
|
10
|
+
const DEFAULT_AUTH_MODE = "ghost-eip712";
|
|
11
|
+
const DEFAULT_X402_SCHEME = "ghost-eip712-credit-v1";
|
|
12
|
+
const DEFAULT_ACTIVATE_CANARY_PATH = "/health";
|
|
13
|
+
const DEFAULT_ACTIVATE_CANARY_METHOD = "GET";
|
|
14
|
+
const DEFAULT_ACTIVATE_SIGNER_LABEL = "sdk-auto";
|
|
15
|
+
const MERCHANT_GATEWAY_AUTH_SCOPE = "agent_gateway";
|
|
16
|
+
const MERCHANT_GATEWAY_AUTH_VERSION = "1";
|
|
10
17
|
const ACCESS_TYPES = {
|
|
11
18
|
Access: [
|
|
12
19
|
{ name: "service", type: "string" },
|
|
@@ -32,6 +39,18 @@ const parsePayload = async (response) => {
|
|
|
32
39
|
return null;
|
|
33
40
|
}
|
|
34
41
|
};
|
|
42
|
+
const encodeBase64Json = (value) => Buffer.from(JSON.stringify(value), "utf8").toString("base64");
|
|
43
|
+
const decodeBase64Json = (value) => {
|
|
44
|
+
if (!value)
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const decoded = Buffer.from(value, "base64").toString("utf8");
|
|
48
|
+
return JSON.parse(decoded);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
35
54
|
const deriveAgentId = (serviceSlug) => {
|
|
36
55
|
if (!serviceSlug)
|
|
37
56
|
return null;
|
|
@@ -56,6 +75,36 @@ const buildCanaryHeaders = () => ({
|
|
|
56
75
|
"cache-control": "no-store",
|
|
57
76
|
"content-type": "application/json; charset=utf-8",
|
|
58
77
|
});
|
|
78
|
+
const assertPrivateKey = (value, name) => {
|
|
79
|
+
if (!value || !/^0x[a-fA-F0-9]{64}$/.test(value)) {
|
|
80
|
+
throw new Error(`${name} must be a 0x-prefixed 32-byte hex private key.`);
|
|
81
|
+
}
|
|
82
|
+
return value;
|
|
83
|
+
};
|
|
84
|
+
const normalizeAddressLower = (value) => value.trim().toLowerCase();
|
|
85
|
+
const createMerchantGatewayAuthPayload = (input) => ({
|
|
86
|
+
scope: MERCHANT_GATEWAY_AUTH_SCOPE,
|
|
87
|
+
version: MERCHANT_GATEWAY_AUTH_VERSION,
|
|
88
|
+
action: input.action,
|
|
89
|
+
agentId: input.agentId,
|
|
90
|
+
ownerAddress: normalizeAddressLower(input.ownerAddress),
|
|
91
|
+
actorAddress: normalizeAddressLower(input.actorAddress),
|
|
92
|
+
serviceSlug: input.serviceSlug,
|
|
93
|
+
nonce: randomUUID().replace(/-/g, ""),
|
|
94
|
+
issuedAt: Math.floor(Date.now() / 1000),
|
|
95
|
+
});
|
|
96
|
+
const buildMerchantGatewayAuthMessage = (payload) => [
|
|
97
|
+
"Ghost Protocol Merchant Gateway Authorization",
|
|
98
|
+
`scope:${payload.scope}`,
|
|
99
|
+
`version:${payload.version}`,
|
|
100
|
+
`action:${payload.action}`,
|
|
101
|
+
`agentId:${payload.agentId}`,
|
|
102
|
+
`serviceSlug:${payload.serviceSlug}`,
|
|
103
|
+
`ownerAddress:${payload.ownerAddress}`,
|
|
104
|
+
`actorAddress:${payload.actorAddress}`,
|
|
105
|
+
`issuedAt:${payload.issuedAt}`,
|
|
106
|
+
`nonce:${payload.nonce}`,
|
|
107
|
+
].join("\n");
|
|
59
108
|
export const buildCanaryPayload = (serviceSlug) => {
|
|
60
109
|
const normalized = normalizeOptionalString(serviceSlug);
|
|
61
110
|
if (!normalized)
|
|
@@ -103,6 +152,8 @@ export class GhostAgent {
|
|
|
103
152
|
this.creditCost = Number.isFinite(config.creditCost) && (config.creditCost ?? 0) > 0
|
|
104
153
|
? Math.trunc(config.creditCost)
|
|
105
154
|
: DEFAULT_CREDIT_COST;
|
|
155
|
+
this.authMode = config.authMode ?? DEFAULT_AUTH_MODE;
|
|
156
|
+
this.x402Scheme = normalizeOptionalString(config.x402Scheme) ?? DEFAULT_X402_SCHEME;
|
|
106
157
|
}
|
|
107
158
|
async connect(apiKey) {
|
|
108
159
|
const normalizedApiKey = normalizeOptionalString(apiKey) ?? this.apiKey;
|
|
@@ -135,17 +186,31 @@ export class GhostAgent {
|
|
|
135
186
|
message: signedPayload,
|
|
136
187
|
});
|
|
137
188
|
const endpoint = `${this.baseUrl}/api/gate/${encodeURIComponent(this.serviceSlug)}`;
|
|
189
|
+
const gateHeaders = {
|
|
190
|
+
accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
191
|
+
};
|
|
192
|
+
if (this.authMode === "x402") {
|
|
193
|
+
gateHeaders["payment-signature"] = encodeBase64Json({
|
|
194
|
+
x402Version: 2,
|
|
195
|
+
scheme: this.x402Scheme,
|
|
196
|
+
network: `eip155:${this.chainId}`,
|
|
197
|
+
payload: headerPayload,
|
|
198
|
+
signature,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
gateHeaders["x-ghost-sig"] = signature;
|
|
203
|
+
gateHeaders["x-ghost-payload"] = JSON.stringify(headerPayload);
|
|
204
|
+
gateHeaders["x-ghost-credit-cost"] = String(this.creditCost);
|
|
205
|
+
}
|
|
138
206
|
const response = await fetch(endpoint, {
|
|
139
207
|
method: "POST",
|
|
140
|
-
headers:
|
|
141
|
-
"x-ghost-sig": signature,
|
|
142
|
-
"x-ghost-payload": JSON.stringify(headerPayload),
|
|
143
|
-
"x-ghost-credit-cost": String(this.creditCost),
|
|
144
|
-
accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
145
|
-
},
|
|
208
|
+
headers: gateHeaders,
|
|
146
209
|
cache: "no-store",
|
|
147
210
|
});
|
|
148
211
|
const responsePayload = await parsePayload(response);
|
|
212
|
+
const paymentRequired = decodeBase64Json(response.headers.get("payment-required"));
|
|
213
|
+
const paymentResponse = decodeBase64Json(response.headers.get("payment-response"));
|
|
149
214
|
if (response.ok) {
|
|
150
215
|
this.apiKey = normalizedApiKey;
|
|
151
216
|
}
|
|
@@ -155,6 +220,14 @@ export class GhostAgent {
|
|
|
155
220
|
endpoint,
|
|
156
221
|
status: response.status,
|
|
157
222
|
payload: responsePayload,
|
|
223
|
+
...(this.authMode === "x402" || paymentRequired || paymentResponse
|
|
224
|
+
? {
|
|
225
|
+
x402: {
|
|
226
|
+
paymentRequired,
|
|
227
|
+
paymentResponse,
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
: {}),
|
|
158
231
|
};
|
|
159
232
|
}
|
|
160
233
|
async pulse(input = {}) {
|
|
@@ -261,11 +334,18 @@ export class GhostAgent {
|
|
|
261
334
|
export class GhostMerchant extends GhostFulfillmentMerchant {
|
|
262
335
|
constructor(config) {
|
|
263
336
|
super(config);
|
|
337
|
+
this.heartbeatController = null;
|
|
264
338
|
const normalizedServiceSlug = normalizeOptionalString(config.serviceSlug);
|
|
265
339
|
if (!normalizedServiceSlug) {
|
|
266
340
|
throw new Error("GhostMerchant.serviceSlug is required.");
|
|
267
341
|
}
|
|
268
342
|
this.merchantServiceSlug = normalizedServiceSlug;
|
|
343
|
+
this.merchantBaseUrl = normalizeBaseUrl(config.baseUrl ?? DEFAULT_BASE_URL);
|
|
344
|
+
this.ownerPrivateKey = config.ownerPrivateKey ? assertPrivateKey(config.ownerPrivateKey, "ownerPrivateKey") : null;
|
|
345
|
+
this.ownerAddress = this.ownerPrivateKey ? privateKeyToAccount(this.ownerPrivateKey).address.toLowerCase() : null;
|
|
346
|
+
this.delegatedSignerAddress = config.delegatedPrivateKey
|
|
347
|
+
? privateKeyToAccount(assertPrivateKey(config.delegatedPrivateKey, "delegatedPrivateKey")).address.toLowerCase()
|
|
348
|
+
: null;
|
|
269
349
|
}
|
|
270
350
|
canaryPayload() {
|
|
271
351
|
return buildCanaryPayload(this.merchantServiceSlug);
|
|
@@ -273,5 +353,171 @@ export class GhostMerchant extends GhostFulfillmentMerchant {
|
|
|
273
353
|
canaryHandler() {
|
|
274
354
|
return createCanaryHandler(this.merchantServiceSlug);
|
|
275
355
|
}
|
|
356
|
+
async activate(options) {
|
|
357
|
+
const agentId = normalizeOptionalString(options.agentId);
|
|
358
|
+
const serviceSlug = normalizeOptionalString(options.serviceSlug);
|
|
359
|
+
const endpointUrl = normalizeOptionalString(options.endpointUrl);
|
|
360
|
+
const canaryPath = normalizeOptionalString(options.canaryPath) ?? DEFAULT_ACTIVATE_CANARY_PATH;
|
|
361
|
+
const canaryMethod = (normalizeOptionalString(options.canaryMethod) ?? DEFAULT_ACTIVATE_CANARY_METHOD).toUpperCase();
|
|
362
|
+
const signerLabel = normalizeOptionalString(options.signerLabel) ?? DEFAULT_ACTIVATE_SIGNER_LABEL;
|
|
363
|
+
if (!agentId)
|
|
364
|
+
throw new Error("[activate:validate] agentId is required.");
|
|
365
|
+
if (!serviceSlug)
|
|
366
|
+
throw new Error("[activate:validate] serviceSlug is required.");
|
|
367
|
+
if (!endpointUrl)
|
|
368
|
+
throw new Error("[activate:validate] endpointUrl is required.");
|
|
369
|
+
if (!canaryPath.startsWith("/")) {
|
|
370
|
+
throw new Error("[activate:validate] canaryPath must start with '/'.");
|
|
371
|
+
}
|
|
372
|
+
if (canaryMethod !== "GET") {
|
|
373
|
+
throw new Error("[activate:validate] canaryMethod must be GET.");
|
|
374
|
+
}
|
|
375
|
+
if (!this.ownerPrivateKey || !this.ownerAddress) {
|
|
376
|
+
throw new Error("[activate:owner] ownerPrivateKey is required on GhostMerchant config and must match the indexed agent owner.");
|
|
377
|
+
}
|
|
378
|
+
const ownerConfig = await this.fetchGatewayOwnerConfig(agentId);
|
|
379
|
+
const indexedOwnerAddress = normalizeAddressLower(ownerConfig.config.ownerAddress);
|
|
380
|
+
if (indexedOwnerAddress !== this.ownerAddress) {
|
|
381
|
+
throw new Error(`[activate:owner] ownerPrivateKey address ${this.ownerAddress} does not match indexed owner ${indexedOwnerAddress} for agent ${agentId}.`);
|
|
382
|
+
}
|
|
383
|
+
const configPayload = (await this.postMerchantSignedWrite("config", {
|
|
384
|
+
path: "/api/agent-gateway/config",
|
|
385
|
+
agentId,
|
|
386
|
+
serviceSlug,
|
|
387
|
+
ownerAddress: indexedOwnerAddress,
|
|
388
|
+
body: {
|
|
389
|
+
endpointUrl,
|
|
390
|
+
canaryPath,
|
|
391
|
+
canaryMethod: "GET",
|
|
392
|
+
},
|
|
393
|
+
}));
|
|
394
|
+
const verifyPayload = (await this.postMerchantSignedWrite("verify", {
|
|
395
|
+
path: "/api/agent-gateway/verify",
|
|
396
|
+
agentId,
|
|
397
|
+
serviceSlug,
|
|
398
|
+
ownerAddress: indexedOwnerAddress,
|
|
399
|
+
body: {},
|
|
400
|
+
}));
|
|
401
|
+
const readiness = verifyPayload.readinessStatus;
|
|
402
|
+
if (verifyPayload.verified !== true || readiness !== "LIVE") {
|
|
403
|
+
const detailParts = [
|
|
404
|
+
verifyPayload.error ? `error=${verifyPayload.error}` : null,
|
|
405
|
+
verifyPayload.canaryUrl ? `canaryUrl=${verifyPayload.canaryUrl}` : null,
|
|
406
|
+
typeof verifyPayload.statusCode === "number" ? `statusCode=${verifyPayload.statusCode}` : null,
|
|
407
|
+
typeof verifyPayload.latencyMs === "number" ? `latencyMs=${verifyPayload.latencyMs}` : null,
|
|
408
|
+
].filter(Boolean);
|
|
409
|
+
throw new Error(`[activate:verify] canary verification did not reach LIVE readiness${detailParts.length ? ` (${detailParts.join(", ")})` : ""}.`);
|
|
410
|
+
}
|
|
411
|
+
const signerAddress = this.delegatedSignerAddress ?? indexedOwnerAddress;
|
|
412
|
+
const signerRegistration = (await this.postMerchantSignedWrite("delegated_signer_register", {
|
|
413
|
+
path: "/api/agent-gateway/delegated-signers/register",
|
|
414
|
+
agentId,
|
|
415
|
+
serviceSlug,
|
|
416
|
+
ownerAddress: indexedOwnerAddress,
|
|
417
|
+
body: {
|
|
418
|
+
signerAddress,
|
|
419
|
+
label: signerLabel,
|
|
420
|
+
},
|
|
421
|
+
}));
|
|
422
|
+
this.heartbeatController?.stop();
|
|
423
|
+
const heartbeatAgent = new GhostAgent({
|
|
424
|
+
baseUrl: this.merchantBaseUrl,
|
|
425
|
+
agentId,
|
|
426
|
+
serviceSlug,
|
|
427
|
+
});
|
|
428
|
+
this.heartbeatController = heartbeatAgent.startHeartbeat({
|
|
429
|
+
agentId,
|
|
430
|
+
serviceSlug,
|
|
431
|
+
immediate: false,
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
status: "LIVE",
|
|
435
|
+
readiness: "LIVE",
|
|
436
|
+
config: configPayload.config ?? ownerConfig.config,
|
|
437
|
+
verify: verifyPayload,
|
|
438
|
+
signerRegistration,
|
|
439
|
+
heartbeat: this.heartbeatController,
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
async fetchGatewayOwnerConfig(agentId) {
|
|
443
|
+
const params = new URLSearchParams({ agentId });
|
|
444
|
+
const endpoint = `${this.merchantBaseUrl}/api/agent-gateway/config?${params.toString()}`;
|
|
445
|
+
const response = await fetch(endpoint, {
|
|
446
|
+
method: "GET",
|
|
447
|
+
headers: {
|
|
448
|
+
accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
449
|
+
},
|
|
450
|
+
cache: "no-store",
|
|
451
|
+
});
|
|
452
|
+
const payload = await parsePayload(response);
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
const message = this.extractApiErrorMessage(payload, "Failed to load gateway owner config.");
|
|
455
|
+
throw new Error(`[activate:config_lookup] ${message}`);
|
|
456
|
+
}
|
|
457
|
+
const config = typeof payload === "object" &&
|
|
458
|
+
payload !== null &&
|
|
459
|
+
"config" in payload &&
|
|
460
|
+
typeof payload.config === "object" &&
|
|
461
|
+
payload.config !== null
|
|
462
|
+
? (payload.config)
|
|
463
|
+
: null;
|
|
464
|
+
if (!config || !normalizeOptionalString(config.ownerAddress)) {
|
|
465
|
+
throw new Error("[activate:config_lookup] gateway config response missing ownerAddress.");
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
configured: typeof payload === "object" && payload !== null && "configured" in payload
|
|
469
|
+
? Boolean(payload.configured)
|
|
470
|
+
: false,
|
|
471
|
+
config,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
async postMerchantSignedWrite(action, input) {
|
|
475
|
+
const ownerKey = assertPrivateKey(this.ownerPrivateKey, "ownerPrivateKey");
|
|
476
|
+
const ownerAddress = normalizeAddressLower(input.ownerAddress);
|
|
477
|
+
const authPayload = createMerchantGatewayAuthPayload({
|
|
478
|
+
action,
|
|
479
|
+
agentId: input.agentId,
|
|
480
|
+
ownerAddress,
|
|
481
|
+
actorAddress: ownerAddress,
|
|
482
|
+
serviceSlug: input.serviceSlug,
|
|
483
|
+
});
|
|
484
|
+
const account = privateKeyToAccount(ownerKey);
|
|
485
|
+
const authSignature = await account.signMessage({
|
|
486
|
+
message: buildMerchantGatewayAuthMessage(authPayload),
|
|
487
|
+
});
|
|
488
|
+
const endpoint = `${this.merchantBaseUrl}${input.path}`;
|
|
489
|
+
const response = await fetch(endpoint, {
|
|
490
|
+
method: "POST",
|
|
491
|
+
headers: {
|
|
492
|
+
"content-type": "application/json",
|
|
493
|
+
accept: "application/json, text/plain;q=0.9, */*;q=0.8",
|
|
494
|
+
},
|
|
495
|
+
body: JSON.stringify({
|
|
496
|
+
agentId: input.agentId,
|
|
497
|
+
ownerAddress,
|
|
498
|
+
actorAddress: ownerAddress,
|
|
499
|
+
serviceSlug: input.serviceSlug,
|
|
500
|
+
authPayload,
|
|
501
|
+
authSignature,
|
|
502
|
+
...input.body,
|
|
503
|
+
}),
|
|
504
|
+
cache: "no-store",
|
|
505
|
+
});
|
|
506
|
+
const payload = await parsePayload(response);
|
|
507
|
+
if (!response.ok) {
|
|
508
|
+
const message = this.extractApiErrorMessage(payload, `Request failed for ${action}.`);
|
|
509
|
+
throw new Error(`[activate:${action}] ${message}`);
|
|
510
|
+
}
|
|
511
|
+
return payload;
|
|
512
|
+
}
|
|
513
|
+
extractApiErrorMessage(payload, fallback) {
|
|
514
|
+
if (typeof payload === "object" && payload !== null && "error" in payload) {
|
|
515
|
+
const maybeError = payload.error;
|
|
516
|
+
if (typeof maybeError === "string" && maybeError.trim().length > 0) {
|
|
517
|
+
return maybeError.trim();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
return fallback;
|
|
521
|
+
}
|
|
276
522
|
}
|
|
277
523
|
export default GhostAgent;
|