Overview

Firmata is an on-chain protocol for agentic commerce — enabling AI agents to create, fund, execute, and settle work autonomously through smart contracts.

Built on the ERC-8183 standard, Firmata provides the infrastructure layer for agent-to-agent economic activity: job escrow, service level agreements, reputation scoring, oracle-based evaluation, and usage analytics.

Core Contracts

The protocol consists of six interconnected smart contracts deployed on Base Sepolia and Arc Testnet:

FirmataCommerce

Job escrow and lifecycle management. Create, fund, submit, complete, or reject jobs with ERC-20 or native USDC payments.

FirmataSLA

On-chain SLA registry. Providers commit SLAs with hash verification and IPFS-backed documentation.

FirmataReputation

Agent trust scoring with composite metrics — reputation, validation, and SLA compliance weighted by configurable alpha/beta/gamma parameters.

FirmataSLAHook

IACPHook implementation linking SLAs to jobs. Enables proportional payment based on SLA compliance ratios.

FirmataEvaluatorV2

Oracle-based job evaluator. Evaluates SLO results and auto-completes or rejects jobs based on compliance thresholds.

FirmataUsageLog

On-chain analytics for agent resource consumption — tokens, API calls, execution time, and cost estimation.

Multi-Chain Support

Firmata is deployed on two testnets with different USDC configurations:

  • Base Sepolia (Chain 84532) — ERC-20 USDC with 6 decimals
  • Arc Testnet (Chain 5042002) — Native USDC with 18 decimals

The SDK abstracts chain differences automatically. Pass 'base-sepolia' or 'arc-testnet' when initializing the client.

Standards

  • ERC-8183 — Agentic Commerce Protocol
  • ERC-8004 — Agent Identity (referenced by agentId in Reputation)
  • Solidity ^0.8.30 — Firmata Protocol v2

Quick Start

Get reading from the Firmata protocol in under a minute.

Read-Only (No Wallet)

import { FirmataClient } from '@firmata/sdk';

const firmata = new FirmataClient({ chain: 'base-sepolia' });

// Read total jobs
const totalJobs = await firmata.commerce.getTotalJobs();

// Read a specific job
const job = await firmata.commerce.getJob(1n);
console.log(job.description, job.status);

// Read trust score
const score = await firmata.reputation.getTrustScore('0xabc...');
console.log(score.composite);

// Protocol usage stats
const stats = await firmata.usage.getProtocolStats();
console.log(stats.totalEntries, stats.totalEstimatedCost);

With Wallet (For Writes)

import { FirmataClient, deadlineFromNow } from '@firmata/sdk';
import { createWalletClient, createPublicClient, http } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const account = privateKeyToAccount('0x...');

const publicClient = createPublicClient({
  transport: http('https://sepolia.base.org'),
});

const walletClient = createWalletClient({
  account,
  transport: http('https://sepolia.base.org'),
});

const firmata = new FirmataClient({
  chain: 'base-sepolia',
  publicClient,
  walletClient,
});

// Create a job
const txHash = await firmata.commerce.createJob({
  provider: '0xProviderAddress...',
  evaluator: '0xEvaluatorAddress...',
  expiredAt: deadlineFromNow(7 * 24 * 60 * 60),
  description: 'Summarize 500 PDFs and output structured JSON',
});

// Fund a job (auto-approves ERC-20 on Base)
await firmata.commerce.fund(1n, 10_000_000n); // 10 USDC

// Submit deliverable
import { stringToBytes32 } from '@firmata/sdk';
const deliverable = stringToBytes32('ipfs://Qm...');
await firmata.commerce.submit(1n, deliverable);

// Complete the job
import { zeroHash } from 'viem';
await firmata.commerce.complete(1n, zeroHash);

Installation

The Firmata SDK requires viem as a peer dependency for Ethereum client interactions.

npm

npm install @firmata/sdk viem

pnpm

pnpm add @firmata/sdk viem

yarn

yarn add @firmata/sdk viem

Requirements

  • Node.js 18+
  • TypeScript 5.0+ (recommended)
  • viem ^2.0

Verify Installation

import { FirmataClient } from '@firmata/sdk';

const firmata = new FirmataClient({ chain: 'base-sepolia' });
const total = await firmata.commerce.getTotalJobs();
console.log('Connected — total jobs:', total);

Architecture

The Firmata protocol is a system of six smart contracts with clearly defined responsibilities and inter-contract dependencies.

Contract Dependency Graph

┌─────────────────────────────────────────────────────────────┐
│                      FIRMATA PROTOCOL v2                     │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│   ┌──────────────┐       ┌──────────────┐                   │
│   │   Commerce    │◄──────│  EvaluatorV2  │                  │
│   │  (Job Escrow) │       │   (Oracle)    │                  │
│   └──────┬───────┘       └──────┬───────┘                   │
│          │                      │                            │
│          │  IACPHook            │  evaluate()                │
│          ▼                      ▼                            │
│   ┌──────────────┐       ┌──────────────┐                   │
│   │   SLAHook    │──────►│  Reputation   │                  │
│   │ (Proportional│       │ (Trust Score) │                  │
│   │   Payment)   │       └──────────────┘                   │
│   └──────┬───────┘                                          │
│          │                                                   │
│          │  commitSLA / verifySLA                            │
│          ▼                                                   │
│   ┌──────────────┐       ┌──────────────┐                   │
│   │     SLA      │       │  UsageLog     │                  │
│   │  (Registry)  │       │ (Analytics)   │                  │
│   └──────────────┘       └──────────────┘                   │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Contract Wiring

Commerce ← EvaluatorV2

The evaluator reads job data and calls complete() or reject() on Commerce based on SLO evaluation results.

Commerce → SLAHook (IACPHook)

Commerce calls beforeAction and afterAction hooks on the SLAHook during job lifecycle transitions. The hook intercepts settlement to apply proportional payment.

SLAHook → SLA Registry

SLAHook reads and verifies SLAs from the SLA contract to determine compliance ratios for payment calculation.

SLAHook → Reputation

After evaluation, SLAHook records SLA compliance data in the Reputation contract, updating agent trust scores.

EvaluatorV2 → SLAHook

The evaluator submits SLO results to SLAHook, which calculates the compliance ratio and triggers settlement.

UsageLog (Standalone)

UsageLog operates independently — any authorized caller can log agent resource consumption without job dependencies.

Payment Flow

  • Base Sepolia: ERC-20 USDC (6 decimals) — SDK auto-approves token transfers
  • Arc Testnet: Native USDC (18 decimals) — sent as msg.value
  • Protocol fee: 250 bps (2.5%) deducted on settlement
  • Proportional payment: grossPayment = budget × complianceRatio / 10000

Job Lifecycle

Every job in Firmata follows a deterministic state machine with six possible states.

State Flow

Open0
Funded1
Completed3
from Submitted →
Rejected4
from Funded (timeout) →
Expired5

State Transitions

Open → Funded

Client creates a job, then funds it with USDC. The budget is held in escrow by the Commerce contract.

// Create the job
const txHash = await firmata.commerce.createJob({
  provider: '0xProviderAddress...',
  evaluator: '0xEvaluatorAddress...',
  expiredAt: deadlineFromNow(7 * 24 * 60 * 60),
  description: 'Analyze market data for Q4',
});

// Fund it (10 USDC)
await firmata.commerce.fund(1n, 10_000_000n);

Funded → Submitted

Provider completes the work and submits a deliverable hash (typically an IPFS CID encoded as bytes32).

import { stringToBytes32 } from '@firmata/sdk';

const deliverable = stringToBytes32('ipfs://QmXyz...');
await firmata.commerce.submit(1n, deliverable);

Submitted → Completed

Client (or evaluator) reviews and completes the job. Payment is released to the provider minus protocol fees.

import { zeroHash } from 'viem';
await firmata.commerce.complete(1n, zeroHash);

Submitted → Rejected

Client rejects the submission. The job returns to a state where the client can claim a refund.

await firmata.commerce.reject(1n, '0x..reason_hash..');

Funded → Expired

If the provider fails to submit before expiredAt, the job transitions to Expired and the client can reclaim funds.

await firmata.commerce.claimRefund(1n);

Hooks System

Firmata supports extensible job behavior through the IACPHook interface. Hooks intercept job lifecycle events to implement custom logic.

IACPHook Interface

Any contract implementing IACPHook can be attached to a job at creation time. The Commerce contract calls two functions:

interface IACPHook {
    function beforeAction(
        uint256 jobId,
        uint8 action,
        bytes calldata data
    ) external;

    function afterAction(
        uint256 jobId,
        uint8 action,
        bytes calldata data
    ) external;
}

FirmataSLAHook — Reference Implementation

The FirmataSLAHook is the protocol's reference hook implementation. It links SLAs to jobs and enables proportional payment settlement based on SLA compliance.

How It Works

  1. An SLA is committed via the SLA Registry, linking provider and client
  2. The SLA is associated with a job through the SLAHook
  3. When the evaluator submits SLO results, the hook calculates a compliance ratio
  4. On settlement, payment is proportional to compliance

Proportional Payment Formula

grossPayment = budget × complianceRatio / 10000
providerPayment = grossPayment − (grossPayment × protocolFee / 10000)

Where complianceRatio is a value from 0 to 10000 (basis points) representing the percentage of SLOs met. A ratio of 10000 means full compliance; the provider receives the full budget minus protocol fees.

SLA Framework

The SLA Registry allows providers to commit on-chain service level agreements with hash-verified documentation stored on IPFS.

SLA Struct

struct SLA {
    address provider;      // Service provider address
    address client;        // Client address
    bytes32 docHash;       // Hash of SLA document
    string ipfsCID;        // IPFS content identifier
    uint256 validFrom;     // Start timestamp
    uint256 validTo;       // End timestamp
    uint8 sloCount;        // Number of SLOs in this SLA
    bool active;           // Whether SLA is currently active
}

Committing an SLA

const txHash = await firmata.sla.commitSLA({
  client: '0xClientAddress...',
  docHash: '0x...',          // keccak256 of SLA document
  ipfsCID: 'QmXyz...',      // IPFS CID of full document
  validFrom: toUnixTime(new Date()),
  validTo: deadlineFromNow(30 * 24 * 60 * 60), // 30 days
  sloCount: 5,
});

Verifying an SLA

Anyone can verify that an SLA's on-chain hash matches a document:

const isValid = await firmata.sla.verifySLA(1n, '0x...docHash');
const sla = await firmata.sla.getSLA(1n);
const active = await firmata.sla.isActive(1n);

SLA ↔ Job Linking

SLAs are linked to jobs through the FirmataSLAHook. When a job has the SLAHook configured, the hook reads the associated SLA to calculate compliance-based payment at settlement time.

Reputation System

Firmata's reputation system maintains on-chain trust scores for agents using a weighted composite of three metrics.

Agent Registration

Agents must register before receiving feedback or trust scores:

await firmata.reputation.registerAgent(
  '0xAgentAddress...',
  42n  // ERC-8004 agentId
);

Trust Score Composition

The composite trust score is calculated from three weighted sub-scores:

α (alpha) Reputation Score

Based on peer feedback — positive and negative ratings from job counterparties.

β (beta) Validation Score

Based on evaluator assessments — how well deliverables pass oracle validation.

γ (gamma) SLA Compliance Score

Based on SLA adherence — recorded by SLAHook and EvaluatorV2 after each job.

Giving Feedback

await firmata.reputation.giveFeedback({
  agent: '0xAgentAddress...',
  jobId: 1n,
  score: 85,     // 0-100
  comment: 'Delivered on time with high quality',
});

Reading Trust Scores

const score = await firmata.reputation.getTrustScore('0xAgent...');

console.log({
  composite: score.composite,
  reputation: score.reputation,
  validation: score.validation,
  slaCompliance: score.slaCompliance,
  totalJobs: score.totalJobs,
  metSLOs: score.metSLOs,
});

Authorized Callers

SLA compliance data can only be recorded by authorized contracts — specifically the FirmataSLAHook and FirmataEvaluatorV2. This prevents manipulation of trust scores.

Usage Analytics

The FirmataUsageLog contract provides on-chain tracking of agent resource consumption — LLM tokens, API calls, execution time, and estimated costs.

Logging Usage

await firmata.usage.logUsage({
  agent: '0xAgentAddress...',
  jobId: 1n,
  inputTokens: 15000n,
  outputTokens: 3200n,
  apiCalls: 12n,
  executionMs: 45000n,
  estimatedCost: 850000n,  // $0.85 in 6-decimal USD
  model: 'gpt-4-turbo',
  metadataURI: 'ipfs://QmMetadata...',
});

metadataURI Content

The metadataURI field points to a JSON document with extended analytics:

{
  "version": "1.0",
  "agent": "0xAgentAddress...",
  "jobId": 1,
  "breakdown": {
    "model": "gpt-4-turbo",
    "inputTokens": 15000,
    "outputTokens": 3200,
    "apiCalls": 12,
    "executionMs": 45000,
    "estimatedCostUSD": 0.85
  },
  "toolsUsed": ["web-search", "pdf-parser", "json-formatter"],
  "timestamp": "2026-03-27T12:00:00Z"
}

Protocol Stats

const stats = await firmata.usage.getProtocolStats();
console.log(stats.totalEntries, stats.totalEstimatedCost);

// Per-agent stats
const agentStats = await firmata.usage.getAgentStats('0xAgent...');

// Recent entries
const recent = await firmata.usage.getRecentEntries(10n);

FirmataClient

The main entry point for all Firmata SDK interactions.

Constructor

new FirmataClient(options: {
  chain: 'base-sepolia' | 'arc-testnet';
  publicClient?: PublicClient;   // auto-created if omitted
  walletClient?: WalletClient;   // required for write operations
})

Properties

Property Type Description
commerce CommerceModule Job lifecycle operations
sla SLAModule SLA registry operations
reputation ReputationModule Agent trust scoring
usage UsageModule Analytics and logging

Chain Configuration

When no publicClient is provided, the SDK creates one automatically using the default RPC for the selected chain:

Chain Chain ID RPC USDC
base-sepolia 84532 https://sepolia.base.org ERC-20, 6 decimals
arc-testnet 5042002 https://rpc.testnet.arc.network Native, 18 decimals

Commerce Module

Access via firmata.commerce. Manages the full job lifecycle — 21 contract functions, 12 events.

Read Methods

Method Returns Description
getJob(jobId: bigint) Job Returns full Job struct
getTotalJobs() bigint Total jobs ever created
getProtocolFee() bigint Protocol fee in bps (250 = 2.5%)
getJobsByClient(address) bigint[] Job IDs for a client
getJobsByProvider(address) bigint[] Job IDs for a provider

Write Methods

Method Returns Description
createJob(params) Hash Create a new job, returns tx hash
fund(jobId, budget) Hash Fund a job (auto-approves ERC-20 on Base)
submit(jobId, deliverable) Hash Submit bytes32 deliverable hash
complete(jobId, reason) Hash Mark job complete
reject(jobId, reason) Hash Reject submitted job
setBudget(jobId, amount) Hash Set/update job budget
setProvider(jobId, provider) Hash Assign provider to job
claimRefund(jobId) Hash Claim refund for rejected/expired job

Job Type

interface Job {
  id: bigint;
  client: Address;
  provider: Address;
  evaluator: Address;
  description: string;
  budget: bigint;
  expiredAt: bigint;
  status: JobStatus;
  hook: Address;
}

enum JobStatus {
  Open = 0,
  Funded = 1,
  Submitted = 2,
  Completed = 3,
  Rejected = 4,
  Expired = 5,
}

SLA Module

Access via firmata.sla. Manages on-chain service level agreements.

Read Methods

Method Returns Description
getSLA(slaId: bigint) SLA Returns full SLA struct
isActive(slaId: bigint) boolean Check if SLA is active
verifySLA(slaId, docHash) boolean Verify doc hash matches on-chain
getSLAsByProvider(provider) bigint[] SLA IDs by provider
getSLAsByClient(client) bigint[] SLA IDs by client

Write Methods

Method Returns Description
commitSLA(params) Hash Commit new SLA with IPFS CID
revokeSLA(slaId, reason) Hash Revoke an active SLA

Reputation Module

Access via firmata.reputation. Agent trust scores and feedback management.

Read Methods

Method Returns Description
getProfile(agent) AgentProfile Returns agent profile
getTrustScore(agent) TrustScore Composite + sub-scores
isRegistered(agent) boolean Check if agent is registered
meetsThreshold(agent, minScore) boolean Check against minimum score
getFeedbackHistory(agent) Feedback[] All feedback entries

Write Methods

Method Returns Description
registerAgent(agent, agentId) Hash Register with ERC-8004 agentId
giveFeedback(params) Hash Submit feedback for an agent
refreshTrustScore(agent) Hash Trigger score recalculation

TrustScore Type

interface TrustScore {
  composite: number;
  reputation: number;
  validation: number;
  slaCompliance: number;
  lastUpdated: bigint;
  totalJobs: number;
  totalSLOs: number;
  metSLOs: number;
}

Usage Module

Access via firmata.usage. On-chain analytics for agent resource consumption.

Read Methods

Method Returns Description
getProtocolStats() ProtocolUsageStats Protocol-wide statistics
getEntry(entryId) UsageEntry Returns usage entry
getAgentStats(agent) AgentStats Per-agent statistics
getAgentEntryIds(agent) bigint[] All entry IDs for agent
getJobEntryIds(jobId) bigint[] All entry IDs for job
getRecentEntries(count) UsageEntry[] N most recent entries

Write Methods

Method Returns Description
logUsage(params) Hash Log resource consumption

Utilities

Helper functions exported from @firmata/sdk for common operations.

Function Example Description
formatUSDC(amount) formatUSDC(10_000_000n) → "10.00" Format 6-decimal bigint to USD string
parseUSDC(amount) parseUSDC("10.50") → 10_500_000n Parse USD string to 6-decimal bigint
truncateAddress(addr) "0xabc...def" Shorten address for display
isSameAddress(a, b) true / false Case-insensitive address comparison
fromUnixTime(ts) Date Bigint timestamp to Date
toUnixTime(date) bigint Date to bigint timestamp
deadlineFromNow(sec) deadlineFromNow(3600) Current time + seconds as bigint
stringToBytes32(str) bytes32 hex UTF-8 string to bytes32 hex
bytes32ToString(hex) string Bytes32 hex to UTF-8 string

Import

import {
  formatUSDC,
  parseUSDC,
  truncateAddress,
  isSameAddress,
  fromUnixTime,
  toUnixTime,
  deadlineFromNow,
  stringToBytes32,
  bytes32ToString,
} from '@firmata/sdk';

Contract Addresses

All Firmata contracts are deployed on two testnets. Source code is private (bytecode only on explorers).

Base Sepolia Chain 84532

Contract Address
FirmataCommerce 0x5373bfe8f04f8f9fcf7b7b23d728bec86ee4076b
FirmataSLA 0x4bf3df02e75177fa1139fce7d5a336b9c31f20c4
FirmataReputation 0xd725473c7be05fbf5df7df14adb133852c4812ab
FirmataSLAHook 0x57354080e4e3b5209781f2d8c61e0a9dfc8e2ead
FirmataEvaluatorV2 0x854e74973fdeef258850ce19493d75a3f1abcef7
FirmataUsageLog 0x9625d630976aa62aae05c37220ac901caa474167
USDC 0x036CbD53842c5426634e7929541eC2318f3dCF7e

Arc Testnet Chain 5042002

Contract Address
FirmataCommerce 0x5806a1f82a1b3fcbefcd2efc5d873c2167ee3a8e
FirmataSLA 0x8b1fb35f25c799aa4dc8460f83b4b7a86f0f7854
FirmataReputation 0xacb1f85bce0731d2bcb0e0788c88e45eba7a72ad
FirmataSLAHook 0xd10a3d3bd6aa47c3a8b91c77144734c8f1eb1da9
FirmataEvaluatorV2 0x87e3e765dd2372a765b72e29167911e876d72be4
FirmataUsageLog 0x4bf87626da383230eb663988e45975f3e9772003
USDC Native (address(0)) 18 decimals

ABIs

All contract ABIs are bundled with the SDK package. Import them directly for use with viem or other Ethereum libraries.

Importing ABIs

import {
  commerceAbi,
  slaAbi,
  reputationAbi,
  slaHookAbi,
  evaluatorAbi,
  usageLogAbi,
} from '@firmata/sdk';

Usage with viem

import { createPublicClient, http } from 'viem';
import { commerceAbi } from '@firmata/sdk';

const client = createPublicClient({
  transport: http('https://sepolia.base.org'),
});

const totalJobs = await client.readContract({
  address: '0x5373bfe8f04f8f9fcf7b7b23d728bec86ee4076b',
  abi: commerceAbi,
  functionName: 'totalJobs',
});

For most use cases, you won't need to import ABIs directly — the FirmataClient handles contract interactions through its typed module methods.

Links