Agents act.
You set the terms.
PACT is a permission layer for agent actions. An owner assigns funds to a narrowly defined job; a separate agent signer can spend only within that permission.
The public application runs real Solidity contracts on PACT Sandbox, an isolated EVM test chain with chain ID 31338. It uses test tokens with no monetary value. It is not a mainnet deployment or an audited custody product.
The AI extracts the task. The owner approves the limits. The contract enforces them. A successful model response never overrides a contract check.
Try a pact in a minute.
- Open the console and leave the example “Send 25 demo USDC to my recipient”.
- Select Prepare task. The browser creates a temporary wallet, the faucet supplies 1,000 demo USDC and test gas, and Qwen parses the instruction.
- Review the suggested budget, per-action limit, recipient, expiry and maximum actions.
- Select Approve & create pact. The test wallet signs an exact token approval and a contract creation transaction.
- Select Test overspend to observe a rejected contract simulation. Select Run agent to submit a real allowed transaction.
- Revoke the remaining permission, or let it expire and reclaim unused funds. Export the receipts to inspect what happened.
For a swap, use “Swap 40 demo USDC for demo WETH”. The manual builder is available if you prefer to set the action yourself or the AI service is unavailable.
The temporary owner key lives only in this browser tab’s memory. Reloading loses access to that test wallet. Close an active pact and export receipts before starting a new session.
One permission, explicit boundaries.
A Grant records the following fields. The agent receives only an execute(id, amount) interface; it cannot replace the owner’s recipient or choose new calldata.
| Field | What it controls |
|---|---|
| owner | The address that funds the grant and can revoke it. |
| agent | The only signer permitted to execute this grant. |
| recipient | The fixed destination for transferred tokens or swap output. |
| remaining | The unspent portion of the deposited budget. |
| perCall | The maximum input amount for one execution. |
| expires | An on-chain timestamp. Execution rejects at or after it. |
| maxCalls / calls | The maximum number of successful executions and current count. |
| action | Transfer or swap, chosen at creation. |
| minRate | Minimum swap output base units per input base unit. |
| closed | A terminal state. Closed grants cannot execute again. |
The vault’s input token, output token and swap adapter are immutable. permittedTarget(id) returns the input token for a transfer or the fixed adapter for a swap. This first build does not accept arbitrary targets, router calldata or token approvals.
Budgets are individually funded. Creating a second grant requires a second deposit; it cannot reserve money already assigned to another owner or grant.
Every pact has an ending.
Create
The owner approves only the intended input budget, then calls create. The vault transfers those tokens in and checks the exact balance increase. A zero amount, invalid recipient, excessive per-action limit, invalid expiry or zero call count is rejected.
Execute
The contract verifies the agent, active state, deadline, remaining budget, per-action limit and call count. It updates accounting before external transfers. If any later step fails, the entire transaction reverts.
Complete
When the budget is exhausted or the maximum number of actions is reached, the grant closes automatically. Any unused input funds are immediately returned to the owner.
Revoke
Only the owner can revoke. Revocation closes the permission and returns its remaining budget. It cannot reverse an action already settled on-chain. Concurrent execution and revocation are resolved by transaction ordering.
Expire and reclaim
Expiry prevents new execution without needing a separate cleanup transaction. Anyone can call reclaimExpired afterward, but the refund always goes to the original owner. Reclaiming is necessary to move expired funds out of the vault.
The AI has a small job.
The planner runs Qwen2.5-0.5B-Instruct locally on the server through llama.cpp. It translates a single supported instruction into structured action and amount fields.
{
"action": "transfer",
"amount": "25"
}Grammar-constrained generation and server validation restrict that output. The service checks that the action and numeric amount agree with the instruction. The budget, destination, expiry and action count come from the owner’s form, not the model.
The planner does not browse, predict prices, choose investments, generate executable contracts or receive wallet keys. Prompt text is untrusted input. If parsing fails, the UI explains the failure and offers manual task entry.
In this build, Run agent triggers the worker. There is no background trading strategy, autonomous scheduler or recurring external task service.
Model references: Qwen model card ↗ and llama.cpp ↗.
Real execution, test assets.
Transfer
The vault sends the requested amount of demo USDC directly to the stored recipient using OpenZeppelin’s SafeERC20. No arbitrary external call is accepted.
Fixed-rate swap
The vault sends input tokens to the fixed DemoSwap adapter. The adapter returns demo WETH from its test reserve at 3,000 demo USDC per 1 demo WETH. This is an illustrative constant, not an exchange rate or market feed.
The vault measures the recipient’s actual output-token balance increase. It compares that increase against the stored minimum rate rather than trusting the adapter’s reported return value. If the output is insufficient, all transfers and accounting changes revert.
Example: 30 demo USDC produces 0.01 demo WETH. A minimum of 0.00033 demo WETH per demo USDC permits this execution. A minimum of 0.0004 causes it to revert.
Units
Demo USDC has six decimals; demo WETH has eighteen. Transaction quantities use integers. The UI converts the human minimum rate to twelve-decimal fixed precision, giving output base units per input base unit. The adapter rounds output down.
Receipts and probes
Executed actions have actual transaction hashes, block numbers, events and gas usage. The UI exports JSON receipts. Boundary probes use eth_call against the actual deployed contract and are explicitly identified as simulations, not settled transactions.
What the implementation checks.
The automated contract suite covers unauthorized callers, per-call and aggregate overspending, zero amounts, automatic closure, repeated execution after closure, revocation, exact-deadline expiry, refund ownership, independent grants, minimum swap output and malicious adapter behavior.
ReentrancyGuardprotects externally callable mutations.- State changes precede external execution; reverting unwinds the whole operation.
- No delegatecall, arbitrary calldata, router approvals or shared owner withdrawal permissions.
- The agent is a different signer from the owner and faucet.
- The public RPC proxy exposes a small method allowlist. Administrative test-chain methods and unlocked-account sends are not exposed.
- A signed, one-use challenge proves ownership of the browser wallet before a session is funded. Session cookies are HTTP-only and SameSite Strict. Mutation endpoints require a permitted origin. Faucet and planner requests are rate limited.
These checks do not constitute an independent audit. Server operators control the sandbox chain and can reset it. The demo tokens, fixed adapter and hosted agent are infrastructure for testing the protocol, not decentralization or production-security claims.
Contract dependencies: OpenZeppelin Contracts 5.4.0. Compiler: Solidity 0.8.26, optimizer enabled, Paris EVM target.
Inspect the implementation.
The current deployment addresses, ABI, chain ID and sandbox epoch are available through GET /api/config. The epoch changes when the chain is recreated; old receipts then refer to a previous sandbox instance.
| Endpoint | Purpose |
|---|---|
| GET /api/health | Independent chain and model availability. |
| POST /api/challenge | Create a short-lived, one-use wallet ownership challenge. |
| POST /api/session | Verify wallet ownership and create a short-lived funded test session. |
| POST /api/plan | Parse one supported instruction using the local model. |
| POST /api/rpc | Read sandbox state and submit validated owner-signed transactions. |
| POST /api/probe | Simulate an agent action against the contract without spending. |
| POST /api/execute | Preflight and execute the session owner’s grant with the agent signer. |
Session-based endpoints are intended for this web application and require the same-origin session cookie. They are not a public production SDK or mainnet relayer.
What is connected today.
- Local-model instruction parsing and a manual builder.
- Browser-created test wallets and a sandbox faucet.
- Owner-signed policy creation, revocation and expired-fund reclamation.
- Agent-signed token transfers and fixed-rate test swaps.
- Contract-enforced limits and downloadable transaction evidence.
Not connected: mainnet wallets or funds, real DEX liquidity, cross-chain bridges, price oracles, unattended scheduling, general-purpose agents, or token-gated access. There is no protocol token contract in this implementation.
Public sessions expire after two hours. The chain is ephemeral: restarting its service resets transactions, balances and grants. Browser reloads discard the temporary owner key. Do not send real assets to addresses shown here.
Run the project locally.
The source includes the contracts, UI, service, deployment scripts and tests. Use Node.js 22 or 24 and install the pinned dependencies.
npm ci
npm test
npm run build
npm startnpm start launches the isolated chain on localhost:8547, deploys fresh contracts, then serves the application on localhost:5215. For UI development, run npm run dev on port 5214 while the service is running.
Set MODEL_URL to a local OpenAI-compatible llama.cpp server serving Qwen2.5-0.5B-Instruct. Without a reachable model the manual builder still works, and AI planning reports its unavailable state.
The deployment operation creates a separate random agent key in .runtime/agent.json. That file is server-only and excluded from source distributions. All generated accounts and contracts are test-chain infrastructure.