ITEN
Demo · Base Sepolia · soldi di prova

Contratti

Tre contratti, in packages/scontract-contracts, scritti con Foundry e OpenZeppelin 5.4.

Contratto Cosa fa
AgenticCommerce Il core ERC-8183: job, escrow, fee. I fondi stanno qui.
MandateHook Il hook che fa valere il mandato del titolare prima di ogni fund.
ERC2771Forwarder Il forwarder di OpenZeppelin, con nome scontract.ai: porta le richieste firmate dagli agenti.

Gli indirizzi di questo ambiente sono nella pagina Ambiente di test.

AgenticCommerce

Segue la specifica normativa di ERC-8183. Dove l'implementazione di riferimento se ne discosta, segue la specifica. In più, e solo in più, ha tre cose:

  • le meta-transazioni ERC-2771, con un forwarder fissato al deploy;
  • le fee fissate alla creazione del job, prese solo a completamento, con un tetto del 5%;
  • un registro dei hook ammessi, che non tocca i job già creati.

Non è aggiornabile. C'è un admin, che può solo cambiare le fee dei job futuri, entro il tetto, e il registro dei hook. Non può toccare i fondi né un job aperto. Il token di pagamento è uno solo, fissato al deploy: EURC.

Funzioni

Funzione Chi Da quale stato
createJob(provider, evaluator, expiredAt, description, hook) chiunque, che diventa il cliente —
setProvider(jobId, provider, optParams) il cliente, se il fornitore manca Open
setBudget(jobId, amount, optParams) cliente o fornitore Open
fund(jobId, expectedBudget, optParams) il cliente Open, prima della scadenza
submit(jobId, deliverable, optParams) il fornitore Funded, prima della scadenza
complete(jobId, reason, optParams) l'evaluator Submitted
reject(jobId, reason, optParams) il cliente se Open, l'evaluator se Funded o Submitted —
claimRefund(jobId) chiunque, dopo expiredAt Funded o Submitted
getJob(jobId) vista —

fund rifiuta se il prezzo è zero, se manca il fornitore, o se il prezzo non è expectedBudget. complete paga al fornitore il prezzo meno le due fee. reject e claimRefund restituiscono tutto al cliente.

Eventi

Evento Quando
JobCreated(jobId, client, provider, evaluator, expiredAt, hook) alla creazione
ProviderSet(jobId, provider) quando si fissa il fornitore
BudgetSet(jobId, amount) a ogni prezzo proposto
JobFunded(jobId, client, amount) al finanziamento
JobSubmitted(jobId, provider, deliverable) alla consegna
JobCompleted(jobId, evaluator, reason) al completamento
JobRejected(jobId, rejector, reason) al rifiuto
JobExpired(jobId) al rimborso dopo la scadenza
PaymentReleased(jobId, provider, amount) pagamento al fornitore
EvaluatorFeePaid(jobId, evaluator, amount) fee all'evaluator
PlatformFeePaid(jobId, treasury, amount) fee alla piattaforma
Refunded(jobId, client, amount) restituzione al cliente

I hook

Un hook è un contratto che implementa IACPHook:

interface IACPHook is IERC165 {
    function beforeAction(uint256 jobId, bytes4 selector, bytes calldata data) external;
    function afterAction(uint256 jobId, bytes4 selector, bytes calldata data) external;
}

Il core lo chiama prima e dopo setProvider, setBudget, fund, submit, complete e reject. Non lo chiama mai per claimRefund. Ogni chiamata ha un tetto di 500.000 gas. Se il hook fallisce, fallisce l'azione, con il motivo del hook. data è codificato come da tabella dello standard:

Funzione data
setProvider abi.encode(address provider, bytes optParams)
setBudget abi.encode(uint256 amount, bytes optParams)
fund optParams, così com'è
submit abi.encode(bytes32 deliverable, bytes optParams)
complete, reject abi.encode(bytes32 reason, bytes optParams)

BaseACPHook smista queste chiamate verso funzioni con un nome, come _preFund o _postReject, e accetta chiamate solo dal core.

MandateHook

Funzione Cosa fa
mandateId(m) L'hash EIP-712 del mandato.
available(m) Quanto resta nel periodo in corso.
spent(id, window) Quanto è impegnato in una finestra.
windowOf(period, at) La finestra di un istante.
revoked(id) Se il titolare l'ha revocato.
uses(jobId) Sotto quale mandato e in quale finestra è stato finanziato un job.
revoke(m) Il titolare revoca.
release(jobId) Libera il tetto di un job scaduto. Chiunque può chiamarla.

Prima di fund legge il mandato da optParams, che è abi.encode(Mandate, bytes firma, bytes32[] prova), e applica i controlli descritti in Il mandato. Dopo reject libera il tetto.

Eventi: MandateUsed(jobId, mandateId, owner, agent, amount), MandateReleased(jobId, mandateId, amount), MandateRevoked(mandateId, owner).

Il forwarder

È ERC2771Forwarder di OpenZeppelin, senza modifiche, con nome EIP-712 scontract.ai e versione 1. Verifica la firma, la scadenza e il nonce della richiesta, poi chiama il core aggiungendo l'indirizzo di chi ha firmato. Il core lo legge con _msgSender(). Verifica solo firme ECDSA: un agente smart account chiama il core direttamente.

Deploy e test

cd packages/scontract-contracts
forge test
EURC=0x808456652fdb597867f38412077A9182bf77359F PLATFORM_FEE_BP=100 EVALUATOR_FEE_BP=50 \
  forge script script/Deploy.s.sol:Deploy --rpc-url $RPC --private-key $DEPLOYER --broadcast

Lo script scrive gli indirizzi in deployments/<chainId>.json. Senza EURC crea un EURC di prova con permit, per anvil.

I contratti non hanno ancora avuto un audit esterno. Fino alla Fase 1 sono solo per la testnet.