Why CrewAI + MoltPe

CrewAI models multi-agent workflows as a crew of specialized roles collaborating on a task graph: a researcher gathers sources, a writer drafts, an editor polishes. That mental model is a natural fit for real-world service economies, where each role has its own budget, earnings, and trust boundary.

The problem: most CrewAI examples share a single LLM key and no money at all. The moment you want a crew to pay external data APIs, tip specialist agents, or charge end-users per deliverable, you need a payment primitive that matches CrewAI's role model — one wallet per agent, not one wallet for the whole app.

MoltPe fits CrewAI because:

If you are new to agent-to-agent payments, AI Agent Payments Guide and AI Agent Spending Policies are the best priors.

Prerequisites

Python 3.10+, CrewAI installed, an LLM key, and three MoltPe agents created in the dashboard. Name them deliberately so the audit trail makes sense — crew-researcher, crew-writer, crew-editor.

pip install "crewai>=0.80" "crewai-tools>=0.12" requests pydantic

export OPENAI_API_KEY="sk-..."
export MOLTPE_BASE_URL="https://api.moltpe.com"
export MOLTPE_TOKEN_RESEARCHER="mpt_live_r_..."
export MOLTPE_TOKEN_WRITER="mpt_live_w_..."
export MOLTPE_TOKEN_EDITOR="mpt_live_e_..."

In the MoltPe dashboard, set policies: researcher pays x402 APIs up to $0.05/call and $2/day; writer pays the researcher wallet up to $1/task; editor pays small tips to either wallet up to $0.50/call. Fund each wallet with test USDC.

Approach 1: One Wallet per Role

CrewAI tools are plain Python classes. Give each tool the agent token for the specific role that will use it. This binding happens at construction time, so a tool instance always authenticates as exactly one wallet.

"""MoltPe-backed CrewAI tools. One instance = one wallet."""
import os, requests
from typing import Optional
from crewai_tools import BaseTool
from pydantic import BaseModel, Field

BASE_URL = os.environ["MOLTPE_BASE_URL"]


class _PayArgs(BaseModel):
    recipient_wallet: str = Field(description="0x-prefixed wallet address")
    amount_usd: float = Field(description="Amount in USD")
    memo: Optional[str] = Field(default=None, description="Purpose, logged on-chain")


class SendPayment(BaseTool):
    name: str = "send_payment"
    description: str = "Send USDC from this agent's wallet. Subject to server-side policy."
    args_schema: type = _PayArgs

    def __init__(self, agent_token: str, **data):
        super().__init__(**data)
        self._token = agent_token

    def _run(self, recipient_wallet: str, amount_usd: float, memo: Optional[str] = None) -> str:
        r = requests.post(
            f"{BASE_URL}/v1/payments",
            headers={"Authorization": f"Bearer {self._token}"},
            json={"to": recipient_wallet, "amount_usd": amount_usd, "memo": memo},
            timeout=30,
        )
        if r.status_code == 403:
            return f"Policy rejection: {r.json().get('error')}"
        r.raise_for_status()
        return f"Paid ${amount_usd} to {recipient_wallet}. Tx: {r.json()['tx_hash']}"


class CheckBalance(BaseTool):
    name: str = "check_balance"
    description: str = "Return the agent's USDC balance."
    args_schema: type = BaseModel

    def __init__(self, agent_token: str, **data):
        super().__init__(**data)
        self._token = agent_token

    def _run(self) -> str:
        r = requests.get(
            f"{BASE_URL}/v1/wallet/balance",
            headers={"Authorization": f"Bearer {self._token}"},
            timeout=10,
        )
        r.raise_for_status()
        return f"{r.json()['usdc_balance']} USDC"

A successful payment returns a JSON body like this — capture tx_hash in your task logs so the ledger and the CrewAI run are linked.

{
  "tx_hash": "0xabc123def456...",
  "from_agent": "crew-writer",
  "to": "0x8f2e4bD7...",
  "amount_usd": 0.50,
  "network": "polygon",
  "settled_at": "2026-04-24T10:12:33Z"
}

Instantiate one tool set per role. The CrewAI Agent only sees tools that authenticate as its own wallet — there is no code path by which the researcher can sign a payment from the writer's wallet, even if the LLM invents one.

Approach 2: Task Callback Payments

The elegant way to wire handoff payments is to keep them out of the LLM's hands entirely. CrewAI lets you attach a callback to a Task that fires with the task output. Use this to trigger settlement deterministically once the upstream role delivers.

"""Deterministic handoff: writer pays researcher when research task completes."""
from crewai import Task

RESEARCHER_WALLET = "0x8f2e4bD7B0A1E8c5F6d9A3b2C7e0D4f8A1B5c9E2"

def pay_researcher_on_delivery(task_output):
    """Called by CrewAI after the research task finishes."""
    writer_pay_tool = SendPayment(agent_token=os.environ["MOLTPE_TOKEN_WRITER"])
    result = writer_pay_tool._run(
        recipient_wallet=RESEARCHER_WALLET,
        amount_usd=0.50,
        memo=f"Research delivery: task {task_output.task_id}",
    )
    print(f"Settlement: {result}")
    return task_output

research_task = Task(
    description="Gather three recent primary sources on {topic}.",
    expected_output="A bulleted list of URLs with one-line summaries.",
    agent=researcher_agent,
    callback=pay_researcher_on_delivery,
)

Two advantages of this pattern. First, the payment is not a tool the LLM can call — it fires from deterministic Python code, so prompt injection cannot change the amount. Second, you can add validation before payment: check the output schema, word count, or a regex; if it fails, skip the callback and the researcher simply does not get paid for a bad delivery.

End-to-End Example: A Paying Research Crew

Full runnable example: a three-role crew where the writer pays the researcher on delivery, the editor pays a small tip on final polish, and the researcher spends some of its income on paid x402 search. This is a tiny but complete internal service economy.

"""Paying research crew: one wallet per role, handoff payments on delivery."""
import os
from crewai import Agent, Task, Crew, Process
from moltpe_crew_tools import SendPayment, CheckBalance

RESEARCHER_WALLET = "0x8f2e4bD7...aaa"
WRITER_WALLET    = "0x8f2e4bD7...bbb"
EDITOR_WALLET    = "0x8f2e4bD7...ccc"

# One tool set per role — each authenticates as the right wallet
researcher_tools = [
    SendPayment(agent_token=os.environ["MOLTPE_TOKEN_RESEARCHER"]),
    CheckBalance(agent_token=os.environ["MOLTPE_TOKEN_RESEARCHER"]),
]
writer_tools = [CheckBalance(agent_token=os.environ["MOLTPE_TOKEN_WRITER"])]
editor_tools = [CheckBalance(agent_token=os.environ["MOLTPE_TOKEN_EDITOR"])]

researcher = Agent(
    role="Primary-Source Researcher",
    goal="Find three credible primary sources for any topic.",
    backstory="You have a wallet and can pay for premium search if needed.",
    tools=researcher_tools, verbose=True,
)
writer = Agent(
    role="Technical Writer",
    goal="Turn sources into a 500-word briefing.",
    backstory="You pay the researcher on delivery.",
    tools=writer_tools, verbose=True,
)
editor = Agent(
    role="Senior Editor",
    goal="Polish for clarity and tip the writer on ship.",
    backstory="You pay a small tip on final polish.",
    tools=editor_tools, verbose=True,
)

def pay_researcher(out):
    SendPayment(os.environ["MOLTPE_TOKEN_WRITER"])._run(
        RESEARCHER_WALLET, 0.50, "research delivery")
    return out

def tip_writer(out):
    SendPayment(os.environ["MOLTPE_TOKEN_EDITOR"])._run(
        WRITER_WALLET, 0.10, "writer tip")
    return out

research_task = Task(description="Find sources on {topic}.",
                    expected_output="List of 3 URLs + summaries",
                    agent=researcher, callback=pay_researcher)
write_task = Task(description="Draft a 500-word briefing.",
                  expected_output="Markdown briefing",
                  agent=writer, context=[research_task])
edit_task = Task(description="Polish and ship.",
                 expected_output="Final markdown",
                 agent=editor, context=[write_task], callback=tip_writer)

crew = Crew(agents=[researcher, writer, editor],
            tasks=[research_task, write_task, edit_task],
            process=Process.sequential, verbose=2)

result = crew.kickoff(inputs={"topic": "x402 adoption in 2026"})
print(result)

After running, the MoltPe dashboard shows three wallets with three payments between them, each memo-tagged, each under its role's policy caps. The external user paid the editor (or the orchestrator); the crew settled internally; everyone's contribution is on-chain auditable.

Common Pitfalls