Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/adr/0002-agent-registry-on-soroban.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ADR-0002: Agent registries on Soroban, aligned with a Stellar 8004 subset

- **Status:** Proposed
- **Status note:** amended by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) (Accepted 2026-10-02). The decision text below is unchanged; each affected section carries a note.
- **Amended by:** [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) · [What this supersedes or amends](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md#what-this-supersedes-or-amends)
- **Date:** 2026-09-23
- **Issue:** #6
- **Research:** [Spike: ERC-8004 on Soroban](../spikes/erc8004-soroban.md)
Expand All @@ -16,10 +18,16 @@ We need to decide whether puls3 uses those contracts, copies them, or writes its

1. **puls3 deploys its own Identity Registry (#13) and Reputation Registry (#14).** They are written by the team, in `contracts/identity-registry/` and `contracts/reputation-registry/`, on the `soroban-sdk` version pinned by #12.
2. **Both align with the Stellar 8004 interface where the MVP subset permits it** (inspected at commit [`d92c2f4`](https://github.com/trionlabs/stellar-8004/tree/d92c2f4ee01858b6da9bf4404ac49322c324958b)). Functions explicitly marked as shared keep the same name, argument order, and types. Unmodified events and error codes retain their Stellar 8004 shape and values. This is **not drop-in compatibility**: clients need a puls3 adapter for omitted functions, typed-error differences, URI uniqueness, and the payment-backed feedback extension; in particular, puls3 changes the `NewFeedback` data and `give_feedback` signature.

> **Amended by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D5:** puls3 keeps its own deployments, but the interface target becomes **identical (drop-in) to Stellar 8004**, not a subset requiring a puls3 adapter. See [What this supersedes or amends](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md#what-this-supersedes-or-amends).

3. **The supported subset and differences are explicit:**
- Functions that panic in Stellar 8004 on bad input return `Result<_, Error>` in ours (our rule: no string panics). On success they return the same value.
- **Out of the MVP:** NFT transfers and approvals (`transfer`, `transfer_from`, `approve`, `approve_for_all`, `get_approved`, `is_approved_for_all`) and upgrades (`propose_upgrade`, `cancel_upgrade`, `execute_upgrade`, `pending_upgrade`). Without approvals, "owner or approved" means "owner".
- **puls3 additions:** an agent URI can be registered only once (`UriAlreadyRegistered`), plus a lookup `agent_id_by_uri`; and reputation feedback must consume a unique, pre-authorized paid hire. ERC-8004 lets one owner hold many agents, so a duplicate is defined by the URI, not by the owner.

> **Superseded by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D5:** the paid-hire gate leaves the registry. It is proven by a completed ERC-8183 escrow job and applied by readers; the trusted `authorize_feedback` authorizer and the payment-backed `give_feedback` are removed.

4. **Validation Registry: out of the MVP.**
5. **No dependency on OpenZeppelin `stellar-tokens`** until a release supports our SDK major (0.7.2 requires `soroban-sdk ^26.1`). The Identity Registry stores ownership itself and exposes the NFT read functions by name, so an NFT base can be swapped in later without changing the interface.

Expand Down Expand Up @@ -216,6 +224,8 @@ pub struct MetadataSet {

The read model and shared feedback fields follow the Stellar 8004 Reputation Registry subset below. puls3 deliberately extends the write path so the contract enforces "one verified paid hire, at most one reputation entry." This makes the Reputation Registry non-drop-in-compatible with Stellar 8004.

> **Superseded by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D5:** the trusted `authorize_feedback` authorizer and the payment-backed `give_feedback` gate are removed. The paid-hire rule is proven by a completed ERC-8183 escrow job and applied by readers; the drop-in registry is [#14](https://github.com/Zer0-Knowledge-Hack/puls3/issues/14).

Before feedback, the Serverpod payment verifier confirms the expected asset, amount, recipient, successful transaction, and that the payment reference has not already funded another hire. It derives a deterministic `hire_id` from the puls3 hire record and verified transaction reference, then asks the configured authorizer to register `(hire_id, agent_id, client_address)`. The authorizer must authenticate, and the contract rejects a second authorization for the same `hire_id`. `give_feedback` requires the same client to authenticate, checks the tuple, and marks the authorization consumed in the same contract invocation that stores feedback. If that invocation fails, neither state change may persist. Revocation does not reset the consumed flag.

The registry therefore enforces **one authorization and at most one feedback per `hire_id`**; it does not prove that the identifier represents a real payment. Payment validity and one-payment/one-hire idempotency remain trusted responsibilities of the Serverpod verifier and authorizer. The authorizer custody and recovery policy remains owned by ADR-0003.
Expand Down Expand Up @@ -272,6 +282,9 @@ impl ReputationRegistryContract {
/// puls3 extension. Loads `FeedbackAuthorizer` from instance storage, requires
/// that configured address to authenticate, and registers one payment-backed
/// hire. A `hire_id` may be registered only once.
///
/// Amended by ADR-0005 D5: the trusted authorizer is removed; the paid-hire
/// rule is proven by a completed ERC-8183 escrow job and applied by readers.
pub fn authorize_feedback(
e: &Env,
hire_id: BytesN<32>,
Expand Down
21 changes: 21 additions & 0 deletions docs/adr/0003-payment-rail-and-custody.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# ADR-0003: Payment rail and agent wallet custody

- **Status:** Proposed
- **Status note:** amended by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) (Accepted 2026-10-02). The decision text below is unchanged; each affected section carries a note.
- **Amended by:** [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) · [What this supersedes or amends](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md#what-this-supersedes-or-amends)
- **Date:** 2026-09-26
- **Issue:** #7
- **Research:** [Spike: payments and wallets](../spikes/payments-and-wallets.md)
Expand All @@ -14,16 +16,28 @@ Consumers pay agents per task in USDC, and every agent has its own wallet ([visi
## Decision

1. **Rail: direct USDC transfer through the SAC.** The consumer's wallet signs `transfer(from = consumer, to = M…, amount = price)` on the USDC Stellar Asset Contract. No x402, no MPP SDK, no escrow in the MVP.

> **Superseded by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D1, D2:** the consumer funds an ERC-8183 escrow job instead of a direct SAC transfer. See [What this supersedes or amends](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md#what-this-supersedes-or-amends).

2. **The hire id travels as the muxed id.** `M…` is the agent wallet's address muxed with the hire id (a `u64`). The SAC's `transfer` event returns it as `to_muxed_id`.

> **Amended by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D1:** the escrow job id identifies the hire in contract calls and events; a muxed id may still tag the escrow's payout to the agent wallet (unverified).

3. **The server verifies before running the agent.** It calls RPC `getTransaction` with `xdrFormat: "json"` over plain HTTP and accepts the payment only if **all** of these hold:
- `status` is `SUCCESS`;
- a `transfer` event was emitted by the **configured USDC SAC contract id** (so a look-alike token cannot pass);
- the event's `to` topic is the agent's wallet;
- `amount` equals the price recorded for the hire (exact match);
- `to_muxed_id` equals the hire id;
- the transaction hash has **not been used** for any other hire, and the hire is not already paid.

> **Superseded by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D1:** the server reads the escrow job's state and events; the asset and amount are enforced by the contract.

4. **Agent wallets: server-custodied G-accounts, testnet only.** The server generates a keypair per agent, stores the secret encrypted, funds the account, and adds the USDC trustline. Earnings are swept to the builder. **Mainnet requires moving agent wallets to smart accounts with spending limits first.**
5. **Server-side chain access:** reads through Stellar RPC over plain HTTP; signing and submitting with the pure-Dart `stellar_dart`, only inside the `LedgerPort` adapter (ADR-0001). Fallback if `stellar_dart` fails: a small TypeScript sidecar.

> **Unchanged by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md).**

6. **Who signs:**

| Transaction | Signer |
Expand All @@ -34,6 +48,8 @@ Consumers pay agents per task in USDC, and every agent has its own wallet ([visi
| `set_agent_wallet` | Transaction source = agent account (server signs the envelope); the builder signs only their authorization entry with Freighter's `signAuthEntry` |
| Sweep to the builder | Server |

> **Amended by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D1, D4:** the consumer signs `create_job` and `fund` and, as evaluator, `complete` and `reject`; the server (agent's custodied key) signs `submit`; `claim_refund` is permissionless.

## Consequences

| Issue | Impact |
Expand All @@ -46,6 +62,8 @@ Consumers pay agents per task in USDC, and every agent has its own wallet ([visi
| **#15, #30** Funding | Testnet USDC from Circle's faucet (manual) or the scripted test asset from the PoC |
| **#55** Escrow contract | Not needed for the MVP rail. Escrow stays out of scope, as in the vision |

> **Superseded by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D1:** the **#55 Escrow contract** row above no longer holds — the escrow is in scope for the hire rail.

- Payments settle in one transaction and are verifiable from one HTTP call. No contract of ours holds user funds.
- The server is a custodian of agent earnings on testnet. A breach could drain agent balances, so balances stay small and are swept.
- Replay protection depends on the server's database (used hashes, paid hires), not on a contract.
Expand All @@ -56,6 +74,9 @@ Consumers pay agents per task in USDC, and every agent has its own wallet ([visi
1. **x402.** Standard per-request payments with a facilitator, but its SDKs are TypeScript, it needs a facilitator, and it is designed for machine clients calling HTTP APIs, not people in an app. Revisit for agent-to-agent API calls.
2. **MPP charge.** On-chain it is the same SAC transfer; the rest is an HTTP protocol with a TypeScript SDK we do not need yet.
3. **Escrow contract (#55).** Protects against agents that fail after payment, but adds a contract, refund logic, and disputes, all out of the MVP. Running the agent only after a confirmed payment keeps the MVP simple.

> **Superseded by [ADR-0005](0005-align-agent-commerce-with-erc-8183-and-erc-8004.md) D1:** escrow moves into scope for the MVP hire rail.

4. **Memo instead of muxed id.** The muxed id is carried inside the SAC event, so one `getTransaction` call proves the hire; a memo is a transaction-level field outside the token event.
5. **Smart accounts for agent wallets now.** Safer, but needs a wallet contract and policies before the Serverpod deadline. Required before mainnet.
6. **Builder's own address as the payment address.** No custody at all, but agents would have no wallet of their own. Kept as the fallback if custody must be dropped.
Loading