@halot/sdk 1.0.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 +243 -0
- package/dist/auth/actor.d.ts +17 -0
- package/dist/auth/actor.js +33 -0
- package/dist/auth/attestation.d.ts +24 -0
- package/dist/auth/attestation.js +47 -0
- package/dist/client/halot-client.d.ts +37 -0
- package/dist/client/halot-client.js +89 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +5 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +11 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +43 -0
- package/dist/middleware/aggregator-client.d.ts +8 -0
- package/dist/middleware/aggregator-client.js +49 -0
- package/dist/middleware/halot.d.ts +4 -0
- package/dist/middleware/halot.js +142 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/types.d.ts +45 -0
- package/dist/middleware/types.js +2 -0
- package/dist/types/common.d.ts +90 -0
- package/dist/types/common.js +66 -0
- package/dist/types/job.d.ts +232 -0
- package/dist/types/job.js +125 -0
- package/dist/types/provider.d.ts +160 -0
- package/dist/types/provider.js +45 -0
- package/dist/types/quote.d.ts +43 -0
- package/dist/types/quote.js +31 -0
- package/dist/types/requester.d.ts +15 -0
- package/dist/types/requester.js +15 -0
- package/dist/types/service.d.ts +140 -0
- package/dist/types/service.js +48 -0
- package/dist/types/verifier.d.ts +144 -0
- package/dist/types/verifier.js +78 -0
- package/dist/types/x402.d.ts +31 -0
- package/dist/types/x402.js +27 -0
- package/dist/utils/amount.d.ts +2 -0
- package/dist/utils/amount.js +11 -0
- package/dist/utils/artifacts.d.ts +42 -0
- package/dist/utils/artifacts.js +378 -0
- package/dist/utils/category.d.ts +4 -0
- package/dist/utils/category.js +25 -0
- package/dist/utils/date.d.ts +2 -0
- package/dist/utils/date.js +10 -0
- package/dist/utils/hash.d.ts +4 -0
- package/dist/utils/hash.js +27 -0
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/id.js +7 -0
- package/dist/utils/json-file.d.ts +3 -0
- package/dist/utils/json-file.js +20 -0
- package/dist/utils/schema.d.ts +5 -0
- package/dist/utils/schema.js +66 -0
- package/dist/x402/payment.d.ts +11 -0
- package/dist/x402/payment.js +68 -0
- package/dist/zero-g/compute.d.ts +53 -0
- package/dist/zero-g/compute.js +249 -0
- package/dist/zero-g/storage.d.ts +28 -0
- package/dist/zero-g/storage.js +93 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# @halot/sdk
|
|
2
|
+
|
|
3
|
+
`@halot/sdk` is the public SDK for integrating provider endpoints and requester flows with Halot.
|
|
4
|
+
|
|
5
|
+
Use it in 2 cases:
|
|
6
|
+
|
|
7
|
+
- provider apps that want to expose a paid route with `halot()`
|
|
8
|
+
- requester apps that want to automate the x402 flow with `HalotClient`
|
|
9
|
+
|
|
10
|
+
The root import is the public surface:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { halot, HalotClient } from '@halot/sdk';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @halot/sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What It Exports
|
|
23
|
+
|
|
24
|
+
The main public exports are:
|
|
25
|
+
|
|
26
|
+
- `halot`
|
|
27
|
+
- `HalotClient`
|
|
28
|
+
- `createActorAuthHeaders`
|
|
29
|
+
- `decodePaymentRequirement`
|
|
30
|
+
- `encodePaymentRequirement`
|
|
31
|
+
- `signPaymentRequirement`
|
|
32
|
+
- shared types and Zod schemas such as `QuoteSchema`, `PreparedJobSchema`, `JobSchema`, `PaymentRequirementSchema`, and `PaymentAuthorizationSchema`
|
|
33
|
+
|
|
34
|
+
## Provider Middleware
|
|
35
|
+
|
|
36
|
+
`halot()` is the provider integration path for Express routes.
|
|
37
|
+
|
|
38
|
+
It handles:
|
|
39
|
+
|
|
40
|
+
- quote challenges
|
|
41
|
+
- receipt verification
|
|
42
|
+
- result reporting
|
|
43
|
+
|
|
44
|
+
against the Halot facilitator endpoints.
|
|
45
|
+
|
|
46
|
+
### Before you use it
|
|
47
|
+
|
|
48
|
+
The route must use a real registered `serviceId`.
|
|
49
|
+
|
|
50
|
+
The actual order is:
|
|
51
|
+
|
|
52
|
+
1. register the provider
|
|
53
|
+
2. register the service under that provider
|
|
54
|
+
3. use the returned `serviceId` in middleware
|
|
55
|
+
|
|
56
|
+
If you are using middleware for a service, you do not need `halot provider run` for that same service.
|
|
57
|
+
|
|
58
|
+
### Basic usage
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import express from 'express';
|
|
62
|
+
import { halot } from '@halot/sdk';
|
|
63
|
+
|
|
64
|
+
const app = express();
|
|
65
|
+
app.use(express.json());
|
|
66
|
+
|
|
67
|
+
app.post('/text', halot({
|
|
68
|
+
serviceId: 'svc_text_gpt54',
|
|
69
|
+
}), async (req, res) => {
|
|
70
|
+
const { jobId, providerId, requesterAddress } = req.halot;
|
|
71
|
+
const text = await runProviderLogic(req.body.message, {
|
|
72
|
+
jobId,
|
|
73
|
+
providerId,
|
|
74
|
+
requesterAddress,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
res.json({ text });
|
|
78
|
+
});
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### One route, multiple services
|
|
82
|
+
|
|
83
|
+
If one route fronts multiple registered services, resolve `serviceId` from the request:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
app.post('/text', halot({
|
|
87
|
+
serviceId: (req) => {
|
|
88
|
+
switch (req.body.model) {
|
|
89
|
+
case 'gpt-5.4':
|
|
90
|
+
return 'svc_text_gpt54';
|
|
91
|
+
case 'gpt-5.4-mini':
|
|
92
|
+
return 'svc_text_gpt54mini';
|
|
93
|
+
default:
|
|
94
|
+
throw new Error('Unsupported model');
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
}), async (req, res) => {
|
|
98
|
+
const text = await runProviderLogic(req.body.message);
|
|
99
|
+
res.json({ text, model: req.body.model });
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Signing facilitator report requests
|
|
104
|
+
|
|
105
|
+
Use `providerHeaders` when your provider app needs to sign `/facilitator/report` as the registered provider:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import express from 'express';
|
|
109
|
+
import { createActorAuthHeaders, halot } from '@halot/sdk';
|
|
110
|
+
|
|
111
|
+
const app = express();
|
|
112
|
+
app.use(express.json());
|
|
113
|
+
|
|
114
|
+
const providerWallet = {
|
|
115
|
+
address: process.env.PROVIDER_ADDRESS!,
|
|
116
|
+
signMessage: async (message: string) => signWithWallet(message),
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
app.post('/text', halot({
|
|
120
|
+
serviceId: 'svc_text_gpt54',
|
|
121
|
+
providerHeaders: async (_req, context) => createActorAuthHeaders(providerWallet, {
|
|
122
|
+
actorId: context.providerId,
|
|
123
|
+
role: 'provider',
|
|
124
|
+
method: 'POST',
|
|
125
|
+
path: '/facilitator/report',
|
|
126
|
+
timestamp: new Date().toISOString(),
|
|
127
|
+
}),
|
|
128
|
+
}), async (req, res) => {
|
|
129
|
+
const text = await runProviderLogic(req.body.message);
|
|
130
|
+
res.json({ text });
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Middleware flow
|
|
135
|
+
|
|
136
|
+
When a requester calls your middleware route:
|
|
137
|
+
|
|
138
|
+
1. if there is no `x-halot-receipt`, the middleware requests a quote and returns `402`
|
|
139
|
+
2. the requester signs the payment requirement
|
|
140
|
+
3. the requester prepares and funds the job through Halot
|
|
141
|
+
4. the requester retries with `x-halot-receipt`
|
|
142
|
+
5. the middleware verifies that receipt through `/facilitator/verify`
|
|
143
|
+
6. your handler runs
|
|
144
|
+
7. on a 2xx handler response, the middleware reports the output through `/facilitator/report`
|
|
145
|
+
|
|
146
|
+
### Request context
|
|
147
|
+
|
|
148
|
+
Inside a middleware-backed route:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
app.post('/text', halot({ serviceId: 'svc_text_gpt54' }), (req, res) => {
|
|
152
|
+
const { jobId, quoteId, serviceId, providerId, requesterAddress } = req.halot;
|
|
153
|
+
const output = runProviderLogic(req.body.message);
|
|
154
|
+
res.json({ output });
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Options
|
|
159
|
+
|
|
160
|
+
`halot()` supports:
|
|
161
|
+
|
|
162
|
+
- `serviceId`
|
|
163
|
+
- `providerHeaders`
|
|
164
|
+
- `reportMaxAttempts`
|
|
165
|
+
- `reportRetryDelayMs`
|
|
166
|
+
- `onError`
|
|
167
|
+
|
|
168
|
+
## Requester Client
|
|
169
|
+
|
|
170
|
+
`HalotClient` is the requester-side helper.
|
|
171
|
+
|
|
172
|
+
It automates:
|
|
173
|
+
|
|
174
|
+
1. triggering a `402` challenge
|
|
175
|
+
2. decoding and signing the payment requirement
|
|
176
|
+
3. preparing the job
|
|
177
|
+
4. funding the prepared job through your adapter
|
|
178
|
+
5. confirming the funding transaction
|
|
179
|
+
6. retrying the target endpoint with `x-halot-receipt`
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { HalotClient } from '@halot/sdk';
|
|
183
|
+
import { Wallet } from 'ethers';
|
|
184
|
+
|
|
185
|
+
const wallet = new Wallet(process.env.HALOT_PRIVATE_KEY!);
|
|
186
|
+
|
|
187
|
+
const client = new HalotClient({
|
|
188
|
+
wallet: {
|
|
189
|
+
address: wallet.address,
|
|
190
|
+
signMessage: (message) => wallet.signMessage(message),
|
|
191
|
+
},
|
|
192
|
+
funding: {
|
|
193
|
+
async fund(preparedJob) {
|
|
194
|
+
const transactionHash = await fundPreparedJobOnChain(preparedJob);
|
|
195
|
+
return { transactionHash };
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
defaultNetwork: '0g:testnet',
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const response = await client.request<{ text: string }>('https://provider.example/text', {
|
|
202
|
+
method: 'POST',
|
|
203
|
+
body: {
|
|
204
|
+
model: 'gpt-5.4',
|
|
205
|
+
message: 'Summarize the latest verifier assignment state.',
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`response` includes:
|
|
211
|
+
|
|
212
|
+
- `data`
|
|
213
|
+
- `jobId`
|
|
214
|
+
- `status`
|
|
215
|
+
|
|
216
|
+
## Auth and Payment Helpers
|
|
217
|
+
|
|
218
|
+
The SDK also exports helpers for:
|
|
219
|
+
|
|
220
|
+
- actor-signed provider or verifier requests via `createActorAuthHeaders`
|
|
221
|
+
- payment requirement encoding and decoding
|
|
222
|
+
- payment requirement signing
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import {
|
|
226
|
+
createActorAuthHeaders,
|
|
227
|
+
decodePaymentRequirement,
|
|
228
|
+
encodePaymentRequirement,
|
|
229
|
+
signPaymentRequirement,
|
|
230
|
+
} from '@halot/sdk';
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Shared Types and Schemas
|
|
234
|
+
|
|
235
|
+
You can also import shared runtime types and validators such as:
|
|
236
|
+
|
|
237
|
+
- `QuoteSchema`
|
|
238
|
+
- `PreparedJobSchema`
|
|
239
|
+
- `JobSchema`
|
|
240
|
+
- `PaymentRequirementSchema`
|
|
241
|
+
- `PaymentAuthorizationSchema`
|
|
242
|
+
|
|
243
|
+
These are useful when you are building custom clients or validating Halot payloads outside the default middleware and client helpers.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type ActorRole = 'provider' | 'verifier';
|
|
2
|
+
type MessageSigner = {
|
|
3
|
+
address: string;
|
|
4
|
+
signMessage(message: string): Promise<string>;
|
|
5
|
+
};
|
|
6
|
+
export type ActorAuthPayload = {
|
|
7
|
+
actorId: string;
|
|
8
|
+
actorAddress: string;
|
|
9
|
+
role: ActorRole;
|
|
10
|
+
method: string;
|
|
11
|
+
path: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function createActorAuthMessage(payload: ActorAuthPayload): string;
|
|
15
|
+
export declare function createActorAuthHeaders(wallet: MessageSigner, payload: Omit<ActorAuthPayload, 'actorAddress'>): Promise<Record<string, string>>;
|
|
16
|
+
export declare function verifyActorAuth(payload: ActorAuthPayload, signature: string): boolean;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createActorAuthMessage = createActorAuthMessage;
|
|
4
|
+
exports.createActorAuthHeaders = createActorAuthHeaders;
|
|
5
|
+
exports.verifyActorAuth = verifyActorAuth;
|
|
6
|
+
const ethers_1 = require("ethers");
|
|
7
|
+
const hash_1 = require("../utils/hash");
|
|
8
|
+
function createActorAuthMessage(payload) {
|
|
9
|
+
return (0, hash_1.hashValue)({
|
|
10
|
+
actorId: payload.actorId,
|
|
11
|
+
actorAddress: (0, ethers_1.getAddress)(payload.actorAddress),
|
|
12
|
+
role: payload.role,
|
|
13
|
+
method: payload.method.toUpperCase(),
|
|
14
|
+
path: payload.path,
|
|
15
|
+
timestamp: payload.timestamp,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async function createActorAuthHeaders(wallet, payload) {
|
|
19
|
+
const actorAddress = (0, ethers_1.getAddress)(wallet.address);
|
|
20
|
+
const message = createActorAuthMessage({ ...payload, actorAddress });
|
|
21
|
+
const signature = await wallet.signMessage(message);
|
|
22
|
+
return {
|
|
23
|
+
'x-halot-actor-id': payload.actorId,
|
|
24
|
+
'x-halot-actor-address': actorAddress,
|
|
25
|
+
'x-halot-actor-role': payload.role,
|
|
26
|
+
'x-halot-actor-timestamp': payload.timestamp,
|
|
27
|
+
'x-halot-actor-signature': signature,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function verifyActorAuth(payload, signature) {
|
|
31
|
+
const message = createActorAuthMessage(payload);
|
|
32
|
+
return (0, ethers_1.getAddress)((0, ethers_1.verifyMessage)(message, signature)) === (0, ethers_1.getAddress)(payload.actorAddress);
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Decision } from '../types/common';
|
|
2
|
+
type MessageSigner = {
|
|
3
|
+
address: string;
|
|
4
|
+
signMessage(message: Uint8Array | string): Promise<string>;
|
|
5
|
+
};
|
|
6
|
+
export type AttestationSignaturePayload = {
|
|
7
|
+
jobHash: string;
|
|
8
|
+
resultHash: string;
|
|
9
|
+
decision: Decision;
|
|
10
|
+
teeDigest: string;
|
|
11
|
+
};
|
|
12
|
+
export type AttestationRecoveryData = {
|
|
13
|
+
compactSignature: string;
|
|
14
|
+
recoveryId: number;
|
|
15
|
+
publicKey: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function createAttestationVote(payload: Pick<AttestationSignaturePayload, 'decision'>): number;
|
|
18
|
+
export declare function createAttestationSettlementDigest(payload: AttestationSignaturePayload): string;
|
|
19
|
+
export declare function createAttestationMessageHash(payload: AttestationSignaturePayload): string;
|
|
20
|
+
export declare function signAttestationPayload(wallet: MessageSigner, payload: AttestationSignaturePayload): Promise<string>;
|
|
21
|
+
export declare function recoverAttestationPublicKey(payload: AttestationSignaturePayload, signature: string): string;
|
|
22
|
+
export declare function extractAttestationRecoveryData(payload: AttestationSignaturePayload, signature: string): AttestationRecoveryData;
|
|
23
|
+
export declare function verifyAttestationPayload(payload: AttestationSignaturePayload, signature: string, expectedAddress: string): boolean;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createAttestationVote = createAttestationVote;
|
|
4
|
+
exports.createAttestationSettlementDigest = createAttestationSettlementDigest;
|
|
5
|
+
exports.createAttestationMessageHash = createAttestationMessageHash;
|
|
6
|
+
exports.signAttestationPayload = signAttestationPayload;
|
|
7
|
+
exports.recoverAttestationPublicKey = recoverAttestationPublicKey;
|
|
8
|
+
exports.extractAttestationRecoveryData = extractAttestationRecoveryData;
|
|
9
|
+
exports.verifyAttestationPayload = verifyAttestationPayload;
|
|
10
|
+
const ethers_1 = require("ethers");
|
|
11
|
+
function createAttestationVote(payload) {
|
|
12
|
+
if (payload.decision === 'approve') {
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
if (payload.decision === 'reject') {
|
|
16
|
+
return 2;
|
|
17
|
+
}
|
|
18
|
+
return 3;
|
|
19
|
+
}
|
|
20
|
+
function createAttestationSettlementDigest(payload) {
|
|
21
|
+
return (0, ethers_1.solidityPackedKeccak256)(['bytes32', 'bytes32', 'uint8', 'bytes32'], [
|
|
22
|
+
payload.jobHash,
|
|
23
|
+
payload.resultHash,
|
|
24
|
+
createAttestationVote(payload),
|
|
25
|
+
payload.teeDigest,
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
function createAttestationMessageHash(payload) {
|
|
29
|
+
return (0, ethers_1.hashMessage)((0, ethers_1.getBytes)(createAttestationSettlementDigest(payload)));
|
|
30
|
+
}
|
|
31
|
+
async function signAttestationPayload(wallet, payload) {
|
|
32
|
+
return wallet.signMessage((0, ethers_1.getBytes)(createAttestationSettlementDigest(payload)));
|
|
33
|
+
}
|
|
34
|
+
function recoverAttestationPublicKey(payload, signature) {
|
|
35
|
+
return ethers_1.SigningKey.recoverPublicKey(createAttestationMessageHash(payload), signature);
|
|
36
|
+
}
|
|
37
|
+
function extractAttestationRecoveryData(payload, signature) {
|
|
38
|
+
const parsed = ethers_1.Signature.from(signature);
|
|
39
|
+
return {
|
|
40
|
+
compactSignature: parsed.compactSerialized,
|
|
41
|
+
recoveryId: Number(parsed.yParity),
|
|
42
|
+
publicKey: recoverAttestationPublicKey(payload, signature),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function verifyAttestationPayload(payload, signature, expectedAddress) {
|
|
46
|
+
return (0, ethers_1.verifyMessage)((0, ethers_1.getBytes)(createAttestationSettlementDigest(payload)), signature).toLowerCase() === expectedAddress.toLowerCase();
|
|
47
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { PreparedJob } from '../types/job.js';
|
|
2
|
+
type MessageSigner = {
|
|
3
|
+
address: string;
|
|
4
|
+
signMessage(message: string): Promise<string>;
|
|
5
|
+
};
|
|
6
|
+
export type FundingAdapter = {
|
|
7
|
+
fund(preparedJob: PreparedJob): Promise<{
|
|
8
|
+
transactionHash: string;
|
|
9
|
+
}>;
|
|
10
|
+
};
|
|
11
|
+
export type HalotClientOptions = {
|
|
12
|
+
aggregatorUrl?: string;
|
|
13
|
+
wallet: MessageSigner;
|
|
14
|
+
funding: FundingAdapter;
|
|
15
|
+
defaultNetwork?: string;
|
|
16
|
+
};
|
|
17
|
+
export type HalotResponse<T = unknown> = {
|
|
18
|
+
data: T;
|
|
19
|
+
jobId: string;
|
|
20
|
+
status: number;
|
|
21
|
+
};
|
|
22
|
+
export declare class HalotClient {
|
|
23
|
+
private readonly aggregatorUrl;
|
|
24
|
+
private readonly wallet;
|
|
25
|
+
private readonly funding;
|
|
26
|
+
private readonly defaultNetwork?;
|
|
27
|
+
constructor(options: HalotClientOptions);
|
|
28
|
+
request<T = unknown>(url: string, options?: {
|
|
29
|
+
method?: string;
|
|
30
|
+
body?: unknown;
|
|
31
|
+
headers?: Record<string, string>;
|
|
32
|
+
network?: string;
|
|
33
|
+
}): Promise<HalotResponse<T>>;
|
|
34
|
+
private prepareJob;
|
|
35
|
+
private confirmJob;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HalotClient = void 0;
|
|
4
|
+
const config_js_1 = require("../config.js");
|
|
5
|
+
const payment_js_1 = require("../x402/payment.js");
|
|
6
|
+
const payment_js_2 = require("../x402/payment.js");
|
|
7
|
+
class HalotClient {
|
|
8
|
+
aggregatorUrl;
|
|
9
|
+
wallet;
|
|
10
|
+
funding;
|
|
11
|
+
defaultNetwork;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.aggregatorUrl = (0, config_js_1.resolveHalotAggregatorUrl)(options.aggregatorUrl);
|
|
14
|
+
this.wallet = options.wallet;
|
|
15
|
+
this.funding = options.funding;
|
|
16
|
+
this.defaultNetwork = options.defaultNetwork;
|
|
17
|
+
}
|
|
18
|
+
async request(url, options = {}) {
|
|
19
|
+
const method = options.method ?? 'POST';
|
|
20
|
+
const network = options.network ?? this.defaultNetwork;
|
|
21
|
+
const initialResponse = await fetch(url, {
|
|
22
|
+
method,
|
|
23
|
+
headers: { 'Content-Type': 'application/json', ...options.headers },
|
|
24
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
if (initialResponse.status !== 402) {
|
|
27
|
+
const data = await initialResponse.json();
|
|
28
|
+
return { data, jobId: '', status: initialResponse.status };
|
|
29
|
+
}
|
|
30
|
+
const paymentRequiredHeader = initialResponse.headers.get('payment-required');
|
|
31
|
+
const body = await initialResponse.json();
|
|
32
|
+
const aggregatorUrl = (0, config_js_1.resolveHalotAggregatorUrl)(body.aggregatorUrl ?? this.aggregatorUrl);
|
|
33
|
+
let requirement;
|
|
34
|
+
if (paymentRequiredHeader) {
|
|
35
|
+
requirement = (0, payment_js_2.decodePaymentRequirement)(paymentRequiredHeader);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
throw new Error('402 response missing PAYMENT-REQUIRED header');
|
|
39
|
+
}
|
|
40
|
+
const authorization = await (0, payment_js_1.signPaymentRequirement)(this.wallet, requirement, network);
|
|
41
|
+
const preparedJob = await this.prepareJob(aggregatorUrl, requirement.quoteId, authorization);
|
|
42
|
+
const { transactionHash } = await this.funding.fund(preparedJob);
|
|
43
|
+
const jobId = await this.confirmJob(aggregatorUrl, preparedJob.prepareId, transactionHash);
|
|
44
|
+
const retryResponse = await fetch(url, {
|
|
45
|
+
method,
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'application/json',
|
|
48
|
+
'x-halot-receipt': jobId,
|
|
49
|
+
...options.headers,
|
|
50
|
+
},
|
|
51
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
52
|
+
});
|
|
53
|
+
const data = await retryResponse.json();
|
|
54
|
+
return { data, jobId, status: retryResponse.status };
|
|
55
|
+
}
|
|
56
|
+
async prepareJob(aggregatorUrl, quoteId, authorization) {
|
|
57
|
+
const encoded = Buffer.from(JSON.stringify(authorization)).toString('base64');
|
|
58
|
+
const response = await fetch(`${aggregatorUrl}/jobs/prepare`, {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'PAYMENT-SIGNATURE': encoded,
|
|
63
|
+
},
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
quoteId,
|
|
66
|
+
settlementAddress: authorization.requesterAddress,
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const error = await response.text();
|
|
71
|
+
throw new Error(`Job preparation failed (${response.status}): ${error}`);
|
|
72
|
+
}
|
|
73
|
+
return response.json();
|
|
74
|
+
}
|
|
75
|
+
async confirmJob(aggregatorUrl, prepareId, transactionHash) {
|
|
76
|
+
const response = await fetch(`${aggregatorUrl}/jobs`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({ prepareId, transactionHash }),
|
|
80
|
+
});
|
|
81
|
+
if (!response.ok) {
|
|
82
|
+
const error = await response.text();
|
|
83
|
+
throw new Error(`Job confirmation failed (${response.status}): ${error}`);
|
|
84
|
+
}
|
|
85
|
+
const job = await response.json();
|
|
86
|
+
return job.jobId;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.HalotClient = HalotClient;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HalotClient = void 0;
|
|
4
|
+
var halot_client_js_1 = require("./halot-client.js");
|
|
5
|
+
Object.defineProperty(exports, "HalotClient", { enumerable: true, get: function () { return halot_client_js_1.HalotClient; } });
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_HALOT_AGGREGATOR_URL = void 0;
|
|
4
|
+
exports.resolveHalotAggregatorUrl = resolveHalotAggregatorUrl;
|
|
5
|
+
exports.DEFAULT_HALOT_AGGREGATOR_URL = 'https://api.halot.xyz';
|
|
6
|
+
function resolveHalotAggregatorUrl(override) {
|
|
7
|
+
return normalizeBaseUrl(override ?? exports.DEFAULT_HALOT_AGGREGATOR_URL);
|
|
8
|
+
}
|
|
9
|
+
function normalizeBaseUrl(url) {
|
|
10
|
+
return url.replace(/\/+$/, '');
|
|
11
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export * from './types/common';
|
|
2
|
+
export * from './types/provider';
|
|
3
|
+
export * from './types/requester';
|
|
4
|
+
export * from './types/service';
|
|
5
|
+
export * from './types/verifier';
|
|
6
|
+
export * from './types/quote';
|
|
7
|
+
export * from './types/job';
|
|
8
|
+
export * from './types/x402';
|
|
9
|
+
export * from './config';
|
|
10
|
+
export * from './utils/date';
|
|
11
|
+
export * from './utils/amount';
|
|
12
|
+
export * from './utils/category';
|
|
13
|
+
export * from './utils/hash';
|
|
14
|
+
export * from './utils/id';
|
|
15
|
+
export * from './utils/json-file';
|
|
16
|
+
export * from './utils/schema';
|
|
17
|
+
export * from './utils/artifacts';
|
|
18
|
+
export * from './auth/actor';
|
|
19
|
+
export * from './auth/attestation';
|
|
20
|
+
export * from './x402/payment';
|
|
21
|
+
export * from './zero-g/compute';
|
|
22
|
+
export * from './zero-g/storage';
|
|
23
|
+
export { halot } from './middleware/halot';
|
|
24
|
+
export type { HalotOptions, HalotRequestContext, ProviderHeadersResolver, ServiceIdResolver } from './middleware/halot';
|
|
25
|
+
export { HalotClient } from './client/halot-client';
|
|
26
|
+
export type { HalotClientOptions, HalotResponse, FundingAdapter } from './client/halot-client';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.HalotClient = exports.halot = void 0;
|
|
18
|
+
__exportStar(require("./types/common"), exports);
|
|
19
|
+
__exportStar(require("./types/provider"), exports);
|
|
20
|
+
__exportStar(require("./types/requester"), exports);
|
|
21
|
+
__exportStar(require("./types/service"), exports);
|
|
22
|
+
__exportStar(require("./types/verifier"), exports);
|
|
23
|
+
__exportStar(require("./types/quote"), exports);
|
|
24
|
+
__exportStar(require("./types/job"), exports);
|
|
25
|
+
__exportStar(require("./types/x402"), exports);
|
|
26
|
+
__exportStar(require("./config"), exports);
|
|
27
|
+
__exportStar(require("./utils/date"), exports);
|
|
28
|
+
__exportStar(require("./utils/amount"), exports);
|
|
29
|
+
__exportStar(require("./utils/category"), exports);
|
|
30
|
+
__exportStar(require("./utils/hash"), exports);
|
|
31
|
+
__exportStar(require("./utils/id"), exports);
|
|
32
|
+
__exportStar(require("./utils/json-file"), exports);
|
|
33
|
+
__exportStar(require("./utils/schema"), exports);
|
|
34
|
+
__exportStar(require("./utils/artifacts"), exports);
|
|
35
|
+
__exportStar(require("./auth/actor"), exports);
|
|
36
|
+
__exportStar(require("./auth/attestation"), exports);
|
|
37
|
+
__exportStar(require("./x402/payment"), exports);
|
|
38
|
+
__exportStar(require("./zero-g/compute"), exports);
|
|
39
|
+
__exportStar(require("./zero-g/storage"), exports);
|
|
40
|
+
var halot_1 = require("./middleware/halot");
|
|
41
|
+
Object.defineProperty(exports, "halot", { enumerable: true, get: function () { return halot_1.halot; } });
|
|
42
|
+
var halot_client_1 = require("./client/halot-client");
|
|
43
|
+
Object.defineProperty(exports, "HalotClient", { enumerable: true, get: function () { return halot_client_1.HalotClient; } });
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FacilitateQuoteResponse, FacilitateVerifyResponse } from './types.js';
|
|
2
|
+
export declare class AggregatorClient {
|
|
3
|
+
private readonly baseUrl;
|
|
4
|
+
constructor(baseUrl: string);
|
|
5
|
+
quote(serviceId: string, input?: unknown): Promise<FacilitateQuoteResponse>;
|
|
6
|
+
verify(jobId: string): Promise<FacilitateVerifyResponse>;
|
|
7
|
+
report(jobId: string, providerId: string, output: unknown, providerHeaders?: Record<string, string>): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AggregatorClient = void 0;
|
|
4
|
+
class AggregatorClient {
|
|
5
|
+
baseUrl;
|
|
6
|
+
constructor(baseUrl) {
|
|
7
|
+
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
|
8
|
+
}
|
|
9
|
+
async quote(serviceId, input) {
|
|
10
|
+
const response = await fetch(`${this.baseUrl}/facilitator/quote`, {
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13
|
+
body: JSON.stringify({ serviceId, input }),
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) {
|
|
16
|
+
const body = await response.text();
|
|
17
|
+
throw new Error(`Aggregator quote failed (${response.status}): ${body}`);
|
|
18
|
+
}
|
|
19
|
+
return response.json();
|
|
20
|
+
}
|
|
21
|
+
async verify(jobId) {
|
|
22
|
+
const response = await fetch(`${this.baseUrl}/facilitator/verify`, {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ jobId }),
|
|
26
|
+
});
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
const body = await response.text();
|
|
29
|
+
throw new Error(`Aggregator verify failed (${response.status}): ${body}`);
|
|
30
|
+
}
|
|
31
|
+
return response.json();
|
|
32
|
+
}
|
|
33
|
+
async report(jobId, providerId, output, providerHeaders) {
|
|
34
|
+
const extraHeaders = providerHeaders ?? {};
|
|
35
|
+
const response = await fetch(`${this.baseUrl}/facilitator/report`, {
|
|
36
|
+
method: 'POST',
|
|
37
|
+
headers: {
|
|
38
|
+
'Content-Type': 'application/json',
|
|
39
|
+
...extraHeaders,
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify({ jobId, providerId, output }),
|
|
42
|
+
});
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
const body = await response.text();
|
|
45
|
+
throw new Error(`Aggregator report failed (${response.status}): ${body}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.AggregatorClient = AggregatorClient;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { NextFunction, Request, Response } from 'express';
|
|
2
|
+
import type { HalotOptions, HalotRequestContext, ProviderHeadersResolver, ServiceIdResolver } from './types.js';
|
|
3
|
+
export declare function halot(options: HalotOptions): (request: Request, response: Response, next: NextFunction) => Promise<void>;
|
|
4
|
+
export type { HalotOptions, HalotRequestContext, ProviderHeadersResolver, ServiceIdResolver };
|