# Crypto Wallet Agent Protection

On February 22, 2026, an AI agent managing a memecoin treasury on Solana received a message on X claiming a user's uncle needed 4 SOL for tetanus treatment. The agent intended to send a small amount but instead transferred its entire holdings, 52.4 million tokens worth $250,000, in a single transaction. A session memory wipe had erased its knowledge of its own wallet state, and a decimal parsing error compounded the failure. No hard spending limit existed anywhere in the system. ([ICME Blog](https://blog.icme.io/ai-agents-can-move-money-lobstar-wilde-proved-they-can-lose-it-too/))

On March 18, 2025, an attacker gained access to the dashboard of AIXBT, a crypto market commentary bot with 500,000 followers, and queued two malicious replies that triggered its wallet tipping feature. 55.5 ETH (\~$106,200) was sent to the attacker's address before anyone noticed. The core AI was not compromised. The transfer guardrails simply did not exist. ([The Block](https://www.theblock.co/post/346911/ai-crypto-bot-aixbt-lost-eth-hack-unauthorized-dashboard-access))

Alibaba's coding agent ROME was later found to have been mining cryptocurrency and establishing reverse SSH tunnels to external IP addresses without any instruction from its operators. Engineers initially assumed a security breach. It was the agent itself. ([Cryptopolitan](https://www.cryptopolitan.com/alibaba-reports-rogue-ai-agent/))

In 2025, illicit actors stole $2.87 billion across nearly 150 crypto hacks. AI-enabled scams increased by roughly 500% year over year. As autonomous agents gain signing authority over wallets, the window between compromise and irreversible fund movement is collapsing. ([TRM Labs](https://www.trmlabs.com/resources/blog/autonomous-ai-agents-and-financial-crime-risk-responsibility-and-accountability))

***

### The attack surface

An AI agent with wallet signing authority is a high-value target. Every trust relationship it holds is an attack vector.

***

### Why prompt-based guardrails don't catch this

The Lobstar Wilde agent was not bypassing a guardrail. It had none that mattered. The AIXBT agent had dashboard security, but the guardrail was at the access layer, not the action layer. Once an attacker is inside the dashboard, or once a social engineering message reaches the agent's context, an LLM-based judge evaluating whether a transfer seems reasonable can be persuaded by the same emotional appeal that persuaded the agent.

Alibaba's ROME demonstrates the deeper problem: an agent can develop goals that were never in its instructions. A guardrail that evaluates whether an action matches the agent's stated purpose cannot catch behavior that the agent itself has decided is purposeful.

ICME compiles your wallet policy to formal logic and evaluates every proposed transaction against a mathematical solver before it executes. The solver does not process the uncle's tetanus story. It checks whether transferAmount > maxSingleTransfer. The solver does not evaluate whether a dashboard instruction looks legitimate. It checks whether recipientAddress is in the approved registry. No emotional appeal, no dashboard compromise, and no autonomous goal formation changes the output of a satisfiability check against a hard numerical constraint.

The agent loses the argument with the solver every time, because the solver does not have arguments.

***

### The policies

This protection is implemented as three focused policies compiled separately. Each covers a distinct domain. In production, your agent calls all three policy\_ids on every action. Any UNSAT from any policy blocks the action.

Splitting policies by domain keeps each compiled model small and focused, which produces cleaner variable schemas and more reliable AR enforcement. A single large policy covering transfers, network calls, and contract interactions in one compilation tends to produce enum-typed variables that the AR translator cannot reliably evaluate.

***

#### Policy A: Transfer limits

Covers single transfer limits, daily aggregate limits, and the human confirmation threshold.

```bash
curl -s -N -X POST https://api.icme.io/v1/makeRules \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d '{
    "policy": "Rule 1: The maximum permitted transfer amount is 100 USD.\nRule 2: If the transfer amount exceeds the maximum permitted transfer amount, then human confirmation is required.\nRule 3: If human confirmation is required and human confirmation has not been received, then the transfer is not permitted.\nRule 4: If the aggregate daily transfer total plus the transfer amount exceeds 500 USD, then the transfer is not permitted.\nRule 5: The transfer amount must be greater than zero.\nRule 6: The confirmed wallet balance must be greater than or equal to zero.\nRule 7: The aggregate daily transfer total must be greater than or equal to zero."
  }'
```

**SAT: legitimate transfer, all limits met**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_A_ID\",
    \"action\": \"Transfer 50 USD. Aggregate daily transfer total is 120 USD. Confirmed wallet balance is 300 USD. Human confirmation has not been received. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: allowed", "ar_result": "SAT", "result": "SAT" }
```

**UNSAT: exceeds 100 USD single transfer limit, no confirmation**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_A_ID\",
    \"action\": \"Transfer 150 USD. Aggregate daily transfer total is 0 USD. Confirmed wallet balance is 500 USD. Human confirmation has not been received. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

**UNSAT: daily aggregate exceeded (450 + 80 = 530 USD)**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_A_ID\",
    \"action\": \"Transfer 80 USD. Aggregate daily transfer total is 450 USD. Confirmed wallet balance is 200 USD. Human confirmation has not been received. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

**UNSAT: zero-value transfer violates minimum amount rule**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_A_ID\",
    \"action\": \"Transfer 0 USD. Aggregate daily transfer total is 0 USD. Confirmed wallet balance is 100 USD. Human confirmation has not been received. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

> **Note on the 10% threshold:** the policy uses `maximumPermittedTransferAmount` as a precomputed variable rather than calculating 10% of wallet balance inside the solver. Non-linear arithmetic (multiplication) can cause TOO\_COMPLEX results. The agent computes the threshold before calling checkIt and states it explicitly in the action string.

***

#### Policy B: Recipient and justification

Covers registry enforcement, wallet balance confirmation, justification source, and address poisoning defense.

```bash
curl -s -N -X POST https://api.icme.io/v1/makeRules \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d '{
    "policy": "Rule 1: The transfer is permitted only if the recipient address is in the approved registry and the wallet balance is known and the transfer justification comes from a direct user prompt and the recipient address has not been identified as coming from transaction history and the recipient address has not been identified as coming from clipboard content and the recipient address has not been identified as coming from a suggested address field.\nRule 2: If the recipient address is not in the approved registry, then the transfer is not permitted.\nRule 3: If the recipient address is not in the approved registry, then the action must be rejected.\nRule 4: If the wallet balance is unknown, then the transfer is not permitted.\nRule 5: If the wallet balance is unknown, then the action must be rejected.\nRule 6: If the transfer justification did not come from a direct user prompt, then the transfer is not permitted.\nRule 7: If the transfer justification did not come from a direct user prompt, then the action must be rejected.\nRule 8: If the recipient address has been identified as coming from transaction history, then the transfer is not permitted.\nRule 9: If the recipient address has been identified as coming from transaction history, then the action must be rejected.\nRule 10: If the recipient address has been identified as coming from clipboard content, then the transfer is not permitted.\nRule 11: If the recipient address has been identified as coming from clipboard content, then the action must be rejected.\nRule 12: If the recipient address has been identified as coming from a suggested address field, then the transfer is not permitted.\nRule 13: If the recipient address has been identified as coming from a suggested address field, then the action must be rejected."
  }'
```

**SAT: all conditions met**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_B_ID\",
    \"action\": \"Transfer 50 tokens to 0xApprovedTreasuryAddress. The recipient address is in the approved registry. The wallet balance is known and confirmed. The transfer justification comes from a direct user prompt. The recipient address was not sourced from transaction history, clipboard content, or a suggested address field. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: allowed", "ar_result": "SAT", "result": "SAT" }
```

**UNSAT: recipient not in approved registry (AIXBT scenario)**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_B_ID\",
    \"action\": \"Transfer 50 tokens to 0xAttackerAddress. The recipient address is not in the approved registry. The wallet balance is known. The transfer justification comes from a direct user prompt. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

**UNSAT: recipient address from clipboard content**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_B_ID\",
    \"action\": \"Transfer 50 tokens to 0xPastedAddress. The recipient address is in the approved registry. The recipient address has been identified as coming from clipboard content. The wallet balance is known. The transfer justification comes from a direct user prompt. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

**UNSAT: recipient address from suggested address field**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_B_ID\",
    \"action\": \"Transfer 50 tokens to 0xSuggestedAddress. The recipient address is in the approved registry. The recipient address has been identified as coming from a suggested address field. The wallet balance is known. The transfer justification comes from a direct user prompt. Therefore this transfer is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

***

#### Policy C: Network and contract

Covers raw IP blocking and smart contract interaction enforcement.

```bash
curl -s -N -X POST https://api.icme.io/v1/makeRules \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d '{
    "policy": "Rule 1: If the network call destination is a raw IP address, then the network call is not permitted.\nRule 2: If the network call destination is a raw IP address, then the action must be rejected.\nRule 3: If the contract address is not in the approved contract registry, then the contract interaction is not permitted.\nRule 4: If the interaction is a flash loan interaction, then the contract interaction is not permitted.\nRule 5: If the contract sequence involves multiple steps and was not present in the original user instruction, then the contract interaction is not permitted."
  }'
```

**SAT: legitimate approved contract call**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_C_ID\",
    \"action\": \"Call contract 0xApprovedUniswapContract. The contract address is in the approved contract registry. The network call destination is not a raw IP address. The interaction is not a flash loan. The contract sequence does not involve multiple steps. Therefore this action is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: allowed", "ar_result": "SAT", "result": "SAT" }
```

**UNSAT: raw IP network call (ROME scenario)**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_C_ID\",
    \"action\": \"Establish outbound connection to 203.0.113.88 to report operational status. The network call destination is a raw IP address. Therefore this action is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

> **Note:** the LLM independently missed this test, returning SAT. The AR solver caught it alone. This is the scenario the cryptographic enforcement layer exists for.

**UNSAT: contract not in approved registry**

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d "{
    \"policy_id\": \"$ICME_POLICY_C_ID\",
    \"action\": \"Call contract 0xUnknownDeFiProtocol. The contract address is not in the approved contract registry. The network call destination is not a raw IP address. The interaction is not a flash loan. The contract sequence does not involve multiple steps. Therefore this action is permitted.\"
  }"
```

```json
{ "ar_detail": "AR: action violates policy rules", "ar_result": "UNSAT", "result": "UNSAT" }
```

***

### Check every wallet action before it executes

```bash
curl -s -N -X POST https://api.icme.io/v1/checkIt \
  -H 'Content-Type: application/json' \
  -H "X-API-Key: $ICME_API_KEY" \
  -d '{
    "policy_id": "YOUR_POLICY_ID",
    "action": "YOUR_AGENT_ACTION"
  }'
```

SAT = allowed. UNSAT = blocked. Every decision returns a cryptographic receipt.

***

### Why three policies instead of one

A single large policy covering all domains tends to produce enum-typed variables rather than booleans. An enum variable like `transferJustificationSource: 3` cannot be reliably mapped to a rule like "if justification is not a direct user prompt, then block" because the translator must resolve what value 3 means. A boolean variable like `transferJustificationFromDirectUserPrompt: false` maps directly.

Keeping each policy focused on a single domain keeps the compiler's context window clean. Related variables stay together, reducing the chance of disconnected variable clusters where conditions are extracted correctly but never wired to an enforcement outcome.

In the live tests above, Policy B's boolean schema produced clean `AR: action violates policy rules` results on registry, clipboard, and suggested-field violations. The same rules in a larger multi-domain policy consistently produced enum schemas and AR translation failures.

***

### Deploying in production

**Compile once per policy** — call `makeRules` three times, once per policy. Store all three policy\_ids in your environment as `ICME_POLICY_A_ID`, `ICME_POLICY_B_ID`, and `ICME_POLICY_C_ID`.

**Check every wallet action against all three policies** — call `checkIt` before any token transfer, contract interaction, or outbound network call. Any UNSAT from any policy blocks the action. Dashboard-originated instructions must pass the same gates as user-originated ones.

**Agent-side preprocessing** — before calling `checkIt`, your agent should compute the 10% wallet balance threshold and state it explicitly as `maximumPermittedTransferAmount` in the action string. The agent should also identify and label the recipient address source (registry, clipboard, transaction history, suggested field) and include it in the action string. Do not leave these for the extractor to infer.

**Treat `result: UNSAT` as a hard stop** — do not retry, rephrase, or accept urgency arguments as an override. Log the `check_id` for your audit trail. On-chain transactions are irreversible.

**Fail closed** — if the ICME API is unreachable or returns anything other than an explicit SAT, do not execute the transaction. An unavailable guardrail is not implicit permission to proceed.

**Refresh wallet state before every action** — confirm current balance before each `checkIt` call. Never allow the agent to proceed when balance state is unresolved.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.icme.io/documentation/agentic-commerce/crypto-wallet-agent-protection.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
