@arc402/sdk 0.3.1 → 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 +57 -0
- package/dist/compute.d.ts +43 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +71 -0
- package/dist/delivery.d.ts +71 -0
- package/dist/delivery.d.ts.map +1 -0
- package/dist/delivery.js +117 -0
- package/dist/endpoint.d.ts +51 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +97 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -1
- package/package.json +1 -1
- package/src/compute.ts +139 -0
- package/src/delivery.ts +153 -0
- package/src/index.ts +10 -0
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"}
|
package/dist/compute.js
ADDED
|
@@ -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"}
|
package/dist/delivery.js
ADDED
|
@@ -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"}
|
package/dist/endpoint.js
ADDED
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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.
|
|
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
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
|
+
}
|
package/src/delivery.ts
ADDED
|
@@ -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
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -28,6 +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";
|
|
32
34
|
export { resolveEndpoint, notifyEndpoint, notifyHire, notifyHandshake, notifyHireAccepted, notifyDelivery, notifyDeliveryAccepted, notifyDispute, notifyMessage, DEFAULT_REGISTRY_ADDRESS } from "./endpoint";
|
|
33
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";
|