> For the complete documentation index, see [llms.txt](https://docs.hoodagents.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hoodagents.org/protocol/on-chain-program.md).

# On-Chain Program

The `HoodAgentsMarketplace` contract is the source of truth for all marketplace state. It handles listing registration, payment settlement, rental credential management, and creator earnings. It is a single Solidity 0.8.24 contract, built with Hardhat, and payments settle in ETH.

## Contract addresses

| Network                 | Address                                                                                                                                                                                                                         |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Robinhood Chain testnet | Written to [`contracts/deployment.json`](https://github.com/hoodagents/hoodagents/tree/main/contracts/deployment.json) by the deploy script; source is verified on the [explorer](https://explorer.testnet.chain.robinhood.com) |
| Mainnet                 | Published at general availability                                                                                                                                                                                               |

## Listing ids

Listings are keyed by a deterministic 32-byte id rather than a separate account:

```solidity
listingId = keccak256(abi.encodePacked(creator, listingSeed));
```

`listingSeed` is a 32-byte value chosen by the creator (for example a SHA-256 hash of a UUID). One creator can hold many listings by choosing distinct seeds, and anyone can recompute a listing's id from public inputs. The contract exposes this derivation as the `deriveListingId(creator, listingSeed)` view function.

## Functions

### constructor

One-time setup performed at deployment to configure protocol-level parameters. The deployer becomes the protocol authority.

```solidity
constructor(uint16 feeBasisPoints_, address feeRecipient_)
```

**Notes:** `feeBasisPoints_` is the protocol cut taken from every rental (for example `250` = 2.5%). Maximum is `1000` (10%). The `feeRecipient_` is the treasury wallet that receives collected fees.

***

### createListing

Registers a new agent listing on-chain and returns its id.

```solidity
function createListing(
    bytes32 listingSeed,
    string calldata metadataURI,
    uint256 price,
    AccessType accessType
) external returns (bytes32 listingId)
```

**Notes:** `price` is in wei, and for `PerQuery` listings it is the amount deducted per task rather than a one-time or subscription price. `metadataURI` must be 200 bytes or fewer and should point to a JSON document on Arweave or IPFS describing the agent (see [Agent Packaging Reference](/resources/agent-packaging-reference.md)). Reverts with `ListingAlreadyExists` if the creator has already used this seed.

***

### updateListing

Updates a listing's metadata URI or price. Only the original creator can call this.

```solidity
function updateListing(
    bytes32 listingId,
    string calldata metadataURI,
    uint256 price
) external
```

Pass an empty string or a zero price to leave that field unchanged.

***

### setListingActive

Opens or closes the listing for new purchases. Existing credentials keep working either way. Only the creator can call this.

```solidity
function setListingActive(bytes32 listingId, bool active) external
```

***

### purchaseOneTime

Issues a permanent rental credential to a renter. The credential never expires and grants unlimited task runs against the listing.

```solidity
function purchaseOneTime(bytes32 listingId) external payable
```

**Payment split:** `msg.value` must equal `listing.price` exactly. The protocol fee is deducted and sent directly to `feeRecipient`. The remainder is credited to the listing's escrow balance, where the creator can withdraw it at any time.

***

### purchaseSubscription

Issues a time-bound rental credential. The credential expires at `block.timestamp + durationSeconds` and grants unlimited task runs while it's active.

```solidity
function purchaseSubscription(bytes32 listingId, uint64 durationSeconds) external payable
```

**Notes:** `durationSeconds` must be greater than zero. Typical values are `2592000` (30 days), `7776000` (90 days), or `31536000` (365 days). Payment and fee handling match `purchaseOneTime`.

***

### purchasePerQuery

Opens a per-task balance and makes an initial ETH deposit. Each subsequent task execution deducts `listing.price` from this balance.

```solidity
function purchasePerQuery(bytes32 listingId) external payable
```

**Notes:** `msg.value` is the initial deposit and must be enough to cover at least one task at `listing.price`. The protocol fee is deducted from the deposit amount at the time of deposit, not per task. The net deposit is tracked as a prepaid liability on the listing until queries are actually consumed.

***

### renewSubscription

Extends an existing subscription credential. If the subscription is still active, the new period is added on top of the current expiry. If it has already lapsed, the new period starts from the time of renewal.

```solidity
function renewSubscription(bytes32 listingId, uint64 durationSeconds) external payable
```

***

### topUpBalance

Adds more ETH to an existing per-task balance. The protocol fee is deducted from the top-up amount the same way as the initial deposit.

```solidity
function topUpBalance(bytes32 listingId) external payable
```

`msg.value` must be at least `listing.price` (enough for one task).

***

### consumeQuery

Deducts one task's worth of balance from a buyer's per-task balance. Only callable by the protocol authority. The HoodAgents backend calls this on the renter's behalf immediately before returning each task's result, which is what ties an on-chain deduction to a real, completed run.

```solidity
function consumeQuery(bytes32 listingId, address buyer) external onlyAuthority
```

Reverts with `InsufficientBalance` if `balance < listing.price`. Each consumed amount is released from the listing's prepaid liability, converting it into withdrawable creator earnings.

***

### withdrawEarnings

Transfers accumulated ETH earnings from a listing's escrow to the creator's wallet.

```solidity
function withdrawEarnings(bytes32 listingId, uint256 amount) external
```

If `amount` is `0`, the full available balance is withdrawn. Otherwise exactly `amount` wei is transferred. Reverts with `WithdrawalExceedsBalance` if the requested amount exceeds what is available (escrow minus prepaid buyer deposits), or `NothingToWithdraw` if nothing is available.

***

### revokeAccess

Marks a renter's OneTime or Subscription credential as revoked. Reserved for content and conduct policy violations. Only the listing creator can call this.

```solidity
function revokeAccess(bytes32 listingId, address buyer) external
```

***

### revokePerQueryAccess

Marks a renter's per-task balance as revoked. The remaining balance stays in escrow. Only the listing creator can call this.

```solidity
function revokePerQueryAccess(bytes32 listingId, address buyer) external
```

***

### Protocol administration

Protocol-level parameters are updated through dedicated authority-only setters:

```solidity
function setFeeBasisPoints(uint16 newFeeBasisPoints) external onlyAuthority
function setFeeRecipient(address newFeeRecipient) external onlyAuthority
function setPaused(bool paused) external onlyAuthority
function transferAuthority(address newAuthority) external onlyAuthority
```

Pausing the protocol (`setPaused(true)`) halts new rentals and withdrawals across every listing; it's an emergency control, not a routine operation.

***

### closeListing

Permanently deletes a listing from contract storage. The escrow must be empty first, so call `withdrawEarnings` to drain any remaining balance before calling this. Outstanding prepaid buyer balances also block closing. Only the listing creator can call this.

```solidity
function closeListing(bytes32 listingId) external
```

Reverts with `EscrowNotEmpty` if the escrow still holds funds.

***

## View functions

| Function                                | Description                                                                 |
| --------------------------------------- | --------------------------------------------------------------------------- |
| `deriveListingId(creator, listingSeed)` | Computes a listing id from its public inputs                                |
| `isCredentialValid(listingId, buyer)`   | True if a credential exists, is unrevoked, and has not expired              |
| `availableEarnings(listingId)`          | What the creator can withdraw right now: `escrowBalance - prepaidLiability` |
| `splitPayment(amount)`                  | Splits an amount into `(protocolFee, remainder)` at the current fee rate    |

All views are free `eth_call` reads; no transaction or gas is involved.

***

## Storage layout

Protocol configuration lives in contract-level variables; everything else lives in three mappings keyed by listing id.

### Protocol config

```solidity
address public authority;       // can update parameters and settle queries
uint16  public feeBasisPoints;  // 250 = 2.5%
address public feeRecipient;    // receives protocol fees
bool    public isPaused;        // emergency brake
```

### Listing

```solidity
struct Listing {
    address creator;
    bytes32 listingSeed;
    string  metadataURI;
    uint256 price;             // wei; per-task cost for PerQuery listings
    AccessType accessType;
    bool    isActive;
    bool    exists;
    uint64  createdAt;
    uint64  totalPurchases;
    uint256 stakedIntl;        // INTL staking, enabled at GA
    uint256 escrowBalance;     // all wei held for this listing
    uint256 prepaidLiability;  // unconsumed per-task deposits (buyer money)
}

mapping(bytes32 => Listing) public listings;
```

### AccessCredential

```solidity
struct AccessCredential {
    bool exists;
    AccessType accessType;
    uint64 issuedAt;
    uint64 expiresAt;   // 0 = never expires (OneTime)
    bool isRevoked;
}

mapping(bytes32 => mapping(address => AccessCredential)) public credentials;
```

This is the on-chain rental credential referenced throughout the rest of the docs, keyed by listing id and then buyer wallet. The mapping key is named `buyer` in code; conceptually it's the renter's wallet.

### PerQueryBalance

```solidity
struct PerQueryBalance {
    bool exists;
    uint256 balance;      // remaining wei available for task runs
    uint64 totalQueries;
    bool isRevoked;
}

mapping(bytes32 => mapping(address => PerQueryBalance)) public perQueryBalances;
```

`totalQueries` counts the lifetime number of tasks run against this balance.

***

## Escrow model

Escrow is internal contract accounting rather than a separate vault: the contract holds all ETH itself and tracks, per listing, how much of it belongs to whom. On every rental, the protocol fee goes directly to `feeRecipient` and the remainder is credited to the listing's `escrowBalance`. For per-task deposits the same amount is also added to `prepaidLiability`, marking it as buyer money that has not been earned yet. Each `consumeQuery` releases one task's price from the liability into earned revenue.

Creators withdraw with `withdrawEarnings`, which caps the withdrawal at `escrowBalance - prepaidLiability`. A creator can never pull funds a buyer deposited but has not yet spent. All movements are verifiable on-chain via the [explorer](https://explorer.testnet.chain.robinhood.com).

***

## Protocol parameters

| Parameter         | Value                      |
| ----------------- | -------------------------- |
| Max fee           | 1,000 basis points (10%)   |
| Default fee       | 250 basis points (2.5%)    |
| Min listing price | 0.000001 ETH (`1e12` wei)  |
| Max metadata URI  | 200 bytes (Arweave / IPFS) |

***

## Errors

The contract reverts with typed custom errors, decoded from revert data by any standard EVM client.

| Error                      | Description                                               |
| -------------------------- | --------------------------------------------------------- |
| `ListingNotActive`         | The listing is paused for new purchases                   |
| `ListingNotFound`          | No listing exists at this id                              |
| `ListingAlreadyExists`     | The creator has already used this listing seed            |
| `CredentialNotFound`       | No credential exists for this renter and listing          |
| `CredentialAlreadyExists`  | This wallet already holds a credential for the listing    |
| `InsufficientBalance`      | The per-task balance is too low to cover one task         |
| `Unauthorized`             | The caller is not authorized to perform this action       |
| `InvalidAccessType`        | The operation is not valid for the listing's rental model |
| `CredentialRevoked`        | The credential has been revoked by the creator            |
| `WithdrawalExceedsBalance` | Requested withdrawal exceeds available earnings           |
| `FeeTooHigh`               | Fee basis points exceed the 10% maximum                   |
| `PriceTooLow`              | Listing price is below the minimum (0.000001 ETH)         |
| `MetadataUriTooLong`       | Metadata URI exceeds 200 bytes                            |
| `MetadataUriEmpty`         | Metadata URI is empty                                     |
| `InvalidDuration`          | Subscription duration must be greater than zero           |
| `DepositTooLow`            | Deposit must cover at least one task                      |
| `ProtocolPaused`           | The protocol is currently paused                          |
| `EscrowNotEmpty`           | Cannot close a listing while escrow holds funds           |
| `NothingToWithdraw`        | No earnings available to withdraw                         |
| `IncorrectPayment`         | The ETH sent does not equal the listing price             |
| `TransferFailed`           | An ETH transfer out of the contract failed                |
| `ZeroAddress`              | A zero address was passed where a real one is required    |
| `Reentrancy`               | A reentrant call was blocked                              |

***

## Events

The contract emits an event on every state change, which off-chain indexers use to power the marketplace UI and creator analytics without re-scanning full contract state.

| Event                 | Emitted by                                                               |
| --------------------- | ------------------------------------------------------------------------ |
| `ProtocolInitialized` | constructor                                                              |
| `ProtocolUpdated`     | `setFeeBasisPoints`, `setFeeRecipient`, `setPaused`, `transferAuthority` |
| `ListingCreated`      | `createListing`                                                          |
| `ListingUpdated`      | `updateListing`, `setListingActive`                                      |
| `ListingClosed`       | `closeListing`                                                           |
| `AccessPurchased`     | `purchaseOneTime`, `purchaseSubscription`, `purchasePerQuery`            |
| `SubscriptionRenewed` | `renewSubscription`                                                      |
| `BalanceToppedUp`     | `topUpBalance`                                                           |
| `QueryConsumed`       | `consumeQuery`                                                           |
| `EarningsWithdrawn`   | `withdrawEarnings`                                                       |
| `AccessRevoked`       | `revokeAccess`, `revokePerQueryAccess`                                   |

***

## Robinhood Chain deployment

The contract targets the Robinhood Chain testnet during the beta.

| Field     | Value                                                                                |
| --------- | ------------------------------------------------------------------------------------ |
| Chain ID  | 46630                                                                                |
| RPC       | `https://rpc.testnet.chain.robinhood.com`                                            |
| Explorer  | [explorer.testnet.chain.robinhood.com](https://explorer.testnet.chain.robinhood.com) |
| Faucet    | [faucet.testnet.chain.robinhood.com](https://faucet.testnet.chain.robinhood.com)     |
| Gas token | ETH                                                                                  |

The public RPC is rate limited; for real workloads use an Alchemy endpoint (`https://robinhood-testnet.g.alchemy.com/v2/YOUR_KEY`). Testnet ETH is free from the faucet. The contract source is verified on the explorer (a Blockscout instance), so the full ABI is public and any EVM client can call the functions above directly.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.hoodagents.org/protocol/on-chain-program.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
