
The two main parts of this app are the agent that crafts transactions, and the front end, that gets the crafted transaction from the agent and sends it to MetaMask for signature and sending.
The agent using Google ADK is quite simple to develop, the craft_eth_transaction function though can be quite complicated depending on the type of operations supported (chains, assets, swaps etc…):
from google.adk.agents import Agent
from web3 import Web3ETH_RPC_URL = "RPC URL"
# (This is the tool function defined in the next section)
def craft_eth_transaction(to_address: str, amount: float, from_address: str, chain_id: int):
# Step 1: Fetch the sender's next transaction count (nonce)
# Step 2: Determine transaction type (ETH transfer or smart contract call)
# Step 3: Construct the 'data' field using ABI
# Step 4: Assemble and return the final, unsigned transaction
# The Agent is defined with a simple, non-custodial instruction
root_agent = Agent(
name="transaction_crafter_agent",
model="gemini-2.0-flash",
description="An agent that crafts Ethereum transactions for a front-end to send via MetaMask.",
instruction=(
"You are an agent that crafts ETH transactions. "
"Your only job is to collect the information from the user to craft Ethereum transactions.. "
"The sender's address will be provided to you as context, along with the chain ID."
"Use the `craft_eth_transaction` tool to generate the transaction object. "
"The tool will return a JSON object that is ready to be sent to MetaMask. "
"Leave gas and gasPrice fields empty; MetaMask will set them."
"**IMPORTANT:** After using the tool, you must present the final transaction JSON in the response, formatted exactly like this:\n"
" ```json\n"
" {\n"
" \"to\": \"0x...\",\n"
" \"from\": \"0x...\",\n"
" \"value\": \"0x...\",\n"
" \"nonce\": \"0x...\",\n"
" \"chainId\": \"0xaa36a7\"\n"
" }\n"
" ```\n"
),
tools=[craft_eth_transaction],
)
On the client-side, the logic is clean and focused on Web3 interactions. The front-end doesn’t need to know anything about LLMs or agent orchestration. It simply calls the agent’s API endpoint (hosted on Google Cloud Run), gets back a standard JSON transaction object, and passes it to MetaMask. ADK’s ability to easily run the agent as an API server provides this clean separation of concerns. The two main functions on the frontend are to extract the transaction from the agent’s response and how to send it to MetaMask. I’m giving an example of those functions here:
/**
* Step 1: Process the agent's response to extract the crafted transaction data.
* The agent's only output is a standard, unsigned transaction object.
*/
function extractTransactionFromAgentResponse(agentEvents) {
const functionResponse = agentEvents.find(
e => e.content?.parts?.[0]?.functionResponse?.name === 'craft_eth_transaction'
)?.content.parts[0].functionResponse;if (functionResponse?.response?.success) {
// The raw transaction object, ready for the user's wallet
return functionResponse.response.transaction;
}
return null;
}
/**
* Step 2: Pass the crafted transaction to the user's wallet for execution.
* This function triggers a MetaMask pop-up, putting the user in full control.
*/
async function executeMetaMaskTransaction(txData) {
if (!txData || typeof window.ethereum === 'undefined') {
console.error("Invalid transaction data or MetaMask not found.");
return;
}
try {
// The 'eth_sendTransaction' call asks the wallet to sign and send.
// The private key is never exposed to our web application.
const txHash = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [txData], // txData is the JSON object from the agent
});
console.log(`Transaction sent successfully! Hash: ${txHash}`);
return txHash;
} catch (error) {
// This error typically means the user rejected the transaction in MetaMask.
console.error("Transaction failed or was rejected by user:", error);
}
}
It Doesn’t “Slow Things Down”, It Confirms Intent
One feedback I got is that it slows things down. However, given the Agent operations is the dialogue with the agent to reach a decision, the MetaMask pop-up isn’t an interruption; it’s the conclusion of that conversation.
Think of it as the digital equivalent of your financial advisor explaining a strategy and then handing you the final document to sign. The signature isn’t a “slowdown” — it’s the deliberate, necessary confirmation that you understand and consent to the action. It turns the agent’s recommendation into a reality with your explicit approval, providing crucial peace of mind. especially given that an agent’s interpretation can vary wildly depending on the underlying LLM, the conversational context, and the data it has access to. It’s always good to double check on a MetaMask / Hardware wallet the transaction, especially with add-ons like Blockaid or Tenderly!
The vision of a future where agents autonomously pay other agents for services necessitates the Agent Controlled model. Agents in this economy will undoubtedly need their own capital to operate.
However, the Transaction Crafter model provides the perfect, secure bridge to that future. It can be used to safely fund an agent, or to simply execute one-off transactions for simpler operations. This flexibility is key.
From a developer’s perspective, adding this capability is not a heavy lift. If a MCP server can already prepare and sign a transaction with a key it holds, it can easily perform the same logic without the final signing step, returning the unsigned transaction instead. This minor change unlocks a much safer and flexible paradigm for users and can even enable more complex designs, like a dedicated “signer agent” in a multi-agent system.
Therefore, any robust MCP server designed for broad adoption should provide developers with the flexibility to build applications that can:
- Advise and Craft for secure, user-centric financial decisions.
- Execute with Delegated Funds for specialized, automated, and clearly defined tasks.
This dual support is the only way to foster real innovation while protecting users. So, to the builders of the crypto community: it’s time to start pushing for the “Transaction Crafter” model as a standard feature in your MCP servers.
If you’re a builder working at this intersection and are interested in collaborating, please feel free to reach out to me directly on LinkedIn!
Source Credit: https://medium.com/google-cloud/the-crypto-agents-dilemma-who-should-hold-the-keys-8ea9f71fe1c9?source=rss—-e52cf94d98af—4