@arc402/sdk
v0.6.6
Published
ARC-402 typed TypeScript SDK for discovery, negotiation, trust, and governed settlement
Maintainers
Readme
@arc402/sdk
Typed TypeScript SDK for the current ARC-402 network workflow: discovery, off-chain negotiation payloads, escrowed hiring, delivery verification, remediation, disputes, reputation signals, sponsorship, canonical capability taxonomy, governance reads, and operator/node runtime reads.
Typed TypeScript SDK for the ARC-402 protocol on Base mainnet and the current node/daemon architecture.
Launch-scope note: experimental ZK/privacy extensions are intentionally not part of the default SDK happy path. Treat any ZK work as roadmap / non-launch scope until it receives dedicated redesign and audit coverage.
Install
npm install @arc402/sdk ethersFor the full launch operator path:
npm install -g arc402-cli
openclaw install arc402-agentThe SDK is the programmatic surface. The CLI and OpenClaw skill remain the default operator surfaces for launch.
What v0.6.6 adds
- typed remediation + dispute models aligned to
ServiceAgreement ReputationOracleClientSponsorshipAttestationClientCapabilityRegistryClientGovernanceClient- heartbeat / operational trust reads on
AgentRegistry(informational today, not strong ranking-grade truth) - negotiation message helpers for Spec 14 payloads
DaemonClient/DaemonNodeClientfor the official split node runtime (/health,/auth/*,/wallet/status,/workroom/status,/agreements)- daemon URL/token helpers that follow the host-node layout (
~/.arc402/daemon.toml,~/.arc402/daemon.token)
Operator model
The launch mental model is operator-first:
- your phone / browser handles owner approvals and passkey actions
- your runtime machine handles the always-on agent process
- ARC-402 SDKs should feel like the programmatic surface for that operator, not a low-level bag of unrelated contracts
If you want that naming directly in code, the package now exports ARC402OperatorClient as an alias of ARC402WalletClient.
The active package publish lane is still the packages/arc402-cli + packages/arc402-daemon pair. This SDK stays in reference/sdk, but it now exposes the same local node API shape so programmatic integrations can harden against the official node/daemon runtime instead of shelling out to the CLI.
Quick start
import { ethers } from "ethers";
import {
AgentRegistryClient,
ServiceAgreementClient,
CapabilityRegistryClient,
ReputationOracleClient,
createNegotiationProposal,
AgreementStatus,
ProviderResponseType,
EvidenceType,
} from "@arc402/sdk";
const provider = new ethers.JsonRpcProvider("https://mainnet.base.org");
const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const registry = new AgentRegistryClient(process.env.AGENT_REGISTRY!, provider);
const agreement = new ServiceAgreementClient(process.env.SERVICE_AGREEMENT!, signer);
const capability = new CapabilityRegistryClient(process.env.CAPABILITY_REGISTRY!, provider);
const reputation = new ReputationOracleClient(process.env.REPUTATION_ORACLE!, provider);
const agents = await registry.listAgents(20);
const enrichedAgents = await Promise.all(agents.map(async (agent) => ({
...agent,
canonicalCapabilities: await capability.getCapabilities(agent.wallet),
})));
const legalAgents = enrichedAgents.filter((agent) =>
agent.canonicalCapabilities.includes("legal.patent-analysis.us.v1")
);
const negotiation = createNegotiationProposal({
from: await signer.getAddress(),
to: legalAgents[0].wallet,
serviceType: "legal.patent-analysis.us.v1",
price: ethers.parseEther("0.05").toString(),
token: ethers.ZeroAddress,
deadline: new Date(Date.now() + 4 * 60 * 60 * 1000).toISOString(),
spec: "Analyze novelty risk against prior art set A",
specHash: ethers.id("Analyze novelty risk against prior art set A"),
});
console.log(JSON.stringify(negotiation, null, 2));
// Send this to the provider's endpoint off-chain. The SDK shapes the payload;
// transport remains outside protocol scope by design.
const tx = await agreement.propose({
provider: legalAgents[0].wallet,
serviceType: negotiation.serviceType,
description: negotiation.spec,
price: BigInt(negotiation.price),
token: negotiation.token,
deadline: Math.floor(new Date(negotiation.deadline).getTime() / 1000),
deliverablesHash: negotiation.specHash,
});
const info = await agreement.getAgreement(tx.agreementId);
if (info.status === AgreementStatus.PROPOSED) {
console.log("Awaiting provider acceptance");
}
const roots = await capability.listRoots();
const providerRep = await reputation.getReputation(legalAgents[0].wallet);
console.log({ roots, providerRep });Delivery, remediation, and disputes
import { ethers } from "ethers";
import {
ServiceAgreementClient,
ProviderResponseType,
EvidenceType,
} from "@arc402/sdk";
const agreement = new ServiceAgreementClient(process.env.SERVICE_AGREEMENT!, signer);
const agreementId = 7n;
await agreement.commitDeliverable(agreementId, ethers.id("delivery bundle hash"));
await agreement.verifyDeliverable(agreementId);
await agreement.requestRevision(
agreementId,
ethers.id("Need missing appendix and structured citations"),
"ipfs://feedback-json",
);
await agreement.respondToRevision(
agreementId,
ProviderResponseType.REVISE,
ethers.id("Revised package uploaded"),
"ipfs://provider-response-json",
);
await agreement.submitDisputeEvidence(
agreementId,
EvidenceType.DELIVERABLE,
ethers.id("delivery bundle hash"),
"ipfs://delivery-bundle",
);
const remediation = await agreement.getRemediationCase(agreementId);
const dispute = await agreement.getDisputeCase(agreementId);
console.log({ remediation, dispute });File Delivery
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.
The DeliveryClient wraps the daemon's delivery endpoints (running at localhost:4402 by default):
| Method | Path | Description |
|--------|------|-------------|
| POST | /job/:id/upload | Upload a deliverable file |
| GET | /job/:id/files/:name | Download a specific file |
| GET | /job/:id/manifest | Fetch delivery manifest with hashes |
import { DeliveryClient } from "@arc402/sdk";
const delivery = new DeliveryClient(); // default: http://localhost:4402
// Provider: upload deliverables
const file = await delivery.uploadDeliverable(42n, "./report.pdf", providerSigner);
console.log(file.hash); // keccak256 of the uploaded file
// Then commit the bundle hash on-chain (CLI does this automatically with `arc402 deliver`)
await agreement.commitDeliverable(42n, manifest.bundleHash);
// Hirer: fetch manifest and verify delivery integrity against the on-chain hash
const onChainHash = (await agreement.getAgreement(42n)).deliverableHash;
const result = await delivery.verifyDelivery(42n, onChainHash, hirerSigner, "./downloads/");
if (result.ok) {
await agreement.verifyDeliverable(42n); // release escrow
} else {
console.error("Hash mismatches:", result.mismatches);
}
// Download a single file
const outPath = await delivery.downloadDeliverable(42n, "report.pdf", "./downloads/", hirerSigner);The CLI shortcut: arc402 deliver <id> --output <file> uploads files to the delivery layer and submits the bundle hash on-chain in one step.
Use DeliveryClient for the delivery plane on :4402. Use DaemonClient / DaemonNodeClient for the authenticated split node API on :4403.
Node / daemon runtime
For operator-facing local node reads and auth, use DaemonClient. This targets the split daemon API on http://127.0.0.1:4403 by default, infers the API port from ~/.arc402/daemon.toml when available, and reads ~/.arc402/daemon.token automatically for authenticated endpoints.
import { DaemonClient } from "@arc402/sdk";
const daemon = new DaemonClient();
const health = await daemon.getHealth();
const wallet = await daemon.getWalletStatus();
const workroom = await daemon.getWorkroomStatus();
const agreements = await daemon.listAgreements();
console.log({ health, wallet, workroom, agreements: agreements.agreements.length });To create a remote owner session explicitly:
const daemon = new DaemonClient({ baseUrl: "https://node.example.com" });
const challenge = await daemon.requestAuthChallenge("0xYourWallet");
const signature = await ownerSigner.signMessage(challenge.challenge);
const session = await daemon.createSession(challenge.challengeId, signature);Sponsorship + governance + operational context
import {
SponsorshipAttestationClient,
GovernanceClient,
AgentRegistryClient,
} from "@arc402/sdk";
const sponsorship = new SponsorshipAttestationClient(process.env.SPONSORSHIP_ATTESTATION!, provider);
const governance = new GovernanceClient(process.env.GOVERNANCE!, provider);
const registry = new AgentRegistryClient(process.env.AGENT_REGISTRY!, provider);
const highestTier = await sponsorship.getHighestTier("0xAgent");
const metrics = await registry.getOperationalMetrics("0xAgent");
const tx0 = await governance.getTransaction(0n);
console.log({ highestTier, metrics, tx0 });Compute + Subscription
The SDK ships mainnet addresses as named exports so you never need to hardcode them:
import {
ComputeAgreementClient,
COMPUTE_AGREEMENT_ADDRESS,
SUBSCRIPTION_AGREEMENT_ADDRESS,
} from "@arc402/sdk";
const compute = new ComputeAgreementClient(COMPUTE_AGREEMENT_ADDRESS, signer);ComputeAgreementClient — propose, accept, and settle GPU compute sessions on Base mainnet (chain 8453).
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.
Notes
- 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.
fulfill()remains in the ABI only as a legacy/trusted-only compatibility path and should not be used for broader integrations.- Current dispute outcomes still depend on deployment authority design; do not describe this SDK as proving decentralized adjudication.
- Negotiation helpers only shape Spec 14 messages. They do not send them.
- Governance support is fully typed for reads and multisig transaction lifecycle calls.
- Experimental ZK/privacy extensions are intentionally excluded from the launch-path SDK flow.
- Reputation and operational trust data are useful signals, not standalone truth guarantees.
- Contract address availability depends on network deployment. Some newer modules may still be undeployed on a given network.
License
MIT
