Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,12 @@
"description": "HTTP 402 payment protocol for AI agent commerce — three-actor model (Client, Resource Server, Facilitator), ERC-3009 transferWithAuthorization, server middleware (@x402/express), client patterns in TypeScript and Python, facilitator integration, agent-to-agent payments, pricing strategies, and replay protection. Works on Base, Ethereum, Arbitrum, Optimism, Polygon, and Solana.",
"category": "AI Agents"
},
{
"name": "zerodust",
"source": "./skills/zerodust",
"description": "ZeroDust chain exit infrastructure — sweep 100% of native gas tokens (ETH, BNB, MATIC, etc.) from 25 EVM chains via EIP-7702 sponsored execution, leaving exactly zero balance. Covers TypeScript SDK with ZeroDustAgent for automated sweeps, REST API for quotes and submissions, MCP server for AI tool calls, and Agent API with batch operations. Free for balances under $1.",
"category": "Cross-Chain"
},
{
"name": "zksync",
"source": "./skills/zksync",
Expand Down
457 changes: 457 additions & 0 deletions skills/zerodust/SKILL.md

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions skills/zerodust/docs/troubleshooting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# ZeroDust Troubleshooting

## 1. "Quote expired" (QUOTE_EXPIRED)

**Symptoms:** POST /sweep returns `{ "error": "Quote expired", "code": "QUOTE_EXPIRED" }`.

**Cause:** Quotes are valid for 55 seconds. The contract enforces a 60-second deadline window, and the backend uses 55 to provide a safety margin. If signing takes longer than 55 seconds, the quote becomes invalid.

**Solution:**
- Request a new quote with `GET /quote`
- Sign immediately after receiving the quote
- For SDK users, the `agent.sweep()` method handles the full flow automatically

**Debug checklist:**
- [ ] Check system clock is synchronized (NTP)
- [ ] Ensure signing flow completes within 55 seconds
- [ ] Don't cache quotes — always request fresh ones

---

## 2. "Balance too low" (BALANCE_TOO_LOW)

**Symptoms:** `GET /quote` returns `{ "error": "Balance too low...", "code": "BALANCE_TOO_LOW" }`.

**Cause:** Each chain has a minimum sweepable balance that covers gas fees. If the balance is below this threshold, the sweep would result in a negative user receive amount.

**Solution:**
- Check `canSweep` field from `GET /balances/:address` before requesting a quote
- Use `getSweepableBalances()` in the SDK to filter automatically
- Very small dust (< $0.01) may not be sweepable — this is expected

---

## 3. "EIP-7702 not supported" / Chain not in list

**Symptoms:** Chain ID returns 404 from `/chains/:chainId` or is not in the supported chains list.

**Cause:** Not all EVM chains support EIP-7702. 41 chains have been tested and confirmed as incompatible, including zkSync, Avalanche, Blast, Abstract, Lens, and others.

**Solution:**
- Check `GET /chains?testnet=false` for current supported chains
- Only 25 mainnet chains support EIP-7702 sweeps
- If a chain was recently added, ensure the backend has been updated

---

## 4. "Revoke nonce mismatch" (REVOKE_NONCE_MISMATCH)

**Symptoms:** POST /sweep returns `{ "error": "Revoke authorization nonce must be N", "code": "REVOKE_NONCE_MISMATCH" }`.

**Cause:** The revoke authorization must have nonce = delegation nonce + 1. This is because the delegation transaction increments the account nonce, so the revoke transaction (executed after) needs the next nonce.

**Solution:**
```typescript
// Correct: revoke nonce = delegation nonce + 1
const delegationAuth = await walletClient.signAuthorization({
contractAddress: ZERODUST_CONTRACT,
chainId: 42161,
});

const revokeAuth = await walletClient.signAuthorization({
contractAddress: '0x0000000000000000000000000000000000000000',
chainId: 42161,
nonce: delegationAuth.nonce + 1, // Must be +1
});
```

---

## 5. "Invalid signature" (INVALID_SIGNATURE / EIP7702_INVALID_SIGNATURE)

**Symptoms:** POST /sweep returns signature-related error.

**Cause:** Most commonly, the EIP-712 typed data was modified or the `verifyingContract` in the domain is wrong. Under EIP-7702, the `verifyingContract` must be the **user's EOA address**, not the ZeroDust contract address.

**Solution:**
- Use the `typedData` returned by `POST /authorization` exactly as-is
- Do not modify any field in the typed data before signing
- Ensure the wallet is signing with the correct account (matches `userAddress` from the quote)
- For EIP-7702 auth: ensure `contractAddress` matches the ZeroDust contract

**Debug checklist:**
- [ ] `typedData.domain.verifyingContract` === user's EOA address
- [ ] Signing account matches the `userAddress` used in the quote
- [ ] Signature is 65 bytes (130 hex chars + 0x prefix)

---

## 6. "Cross-chain unavailable" (SOURCE_CHAIN_DISABLED / DEST_CHAIN_DISABLED)

**Symptoms:** Cross-chain quote fails with chain disabled error.

**Cause:** Gas.zip may temporarily disable certain source or destination chains. This is outside ZeroDust's control.

**Solution:**
- Fall back to same-chain sweep (`toChainId === fromChainId`)
- Try a different destination chain
- Check Gas.zip status for route availability
- The error message includes the specific chain name

---

## 7. "Rate limited" (429 Too Many Requests)

**Symptoms:** API returns HTTP 429.

**Cause:** Agent API has per-minute (300) and daily (1000) rate limits. Public endpoints also have rate limits.

**Solution:**
- Check remaining quota: `GET /agent/me` returns `rateLimits.dailyRemaining`
- Implement exponential backoff on 429 responses
- For batch operations, use `POST /agent/batch-sweep` instead of individual calls
- Contact team for higher limits if needed for production integration

---

## 8. "Insufficient for fees" (INSUFFICIENT_FOR_FEES)

**Symptoms:** Quote returns `INSUFFICIENT_FOR_FEES` even though balance shows a non-zero amount.

**Cause:** The balance exists but is less than the total fees (gas + service fee + revoke gas). Cross-chain sweeps have higher overhead than same-chain due to bridge gas.

**Solution:**
- Try a same-chain sweep instead (lower fees)
- Wait for gas prices to decrease on the source chain
- Very small balances (< $0.10) may not be sweepable on expensive chains like Ethereum mainnet
106 changes: 106 additions & 0 deletions skills/zerodust/examples/agent-api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Agent API (REST)

Use the Agent API for server-side integrations or when not using the TypeScript SDK.

## Register for an API Key

```bash
curl -X POST https://api.zerodust.xyz/agent/register \
-H "Content-Type: application/json" \
-d '{
"name": "My Sweep Agent",
"agentId": "agent-001",
"contactEmail": "dev@example.com"
}'
```

Response:
```json
{
"apiKey": "zd_abc123...",
"keyPrefix": "zd_abc",
"keyId": "uuid",
"keyType": "agent",
"rateLimits": { "perMinute": 300, "daily": 1000 },
"message": "IMPORTANT: Save your API key now - it will not be shown again!"
}
```

## Single Sweep (Combined Quote + Auth)

```bash
curl -X POST https://api.zerodust.xyz/agent/sweep \
-H "Authorization: Bearer zd_abc123..." \
-H "Content-Type: application/json" \
-d '{
"fromChainId": 42161,
"toChainId": 8453,
"userAddress": "0x1234...",
"destination": "0x1234..."
}'
```

Returns quote, typed data, and EIP-7702 parameters in a single response. The agent still needs to sign and submit via `POST /sweep`.

## Batch Sweep

```bash
curl -X POST https://api.zerodust.xyz/agent/batch-sweep \
-H "Authorization: Bearer zd_abc123..." \
-H "Content-Type: application/json" \
-d '{
"sweeps": [
{ "fromChainId": 42161 },
{ "fromChainId": 10 },
{ "fromChainId": 137 }
],
"destination": "0x1234...",
"consolidateToChainId": 8453
}'
```

## Check Usage Stats

```bash
curl https://api.zerodust.xyz/agent/me \
-H "Authorization: Bearer zd_abc123..."
```

Response:
```json
{
"keyId": "uuid",
"keyType": "agent",
"rateLimits": {
"perMinute": 300,
"daily": 1000,
"dailyUsed": 42,
"dailyRemaining": 958
}
}
```

## TypeScript Client

```typescript
const API_KEY = process.env.ZERODUST_API_KEY;
const BASE_URL = 'https://api.zerodust.xyz';

async function agentSweep(fromChainId: number, toChainId: number, userAddress: string) {
const res = await fetch(`${BASE_URL}/agent/sweep`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ fromChainId, toChainId, userAddress }),
});

if (!res.ok) {
const err = await res.json();
throw new Error(`Agent sweep failed: ${err.error} (${err.code})`);
}

return res.json();
}
```
69 changes: 69 additions & 0 deletions skills/zerodust/examples/batch-sweep/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Batch Sweep Multiple Chains

Sweep dust from multiple chains and consolidate to a single destination.

## Prerequisites

```bash
npm install @zerodust/sdk viem
```

## Sweep Specific Chains

```typescript
import { ZeroDustAgent } from '@zerodust/sdk';
import { privateKeyToAccount } from 'viem/accounts';
import type { Hex } from 'viem';

const agent = new ZeroDustAgent({
account: privateKeyToAccount(process.env.PRIVATE_KEY as Hex),
environment: 'mainnet',
});

const results = await agent.batchSweep({
sweeps: [
{ fromChainId: 42161 }, // Arbitrum
{ fromChainId: 10 }, // Optimism
{ fromChainId: 137 }, // Polygon
{ fromChainId: 56 }, // BSC
],
consolidateToChainId: 8453, // All to Base
continueOnError: true, // Don't stop if one chain fails
});

console.log(`Results: ${results.successful}/${results.total} succeeded`);

for (const r of results.results) {
const status = r.success ? `OK (${r.txHash})` : `FAILED: ${r.error}`;
console.log(` Chain ${r.fromChainId} -> ${r.toChainId}: ${status}`);
}
```

## Sweep All Sweepable Chains

```typescript
// Automatically discovers all chains with sweepable dust
const results = await agent.sweepAll({
toChainId: 8453, // Everything to Base
continueOnError: true,
});

console.log(`Swept ${results.successful} chains, ${results.failed} failed`);
```

## Error Handling

With `continueOnError: true`, failed chains don't stop the batch. Check individual results:

```typescript
const failed = results.results.filter(r => !r.success);
for (const f of failed) {
if (f.error?.includes('BALANCE_TOO_LOW')) {
console.log(`Chain ${f.fromChainId}: balance too small, skipping`);
} else if (f.error?.includes('SOURCE_CHAIN_DISABLED')) {
console.log(`Chain ${f.fromChainId}: bridge temporarily down`);
} else {
console.log(`Chain ${f.fromChainId}: unexpected error: ${f.error}`);
}
}
```
59 changes: 59 additions & 0 deletions skills/zerodust/examples/check-balances/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Check Balances Across Chains

Check native token dust balances across all 25 supported chains.

## Prerequisites

```bash
npm install @zerodust/sdk viem
```

## SDK Example

```typescript
import { ZeroDust } from '@zerodust/sdk';

const zerodust = new ZeroDust({ environment: 'mainnet' });

const address = '0x1234567890abcdef1234567890abcdef12345678';
const { chains } = await zerodust.getBalances(address);

// Filter to sweepable chains
const sweepable = chains.filter(c => c.canSweep);

console.log(`Found ${sweepable.length} sweepable chains:`);
for (const chain of sweepable) {
console.log(` ${chain.name} (${chain.chainId}): ${chain.balanceFormatted} ${chain.nativeToken}`);
}
```

## REST API Example

```bash
# All chains (mainnet)
curl "https://api.zerodust.xyz/balances/0x1234...?testnet=false"

# Specific chain
curl "https://api.zerodust.xyz/balances/0x1234.../42161"
```

## Response

```json
{
"address": "0x...",
"chains": [
{
"chainId": 42161,
"name": "Arbitrum",
"nativeToken": "ETH",
"balance": "800000000000000",
"balanceFormatted": "0.0008",
"canSweep": true,
"minBalance": "10000000000000"
}
]
}
```

`canSweep` is `true` when the balance exceeds the chain's minimum (covers gas fees). Only request quotes for chains where `canSweep` is `true`.
Loading