This content originally appeared on DEV Community and was authored by Sushmita - aka meowy
It was only a matter of time before AI Agents and web3 joined forces, creating a more accessible and democratic user experience. For years there was a strong technical barrier that stood as a fortress wall for users, but now AI agents are acting as a skilled guide, interpreting complex protocols with simple chat conversations and intuitive responses. There’s always sophisticated web3 technology advancement happening, but how we engage with these protocols with a layer of intelligence that adapts with our natural communication pattern with AI, rather than forcing us to adapt with the technical barrier, is what makes artificial intelligence so important. In this blog post, we will go over how to implement MetaMask Delegation Toolkit with Gaia to perform smart contract operations like the token creation process.
Web3 UX: Still a challenge for users
Even though there’s a huge potential for blockchain technology, one challenge that still remains is user experience. Creating and deploying a token on any L1/L2/altVM blockchain requires specialized knowledge of writing smart contract in programming languages like solidity, Rust, Sway, Vyper and even new languages like Cairo and there’s also the complex deployment process. Even for experienced developers, the specified process can be time-consuming. We also have to consider the fact that smart contracts can also contain vulnerabilities which can result in potential fund loss.
Let’s take a look at what it typically takes to create a simple ERC20 token:
- Write Smart Contract code for the token contract
- Compile the code with the correct and specific compiler version
- Deploy the smart contract to a testnet for verification.
- Configure gas settings to make sure of the successful deployment
- Deploy to the contract to mainnet while managing private keys securely
- Verify the contract on block explorers (so that it’s not an unverified token contract)
For an average user, this barrier can be quite the challenge, which ultimately limits the participation in one of the most basic activities in the web3 ecosystem - creating tokens. This is where the combination of AI and smart wallet like delegation technology offers an amazing solution.
Understanding the MetaMask Delegation Toolkit
The MetaMask Delegation Toolkit (DTK) introduces a paradigm shift in how permissions can be granted on the blockchain through AI. At its heart, DTK enables a security model where one account (the delegator) can authorize another account (the delegate) to perform specific actions on its behalf, with fine-grained control over what those actions can be.
The DTK operates through several key components that work together to create a secure delegation system:
1. Smart Accounts
Smart accounts are contract-based accounts that can enforce rules and permissions beyond what traditional EOA (Externally Owned Accounts) can do. These accounts are the foundation of the delegation system.
In this implementation, two smart accounts are created:
// Creating a smart account for the user (delegator)
const delegatorSmartAccount = await toMetaMaskSmartAccount({
client: publicClient, // Connection to the blockchain
implementation: Implementation.Hybrid, // Account implementation type
deployParams: [address, [], [], []], // Parameters for account initialization
deploySalt: createSalt(), // Unique identifier for deterministic deployment
signatory: { walletClient }, // Connection to user's wallet for signing
});
// Creating a smart account for Gaia (delegate)
const aiPrivateKey = generatePrivateKey(); // Generate a new private key for the AI
const aiAccount = privateKeyToAccount(aiPrivateKey); // Create an EOA from the private key
const aiSmartAccount = await toMetaMaskSmartAccount({
client: publicClient,
implementation: Implementation.Hybrid,
deployParams: [aiAccount.address, [], [], []],
deploySalt: createSalt(),
signatory: { account: aiAccount }, // AI's account will sign its transactions
});
These accounts are "counterfactual" - they exist with deterministic addresses before they're actually deployed on the blockchain. This allows for gas-efficient operation as the accounts only get deployed when they first perform a transaction.
2. Delegations with Caveats
A delegation is a cryptographically signed permission grant that specifies what actions are allowed. Caveats are restrictions that define the precise boundaries of what a delegate can do.
The implementation of caveats is where the real security magic happens:
// Define strict boundaries for what Gaia can do
const caveats = createCaveatBuilder(delegatorAccount.environment)
// Only allow interaction with our factory contract
.addCaveat("allowedTargets", [FACTORY_CONTRACT_ADDRESS])
// Prevent ETH spending entirely
.addCaveat("valueLte", BigInt(0))
// Only allow calling the createToken function
.addCaveat("allowedMethods", [CREATE_TOKEN_SELECTOR]);
// Create the delegation with these restrictions
const newDelegation = createRootDelegation(
aiDelegateAccount.address, // Who receives permission (Gaia)
delegatorAccount.address, // Who grants permission (User)
caveats, // The restrictions
BigInt(createSalt()) // Unique identifier
);
// Sign the delegation with the user's account
const signature = await delegatorAccount.signDelegation({
delegation: newDelegation
});
This code creates a delegation that:
- Restricts AI to interact only with the specified factory contract address
- Prevents AI from spending any of the user's ETH (value limit set to zero)
- Allows AI to call only the createToken function on that contract
These restrictions create a secure sandbox that protects the user's assets while enabling specific functionality. Think of it as giving someone a limited power of attorney to perform only one very specific action on your behalf, rather than handing over full control of your assets.
3. Delegation Storage and Persistence
For the delegation system to be useful across sessions, AI stores the delegation information securely using the DTK's delegation storage service:
// Store a delegation in the service
const delegationStorageClient = new DelegationStorageClient({
apiKey: process.env.NEXT_PUBLIC_DELEGATION_STORAGE_API_KEY!,
apiKeyId: process.env.NEXT_PUBLIC_DELEGATION_STORAGE_API_KEY_ID!,
environment: DelegationStorageEnvironment.dev,
});
// Store the delegation in the delegation storage service
const storedDelegation = await delegationStorageClient.storeDelegation(signedDelegation);
This storage system allows delegations to persist beyond browser sessions, enabling users to return to the application and have AI retain its permissions without repeating the setup process.
Gaia: The Decentralized AI Infrastructure
Now that we understand the delegation framework, let's examine how Gaia’s Public Models utilizes these delegations to deploy tokens based on natural language requests using API.
AI is integrated into the application through a chat interface where users can describe the token they want to create. The agent processes these descriptions, extracts the key parameters, and then executes the token creation through the delegation system.
The AI Tool for Token Deployment
The bridge between agents natural language understanding and blockchain execution is implemented as a custom AI tool.
Tools are actions that an LLM can invoke. The results of these actions can be reported back to the LLM to be considered in the next response. I’ve used vercel’s AI sdk to get started with tools.
Let’s break down the code from here.
// Delegation Storage Singleton
let delegationStorageInstance: DelegationStorageClient | null = null;
// Helper function to log storage configuration
const logStorageConfig = (apiKey?: string, apiKeyId?: string) => {
console.group("=== Delegation Storage Configuration ===");
console.log("API Key format check:", {
exists: !!apiKey,
length: apiKey?.length,
firstChars: apiKey?.substring(0, 4),
lastChars: apiKey?.substring(apiKey.length - 4),
hasSpecialChars: apiKey?.match(/[^a-zA-Z0-9]/) ? true : false,
});
console.log("API Key ID format check:", {
exists: !!apiKeyId,
length: apiKeyId?.length,
firstChars: apiKeyId?.substring(0, 4),
lastChars: apiKeyId?.substring(apiKeyId.length - 4),
hasSpecialChars: apiKeyId?.match(/[^a-zA-Z0-9]/) ? true : false,
});
console.log("Environment:", DelegationStorageEnvironment.dev);
console.log(
"Running on:",
typeof window !== "undefined" ? "client" : "server"
);
console.groupEnd();
};
/**
* Gets the delegation storage client, initializing it if necessary
* @returns A configured DelegationStorageClient instance
*/
export const getDelegationStorageClient = (): DelegationStorageClient => {
if (!delegationStorageInstance) {
const apiKey = process.env.NEXT_PUBLIC_DELEGATION_STORAGE_API_KEY;
const apiKeyId = process.env.NEXT_PUBLIC_DELEGATION_STORAGE_API_KEY_ID;
logStorageConfig(apiKey, apiKeyId);
if (!apiKey || !apiKeyId) {
throw new Error("Delegation storage API key and key ID are required");
}
try {
delegationStorageInstance = new DelegationStorageClient({
apiKey,
apiKeyId,
environment: DelegationStorageEnvironment.dev,
fetcher:
typeof window !== "undefined" ? window.fetch.bind(window) : undefined,
});
console.log("DelegationStorageClient initialized successfully");
} catch (error) {
console.error("Error creating DelegationStorageClient:", error);
throw error;
}
}
return delegationStorageInstance;
};
/**
* Stores a delegation in the delegation storage service
* @param delegation The delegation to store
* @returns The result of the store operation
*/
export const storeDelegation = async (delegation: DelegationStruct) => {
try {
console.group("=== Storing Delegation ===");
console.log("Delegation details:", {
delegate: delegation.delegate,
delegator: delegation.delegator,
hasSignature: !!delegation.signature,
salt: delegation.salt.toString(),
});
const delegationStorageClient = getDelegationStorageClient();
const result = await delegationStorageClient.storeDelegation(delegation);
console.log("Delegation stored successfully:", result);
console.groupEnd();
return result;
} catch (error: any) {
console.error("Delegation storage error:", {
name: error.name,
message: error.message,
status: error.status,
details: error.details,
stack: error.stack,
});
console.groupEnd();
throw error;
}
};
/**
* Retrieves a delegation chain by its hash
* @param hash The hash of the delegation chain to retrieve
* @returns The delegation chain
*/
export const getDelegationChain = async (hash: Hex) => {
try {
console.log("Fetching delegation chain for hash:", hash);
const delegationStorageClient = getDelegationStorageClient();
const result = await delegationStorageClient.getDelegationChain(hash);
console.log("Delegation chain fetched:", result);
return result;
} catch (error) {
console.error("Error fetching delegation chain:", error);
throw error;
}
};
/**
* Fetches delegations for a specific address
* @param address The address to fetch delegations for
* @param filter Whether to fetch given or received delegations
* @returns The delegations for the address
*/
export const fetchDelegations = async (
address: Hex,
filter: DelegationStoreFilter
) => {
try {
console.log(
"Fetching delegations for address:",
address,
"filter:",
filter
);
const delegationStorageClient = getDelegationStorageClient();
const result = await delegationStorageClient.fetchDelegations(
address,
filter
);
console.log("Delegations fetched:", result);
return result;
} catch (error) {
console.error("Error fetching delegations:", error);
throw error;
}
};
/**
* Gets delegation info from session storage (if available)
* @returns The delegation info or null if not found
*/
export const getDelegationInfoFromSession = () => {
if (typeof window === "undefined") return null;
try {
const delegationInfoStr = sessionStorage.getItem("aiDelegateInfo");
if (!delegationInfoStr) return null;
return JSON.parse(delegationInfoStr);
} catch (error) {
console.error("Error retrieving delegation info from session:", error);
return null;
}
};
/**
* Gets the full delegation from session storage (if available)
* @returns The full delegation or null if not found
*/
export const getFullDelegationFromSession = () => {
if (typeof window === "undefined") return null;
try {
const delegationStr = sessionStorage.getItem("delegation");
if (!delegationStr) return null;
const delegation = JSON.parse(delegationStr);
// Convert string salt back to BigInt
if (delegation && typeof delegation.salt === "string") {
delegation.salt = BigInt(delegation.salt);
}
return delegation;
} catch (error) {
console.error("Error retrieving delegation from session:", error);
return null;
}
};
/**
* Clears delegation info from session storage
*/
export const clearDelegationSession = () => {
if (typeof window === "undefined") return;
sessionStorage.removeItem("aiDelegateInfo");
sessionStorage.removeItem("delegation");
sessionStorage.removeItem("aiDelegatePrivateKey");
};
const createTokenTool = createTool({""});
export const tools = {
createToken: createTokenTool,
};
this tool encapsulates all the complexity of token deployment. When the AI determines that the user wants to create a token, it invokes this tool with the appropriate parameters extracted from the conversation.
The Token Factory Contract: Writing the smart contract for the Agent
The ERC20 Factory contract that Gaia interacts with is designed to create new token instances based on the provided parameters:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./BaseERC20Token.sol";
contract ERC20Factory {
address public owner;
mapping(address => bool) public tokenExists;
address[] public allTokens;
event TokenCreated(
address indexed tokenAddress,
string name,
string symbol,
uint256 initialSupply,
address owner
);
constructor() {
owner = msg.sender;
}
function createToken(
string memory name,
string memory symbol,
uint256 initialSupply
) external returns (address) {
BaseERC20Token newToken = new BaseERC20Token(
name,
symbol,
initialSupply,
msg.sender
);
address tokenAddress = address(newToken);
tokenExists[tokenAddress] = true;
allTokens.push(tokenAddress);
emit TokenCreated(
tokenAddress,
name,
symbol,
initialSupply,
msg.sender
);
return tokenAddress;
}
function getAllTokens() external view returns (address[] memory) {
return allTokens;
}
function getTokenCount() external view returns (uint256) {
return allTokens.length;
}
}
The base ERC20 token that gets created:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract BaseERC20Token is ERC20, Ownable {
constructor(
string memory name,
string memory symbol,
uint256 initialSupply,
address owner
) ERC20(name, symbol) Ownable(owner) {
_mint(owner, initialSupply * (10 ** decimals()));
}
}
This factory pattern provides several benefits:
- The base token implementation is fixed and secure
- Each token is customized through constructor parameters
- The factory maintains a registry of all created tokens
- Deployment is gas-efficient
You can check the whole smart contract code here: https://github.com/meowyx/erc20-factory
The User Experience: From Conversation to Blockchain
What makes AI special is how it transforms a complex blockchain operation into a simple conversation. Let's explore the user experience of interacting with AI to create tokens.
The Chat Interface
The chat interface is where users interact with Gaia through natural language:
export const Chat = () => {
const { address } = useAccount();
const [delegationEnabled, setDelegationEnabled] = useState(false);
// Check if delegation is set up
useEffect(() => {
const checkDelegation = () => {
const delegateInfo = sessionStorage.getItem('aiDelegateInfo');
setDelegationEnabled(!!delegateInfo);
};
checkDelegation();
window.addEventListener('storage', checkDelegation);
return () => {
window.removeEventListener('storage', checkDelegation);
};
}, []);
const { messages, input, handleInputChange, handleSubmit, isLoading } =
useChat({
initialMessages: [
{
role: "system",
content: `You have connected your wallet successfully. Your wallet address is ${address}. ${
delegationEnabled
? "You have delegated token creation permissions to me. I can deploy ERC20 tokens on your behalf through a factory contract. Just ask me to create a token and specify the name, symbol, and initial supply."
: "You can delegate token creation permissions to me using the delegation manager above. Once set up, I'll be able to create custom ERC20 tokens for you with limited permissions."
}`,
id: "system",
},
],
});
// Render chat interface and handle responses
return (
<div className="h-full w-full space-y-4 max-w-3xl">
<div className="border h-full max-h-96 rounded-md p-4 space-y-6 justify-end overflow-y-scroll">
{messages.map((message) => (
<div key={message.id}>
{/* Render user and AI messages */}
{message.role === "user" ? (
<div className="flex w-full justify-start">
<div className="w-fit max-w-md bg-gray-800 text-gray-50 rounded-md p-2">
<strong>User:</strong> {message.content}
</div>
</div>
) : (
<div className="flex w-full justify-end">
<div className="w-fit max-w-md bg-gray-100 text-gray-900 rounded-md p-2">
<strong>AI:</strong> {message.content}
</div>
</div>
)}
{/* Handle tool invocation results */}
<div>
{message.toolInvocations?.map((toolInvocation) => {
const { toolName, toolCallId, state } = toolInvocation;
if (state === "result" && toolName === "deployERC20") {
const { result } = toolInvocation;
return (
<div key={toolCallId} className="p-3 my-2 rounded">
{result.success ? (
<div className="bg-green-50 border border-green-200 p-3 rounded">
<h3 className="font-bold text-green-800">Token Deployed Successfully!</h3>
<div className="mt-2 space-y-1">
<p><strong>Name:</strong> {result.tokenName}</p>
<p><strong>Symbol:</strong> {result.tokenSymbol}</p>
<p><strong>Address:</strong> {result.tokenAddress}</p>
<p className="mt-2">
<a
href={`https://sepolia.etherscan.io/tx/${result.transactionHash}`}
target="_blank"
className="text-blue-600 underline"
>
View transaction on Etherscan
</a>
</p>
</div>
</div>
) : (
<div className="bg-red-50 border border-red-200 p-3 rounded">
<h3 className="font-bold text-red-800">Token Deployment Failed</h3>
<p className="text-red-700 mt-1">
{result.error}
</p>
</div>
);
</div>
);
}
})}
</div>
</div>
))}
</div>
<form className="flex gap-3" onSubmit={handleSubmit}>
<Input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
/>
<Button type="submit">Send</Button>
</form>
</div>
);
};
Complete Flow: From User Request to Token Deployment
The entire process flows through these steps:
- User Connects Wallet: The application detects the connected wallet and displays the Delegation Manager.
- Initial Setup: The user clicks "Set Up Delegation Accounts", which creates two smart accounts and prepares for delegation.
- Delegation Creation: The user clicks "Create Delegation with Restrictions", which grants Gaia limited permissions to deploy tokens.
- Natural Language Interaction: The user types a request like: "Please create a token called Community Rewards Token with symbol CRT and an initial supply of 10000 tokens."
- AI Processing: AI processes this request, recognizing it as a token creation instruction and extracting the key parameters.
- Tool Invocation: AI invokes the deployERC20Tool with the extracted parameters (name, symbol, supply).
-
Delegation Redemption: The tool:
- Retrieves AI's account information and the delegation
- Encodes a call to the createToken function with the specified parameters
- Wraps this call in a delegation redemption that proves Gaia has permission
- Submits the transaction as a user operation through a bundler
-
On-Chain Execution:
- The bundler verifies the delegation and that the action is allowed by the caveats
- The factory contract creates a new ERC20 token with the specified parameters
- The transaction is confirmed and a TokenCreated event is emitted
-
Result Display: AI presents the results to the user, including:
- Confirmation of successful deployment
- The token name, symbol, and contract address
This entire process happens without the user needing to understand the underlying smart contract interactions, gas optimization, or transaction signing. They simply express what they want in natural language, and Gaia handles the rest.
The Technical Magic: How It Really Works
To fully understand this system, let's dig deeper into the technical mechanisms that make it possible.
Account Abstraction (ERC-4337)
The foundation of the delegation system is ERC-4337, the Account Abstraction standard. This separates transaction signing from transaction payment and execution, making more flexible account models.
In traditional Ethereum transactions, the account that signs a transaction must also pay for gas and must be the one executing the transaction. Account abstraction breaks these dependencies apart, enabling:
- Separate accounts for signing, paying, and executing
- More complex validation logic beyond simple signature verification
- Batching of multiple actions into a single transaction
- Gas sponsorship and alternative payment methods
Gaia leverages account abstraction through the bundler client:
// Send a user operation instead of a regular transaction
const userOpHash = await bundlerClient.sendUserOperation({
account: aiSmartAccount,
calls: [
{
to: aiSmartAccount.address,
data: redeemDelegationCalldata,
},
],
});
This approach allows AI to act as a delegate without requiring the user to send their private keys or pre-sign transactions. Instead, the delegation itself serves as the authorization.
The Function Selector: A Critical Security Boundary
One of the most important security aspects of the implementation is the use of function selectors in caveats. A function selector is the first 4 bytes of the keccak256 hash of a function signature, uniquely identifying that function.
For the factory's createToken function, the selector is calculated as:
keccak256("createToken(string,string,uint256)").substring(0, 10)
// Results in something like: "0x45ba7a0f"
This selector is used in the caveats to ensure Gaia can only call this specific function:
.addCaveat("allowedMethods", [CREATE_TOKEN_SELECTOR])
This is a critical security boundary because even if AI (or an attacker who somehow gained control of Gaia's private key) tried to call a different function on the target contract, the delegation system would reject the transaction as unauthorized.
Counterfactual Accounts: Efficiency Through Delayed Deployment
The smart accounts used in the system are "counterfactual" - they exist with predictable addresses before they're actually deployed on the blockchain. This is achieved through a technique called "CREATE2" that allows contracts to be deployed to deterministic addresses.
The benefits of this approach include:
- Gas Efficiency: Accounts are only deployed when they first transact, saving deployment costs if they're never used.
- Predictable Addresses: You can know an account's address before it's deployed, enabling references to it in other contracts.
- Just-in-Time Deployment: Accounts can be deployed as part of the first transaction that uses them, simplifying the user experience.
This functionality is encapsulated in the toMetaMaskSmartAccount function, which creates accounts with deterministic addresses based on the deployment parameters and salt.
The interaction of AI agents with smart account/delegation technology shows a different approach of human interaction with blockchain. By allowing users to express their intentions in natural language while maintaining strict security boundaries, systems like Gaia can help bridge the gap between Web3's capabilities and mainstream adoption. This approach doesn't just make existing processes easier—it enables entirely new patterns of interaction that would be impossible with traditional interfaces. The combination of AI and delegation technology is just beginning to reveal its potential. There are more exciting directions could be taken in the future, for example: multi-chain operation, conditional multi-delegations, AI governance and even collaborative creation of tokens or other smart contract functionalities.
Some Resources:
The Code: https://github.com/meowyx/metamask-gaia-starter
Gaia Docs: https://docs.gaianet.ai/intro
Happy Coding 🚀
This content originally appeared on DEV Community and was authored by Sushmita - aka meowy

Sushmita - aka meowy | Sciencx (2025-08-26T13:31:59+00:00) Reimagining Web3 Interactions: How Gaia Powers Smart Token Creation on Linea with MetaMask Delegation Toolkit. Retrieved from https://www.scien.cx/2025/08/26/reimagining-web3-interactions-how-gaia-powers-smart-token-creation-on-linea-with-metamask-delegation-toolkit/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.