@arc402/sdk 0.3.0 → 0.4.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 CHANGED
@@ -150,6 +150,45 @@ const dispute = await agreement.getDisputeCase(agreementId);
150
150
  console.log({ remediation, dispute });
151
151
  ```
152
152
 
153
+ ## File Delivery
154
+
155
+ Files are **private by default** — only the keccak256 bundle hash is published on-chain. Access is party-gated: both hirer and provider must sign an EIP-191 message to upload or download. The arbitrator receives a time-limited token for dispute resolution.
156
+
157
+ The `DeliveryClient` wraps the daemon's delivery endpoints (running at `localhost:4402` by default):
158
+
159
+ | Method | Path | Description |
160
+ |--------|------|-------------|
161
+ | `POST` | `/job/:id/upload` | Upload a deliverable file |
162
+ | `GET` | `/job/:id/files/:name` | Download a specific file |
163
+ | `GET` | `/job/:id/manifest` | Fetch delivery manifest with hashes |
164
+
165
+ ```ts
166
+ import { DeliveryClient } from "@arc402/sdk";
167
+
168
+ const delivery = new DeliveryClient(); // default: http://localhost:4402
169
+
170
+ // Provider: upload deliverables
171
+ const file = await delivery.uploadDeliverable(42n, "./report.pdf", providerSigner);
172
+ console.log(file.hash); // keccak256 of the uploaded file
173
+
174
+ // Then commit the bundle hash on-chain (CLI does this automatically with `arc402 deliver`)
175
+ await agreement.commitDeliverable(42n, manifest.bundleHash);
176
+
177
+ // Hirer: fetch manifest and verify delivery integrity against the on-chain hash
178
+ const onChainHash = (await agreement.getAgreement(42n)).deliverableHash;
179
+ const result = await delivery.verifyDelivery(42n, onChainHash, hirerSigner, "./downloads/");
180
+ if (result.ok) {
181
+ await agreement.verifyDeliverable(42n); // release escrow
182
+ } else {
183
+ console.error("Hash mismatches:", result.mismatches);
184
+ }
185
+
186
+ // Download a single file
187
+ const outPath = await delivery.downloadDeliverable(42n, "report.pdf", "./downloads/", hirerSigner);
188
+ ```
189
+
190
+ The CLI shortcut: `arc402 deliver <id> --output <file>` uploads files to the delivery layer and submits the bundle hash on-chain in one step.
191
+
153
192
  ## Sponsorship + governance + operational context
154
193
 
155
194
  ```ts
@@ -170,6 +209,24 @@ const tx0 = await governance.getTransaction(0n);
170
209
  console.log({ highestTier, metrics, tx0 });
171
210
  ```
172
211
 
212
+ ## Compute + Subscription
213
+
214
+ The SDK ships mainnet addresses as named exports so you never need to hardcode them:
215
+
216
+ ```ts
217
+ import {
218
+ ComputeAgreementClient,
219
+ COMPUTE_AGREEMENT_ADDRESS,
220
+ SUBSCRIPTION_AGREEMENT_ADDRESS,
221
+ } from "@arc402/sdk";
222
+
223
+ const compute = new ComputeAgreementClient(COMPUTE_AGREEMENT_ADDRESS, signer);
224
+ ```
225
+
226
+ `ComputeAgreementClient` — propose, accept, and settle GPU compute sessions on Base mainnet (chain 8453).
227
+
228
+ `SUBSCRIPTION_AGREEMENT_ADDRESS` — Base mainnet address for the SubscriptionAgreement contract. A `SubscriptionAgreementClient` wrapper is on the roadmap; use the raw address with ethers until then.
229
+
173
230
  ## Notes
174
231
 
175
232
  - The default settlement flow is propose -> accept -> commitDeliverable -> verifyDeliverable/autoRelease, with remediation required before dispute in normal cases. Direct dispute is reserved for explicit hard-fail cases: no delivery, hard deadline breach, clearly invalid/fraudulent deliverables, or safety-critical violations.
@@ -0,0 +1,43 @@
1
+ import { ethers, ContractTransactionResponse } from "ethers";
2
+ export interface ComputeSession {
3
+ client: string;
4
+ provider: string;
5
+ token: string;
6
+ ratePerHour: bigint;
7
+ maxHours: bigint;
8
+ depositAmount: bigint;
9
+ startedAt: bigint;
10
+ endedAt: bigint;
11
+ consumedMinutes: bigint;
12
+ proposedAt: bigint;
13
+ disputedAt: bigint;
14
+ gpuSpecHash: string;
15
+ status: number;
16
+ }
17
+ export interface ComputeUsageReport {
18
+ periodStart: bigint;
19
+ periodEnd: bigint;
20
+ computeMinutes: bigint;
21
+ avgUtilization: bigint;
22
+ providerSignature: string;
23
+ metricsHash: string;
24
+ }
25
+ export declare class ComputeAgreementClient {
26
+ private contract;
27
+ constructor(contractAddress: string, signerOrProvider: ethers.Signer | ethers.Provider);
28
+ proposeSession(sessionId: string, provider: string, ratePerHour: bigint, maxHours: bigint, gpuSpecHash: string, token: string, deposit: bigint): Promise<ContractTransactionResponse>;
29
+ acceptSession(sessionId: string): Promise<ContractTransactionResponse>;
30
+ startSession(sessionId: string): Promise<ContractTransactionResponse>;
31
+ submitUsageReport(sessionId: string, periodStart: bigint, periodEnd: bigint, computeMinutes: bigint, avgUtilization: bigint, providerSignature: string, metricsHash: string): Promise<ContractTransactionResponse>;
32
+ endSession(sessionId: string): Promise<ContractTransactionResponse>;
33
+ disputeSession(sessionId: string): Promise<ContractTransactionResponse>;
34
+ cancelSession(sessionId: string): Promise<ContractTransactionResponse>;
35
+ resolveDispute(sessionId: string, providerAmount: bigint, clientAmount: bigint): Promise<ContractTransactionResponse>;
36
+ claimDisputeTimeout(sessionId: string): Promise<ContractTransactionResponse>;
37
+ withdraw(token: string): Promise<ContractTransactionResponse>;
38
+ getSession(sessionId: string): Promise<ComputeSession>;
39
+ calculateCost(sessionId: string): Promise<bigint>;
40
+ getUsageReports(sessionId: string): Promise<ComputeUsageReport[]>;
41
+ pendingWithdrawals(user: string, token: string): Promise<bigint>;
42
+ }
43
+ //# sourceMappingURL=compute.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute.d.ts","sourceRoot":"","sources":["../src/compute.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,2BAA2B,EAAE,MAAM,QAAQ,CAAC;AAuB7D,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAID,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAkB;gBAEtB,eAAe,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ;IAIhF,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,2BAA2B,CAAC;IAQjC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAItE,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAIrE,iBAAiB,CACrB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,iBAAiB,EAAE,MAAM,EACzB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,2BAA2B,CAAC;IAMjC,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAInE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAIvE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAItE,cAAc,CAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,2BAA2B,CAAC;IAIjC,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAI5E,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,CAAC;IAI7D,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAItD,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIjD,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC;IAIjE,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAGvE"}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ComputeAgreementClient = void 0;
4
+ const ethers_1 = require("ethers");
5
+ // ─── ABI ──────────────────────────────────────────────────────────────────────
6
+ const COMPUTE_AGREEMENT_ABI = [
7
+ "function proposeSession(bytes32 sessionId, address provider, uint256 ratePerHour, uint256 maxHours, bytes32 gpuSpecHash, address token) external payable",
8
+ "function acceptSession(bytes32 sessionId) external",
9
+ "function startSession(bytes32 sessionId) external",
10
+ "function submitUsageReport(bytes32 sessionId, uint256 periodStart, uint256 periodEnd, uint256 computeMinutes, uint256 avgUtilization, bytes providerSignature, bytes32 metricsHash) external",
11
+ "function endSession(bytes32 sessionId) external",
12
+ "function disputeSession(bytes32 sessionId) external",
13
+ "function cancelSession(bytes32 sessionId) external",
14
+ "function resolveDispute(bytes32 sessionId, uint256 providerAmount, uint256 clientAmount) external",
15
+ "function claimDisputeTimeout(bytes32 sessionId) external",
16
+ "function withdraw(address token) external",
17
+ "function getSession(bytes32 sessionId) external view returns (tuple(address client, address provider, address token, uint256 ratePerHour, uint256 maxHours, uint256 depositAmount, uint256 startedAt, uint256 endedAt, uint256 consumedMinutes, uint256 proposedAt, uint256 disputedAt, bytes32 gpuSpecHash, uint8 status))",
18
+ "function calculateCost(bytes32 sessionId) external view returns (uint256)",
19
+ "function getUsageReports(bytes32 sessionId) external view returns (tuple(uint256 periodStart, uint256 periodEnd, uint256 computeMinutes, uint256 avgUtilization, bytes providerSignature, bytes32 metricsHash)[])",
20
+ "function pendingWithdrawals(address user, address token) external view returns (uint256)",
21
+ ];
22
+ // ─── Client ───────────────────────────────────────────────────────────────────
23
+ class ComputeAgreementClient {
24
+ constructor(contractAddress, signerOrProvider) {
25
+ this.contract = new ethers_1.ethers.Contract(contractAddress, COMPUTE_AGREEMENT_ABI, signerOrProvider);
26
+ }
27
+ async proposeSession(sessionId, provider, ratePerHour, maxHours, gpuSpecHash, token, deposit) {
28
+ const isEth = token === ethers_1.ethers.ZeroAddress;
29
+ return this.contract.proposeSession(sessionId, provider, ratePerHour, maxHours, gpuSpecHash, token, { value: isEth ? deposit : 0n });
30
+ }
31
+ async acceptSession(sessionId) {
32
+ return this.contract.acceptSession(sessionId);
33
+ }
34
+ async startSession(sessionId) {
35
+ return this.contract.startSession(sessionId);
36
+ }
37
+ async submitUsageReport(sessionId, periodStart, periodEnd, computeMinutes, avgUtilization, providerSignature, metricsHash) {
38
+ return this.contract.submitUsageReport(sessionId, periodStart, periodEnd, computeMinutes, avgUtilization, providerSignature, metricsHash);
39
+ }
40
+ async endSession(sessionId) {
41
+ return this.contract.endSession(sessionId);
42
+ }
43
+ async disputeSession(sessionId) {
44
+ return this.contract.disputeSession(sessionId);
45
+ }
46
+ async cancelSession(sessionId) {
47
+ return this.contract.cancelSession(sessionId);
48
+ }
49
+ async resolveDispute(sessionId, providerAmount, clientAmount) {
50
+ return this.contract.resolveDispute(sessionId, providerAmount, clientAmount);
51
+ }
52
+ async claimDisputeTimeout(sessionId) {
53
+ return this.contract.claimDisputeTimeout(sessionId);
54
+ }
55
+ async withdraw(token) {
56
+ return this.contract.withdraw(token);
57
+ }
58
+ async getSession(sessionId) {
59
+ return this.contract.getSession(sessionId);
60
+ }
61
+ async calculateCost(sessionId) {
62
+ return this.contract.calculateCost(sessionId);
63
+ }
64
+ async getUsageReports(sessionId) {
65
+ return this.contract.getUsageReports(sessionId);
66
+ }
67
+ async pendingWithdrawals(user, token) {
68
+ return this.contract.pendingWithdrawals(user, token);
69
+ }
70
+ }
71
+ exports.ComputeAgreementClient = ComputeAgreementClient;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * File Delivery Layer — wraps daemon HTTP endpoints for content-addressed
3
+ * file delivery with party-gated access (EIP-191 signatures).
4
+ *
5
+ * Files are private by default. Only the keccak256 bundle hash is published
6
+ * on-chain. Downloads require a valid EIP-191 signature from either the hirer
7
+ * or the provider. Arbitrators receive a time-limited token for dispute access.
8
+ *
9
+ * Daemon endpoints:
10
+ * POST /job/:id/upload — upload a deliverable file
11
+ * GET /job/:id/files — list all delivered files
12
+ * GET /job/:id/files/:name — download a specific file
13
+ * GET /job/:id/manifest — fetch the delivery manifest
14
+ */
15
+ import { ethers } from "ethers";
16
+ export declare const DEFAULT_DAEMON_URL = "http://localhost:4402";
17
+ export interface DeliveryFile {
18
+ name: string;
19
+ hash: string;
20
+ size: number;
21
+ }
22
+ export interface DeliveryManifest {
23
+ agreementId: string;
24
+ files: DeliveryFile[];
25
+ bundleHash: string;
26
+ }
27
+ export interface DeliveryClientOptions {
28
+ /** Daemon base URL. Defaults to http://localhost:4402. */
29
+ daemonUrl?: string;
30
+ }
31
+ export declare class DeliveryClient {
32
+ private daemonUrl;
33
+ constructor(options?: DeliveryClientOptions);
34
+ /** Sign the standard auth message for a job. Used for upload and download. */
35
+ private signAuth;
36
+ /**
37
+ * Upload a file as a deliverable for the agreement.
38
+ * Returns the file entry recorded by the daemon (name, keccak256 hash, size).
39
+ * Auth: EIP-191 signature from the provider's signer.
40
+ *
41
+ * After uploading all files, commit the manifest bundleHash on-chain via
42
+ * ServiceAgreementClient.commitDeliverable(). The CLI does this automatically
43
+ * when you run `arc402 deliver <id>`.
44
+ */
45
+ uploadDeliverable(agreementId: bigint | string, filePath: string, signer: ethers.Signer): Promise<DeliveryFile>;
46
+ /**
47
+ * Download a named file from the agreement's delivery.
48
+ * Writes the file to <outputDir>/<name> and returns the output path.
49
+ * Auth: EIP-191 signature from the hirer's or provider's signer.
50
+ */
51
+ downloadDeliverable(agreementId: bigint | string, fileName: string, outputDir: string, signer: ethers.Signer): Promise<string>;
52
+ /**
53
+ * Fetch the delivery manifest for an agreement.
54
+ * Lists all delivered files with their keccak256 hashes and the overall bundleHash.
55
+ * Auth: EIP-191 signature from the hirer's or provider's signer.
56
+ */
57
+ getManifest(agreementId: bigint | string, signer: ethers.Signer): Promise<DeliveryManifest>;
58
+ /**
59
+ * Download all files and verify their keccak256 hashes against the manifest.
60
+ * Also checks that the manifest bundleHash matches the expectedBundleHash
61
+ * (which should equal the value committed on-chain via commitDeliverable).
62
+ *
63
+ * Returns { ok: true } when all hashes match, or
64
+ * { ok: false, mismatches: [...] } listing any files that failed.
65
+ */
66
+ verifyDelivery(agreementId: bigint | string, expectedBundleHash: string, signer: ethers.Signer, outputDir: string): Promise<{
67
+ ok: boolean;
68
+ mismatches: string[];
69
+ }>;
70
+ }
71
+ //# sourceMappingURL=delivery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delivery.d.ts","sourceRoot":"","sources":["../src/delivery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAIhC,eAAO,MAAM,kBAAkB,0BAA0B,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,CAAC,EAAE,qBAAqB;IAI3C,8EAA8E;YAChE,QAAQ;IAItB;;;;;;;;OAQG;IACG,iBAAiB,CACrB,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,GACpB,OAAO,CAAC,YAAY,CAAC;IAiBxB;;;;OAIG;IACG,mBAAmB,CACvB,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,GACpB,OAAO,CAAC,MAAM,CAAC;IAelB;;;;OAIG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAWjG;;;;;;;OAOG;IACG,cAAc,CAClB,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAmBlD"}
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DeliveryClient = exports.DEFAULT_DAEMON_URL = void 0;
7
+ /**
8
+ * File Delivery Layer — wraps daemon HTTP endpoints for content-addressed
9
+ * file delivery with party-gated access (EIP-191 signatures).
10
+ *
11
+ * Files are private by default. Only the keccak256 bundle hash is published
12
+ * on-chain. Downloads require a valid EIP-191 signature from either the hirer
13
+ * or the provider. Arbitrators receive a time-limited token for dispute access.
14
+ *
15
+ * Daemon endpoints:
16
+ * POST /job/:id/upload — upload a deliverable file
17
+ * GET /job/:id/files — list all delivered files
18
+ * GET /job/:id/files/:name — download a specific file
19
+ * GET /job/:id/manifest — fetch the delivery manifest
20
+ */
21
+ const ethers_1 = require("ethers");
22
+ const promises_1 = require("fs/promises");
23
+ const path_1 = __importDefault(require("path"));
24
+ exports.DEFAULT_DAEMON_URL = "http://localhost:4402";
25
+ class DeliveryClient {
26
+ constructor(options) {
27
+ this.daemonUrl = (options?.daemonUrl ?? exports.DEFAULT_DAEMON_URL).replace(/\/$/, "");
28
+ }
29
+ /** Sign the standard auth message for a job. Used for upload and download. */
30
+ async signAuth(agreementId, signer) {
31
+ return signer.signMessage(`arc402:job:${agreementId}`);
32
+ }
33
+ /**
34
+ * Upload a file as a deliverable for the agreement.
35
+ * Returns the file entry recorded by the daemon (name, keccak256 hash, size).
36
+ * Auth: EIP-191 signature from the provider's signer.
37
+ *
38
+ * After uploading all files, commit the manifest bundleHash on-chain via
39
+ * ServiceAgreementClient.commitDeliverable(). The CLI does this automatically
40
+ * when you run `arc402 deliver <id>`.
41
+ */
42
+ async uploadDeliverable(agreementId, filePath, signer) {
43
+ const signature = await this.signAuth(agreementId, signer);
44
+ const address = await signer.getAddress();
45
+ const filename = path_1.default.basename(filePath);
46
+ const fileBuffer = await (0, promises_1.readFile)(filePath);
47
+ const form = new FormData();
48
+ form.append("file", new Blob([fileBuffer]), filename);
49
+ form.append("address", address);
50
+ form.append("signature", signature);
51
+ const res = await fetch(`${this.daemonUrl}/job/${agreementId}/upload`, {
52
+ method: "POST",
53
+ body: form,
54
+ });
55
+ if (!res.ok)
56
+ throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
57
+ return (await res.json());
58
+ }
59
+ /**
60
+ * Download a named file from the agreement's delivery.
61
+ * Writes the file to <outputDir>/<name> and returns the output path.
62
+ * Auth: EIP-191 signature from the hirer's or provider's signer.
63
+ */
64
+ async downloadDeliverable(agreementId, fileName, outputDir, signer) {
65
+ const signature = await this.signAuth(agreementId, signer);
66
+ const address = await signer.getAddress();
67
+ const res = await fetch(`${this.daemonUrl}/job/${agreementId}/files/${encodeURIComponent(fileName)}`, { headers: { "X-Arc402-Address": address, "X-Arc402-Signature": signature } });
68
+ if (!res.ok)
69
+ throw new Error(`Download failed: ${res.status} ${res.statusText}`);
70
+ const buffer = Buffer.from(await res.arrayBuffer());
71
+ await (0, promises_1.mkdir)(outputDir, { recursive: true });
72
+ const outPath = path_1.default.join(outputDir, fileName);
73
+ await (0, promises_1.writeFile)(outPath, buffer);
74
+ return outPath;
75
+ }
76
+ /**
77
+ * Fetch the delivery manifest for an agreement.
78
+ * Lists all delivered files with their keccak256 hashes and the overall bundleHash.
79
+ * Auth: EIP-191 signature from the hirer's or provider's signer.
80
+ */
81
+ async getManifest(agreementId, signer) {
82
+ const signature = await this.signAuth(agreementId, signer);
83
+ const address = await signer.getAddress();
84
+ const res = await fetch(`${this.daemonUrl}/job/${agreementId}/manifest`, { headers: { "X-Arc402-Address": address, "X-Arc402-Signature": signature } });
85
+ if (!res.ok)
86
+ throw new Error(`Manifest fetch failed: ${res.status} ${res.statusText}`);
87
+ return (await res.json());
88
+ }
89
+ /**
90
+ * Download all files and verify their keccak256 hashes against the manifest.
91
+ * Also checks that the manifest bundleHash matches the expectedBundleHash
92
+ * (which should equal the value committed on-chain via commitDeliverable).
93
+ *
94
+ * Returns { ok: true } when all hashes match, or
95
+ * { ok: false, mismatches: [...] } listing any files that failed.
96
+ */
97
+ async verifyDelivery(agreementId, expectedBundleHash, signer, outputDir) {
98
+ const manifest = await this.getManifest(agreementId, signer);
99
+ if (manifest.bundleHash.toLowerCase() !== expectedBundleHash.toLowerCase()) {
100
+ return {
101
+ ok: false,
102
+ mismatches: [`bundleHash mismatch: expected ${expectedBundleHash}, got ${manifest.bundleHash}`],
103
+ };
104
+ }
105
+ const mismatches = [];
106
+ for (const file of manifest.files) {
107
+ const outPath = await this.downloadDeliverable(agreementId, file.name, outputDir, signer);
108
+ const buffer = await (0, promises_1.readFile)(outPath);
109
+ const localHash = ethers_1.ethers.keccak256(buffer);
110
+ if (localHash.toLowerCase() !== file.hash.toLowerCase()) {
111
+ mismatches.push(`${file.name}: expected ${file.hash}, got ${localHash}`);
112
+ }
113
+ }
114
+ return { ok: mismatches.length === 0, mismatches };
115
+ }
116
+ }
117
+ exports.DeliveryClient = DeliveryClient;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * HTTP endpoint helpers — resolve an agent's endpoint from AgentRegistry
3
+ * and notify it after onchain events (hire, handshake).
4
+ */
5
+ import { ethers } from "ethers";
6
+ export declare const DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
7
+ export interface EndpointNotifyResult {
8
+ ok: boolean;
9
+ status?: number;
10
+ error?: string;
11
+ }
12
+ /**
13
+ * Reads an agent's public HTTP endpoint from AgentRegistry.
14
+ * Returns an empty string if the agent is not registered or has no endpoint.
15
+ */
16
+ export declare function resolveEndpoint(agentAddress: string, provider: ethers.Provider, registryAddress?: string): Promise<string>;
17
+ /**
18
+ * POSTs a JSON payload to `${endpoint}${path}`.
19
+ * Returns { ok, status } on success, { ok: false, error } on failure.
20
+ * Never throws.
21
+ */
22
+ export declare function notifyEndpoint(endpoint: string, path: string, payload: Record<string, unknown>): Promise<EndpointNotifyResult>;
23
+ /**
24
+ * Convenience: resolve an agent's endpoint then POST to /hire.
25
+ */
26
+ export declare function notifyHire(agentAddress: string, proposal: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
27
+ /**
28
+ * Convenience: resolve an agent's endpoint then POST to /handshake.
29
+ */
30
+ export declare function notifyHandshake(agentAddress: string, payload: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
31
+ /**
32
+ * Convenience: resolve an agent's endpoint then POST to /hire/accepted.
33
+ */
34
+ export declare function notifyHireAccepted(agentAddress: string, payload: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
35
+ /**
36
+ * Convenience: resolve an agent's endpoint then POST to /delivery.
37
+ */
38
+ export declare function notifyDelivery(agentAddress: string, payload: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
39
+ /**
40
+ * Convenience: resolve an agent's endpoint then POST to /delivery/accepted.
41
+ */
42
+ export declare function notifyDeliveryAccepted(agentAddress: string, payload: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
43
+ /**
44
+ * Convenience: resolve an agent's endpoint then POST to /dispute.
45
+ */
46
+ export declare function notifyDispute(agentAddress: string, payload: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
47
+ /**
48
+ * Convenience: resolve an agent's endpoint then POST to /message.
49
+ */
50
+ export declare function notifyMessage(agentAddress: string, payload: Record<string, unknown>, provider: ethers.Provider, registryAddress?: string): Promise<EndpointNotifyResult>;
51
+ //# sourceMappingURL=endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../src/endpoint.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,eAAO,MAAM,wBAAwB,+CAA+C,CAAC;AAErF,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,MAAM,CAAC,CAIjB;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,oBAAoB,CAAC,CAY/B;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EACzB,eAAe,SAA2B,GACzC,OAAO,CAAC,oBAAoB,CAAC,CAG/B"}
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_REGISTRY_ADDRESS = void 0;
4
+ exports.resolveEndpoint = resolveEndpoint;
5
+ exports.notifyEndpoint = notifyEndpoint;
6
+ exports.notifyHire = notifyHire;
7
+ exports.notifyHandshake = notifyHandshake;
8
+ exports.notifyHireAccepted = notifyHireAccepted;
9
+ exports.notifyDelivery = notifyDelivery;
10
+ exports.notifyDeliveryAccepted = notifyDeliveryAccepted;
11
+ exports.notifyDispute = notifyDispute;
12
+ exports.notifyMessage = notifyMessage;
13
+ /**
14
+ * HTTP endpoint helpers — resolve an agent's endpoint from AgentRegistry
15
+ * and notify it after onchain events (hire, handshake).
16
+ */
17
+ const ethers_1 = require("ethers");
18
+ const contracts_1 = require("./contracts");
19
+ exports.DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
20
+ /**
21
+ * Reads an agent's public HTTP endpoint from AgentRegistry.
22
+ * Returns an empty string if the agent is not registered or has no endpoint.
23
+ */
24
+ async function resolveEndpoint(agentAddress, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
25
+ const registry = new ethers_1.ethers.Contract(registryAddress, contracts_1.AGENT_REGISTRY_ABI, provider);
26
+ const agentData = await registry.getAgent(agentAddress);
27
+ return agentData.endpoint ?? "";
28
+ }
29
+ /**
30
+ * POSTs a JSON payload to `${endpoint}${path}`.
31
+ * Returns { ok, status } on success, { ok: false, error } on failure.
32
+ * Never throws.
33
+ */
34
+ async function notifyEndpoint(endpoint, path, payload) {
35
+ if (!endpoint)
36
+ return { ok: false, error: "no endpoint" };
37
+ try {
38
+ const res = await fetch(`${endpoint}${path}`, {
39
+ method: "POST",
40
+ headers: { "Content-Type": "application/json" },
41
+ body: JSON.stringify(payload),
42
+ });
43
+ return { ok: res.ok, status: res.status };
44
+ }
45
+ catch (err) {
46
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
47
+ }
48
+ }
49
+ /**
50
+ * Convenience: resolve an agent's endpoint then POST to /hire.
51
+ */
52
+ async function notifyHire(agentAddress, proposal, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
53
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
54
+ return notifyEndpoint(endpoint, "/hire", proposal);
55
+ }
56
+ /**
57
+ * Convenience: resolve an agent's endpoint then POST to /handshake.
58
+ */
59
+ async function notifyHandshake(agentAddress, payload, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
60
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
61
+ return notifyEndpoint(endpoint, "/handshake", payload);
62
+ }
63
+ /**
64
+ * Convenience: resolve an agent's endpoint then POST to /hire/accepted.
65
+ */
66
+ async function notifyHireAccepted(agentAddress, payload, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
67
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
68
+ return notifyEndpoint(endpoint, "/hire/accepted", payload);
69
+ }
70
+ /**
71
+ * Convenience: resolve an agent's endpoint then POST to /delivery.
72
+ */
73
+ async function notifyDelivery(agentAddress, payload, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
74
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
75
+ return notifyEndpoint(endpoint, "/delivery", payload);
76
+ }
77
+ /**
78
+ * Convenience: resolve an agent's endpoint then POST to /delivery/accepted.
79
+ */
80
+ async function notifyDeliveryAccepted(agentAddress, payload, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
81
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
82
+ return notifyEndpoint(endpoint, "/delivery/accepted", payload);
83
+ }
84
+ /**
85
+ * Convenience: resolve an agent's endpoint then POST to /dispute.
86
+ */
87
+ async function notifyDispute(agentAddress, payload, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
88
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
89
+ return notifyEndpoint(endpoint, "/dispute", payload);
90
+ }
91
+ /**
92
+ * Convenience: resolve an agent's endpoint then POST to /message.
93
+ */
94
+ async function notifyMessage(agentAddress, payload, provider, registryAddress = exports.DEFAULT_REGISTRY_ADDRESS) {
95
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
96
+ return notifyEndpoint(endpoint, "/message", payload);
97
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { ARC402WalletClient, ARC402Wallet, type ContextBinding } from "./wallet";
2
+ export { ARC402WalletClient as ARC402OperatorClient, ARC402Wallet as ARC402Operator } from "./wallet";
2
3
  export { ContractInteractionClient, type IntentPayload, type ContractCallAttestation, type ContractCallResult } from "./contractinteraction";
3
4
  export { PolicyClient, PolicyObject, PolicyValidator } from "./policy";
4
5
  export { TrustClient, TrustPrimitive } from "./trust";
@@ -27,5 +28,15 @@ export * from "./types";
27
28
  export { buildMetadata, validateMetadata, encodeMetadata, decodeMetadata, uploadMetadata, AGENT_METADATA_SCHEMA } from "./metadata";
28
29
  export type { AgentMetadata, AgentMetadataModel, AgentMetadataTraining, AgentMetadataPricing, AgentMetadataSla, AgentMetadataContact, AgentMetadataSecurity } from "./metadata";
29
30
  export { ColdStartClient } from "./coldstart";
31
+ export { ComputeAgreementClient } from "./compute";
32
+ export type { ComputeSession, ComputeUsageReport } from "./compute";
30
33
  export { MigrationClient } from "./migration";
34
+ export { resolveEndpoint, notifyEndpoint, notifyHire, notifyHandshake, notifyHireAccepted, notifyDelivery, notifyDeliveryAccepted, notifyDispute, notifyMessage, DEFAULT_REGISTRY_ADDRESS } from "./endpoint";
35
+ export type { EndpointNotifyResult } from "./endpoint";
36
+ export { DeliveryClient, DEFAULT_DAEMON_URL } from "./delivery";
37
+ export type { DeliveryFile, DeliveryManifest, DeliveryClientOptions } from "./delivery";
38
+ export declare const COMPUTE_AGREEMENT_ADDRESS = "0x0e06afE90aAD3e0D91e217C46d98F049C2528AF7";
39
+ export declare const SUBSCRIPTION_AGREEMENT_ADDRESS = "0xe1b6D3d0890E09582166EB450a78F6bff038CE5A";
40
+ export declare const ARC402_REGISTRY_V2_ADDRESS = "0xcc0D8731ccCf6CFfF4e66F6d68cA86330Ea8B622";
41
+ export declare const ARC402_REGISTRY_V3_ADDRESS = "0x6EafeD4FA103D2De04DDee157e35A8e8df91B6A6";
31
42
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,KAAK,aAAa,EAAE,KAAK,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC7I,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,KAAK,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC1Q,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAChJ,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpL,YAAY,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC9I,YAAY,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AACrH,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACvF,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACpI,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAChL,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AACjF,OAAO,EAAE,kBAAkB,IAAI,oBAAoB,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,UAAU,CAAC;AACtG,OAAO,EAAE,yBAAyB,EAAE,KAAK,aAAa,EAAE,KAAK,uBAAuB,EAAE,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC7I,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AACtE,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,KAAK,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAC3E,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,4BAA4B,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAC1Q,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAChJ,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpL,YAAY,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC9I,YAAY,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,wBAAwB,EAAE,MAAM,SAAS,CAAC;AACrH,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACvF,cAAc,aAAa,CAAC;AAC5B,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AACpI,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAChL,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,kBAAkB,EAAE,cAAc,EAAE,sBAAsB,EAAE,aAAa,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAC9M,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAGxF,eAAO,MAAM,yBAAyB,+CAA+C,CAAC;AACtF,eAAO,MAAM,8BAA8B,+CAA+C,CAAC;AAC3F,eAAO,MAAM,0BAA0B,+CAA+C,CAAC;AACvF,eAAO,MAAM,0BAA0B,+CAA+C,CAAC"}
package/dist/index.js CHANGED
@@ -14,10 +14,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.MigrationClient = exports.ColdStartClient = exports.AGENT_METADATA_SCHEMA = exports.uploadMetadata = exports.decodeMetadata = exports.encodeMetadata = exports.validateMetadata = exports.buildMetadata = exports.uploadEncryptedIPFS = exports.decryptDeliverable = exports.encryptDeliverable = exports.uploadToIPFS = exports.hashDeliverableFile = exports.hashDeliverable = exports.DeliverableType = exports.DeliverableClient = exports.WatchtowerClient = exports.AgreementTreeClient = exports.ChannelClient = exports.SessionManager = exports.NegotiationGuard = exports.parseNegotiationMessage = exports.createNegotiationReject = exports.createNegotiationAccept = exports.createNegotiationCounter = exports.createNegotiationProposal = exports.createSignedReject = exports.createSignedAccept = exports.createSignedCounter = exports.createSignedProposal = exports.signNegotiationMessage = exports.GovernanceClient = exports.CapabilityRegistryClient = exports.SponsorshipAttestationClient = exports.ReputationOracleClient = exports.DisputeArbitrationClient = exports.ServiceAgreementClient = exports.AgentRegistryClient = exports.MultiAgentSettlement = exports.SettlementClient = exports.IntentAttestation = exports.IntentAttestationClient = exports.TrustPrimitive = exports.TrustClient = exports.PolicyValidator = exports.PolicyObject = exports.PolicyClient = exports.ContractInteractionClient = exports.ARC402Wallet = exports.ARC402WalletClient = void 0;
17
+ exports.AGENT_METADATA_SCHEMA = exports.uploadMetadata = exports.decodeMetadata = exports.encodeMetadata = exports.validateMetadata = exports.buildMetadata = exports.uploadEncryptedIPFS = exports.decryptDeliverable = exports.encryptDeliverable = exports.uploadToIPFS = exports.hashDeliverableFile = exports.hashDeliverable = exports.DeliverableType = exports.DeliverableClient = exports.WatchtowerClient = exports.AgreementTreeClient = exports.ChannelClient = exports.SessionManager = exports.NegotiationGuard = exports.parseNegotiationMessage = exports.createNegotiationReject = exports.createNegotiationAccept = exports.createNegotiationCounter = exports.createNegotiationProposal = exports.createSignedReject = exports.createSignedAccept = exports.createSignedCounter = exports.createSignedProposal = exports.signNegotiationMessage = exports.GovernanceClient = exports.CapabilityRegistryClient = exports.SponsorshipAttestationClient = exports.ReputationOracleClient = exports.DisputeArbitrationClient = exports.ServiceAgreementClient = exports.AgentRegistryClient = exports.MultiAgentSettlement = exports.SettlementClient = exports.IntentAttestation = exports.IntentAttestationClient = exports.TrustPrimitive = exports.TrustClient = exports.PolicyValidator = exports.PolicyObject = exports.PolicyClient = exports.ContractInteractionClient = exports.ARC402Operator = exports.ARC402OperatorClient = exports.ARC402Wallet = exports.ARC402WalletClient = void 0;
18
+ exports.ARC402_REGISTRY_V3_ADDRESS = exports.ARC402_REGISTRY_V2_ADDRESS = exports.SUBSCRIPTION_AGREEMENT_ADDRESS = exports.COMPUTE_AGREEMENT_ADDRESS = exports.DEFAULT_DAEMON_URL = exports.DeliveryClient = exports.DEFAULT_REGISTRY_ADDRESS = exports.notifyMessage = exports.notifyDispute = exports.notifyDeliveryAccepted = exports.notifyDelivery = exports.notifyHireAccepted = exports.notifyHandshake = exports.notifyHire = exports.notifyEndpoint = exports.resolveEndpoint = exports.MigrationClient = exports.ComputeAgreementClient = exports.ColdStartClient = void 0;
18
19
  var wallet_1 = require("./wallet");
19
20
  Object.defineProperty(exports, "ARC402WalletClient", { enumerable: true, get: function () { return wallet_1.ARC402WalletClient; } });
20
21
  Object.defineProperty(exports, "ARC402Wallet", { enumerable: true, get: function () { return wallet_1.ARC402Wallet; } });
22
+ var wallet_2 = require("./wallet");
23
+ Object.defineProperty(exports, "ARC402OperatorClient", { enumerable: true, get: function () { return wallet_2.ARC402WalletClient; } });
24
+ Object.defineProperty(exports, "ARC402Operator", { enumerable: true, get: function () { return wallet_2.ARC402Wallet; } });
21
25
  var contractinteraction_1 = require("./contractinteraction");
22
26
  Object.defineProperty(exports, "ContractInteractionClient", { enumerable: true, get: function () { return contractinteraction_1.ContractInteractionClient; } });
23
27
  var policy_1 = require("./policy");
@@ -88,5 +92,26 @@ Object.defineProperty(exports, "uploadMetadata", { enumerable: true, get: functi
88
92
  Object.defineProperty(exports, "AGENT_METADATA_SCHEMA", { enumerable: true, get: function () { return metadata_1.AGENT_METADATA_SCHEMA; } });
89
93
  var coldstart_1 = require("./coldstart");
90
94
  Object.defineProperty(exports, "ColdStartClient", { enumerable: true, get: function () { return coldstart_1.ColdStartClient; } });
95
+ var compute_1 = require("./compute");
96
+ Object.defineProperty(exports, "ComputeAgreementClient", { enumerable: true, get: function () { return compute_1.ComputeAgreementClient; } });
91
97
  var migration_1 = require("./migration");
92
98
  Object.defineProperty(exports, "MigrationClient", { enumerable: true, get: function () { return migration_1.MigrationClient; } });
99
+ var endpoint_1 = require("./endpoint");
100
+ Object.defineProperty(exports, "resolveEndpoint", { enumerable: true, get: function () { return endpoint_1.resolveEndpoint; } });
101
+ Object.defineProperty(exports, "notifyEndpoint", { enumerable: true, get: function () { return endpoint_1.notifyEndpoint; } });
102
+ Object.defineProperty(exports, "notifyHire", { enumerable: true, get: function () { return endpoint_1.notifyHire; } });
103
+ Object.defineProperty(exports, "notifyHandshake", { enumerable: true, get: function () { return endpoint_1.notifyHandshake; } });
104
+ Object.defineProperty(exports, "notifyHireAccepted", { enumerable: true, get: function () { return endpoint_1.notifyHireAccepted; } });
105
+ Object.defineProperty(exports, "notifyDelivery", { enumerable: true, get: function () { return endpoint_1.notifyDelivery; } });
106
+ Object.defineProperty(exports, "notifyDeliveryAccepted", { enumerable: true, get: function () { return endpoint_1.notifyDeliveryAccepted; } });
107
+ Object.defineProperty(exports, "notifyDispute", { enumerable: true, get: function () { return endpoint_1.notifyDispute; } });
108
+ Object.defineProperty(exports, "notifyMessage", { enumerable: true, get: function () { return endpoint_1.notifyMessage; } });
109
+ Object.defineProperty(exports, "DEFAULT_REGISTRY_ADDRESS", { enumerable: true, get: function () { return endpoint_1.DEFAULT_REGISTRY_ADDRESS; } });
110
+ var delivery_1 = require("./delivery");
111
+ Object.defineProperty(exports, "DeliveryClient", { enumerable: true, get: function () { return delivery_1.DeliveryClient; } });
112
+ Object.defineProperty(exports, "DEFAULT_DAEMON_URL", { enumerable: true, get: function () { return delivery_1.DEFAULT_DAEMON_URL; } });
113
+ // Base Mainnet contract addresses
114
+ exports.COMPUTE_AGREEMENT_ADDRESS = "0x0e06afE90aAD3e0D91e217C46d98F049C2528AF7";
115
+ exports.SUBSCRIPTION_AGREEMENT_ADDRESS = "0xe1b6D3d0890E09582166EB450a78F6bff038CE5A";
116
+ exports.ARC402_REGISTRY_V2_ADDRESS = "0xcc0D8731ccCf6CFfF4e66F6d68cA86330Ea8B622";
117
+ exports.ARC402_REGISTRY_V3_ADDRESS = "0x6EafeD4FA103D2De04DDee157e35A8e8df91B6A6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arc402/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "ARC-402 typed TypeScript SDK for discovery, negotiation, trust, and governed settlement",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/compute.ts ADDED
@@ -0,0 +1,139 @@
1
+ import { ethers, ContractTransactionResponse } from "ethers";
2
+
3
+ // ─── ABI ──────────────────────────────────────────────────────────────────────
4
+
5
+ const COMPUTE_AGREEMENT_ABI = [
6
+ "function proposeSession(bytes32 sessionId, address provider, uint256 ratePerHour, uint256 maxHours, bytes32 gpuSpecHash, address token) external payable",
7
+ "function acceptSession(bytes32 sessionId) external",
8
+ "function startSession(bytes32 sessionId) external",
9
+ "function submitUsageReport(bytes32 sessionId, uint256 periodStart, uint256 periodEnd, uint256 computeMinutes, uint256 avgUtilization, bytes providerSignature, bytes32 metricsHash) external",
10
+ "function endSession(bytes32 sessionId) external",
11
+ "function disputeSession(bytes32 sessionId) external",
12
+ "function cancelSession(bytes32 sessionId) external",
13
+ "function resolveDispute(bytes32 sessionId, uint256 providerAmount, uint256 clientAmount) external",
14
+ "function claimDisputeTimeout(bytes32 sessionId) external",
15
+ "function withdraw(address token) external",
16
+ "function getSession(bytes32 sessionId) external view returns (tuple(address client, address provider, address token, uint256 ratePerHour, uint256 maxHours, uint256 depositAmount, uint256 startedAt, uint256 endedAt, uint256 consumedMinutes, uint256 proposedAt, uint256 disputedAt, bytes32 gpuSpecHash, uint8 status))",
17
+ "function calculateCost(bytes32 sessionId) external view returns (uint256)",
18
+ "function getUsageReports(bytes32 sessionId) external view returns (tuple(uint256 periodStart, uint256 periodEnd, uint256 computeMinutes, uint256 avgUtilization, bytes providerSignature, bytes32 metricsHash)[])",
19
+ "function pendingWithdrawals(address user, address token) external view returns (uint256)",
20
+ ];
21
+
22
+ // ─── Types ────────────────────────────────────────────────────────────────────
23
+
24
+ export interface ComputeSession {
25
+ client: string;
26
+ provider: string;
27
+ token: string;
28
+ ratePerHour: bigint;
29
+ maxHours: bigint;
30
+ depositAmount: bigint;
31
+ startedAt: bigint;
32
+ endedAt: bigint;
33
+ consumedMinutes: bigint;
34
+ proposedAt: bigint;
35
+ disputedAt: bigint;
36
+ gpuSpecHash: string;
37
+ status: number;
38
+ }
39
+
40
+ export interface ComputeUsageReport {
41
+ periodStart: bigint;
42
+ periodEnd: bigint;
43
+ computeMinutes: bigint;
44
+ avgUtilization: bigint;
45
+ providerSignature: string;
46
+ metricsHash: string;
47
+ }
48
+
49
+ // ─── Client ───────────────────────────────────────────────────────────────────
50
+
51
+ export class ComputeAgreementClient {
52
+ private contract: ethers.Contract;
53
+
54
+ constructor(contractAddress: string, signerOrProvider: ethers.Signer | ethers.Provider) {
55
+ this.contract = new ethers.Contract(contractAddress, COMPUTE_AGREEMENT_ABI, signerOrProvider);
56
+ }
57
+
58
+ async proposeSession(
59
+ sessionId: string,
60
+ provider: string,
61
+ ratePerHour: bigint,
62
+ maxHours: bigint,
63
+ gpuSpecHash: string,
64
+ token: string,
65
+ deposit: bigint,
66
+ ): Promise<ContractTransactionResponse> {
67
+ const isEth = token === ethers.ZeroAddress;
68
+ return this.contract.proposeSession(
69
+ sessionId, provider, ratePerHour, maxHours, gpuSpecHash, token,
70
+ { value: isEth ? deposit : 0n },
71
+ ) as Promise<ContractTransactionResponse>;
72
+ }
73
+
74
+ async acceptSession(sessionId: string): Promise<ContractTransactionResponse> {
75
+ return this.contract.acceptSession(sessionId) as Promise<ContractTransactionResponse>;
76
+ }
77
+
78
+ async startSession(sessionId: string): Promise<ContractTransactionResponse> {
79
+ return this.contract.startSession(sessionId) as Promise<ContractTransactionResponse>;
80
+ }
81
+
82
+ async submitUsageReport(
83
+ sessionId: string,
84
+ periodStart: bigint,
85
+ periodEnd: bigint,
86
+ computeMinutes: bigint,
87
+ avgUtilization: bigint,
88
+ providerSignature: string,
89
+ metricsHash: string,
90
+ ): Promise<ContractTransactionResponse> {
91
+ return this.contract.submitUsageReport(
92
+ sessionId, periodStart, periodEnd, computeMinutes, avgUtilization, providerSignature, metricsHash,
93
+ ) as Promise<ContractTransactionResponse>;
94
+ }
95
+
96
+ async endSession(sessionId: string): Promise<ContractTransactionResponse> {
97
+ return this.contract.endSession(sessionId) as Promise<ContractTransactionResponse>;
98
+ }
99
+
100
+ async disputeSession(sessionId: string): Promise<ContractTransactionResponse> {
101
+ return this.contract.disputeSession(sessionId) as Promise<ContractTransactionResponse>;
102
+ }
103
+
104
+ async cancelSession(sessionId: string): Promise<ContractTransactionResponse> {
105
+ return this.contract.cancelSession(sessionId) as Promise<ContractTransactionResponse>;
106
+ }
107
+
108
+ async resolveDispute(
109
+ sessionId: string,
110
+ providerAmount: bigint,
111
+ clientAmount: bigint,
112
+ ): Promise<ContractTransactionResponse> {
113
+ return this.contract.resolveDispute(sessionId, providerAmount, clientAmount) as Promise<ContractTransactionResponse>;
114
+ }
115
+
116
+ async claimDisputeTimeout(sessionId: string): Promise<ContractTransactionResponse> {
117
+ return this.contract.claimDisputeTimeout(sessionId) as Promise<ContractTransactionResponse>;
118
+ }
119
+
120
+ async withdraw(token: string): Promise<ContractTransactionResponse> {
121
+ return this.contract.withdraw(token) as Promise<ContractTransactionResponse>;
122
+ }
123
+
124
+ async getSession(sessionId: string): Promise<ComputeSession> {
125
+ return this.contract.getSession(sessionId) as Promise<ComputeSession>;
126
+ }
127
+
128
+ async calculateCost(sessionId: string): Promise<bigint> {
129
+ return this.contract.calculateCost(sessionId) as Promise<bigint>;
130
+ }
131
+
132
+ async getUsageReports(sessionId: string): Promise<ComputeUsageReport[]> {
133
+ return this.contract.getUsageReports(sessionId) as Promise<ComputeUsageReport[]>;
134
+ }
135
+
136
+ async pendingWithdrawals(user: string, token: string): Promise<bigint> {
137
+ return this.contract.pendingWithdrawals(user, token) as Promise<bigint>;
138
+ }
139
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * File Delivery Layer — wraps daemon HTTP endpoints for content-addressed
3
+ * file delivery with party-gated access (EIP-191 signatures).
4
+ *
5
+ * Files are private by default. Only the keccak256 bundle hash is published
6
+ * on-chain. Downloads require a valid EIP-191 signature from either the hirer
7
+ * or the provider. Arbitrators receive a time-limited token for dispute access.
8
+ *
9
+ * Daemon endpoints:
10
+ * POST /job/:id/upload — upload a deliverable file
11
+ * GET /job/:id/files — list all delivered files
12
+ * GET /job/:id/files/:name — download a specific file
13
+ * GET /job/:id/manifest — fetch the delivery manifest
14
+ */
15
+ import { ethers } from "ethers";
16
+ import { mkdir, readFile, writeFile } from "fs/promises";
17
+ import path from "path";
18
+
19
+ export const DEFAULT_DAEMON_URL = "http://localhost:4402";
20
+
21
+ export interface DeliveryFile {
22
+ name: string;
23
+ hash: string; // keccak256 hex
24
+ size: number;
25
+ }
26
+
27
+ export interface DeliveryManifest {
28
+ agreementId: string;
29
+ files: DeliveryFile[];
30
+ bundleHash: string; // keccak256 of the full manifest — matches on-chain deliverableHash
31
+ }
32
+
33
+ export interface DeliveryClientOptions {
34
+ /** Daemon base URL. Defaults to http://localhost:4402. */
35
+ daemonUrl?: string;
36
+ }
37
+
38
+ export class DeliveryClient {
39
+ private daemonUrl: string;
40
+
41
+ constructor(options?: DeliveryClientOptions) {
42
+ this.daemonUrl = (options?.daemonUrl ?? DEFAULT_DAEMON_URL).replace(/\/$/, "");
43
+ }
44
+
45
+ /** Sign the standard auth message for a job. Used for upload and download. */
46
+ private async signAuth(agreementId: bigint | string, signer: ethers.Signer): Promise<string> {
47
+ return signer.signMessage(`arc402:job:${agreementId}`);
48
+ }
49
+
50
+ /**
51
+ * Upload a file as a deliverable for the agreement.
52
+ * Returns the file entry recorded by the daemon (name, keccak256 hash, size).
53
+ * Auth: EIP-191 signature from the provider's signer.
54
+ *
55
+ * After uploading all files, commit the manifest bundleHash on-chain via
56
+ * ServiceAgreementClient.commitDeliverable(). The CLI does this automatically
57
+ * when you run `arc402 deliver <id>`.
58
+ */
59
+ async uploadDeliverable(
60
+ agreementId: bigint | string,
61
+ filePath: string,
62
+ signer: ethers.Signer,
63
+ ): Promise<DeliveryFile> {
64
+ const signature = await this.signAuth(agreementId, signer);
65
+ const address = await signer.getAddress();
66
+ const filename = path.basename(filePath);
67
+ const fileBuffer = await readFile(filePath);
68
+ const form = new FormData();
69
+ form.append("file", new Blob([fileBuffer]), filename);
70
+ form.append("address", address);
71
+ form.append("signature", signature);
72
+ const res = await fetch(`${this.daemonUrl}/job/${agreementId}/upload`, {
73
+ method: "POST",
74
+ body: form,
75
+ });
76
+ if (!res.ok) throw new Error(`Upload failed: ${res.status} ${res.statusText}`);
77
+ return (await res.json()) as DeliveryFile;
78
+ }
79
+
80
+ /**
81
+ * Download a named file from the agreement's delivery.
82
+ * Writes the file to <outputDir>/<name> and returns the output path.
83
+ * Auth: EIP-191 signature from the hirer's or provider's signer.
84
+ */
85
+ async downloadDeliverable(
86
+ agreementId: bigint | string,
87
+ fileName: string,
88
+ outputDir: string,
89
+ signer: ethers.Signer,
90
+ ): Promise<string> {
91
+ const signature = await this.signAuth(agreementId, signer);
92
+ const address = await signer.getAddress();
93
+ const res = await fetch(
94
+ `${this.daemonUrl}/job/${agreementId}/files/${encodeURIComponent(fileName)}`,
95
+ { headers: { "X-Arc402-Address": address, "X-Arc402-Signature": signature } },
96
+ );
97
+ if (!res.ok) throw new Error(`Download failed: ${res.status} ${res.statusText}`);
98
+ const buffer = Buffer.from(await res.arrayBuffer());
99
+ await mkdir(outputDir, { recursive: true });
100
+ const outPath = path.join(outputDir, fileName);
101
+ await writeFile(outPath, buffer);
102
+ return outPath;
103
+ }
104
+
105
+ /**
106
+ * Fetch the delivery manifest for an agreement.
107
+ * Lists all delivered files with their keccak256 hashes and the overall bundleHash.
108
+ * Auth: EIP-191 signature from the hirer's or provider's signer.
109
+ */
110
+ async getManifest(agreementId: bigint | string, signer: ethers.Signer): Promise<DeliveryManifest> {
111
+ const signature = await this.signAuth(agreementId, signer);
112
+ const address = await signer.getAddress();
113
+ const res = await fetch(
114
+ `${this.daemonUrl}/job/${agreementId}/manifest`,
115
+ { headers: { "X-Arc402-Address": address, "X-Arc402-Signature": signature } },
116
+ );
117
+ if (!res.ok) throw new Error(`Manifest fetch failed: ${res.status} ${res.statusText}`);
118
+ return (await res.json()) as DeliveryManifest;
119
+ }
120
+
121
+ /**
122
+ * Download all files and verify their keccak256 hashes against the manifest.
123
+ * Also checks that the manifest bundleHash matches the expectedBundleHash
124
+ * (which should equal the value committed on-chain via commitDeliverable).
125
+ *
126
+ * Returns { ok: true } when all hashes match, or
127
+ * { ok: false, mismatches: [...] } listing any files that failed.
128
+ */
129
+ async verifyDelivery(
130
+ agreementId: bigint | string,
131
+ expectedBundleHash: string,
132
+ signer: ethers.Signer,
133
+ outputDir: string,
134
+ ): Promise<{ ok: boolean; mismatches: string[] }> {
135
+ const manifest = await this.getManifest(agreementId, signer);
136
+ if (manifest.bundleHash.toLowerCase() !== expectedBundleHash.toLowerCase()) {
137
+ return {
138
+ ok: false,
139
+ mismatches: [`bundleHash mismatch: expected ${expectedBundleHash}, got ${manifest.bundleHash}`],
140
+ };
141
+ }
142
+ const mismatches: string[] = [];
143
+ for (const file of manifest.files) {
144
+ const outPath = await this.downloadDeliverable(agreementId, file.name, outputDir, signer);
145
+ const buffer = await readFile(outPath);
146
+ const localHash = ethers.keccak256(buffer);
147
+ if (localHash.toLowerCase() !== file.hash.toLowerCase()) {
148
+ mismatches.push(`${file.name}: expected ${file.hash}, got ${localHash}`);
149
+ }
150
+ }
151
+ return { ok: mismatches.length === 0, mismatches };
152
+ }
153
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * HTTP endpoint helpers — resolve an agent's endpoint from AgentRegistry
3
+ * and notify it after onchain events (hire, handshake).
4
+ */
5
+ import { ethers } from "ethers";
6
+ import { AGENT_REGISTRY_ABI } from "./contracts";
7
+
8
+ export const DEFAULT_REGISTRY_ADDRESS = "0xD5c2851B00090c92Ba7F4723FB548bb30C9B6865";
9
+
10
+ export interface EndpointNotifyResult {
11
+ ok: boolean;
12
+ status?: number;
13
+ error?: string;
14
+ }
15
+
16
+ /**
17
+ * Reads an agent's public HTTP endpoint from AgentRegistry.
18
+ * Returns an empty string if the agent is not registered or has no endpoint.
19
+ */
20
+ export async function resolveEndpoint(
21
+ agentAddress: string,
22
+ provider: ethers.Provider,
23
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
24
+ ): Promise<string> {
25
+ const registry = new ethers.Contract(registryAddress, AGENT_REGISTRY_ABI, provider);
26
+ const agentData = await registry.getAgent(agentAddress);
27
+ return (agentData.endpoint as string) ?? "";
28
+ }
29
+
30
+ /**
31
+ * POSTs a JSON payload to `${endpoint}${path}`.
32
+ * Returns { ok, status } on success, { ok: false, error } on failure.
33
+ * Never throws.
34
+ */
35
+ export async function notifyEndpoint(
36
+ endpoint: string,
37
+ path: string,
38
+ payload: Record<string, unknown>
39
+ ): Promise<EndpointNotifyResult> {
40
+ if (!endpoint) return { ok: false, error: "no endpoint" };
41
+ try {
42
+ const res = await fetch(`${endpoint}${path}`, {
43
+ method: "POST",
44
+ headers: { "Content-Type": "application/json" },
45
+ body: JSON.stringify(payload),
46
+ });
47
+ return { ok: res.ok, status: res.status };
48
+ } catch (err) {
49
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Convenience: resolve an agent's endpoint then POST to /hire.
55
+ */
56
+ export async function notifyHire(
57
+ agentAddress: string,
58
+ proposal: Record<string, unknown>,
59
+ provider: ethers.Provider,
60
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
61
+ ): Promise<EndpointNotifyResult> {
62
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
63
+ return notifyEndpoint(endpoint, "/hire", proposal);
64
+ }
65
+
66
+ /**
67
+ * Convenience: resolve an agent's endpoint then POST to /handshake.
68
+ */
69
+ export async function notifyHandshake(
70
+ agentAddress: string,
71
+ payload: Record<string, unknown>,
72
+ provider: ethers.Provider,
73
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
74
+ ): Promise<EndpointNotifyResult> {
75
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
76
+ return notifyEndpoint(endpoint, "/handshake", payload);
77
+ }
78
+
79
+ /**
80
+ * Convenience: resolve an agent's endpoint then POST to /hire/accepted.
81
+ */
82
+ export async function notifyHireAccepted(
83
+ agentAddress: string,
84
+ payload: Record<string, unknown>,
85
+ provider: ethers.Provider,
86
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
87
+ ): Promise<EndpointNotifyResult> {
88
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
89
+ return notifyEndpoint(endpoint, "/hire/accepted", payload);
90
+ }
91
+
92
+ /**
93
+ * Convenience: resolve an agent's endpoint then POST to /delivery.
94
+ */
95
+ export async function notifyDelivery(
96
+ agentAddress: string,
97
+ payload: Record<string, unknown>,
98
+ provider: ethers.Provider,
99
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
100
+ ): Promise<EndpointNotifyResult> {
101
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
102
+ return notifyEndpoint(endpoint, "/delivery", payload);
103
+ }
104
+
105
+ /**
106
+ * Convenience: resolve an agent's endpoint then POST to /delivery/accepted.
107
+ */
108
+ export async function notifyDeliveryAccepted(
109
+ agentAddress: string,
110
+ payload: Record<string, unknown>,
111
+ provider: ethers.Provider,
112
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
113
+ ): Promise<EndpointNotifyResult> {
114
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
115
+ return notifyEndpoint(endpoint, "/delivery/accepted", payload);
116
+ }
117
+
118
+ /**
119
+ * Convenience: resolve an agent's endpoint then POST to /dispute.
120
+ */
121
+ export async function notifyDispute(
122
+ agentAddress: string,
123
+ payload: Record<string, unknown>,
124
+ provider: ethers.Provider,
125
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
126
+ ): Promise<EndpointNotifyResult> {
127
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
128
+ return notifyEndpoint(endpoint, "/dispute", payload);
129
+ }
130
+
131
+ /**
132
+ * Convenience: resolve an agent's endpoint then POST to /message.
133
+ */
134
+ export async function notifyMessage(
135
+ agentAddress: string,
136
+ payload: Record<string, unknown>,
137
+ provider: ethers.Provider,
138
+ registryAddress = DEFAULT_REGISTRY_ADDRESS
139
+ ): Promise<EndpointNotifyResult> {
140
+ const endpoint = await resolveEndpoint(agentAddress, provider, registryAddress);
141
+ return notifyEndpoint(endpoint, "/message", payload);
142
+ }
package/src/index.ts CHANGED
@@ -28,4 +28,16 @@ export * from "./types";
28
28
  export { buildMetadata, validateMetadata, encodeMetadata, decodeMetadata, uploadMetadata, AGENT_METADATA_SCHEMA } from "./metadata";
29
29
  export type { AgentMetadata, AgentMetadataModel, AgentMetadataTraining, AgentMetadataPricing, AgentMetadataSla, AgentMetadataContact, AgentMetadataSecurity } from "./metadata";
30
30
  export { ColdStartClient } from "./coldstart";
31
+ export { ComputeAgreementClient } from "./compute";
32
+ export type { ComputeSession, ComputeUsageReport } from "./compute";
31
33
  export { MigrationClient } from "./migration";
34
+ export { resolveEndpoint, notifyEndpoint, notifyHire, notifyHandshake, notifyHireAccepted, notifyDelivery, notifyDeliveryAccepted, notifyDispute, notifyMessage, DEFAULT_REGISTRY_ADDRESS } from "./endpoint";
35
+ export type { EndpointNotifyResult } from "./endpoint";
36
+ export { DeliveryClient, DEFAULT_DAEMON_URL } from "./delivery";
37
+ export type { DeliveryFile, DeliveryManifest, DeliveryClientOptions } from "./delivery";
38
+
39
+ // Base Mainnet contract addresses
40
+ export const COMPUTE_AGREEMENT_ADDRESS = "0x0e06afE90aAD3e0D91e217C46d98F049C2528AF7";
41
+ export const SUBSCRIPTION_AGREEMENT_ADDRESS = "0xe1b6D3d0890E09582166EB450a78F6bff038CE5A";
42
+ export const ARC402_REGISTRY_V2_ADDRESS = "0xcc0D8731ccCf6CFfF4e66F6d68cA86330Ea8B622";
43
+ export const ARC402_REGISTRY_V3_ADDRESS = "0x6EafeD4FA103D2De04DDee157e35A8e8df91B6A6";