ITEN
Demo · Base Sepolia · soldi di prova

Il mandato

Il mandato è il documento con cui il titolare autorizza un agente a spendere per conto suo. Lo firma il titolare, lo presenta l'agente a ogni finanziamento, e lo fa valere il MandateHook sulla chain. Se il job non ci sta dentro, il finanziamento non avviene e il motivo arriva leggibile.

I campi

Campo Tipo Cosa vuol dire
owner address Il titolare. Firma il mandato e ne risponde.
agent address La chiave dell'agente, che deve essere il cliente del job. Mai uguale al titolare.
maxPerJob uint256 Il massimo per un singolo job, in unità minime di EURC (6 decimali).
capPerPeriod uint256 Il tetto di spesa per periodo.
period uint64 La durata del periodo in secondi. Zero vuol dire un periodo solo per tutta la vita del mandato.
payeesRoot bytes32 La radice di Merkle dei fornitori ammessi. Zero vuol dire chiunque.
validUntil uint64 Dopo questo istante il mandato non vale più.
allowSelfEvaluation bool Se vero, l'agente può essere l'evaluator dei suoi job.
nonce bytes32 Casuale: due mandati con gli stessi termini restano distinti.

La firma

Il mandato si firma in EIP-712, nel dominio del MandateHook:

{
  "domain": { "name": "scontract.ai", "version": "1", "chainId": 84532, "verifyingContract": "<MandateHook>" },
  "types": {
    "Mandate": [
      { "name": "owner", "type": "address" },
      { "name": "agent", "type": "address" },
      { "name": "maxPerJob", "type": "uint256" },
      { "name": "capPerPeriod", "type": "uint256" },
      { "name": "period", "type": "uint64" },
      { "name": "payeesRoot", "type": "bytes32" },
      { "name": "validUntil", "type": "uint64" },
      { "name": "allowSelfEvaluation", "type": "bool" },
      { "name": "nonce", "type": "bytes32" }
    ]
  },
  "primaryType": "Mandate"
}

L'hash EIP-712 è l'id del mandato: lo stesso nell'API, nel registro e sulla chain. Una firma vale per un solo MandateHook su una sola chain.

Il titolare può essere un wallet oppure uno smart account: il hook accetta le firme ERC-1271, quindi un'azienda può firmare dal suo multisig.

Firmarlo e registrarlo

Per il pilota c'è una CLI, che firma con una chiave in una variabile d'ambiente e registra il mandato:

OWNER_PRIVATE_KEY=0x... SCONTRACT_API_URL=https://scontratto-api.scontratto.workers.dev \
  node apps/scontract-mcp/dist/mandato.mjs \
  --agent 0xAGENTE \
  --per-job 50 --per-period 200 --period-days 30 \
  --valid-days 90 \
  --provider 0xFORNITORE_1 --provider 0xFORNITORE_2
Opzione Predefinito Cosa fa
--agent, --agente obbligatoria L'indirizzo dell'agente.
--per-job obbligatoria Il massimo per job, in EURC.
--per-period, --per-periodo obbligatoria Il tetto per periodo, in EURC. Non può essere minore del massimo per job.
--period-days, --giorni-periodo 30 La durata del periodo. Con 0, un periodo solo.
--valid-days, --valido-giorni 90 La durata del mandato.
--provider, --fornitore nessuno Un fornitore ammesso. Si ripete. Senza, vale chiunque.
--self-evaluation, --auto-valutazione spento L'agente può essere l'evaluator dei suoi job.

I messaggi della CLI seguono SCONTRACT_LANG, come il server MCP.

Chi firma dal proprio wallet può costruire il mandato da sé e registrarlo con POST /v1/mandates: vedi API.

I fornitori ammessi

La lista in chiaro resta nel registro. Sulla chain va solo la sua radice di Merkle. Ogni foglia è keccak256 dei 20 byte dell'indirizzo, e ogni coppia si hasha in ordine crescente, come fa MerkleProof di OpenZeppelin. Un nodo senza fratello sale da solo. Con un solo fornitore, la radice è la sua foglia e la prova è vuota.

L'agente allega la prova per il fornitore del job a ogni finanziamento. Il server MCP la calcola da solo dalla lista registrata. L'ordine della lista conta, perché fa parte del documento firmato.

I periodi

Il periodo è una finestra fissa: timestamp / period. Tutto quello che l'agente finanzia in una finestra si somma, e non può superare capPerPeriod. Alla finestra successiva il conto riparte da zero.

Un job respinto dall'evaluator libera subito la sua parte di tetto. Un job scaduto la libera quando qualcuno chiama MandateHook.release(jobId): il cron di scontract.ai lo fa insieme al rimborso. Un job completato resta speso.

La revoca

Il titolare revoca chiamando MandateHook.revoke(mandato) dal suo wallet, o dal suo smart account. Serve un po' di ETH per il gas. Da quel momento nessun job nuovo si può finanziare sotto quel mandato. I job già finanziati seguono il loro corso.

Cosa controlla il hook

Prima di ogni fund, nell'ordine:

  1. Il cliente del job è l'agente del mandato.
  2. La firma è del titolare.
  3. Il mandato non è revocato.
  4. Il mandato non è scaduto.
  5. Il prezzo non supera il massimo per job.
  6. Con questo prezzo, il periodo non supera il tetto.
  7. Il fornitore è ammesso.
  8. L'evaluator non è il fornitore, e non è l'agente se il mandato non lo consente.

L'API fa gli stessi controlli prima di mandare la transazione, così un rifiuto costa zero gas e arriva con il suo motivo.

Dopo la Fase 0

Oggi il titolare è un wallet. Nella v2 il mandato diventa la credenziale derivata di Agent Rail: firmata in QES e radicata nella CIE o nell'EUDI Wallet del titolare. Nella v3 arrivano le persone giuridiche, con i poteri di firma dal Registro Imprese. Il contratto resta lo stesso: cambia chi può firmare.