A trust-gated payment and identity skill for AI agents on the Pharos network.
AgentLeash introduces a reputation layer for autonomous AI agents operating on Pharos. Each agent registers a human-readable .leash username and earns an on-chain reputation score through verified payments and completed tasks. Payments between agents are gated by that score — a caller sets a minimum reputation threshold, and the contract enforces it on-chain before any value moves.
The skill is designed as a reusable, composable module. Any agent, regardless of which LLM powers it or which framework it runs on, can register an identity, query reputation, and execute trust-gated transfers by calling the AgentLeash contracts directly or through the provided LangChain and MCP interfaces.
.leashIdentity Registry: Each agent registers a uniqueprefix.leashusername mapped bidirectionally to their wallet address. Accepts"alice"or"alice.leash"interchangeably across all functions.- On-Chain Reputation Scoring: Scores start at 0 and increase through received payments (+1) and completed tasks (+3). Failed tasks deduct 2 points with a floor of 0. Scores are stored in packed
uint64fields for gas efficiency. - Trust-Gated Transfers:
leashedTransferresolves a.leashusername, checks the recipient's score against a caller-defined minimum, updates reputation, then moves PHRS — in that order. The Checks-Effects-Interactions pattern is enforced with anonReentrantmutex. - Task Recording: Authorized oracles or contracts record task outcomes for any registered agent. Access is controlled through a two-layer authorization model:
LeashGatedTransferis authorized inAgentRegistry, and callers ofrecordTaskmust be authorized inLeashGatedTransfer. - Reputation Leaderboard:
getTopAgents(n)returns the top N agents sorted by score descending, built from storage at read time with no off-chain dependencies. - LangChain and MCP Compatible: All six capabilities are exposed as
DynamicStructuredToolinstances with Zod-validated schemas, and wrapped in a stdio MCP server for framework-agnostic agent integration. - Ownership and Authorization Management: Both contracts implement
transferOwnershipwith zero-address guard and event emission, and asetAuthorizedfunction for managing reputation writers.
AgentLeash is composed of two Solidity contracts and a TypeScript skill layer.
AgentRegistry is the identity and reputation store. It maintains three mappings: a bytes32-keyed username-to-address lookup (keyed by keccak256 of the username), an address-to-username reverse mapping, and an address-to-Agent struct mapping. The Agent struct is packed into two storage slots using uint64 fields. A separate AgentView struct is built at read time and returned to external callers, including the wallet address and username reconstructed from mappings rather than stored redundantly.
LeashGatedTransfer is the payment and task gateway. It holds an immutable reference to AgentRegistry and is the sole authorized reputation writer registered at deploy time. It exposes leashedTransfer for gated PHRS transfers and recordTask for task outcome recording. Both functions require authorization to prevent arbitrary reputation manipulation.
Skill Layer wraps both contracts in six TypeScript tools using LangChain DynamicStructuredTool and Zod schema validation. A stdio MCP server exposes all six tools to any MCP-compatible agent host.
┌─────────────────────────────────────────┐
│ Agent (any framework) │
│ LangChain / MCP / direct viem call │
└──────────────┬──────────────────────────┘
│
┌───────────▼───────────┐
│ LeashGatedTransfer │
│ - leashedTransfer() │
│ - recordTask() │
└───────────┬───────────┘
│ authorized writes
┌───────────▼───────────┐
│ AgentRegistry │
│ - register() │
│ - resolve() │
│ - getTopAgents() │
│ - recordPayment() │
│ - recordTask() │
└───────────────────────┘
| Event | Score Delta |
|---|---|
| Payment received | +1 |
| Task completed successfully | +3 |
| Task failed | −2 (floor: 0) |
| Score Range | Tier |
|---|---|
| 0 | Unproven |
| 1–9 | Novice |
| 10–29 | Established |
| 30–74 | Trusted |
| 75–149 | Verified |
| 150+ | Elite |
Network: Pharos Atlantic Testnet (Chain ID: 688689)
| Contract | Address |
|---|---|
AgentRegistry |
0x17B2B1516B40218ac55581E85df25EFeCbD335cD |
LeashGatedTransfer |
0x83C2ad9279BD81EB7B0EEBFd6889B2274BA89ac6 |
Explorer: atlantic.pharosscan.xyz
| Tool | Function | Description |
|---|---|---|
register_agent |
AgentRegistry.register |
Register a .leash username for the calling wallet |
resolve_agent |
AgentRegistry.resolve |
Resolve a username to a wallet address |
check_reputation |
AgentRegistry.getProfile |
Fetch full profile and reputation tier |
leashed_transfer |
LeashGatedTransfer.leashedTransfer |
Send PHRS gated by minimum reputation score |
record_task |
LeashGatedTransfer.recordTask |
Record task success or failure for an agent |
get_leaderboard |
AgentRegistry.getTopAgents |
Retrieve top N agents ranked by score |
await walletClient.writeContract({
address: REGISTRY_ADDRESS,
abi: REGISTRY_ABI,
functionName: "register",
args: ["alice"], // stored as alice.leash
});const profile = await publicClient.readContract({
address: REGISTRY_ADDRESS,
abi: REGISTRY_ABI,
functionName: "getProfile",
args: ["alice"], // accepts "alice" or "alice.leash"
});
// { wallet, username, reputationScore, successfulPayments, successfulTasks, failedTasks, registeredAt }await walletClient.writeContract({
address: TRANSFER_ADDRESS,
abi: TRANSFER_ABI,
functionName: "leashedTransfer",
args: ["bob.leash", BigInt(20)], // recipient username, minimum reputation score
value: parseEther("0.5"),
});
// Reverts with "bob.leash has reputation score 4 - minimum required is 20" if score is insufficient// Caller must be authorized in LeashGatedTransfer
await walletClient.writeContract({
address: TRANSFER_ADDRESS,
abi: TRANSFER_ABI,
functionName: "recordTask",
args: ["alice.leash", true], // username, success
});// Owner of LeashGatedTransfer authorizes a task oracle or marketplace contract
await walletClient.writeContract({
address: TRANSFER_ADDRESS,
abi: TRANSFER_ABI,
functionName: "setAuthorized",
args: ["0xTaskOracleAddress", true],
});const top = await publicClient.readContract({
address: REGISTRY_ADDRESS,
abi: REGISTRY_ABI,
functionName: "getTopAgents",
args: [BigInt(10)],
});import { agentLeashTools } from "./src";
import { ChatAnthropic } from "@langchain/anthropic";
import { AgentExecutor, createToolCallingAgent } from "langchain/agents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const llm = new ChatAnthropic({ model: "claude-sonnet-4-6" });
const prompt = ChatPromptTemplate.fromMessages([
["system", "You are an agent on Pharos. Use AgentLeash to verify trust before sending payments."],
["human", "{input}"],
["placeholder", "{agent_scratchpad}"],
]);
const agent = createToolCallingAgent({ llm, tools: agentLeashTools, prompt });
const executor = new AgentExecutor({ agent, tools: agentLeashTools });
await executor.invoke({
input: "Send 0.5 PHRS to bob.leash only if his reputation is above 20",
});Start the MCP server:
npm run mcpConfigure in your agent host:
{
"mcpServers": {
"agentleash": {
"command": "node",
"args": ["/path/to/agentleash/dist/mcp/server.js"],
"env": {
"PRIVATE_KEY": "0x...",
"PHAROS_RPC_URL": "https://atlantic.dplabs-internal.com",
"REGISTRY_ADDRESS": "0x17B2B1516B40218ac55581E85df25EFeCbD335cD",
"TRANSFER_ADDRESS": "0x83C2ad9279BD81EB7B0EEBFd6889B2274BA89ac6"
}
}
}
}Requirements
- Node.js 22+
- A funded Pharos Atlantic Testnet wallet (atlantic.pharosscan.xyz)
Install
git clone https://github.com/Myles181/AgentLeash
cd agentleash
npm installConfigure
cp .env.example .env
# Add PRIVATE_KEY, PHAROS_RPC_URL, and contract addressesDeploy your own instance
npm run deploy
# Deploys AgentRegistry, deploys LeashGatedTransfer, authorizes transfer contract
# Prints both addresses — add them to .envRun tests
npm test
# Verifies access control, registration, scoring, reputation gate, CEI pattern, leaderboard| Message | Cause |
|---|---|
Prefix must be 1-32 chars |
Prefix length out of range |
Already registered |
Wallet already holds a .leash username |
Username already taken |
Another wallet registered that prefix |
Prefix: lowercase letters, numbers, underscores only |
Invalid characters in prefix |
No agent registered as X |
Username not found in registry |
X has reputation score Y - minimum required is Z |
Recipient score below caller threshold |
Not authorized |
Caller not in the authorized list |
Amount must be greater than 0 |
Zero value sent to leashedTransfer |
Reentrant call |
Reentrancy attempt blocked by mutex |
New owner is zero address |
transferOwnership called with zero address |
| Language | Solidity 0.8.20, TypeScript |
| Network | Pharos Atlantic Testnet (Chain ID: 688689) |
| RPC | https://atlantic.dplabs-internal.com |
| Skill Interface | LangChain DynamicStructuredTool, MCP stdio |
| Schema Validation | Zod |
| Blockchain Client | viem |
| License | MIT |
contracts/
AgentRegistry.sol — identity registry, reputation storage, leaderboard
LeashGatedTransfer.sol — gated transfers, task recording, authorization
src/
client.ts — Pharos testnet viem client configuration
contracts.ts — ABI definitions and contract addresses
tools/ — six LangChain DynamicStructuredTool implementations
index.ts — agentLeashTools[] export
mcp/server.ts — stdio MCP server wrapping all six tools
scripts/
hardhat-deploy.ts — deployment script
test.ts — end-to-end test suite
SKILL.md — Pharos skill manifest