Distribute Tokens
This Claude Code skill automates token distribution to a list of contributors via the Bankr Wallet API, with built-in safeguards against double-sending through per-recipient idempotency tracking, a two-phase resolve-then-execute workflow, dry-run preview mode, and recovery from partial failures. Use it to conduct secure, auditable token payouts to multiple recipients on Base chain, whether identified by Twitter handles or direct wallet addresses.
git clone --depth 1 https://github.com/aaronjmars/aeon /tmp/distribute-tokens && cp -r /tmp/distribute-tokens/skills/distribute-tokens ~/.claude/skills/distribute-tokensSKILL.md
<!-- autoresearch: variation C — robustness via per-recipient idempotency state, two-phase resolve→execute, dry-run, retries, 403/429 handling, recovery -->
> **${var}** — Distribution list label. If empty, uses the first list in `memory/distributions.yml`. Pass `dry-run:LABEL` to preview without sending. Pass `LABEL` alone to execute.
## Why this design
This skill moves real money. The biggest failure mode is double-sending (re-runs, retries after partial failures, day-rollover bypass of "skip if today" logic) or sending into a black hole (no preflight balance, deprecated API path, missing handle resolution). The skill therefore:
1. **Persists per-recipient idempotency state** in `memory/state/distributions.json` keyed on `(list, recipient, date_utc)` with the txHash. A successful transfer is *never* re-sent within the same UTC day, even across re-runs or workflow restarts.
2. **Two-phase execution**: RESOLVE (validate config, key, balance, resolve all handles → addresses, build plan) → EXECUTE (send each transfer, persist state after each one). RESOLVE failures abort before any send.
3. **Dry-run mode** outputs the full plan with no transfers.
4. **Wallet API only** for actual transfers — Bankr's docs deprecate the Agent API for transfers. Agent API is used only for handle→address resolution.
## Config
Reads `memory/distributions.yml`. If missing, bootstrap with a commented template (see Bootstrap step) and exit cleanly with `DISTRIBUTE_TOKENS_OK — bootstrapped distributions.yml; edit and re-run`.
```yaml
# memory/distributions.yml
defaults:
token: USDC # USDC | ETH (Base only)
amount: "5"
chain: base
lists:
contributors:
description: "Weekly contributor rewards"
token: USDC
amount: "10"
recipients:
- handle: "@alice_dev" # Twitter/X — resolved via Bankr Agent API
amount: "15"
- handle: "@bob_builder"
- address: "0x742d...5678" # direct EVM address — preferred path
label: "Charlie"
amount: "20"
```
### Required secrets
| Secret | Purpose |
|--------|---------|
| `BANKR_API_KEY` | Bankr API key (`bk_...`). Must be **read-write** with **Wallet API** enabled. Read-only keys → 403. |
### Token addresses on Base
- USDC: `0x833589fcd6edb6e08f4c7c32d4f71b54bda02913`
- ETH (native): `tokenAddress: "0x0000000000000000000000000000000000000000"`, `isNativeToken: true`
---
Read `memory/MEMORY.md` and `memory/distributions.yml`.
Read `memory/state/distributions.json` (if present) for idempotency state.
## Steps
### 1. Parse var and load config
- If `${var}` starts with `dry-run:`, set `MODE=dry-run` and `LABEL=${var#dry-run:}`. Otherwise `MODE=execute` and `LABEL=${var}`.
- If `memory/distributions.yml` missing → **Bootstrap**: write the example config (commented out so it's inert), notify `DISTRIBUTE_TOKENS_OK — bootstrapped distributions.yml; edit and re-run`, log, exit.
- Parse YAML. If `LABEL` empty, use the first list. Else find the matching list. If not found → notify `DISTRIBUTE_TOKENS_ERROR — list '${LABEL}' not found`, log, exit.
- Resolve `today_utc=$(date -u +%F)`.
### 2. Pre-flight: key, write access, balance
If `BANKR_API_KEY` not set → `DISTRIBUTE_TOKENS_ERROR — BANKR_API_KEY not configured`, log, exit.
```bash
ME=$(curl -fsS "https://api.bankr.bot/wallet/me" -H "X-API-Key: ${BANKR_API_KEY}")
```
- HTTP 403 → `DISTRIBUTE_TOKENS_ERROR — API key is read-only; needs wallet write scope`, exit.
- HTTP 429 → `DISTRIBUTE_TOKENS_ERROR — rate-limited at /wallet/me; aborting`, exit.
- Network failure → use **WebFetch** fallback. If still failing → `DISTRIBUTE_TOKENS_ERROR — Bankr /wallet/me unreachable`, exit.
```bash
PORTFOLIO=$(curl -fsS "https://api.bankr.bot/wallet/portfolio?chain=base" -H "X-API-Key: ${BANKR_API_KEY}")
```
Extract sender's balance for the target token. Compute `total_required` from the recipient list (sum of per-recipient amounts, applying overrides). If `balance < total_required * 1.05` (5% headroom for any failed retries) → `DISTRIBUTE_TOKENS_ERROR — insufficient balance: have X, need Y ${TOKEN}`, exit. Do not start a partial run.
### 3. RESOLVE phase — build the plan
For each recipient, build a row: `{key, type, amount, token, target_address, label, status}` where `key = sha256("${LABEL}|${recipient_id}|${today_utc}")` and `recipient_id` is the handle (lowercase) or address (lowercase).
**Idempotency check** (before resolving): if `memory/state/distributions.json` contains `key` with `status=completed` → mark row `SKIPPED_DEDUP`, carry forward the prior `txHash`.
**Handle resolution** (`@username`): use Bankr Agent API to look up the linked wallet:
```bash
JOB=$(curl -fsS -X POST "https://api.bankr.bot/agent/prompt" \
-H "X-API-Key: ${BANKR_API_KEY}" -H "Content-Type: application/json" \
-d "{\"prompt\":\"What is the EVM address linked to ${HANDLE} on Base? Respond with only the address.\"}" | jq -r '.jobId')
# Poll every 2s, max 30s
for i in $(seq 1 15); do
R=$(curl -fsS "https://api.bankr.bot/agent/job/${JOB}" -H "X-API-Key: ${BANKR_API_KEY}")
S=$(echo "$R" | jq -r '.status')
[ "$S" = "completed" ] || [ "$S" = "failed" ] && break
sleep 2
done
```
Extract the address from the response (regex `0x[a-fA-F0-9]{40}`). If extraction fails → mark row `RESOLVE_FAILED` with reason `NO_LINKED_WALLET`. Do **not** abort the whole plan; let the executor skip this row.
**Address resolution** (`0x...`): validate format `^0x[a-fA-F0-9]{40}$`. If invalid → `RESOLVE_FAILED` reason `BAD_ADDRESS`.
After RESOLVE, print the plan to the console (and to the dry-run notification if `MODE=dry-run`):
```
Plan for list '${LABEL}' (${today_utc}):
✓ @alice_dev → 0x1234... — 15 USDC [READY]
✓ Charlie → 0x742d... — 20 USDC [READY]
↻ @bob_builder → 0xabcd... — 10 USDC [SKIPPED_DEDUP] (tx 0xprev...)
✗ @inactive → ? [RESOLVE_FAILED: NO_LINKED_WALLET]
Summary: 2 to send (35 USDC), 1 deduped, 1 unresolvable. Sender balanMention/keyword sweep on social platforms for [REPLACE: KEYWORDS] — trends, sentiment, top posts
5 concrete real-life actions, leverage-scored against open loops with specificity and anti-fluff gates
Curated AI-agent tweets, clustered into narratives with insight summaries
Tracker of AI agent substitution signals — which roles, companies, and industries show real headcount displacement. Named roles + real deployments only.
Competitive-intelligence digest on the AI agent framework space — momentum, releases, breaking changes across a curated watchlist
Cross-domain market pulse from AIXBT's free grounding endpoint — crypto, macro, tradfi, geopolitics. Refreshes taxonomy references (clusters, chains) as a bonus.
Pre-batch API provider health check — detects credit exhaustion or auth failure for every configured provider key before the scheduled batch runs, giving the operator a window to act before skills degrade
List a wallet's live ERC-20 token approvals on Base and flag unlimited / risky spender grants. Keyless via Base RPC (eth_getLogs + eth_call) — no explorer key needed.