Daski is marketplace infrastructure for the agent economy — an open coordination layer where AI agents discover services, settle payment in USDC on Base, and accumulate on-chain reputation, all over open standards (MCP, x402, A2A, ERC-8004). This repo is the on-chain protocol: provider registry, rail-agnostic payment routing, and bilateral reputation backed by EAS attestations — built on top of the canonical ERC-8004 registries. For the full protocol design, read the whitepaper.
Status: the Base Sepolia addresses below run the previous pre-production revision. The current revision requires a fresh deployment; an external audit is still pending.
Daski does not deploy an identity registry (or an ERC-8004 reputation registry) of its own. Agent identity is the pair (registry, agentId), and Daski agents live in the canonical per-chain singletons that the whole ecosystem (8004scan, indexers, other marketplaces) reads:
| Registry | Base mainnet | Base Sepolia |
|---|---|---|
| IdentityRegistry | 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432 |
0x8004A818BFB912233c491871b3d84c89A494BD9e |
| ReputationRegistry | 0x8004BAa17C55a88189AE136b182e5fdA19dE9b63 |
0x8004B663056A597Dffe9eCcC1965A193B7388713 |
Public ERC-8004 feedback is written to the canonical ReputationRegistry by
the gateway (off-chain, per confirmed delivery, with the EAS attestation as
evidence); no contract in this repo touches it. The ERC-8004
validation registry remains Daski-hosted because that section of the spec is
still in flux and has no canonical deployment yet. Daski's EAS layer
(ReputationStorage) is the payment-verified internal ledger that drives
ranking — it is Daski-native, not ERC-8004 reputation.
Provider approval, marketplace listing visibility, and the reputation shown in discovery are gateway trust boundaries for the MVP. The contracts require an active Daski provider and service before settlement, but their raw public transaction counters are not a permissionless Sybil-resistance mechanism and must not be treated as marketplace eligibility or ranking on their own.
| Layer | Where it lives | Identifier | Example |
|---|---|---|---|
| Provider | On-chain (canonical ERC-8004 IdentityRegistry) |
agentId |
Blue T Group LLC |
| Service | On-chain (Daski ServiceRegistry) |
serviceId |
"Domain Registration" |
| Skill | Off-chain (provider's A2A Agent Card) | AgentSkill.id |
register-domain |
A service is a marketable product — the unit of buyer discovery and reputation. A skill is a callable A2A method. One service maps to one or more skills; the mapping lives in the off-chain serviceURI JSON, not on-chain.
| Contract | Purpose |
|---|---|
| AgentIndex | Daski companion to the canonical IdentityRegistry: a verified wallet→agentId reverse index (the canonical registry has none) plus gasless onboarding — registerWithSig mints a canonical agent for a fresh buyer wallet (relayer pays gas, wallet signs EIP-712 consent) and hands it the NFT. resolve returns an explicit found flag because agent ID 0 is valid. |
| ProviderRegistry | Provider listings: USDC listing fee, active toggle. Gates canonical ERC-8004 agents into the Daski "provider" role (caller must own the agent NFT). |
| ServiceRegistry | Per-provider product catalog. A service is a row, not its own NFT — keyed by keccak256(providerAgentId, serviceSlug, version). Optional service payee overrides are bound to both the authorizing NFT owner and the live canonical agentWallet; skills are declared off-chain. |
| PaymentRouter | Rail-agnostic settlement that splits accepted tokens between provider/service wallet and the payment treasury. Per-token policy independently controls payment acceptance and reputation eligibility/minimums. It validates (provider, service) on every settlement, namespaces replay protection, and excludes relationships provable from on-chain identity state at settlement time from reputation. |
| X402Adapter | EIP-3009 transferWithAuthorization rail (Circle USDC). |
| PermitAdapter | EIP-2612 permit rail. |
| ApprovalAdapter | Plain approve + transferFrom rail (fallback). |
| DaskiValidationRegistry | Daski-specific, ERC-8004-inspired validation requests with namespaced keys and paginated reads. validationRequest returns computeValidationKey(agentId, requestHash); calls and paginated lists use that key, while events carry both it and the raw payload hash. This intentionally avoids the draft registry's global request-hash squatting and unbounded getter behavior rather than claiming drop-in compatibility. |
| ReputationStorage | Bilateral reputation resolver: qualified payments contribute to counters, providers record outcomes, and buyers confirm. Payment/refund mirroring is retryable so a resolver outage cannot block settlement. EAS-backed; counters split per-provider AND per-service. Resolver addresses and schema UIDs are permanently locked by one-time configuration finalization before payment rails can be enabled. |
| MockUSDC | Testnet ERC-20 (6 decimals, public mint). Test deploys only. |
All contracts are UUPS-upgradeable (OpenZeppelin v5) behind a 2-step admin. Fresh deployments require one deployed governance contract (multisig or timelock) as the pending admin of every proxy. Payment rails remain disabled until that governance contract accepts every admin role.
Allowlisted adapters are trusted to authenticate or establish buyer consent and to deliver the exact settlement amount. Adapter enablement verifies the adapter's router binding on-chain; deployment verification additionally checks its exact AgentIndex binding.
Every participant wallet is screened on-chain against the configured
Chainalysis-compatible sanctions oracle. Adapters reject payers before token
calls and the router independently rechecks live payer, controller, payee, and
treasury addresses. Covered operations fail closed when screening is
unavailable. Integrators should decode SanctionedAddress(address) and
SanctionsOracleUnavailable(address) rather than matching revert text.
Staged deployment: every proxy is admined by the governance Safe below; the sanctions oracle is the explicitly marked test mock (Chainalysis publishes no Base Sepolia deployment).
Canonical ERC-8004 singletons (external, never Daski-deployed):
IdentityRegistry 0x8004A818BFB912233c491871b3d84c89A494BD9e,
ReputationRegistry 0x8004B663056A597Dffe9eCcC1965A193B7388713.
| Contract | Address |
|---|---|
| Governance Safe (admin, 1-of-1 testnet) | 0xe6724f9317E872a0a7fa59B93614cc73C7529DDc |
| USDC (Circle) | 0x036CbD53842c5426634e7929541eC2318f3dCF7e |
| SanctionsOracle (MockSanctionsList) | 0xa94d2168820f349aafBa585c12E69aA387dCB815 |
| AgentIndex | 0x3C0F12ce13DF39C82bb6A4861BE5D77BD28A695e |
| DaskiValidationRegistry | 0x7B00F8AB014d6D7289DE743240131C4349232a5E |
| ProviderRegistry | 0x1B17fB18968F099e2878194c8699c64e14F22080 |
| ServiceRegistry | 0x9EFcAB44a33bBD1bBe8DEf481f896cD287b904a0 |
| PaymentRouter | 0x50046ce6DcC49C900917B796eF53d435c57a7B6c |
| ReputationStorage | 0xE024C8CB72Ddb08C6602e94c5585cD1DBE74DF6d |
| X402Adapter | 0x6F77a5feFFDC16d8CE743897936DF825011E7878 |
| PermitAdapter | 0x90765d099D853D029Eb7d56b023Af10FEC21Da10 |
| ApprovalAdapter | 0xB3A23C4C21D6A02B141e1d829fc1A411A3fF198c |
| EAS | 0x4200000000000000000000000000000000000021 |
| Schema Registry | 0x4200000000000000000000000000000000000020 |
EAS schema UIDs (resolver = ReputationStorage):
- Outcome:
0x6b7be197d0d05238289a44c7a54f3cd6ef18770f56a3287e174f757421333360 - Confirmation:
0x0d90927c3531d12664d8aebf8b40964123cfba655a5d02be2c3b0459b8b11142
The prior stack (deployed 2026-07-12, EOA-admined, incl. the
DirectTransferAdapter) is retired by this deployment — full address set under
retired in the machine-readable file; quiescing per the procedure below.
Machine-readable copy: deployments/base-sepolia.json
Retiring this legacy stack is an operator action, not a proxy upgrade. At the
retirement block, re-check that the router holds no USDC, call
setAcceptedToken(USDC, false), disable DirectTransfer/X402/Permit/Approval,
and scan all historical AdapterSet and AcceptedTokenSet events for any
other enabled entries. Then replace the old router address in every dependent
service. Bare token transfers can arrive at any time, so repeat the balance
check immediately before and after quiescing the router.
script/RetireStack.s.sol performs the disable pass (idempotent; refuses a
router with a residual token balance unless overridden) for EOA-admined
legacy routers:
export RETIRE_PAYMENT_ROUTER_ADDRESS=<old router>
export RETIRE_TOKENS=<comma-separated tokens ever accepted>
export RETIRE_ADAPTERS=<comma-separated adapters ever enabled>
forge script script/RetireStack.s.sol --rpc-url <RPC_URL> --broadcastThe event-history scan before and after is still mandatory — mappings are
not enumerable, so the env lists must be proven exhaustive against the
router's full AcceptedTokenSet/AdapterSet log history. A Safe-admined
router (v0.6.0+) is retired through a governance batch instead.
The previous (pre-canonical-migration) stack at PaymentRouter
0x78f9b15F…459d8f is orphaned and quiesced (2026-07-22): USDC
disabled, all three adapters disabled, zero balance, event history scanned
exhaustive. The seven earlier May-2026 iteration stacks (routers recorded in
this repo's deployments/base-sepolia.json history, all of which accepted
real Circle USDC) were swept and quiesced the same day, the same way. Legacy
Identity/Reputation registries remain orphaned — historical record in the
deploy-testnet repo's deployment records.
Not yet deployed. Pending audit.
UUPS proxy pattern; deploy order matters due to cross-contract dependencies.
IdentityRegistry below means the CANONICAL singleton (env-supplied, never
deployed by Daski):
1. AgentIndex (canonical IdentityRegistry)
2. DaskiValidationRegistry (canonical IdentityRegistry)
3. ProviderRegistry (canonical IdentityRegistry, USDC, treasury)
4. ServiceRegistry (canonical IdentityRegistry, ProviderRegistry)
5. PaymentRouter (canonical IdentityRegistry, ProviderRegistry, ServiceRegistry, USDC, treasury)
6. ReputationStorage (canonical IdentityRegistry, PaymentRouter, EAS, schema UIDs)
7. Adapters (X402/Permit/Approval) — initialized with AgentIndex, registered with PaymentRouter
Requires Foundry.
forge build
forge test # 244 tests across 15 suites
forge test -vvv # verbose
forge fmt| Suite | Tests |
|---|---|
| PaymentRouter | 78 |
| ReputationStorage | 35 |
| ReputationConfiguration | 10 |
| ServiceRegistry | 26 |
| AgentIndex | 19 |
| ProviderRegistry | 19 |
| X402Adapter | 15 |
| DaskiValidationRegistry | 16 |
| PermitAdapter | 6 |
| ApprovalAdapter | 5 |
| DeploymentValidation | 4 |
| DeploymentGuards | 5 |
| Integration | 3 |
| Canonical identity mock | 2 |
| EAS mock | 1 |
Tests run against test/mocks/MockCanonicalIdentityRegistry.sol, a faithful
double of the canonical registry surface (ERC-721 + registration +
agentWallet, IDs beginning at 0, registration-time wallet initialization, and
no reverse index).
export DEPLOYER_PRIVATE_KEY=<key>
export PROVIDER_TREASURY_ADDRESS=<listing-fee treasury>
export PAYMENT_TREASURY_ADDRESS=<payment-commission treasury>
# Deployed governance contract (never an EOA) — see "Governance Safe" below.
export ADMIN_ADDRESS=<deployed multisig or timelock>
# REQUIRED: the canonical ERC-8004 IdentityRegistry for the target chain.
# Base Sepolia: 0x8004A818BFB912233c491871b3d84c89A494BD9e
# Base mainnet: 0x8004A169FB4a3325136EB29fA0ceB6D2e539a432
export IDENTITY_REGISTRY_ADDRESS=0x8004A818BFB912233c491871b3d84c89A494BD9e
# REQUIRED: deployed USDC-compatible token for the target chain.
# Base Sepolia (Circle): 0x036CbD53842c5426634e7929541eC2318f3dCF7e
# Base mainnet: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
export USDC_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e
# REQUIRED: Chainalysis Sanctions Oracle.
# Base mainnet: 0x3A91A31cB3dC49b4db9Ce721F50a9D076c8D739B
# Base Sepolia: no official deployment; use an explicitly marked mock only.
export SANCTIONS_ORACLE_ADDRESS=<oracle-address>
# Optional (defaults shown)
export LISTING_FEE=1000000 # 1 USDC
export COMMISSION_BPS=500 # 5%
export USDC_REPUTATION_MINIMUM=250000 # $0.25 in Circle USDC units
forge script script/Deploy.s.sol --rpc-url <RPC_URL> --broadcastOn Base mainnet and Base Sepolia, deployment enforces the pinned canonical
IdentityRegistry, Circle USDC, EAS, and SchemaRegistry addresses. Base mainnet
also pins the documented Chainalysis oracle. Base Sepolia and isolated chains
require ALLOW_MOCK_SANCTIONS_ORACLE=true because Chainalysis does not publish
an official Base Sepolia deployment. Deployment also
checks ERC-165/ERC-721 support, six-decimal USDC semantics, the EAS
SchemaRegistry binding, the oracle ABI, both registered schemas, and every
cross-contract wiring relationship. Unsupported chains are rejected unless the operator
explicitly sets ALLOW_UNSUPPORTED_CHAIN=true; semantic dependency checks
still apply.
For an isolated non-mainnet environment without a token, deploy the unrestricted-mint test double separately, then supply its logged address to the production-shaped deployment:
forge script script/DeployMockUSDC.s.sol --rpc-url <RPC_URL> --broadcast
export USDC_ADDRESS=<logged MockUSDC address>
forge script script/DeployMockSanctionsList.s.sol --rpc-url <RPC_URL> --broadcast
export SANCTIONS_ORACLE_ADDRESS=<logged MockSanctionsList address>
export ALLOW_UNSUPPORTED_CHAIN=true
export ALLOW_MOCK_SANCTIONS_ORACLE=true
forge script script/Deploy.s.sol --rpc-url <RPC_URL> --broadcastDeploy.s.sol never creates a mock token or silently substitutes one for a
missing production dependency.
ADMIN_ADDRESS must be an already-deployed governance contract — an EOA is
rejected. The supported governance contract is a Safe created through the
canonical Safe v1.4.1 deployment (script/SafeDeployment.sol pins the
SafeProxyFactory, SafeL2 singleton, CompatibilityFallbackHandler, and
MultiSendCallOnly addresses by codehash; verified identical on Base mainnet
and Base Sepolia).
On testnet the Safe is auto-deployed — no prior setup, defaults to a 1-of-1 Safe owned by the deployer:
forge script script/DeploySafe.s.sol --rpc-url <RPC_URL> --broadcast
export ADMIN_ADDRESS=<logged Safe address>SAFE_OWNERS (comma-separated), SAFE_THRESHOLD, and SAFE_SALT_NONCE
override the defaults; the same owners+threshold+salt always yields the same
create2 address, so a fresh Safe needs a new salt. On Base mainnet the script
refuses anything weaker than 2 owners with threshold 2 — create the real
multisig there (with this script or the Safe app) before deploying.
Deploy.s.sol creates a dark deployment: ReputationStorage is configured, but
no token or adapter is enabled. Contract addresses, EAS schema UIDs, and
resolver wiring are logged at the end. Accept all nine pending admin roles from
the configured governance contract before activating the token and adapters.
Both governance batches (script/GovernanceBatches.sol) are driven through
the Safe by script/ExecuteGovernanceBatches.s.sol, which validates the
expected pre-state, executes the batch as a single MultiSendCallOnly
transaction, and re-validates afterwards:
# after Deploy.s.sol, with the stack env exported (same names as the verifier):
GOVERNANCE_BATCH=accept forge script script/ExecuteGovernanceBatches.s.sol --rpc-url <RPC_URL> --broadcast
GOVERNANCE_BATCH=activate forge script script/ExecuteGovernanceBatches.s.sol --rpc-url <RPC_URL> --broadcastScripted execution requires the broadcasting EOA to be an owner of a 1-of-1 Safe (the testnet shape). With a higher threshold the script prints every call plus the packed MultiSend payload and stops — execute those from the Safe app instead.
After exporting the logged component addresses and schema UIDs, run the
read-only verifier first with DEPLOYMENT_ACTIVE=false after all admin roles
have been accepted, then with DEPLOYMENT_ACTIVE=true after the activation
batch:
export IDENTITY_REGISTRY_ADDRESS=<canonical-address>
export USDC_ADDRESS=<circle-usdc-address>
export SANCTIONS_ORACLE_ADDRESS=<configured-oracle-address>
export EAS_ADDRESS=0x4200000000000000000000000000000000000021
export EAS_SCHEMA_REGISTRY_ADDRESS=0x4200000000000000000000000000000000000020
export PROVIDER_TREASURY_ADDRESS=<address>
export PAYMENT_TREASURY_ADDRESS=<address>
export ADMIN_ADDRESS=<governance-contract-address>
export AGENT_INDEX_ADDRESS=<address>
export DASKI_VALIDATION_REGISTRY_ADDRESS=<address>
export PROVIDER_REGISTRY_ADDRESS=<address>
export SERVICE_REGISTRY_ADDRESS=<address>
export PAYMENT_ROUTER_ADDRESS=<address>
export REPUTATION_STORAGE_ADDRESS=<address>
export X402_ADAPTER_ADDRESS=<address>
export PERMIT_ADAPTER_ADDRESS=<address>
export APPROVAL_ADAPTER_ADDRESS=<address>
export OUTCOME_SCHEMA_UID=<bytes32>
export CONFIRMATION_SCHEMA_UID=<bytes32>
export LISTING_FEE=1000000
export COMMISSION_BPS=500
export USDC_REPUTATION_MINIMUM=250000
# Copy the nine implementation code hashes logged by Deploy.s.sol.
export AGENT_INDEX_IMPLEMENTATION_CODEHASH=<bytes32>
export DASKI_VALIDATION_REGISTRY_IMPLEMENTATION_CODEHASH=<bytes32>
export PROVIDER_REGISTRY_IMPLEMENTATION_CODEHASH=<bytes32>
export SERVICE_REGISTRY_IMPLEMENTATION_CODEHASH=<bytes32>
export PAYMENT_ROUTER_IMPLEMENTATION_CODEHASH=<bytes32>
export REPUTATION_STORAGE_IMPLEMENTATION_CODEHASH=<bytes32>
export X402_ADAPTER_IMPLEMENTATION_CODEHASH=<bytes32>
export PERMIT_ADAPTER_IMPLEMENTATION_CODEHASH=<bytes32>
export APPROVAL_ADAPTER_IMPLEMENTATION_CODEHASH=<bytes32>
export DEPLOYMENT_ACTIVE=false
forge script script/VerifyDeployment.s.sol --rpc-url <RPC_URL>
# After the Safe activation batch:
export DEPLOYMENT_ACTIVE=true
forge script script/VerifyDeployment.s.sol --rpc-url <RPC_URL>ProviderRegistry and PaymentRouter treasury controls are intentionally independent. A governance treasury change must review both destinations and record whether equality or divergence is intended.
EIP-3009 payments use X402Adapter, which executes the token authorization
and router settlement atomically. The authorization nonce commits to
(serviceRef, providerAgentId, serviceId) so a relayer cannot redirect it.
Provider-facing services should map SanctionedAddress(account) to the stable
code SANCTIONS_ADDRESS_REJECTED (not retryable) and
SanctionsOracleUnavailable(oracle) to SANCTIONS_SCREENING_UNAVAILABLE
(retryable with a bounded policy). The contracts enforce the restriction even
when the gateway is bypassed.
Release coordination — develop→main merges, semver tags, the cross-repo
address cascade (gateway/provider env, test-suite config, website llms.txt),
DB resets, and post-deploy verification — lives in
daski-io/deploy-testnet.
This protocol has not yet been audited. For non-security bugs, please open a GitHub issue. For security-relevant findings, please use GitHub's private vulnerability reporting instead of opening a public issue.