Policy-Driven Development
Secure wallet operations with intelligent policy enforcement for signTransaction, signMessage, and signTypedData
Policy-Driven Development enables users to define granular security rules for wallet operations, preventing unauthorized or dangerous transactions while allowing legitimate ones. Policies are enforced automatically before any wallet operation is executed.
Wallet Operations Only
Important: Policy enforcement is only active for wallet-based authentication, not for OAuth or secret sharing. This includes operations like signTransaction
, signMessage
, signTypedData
, and signRawPayloads
.
Why Wallet Policies Matter
Unlike traditional APIs, blockchain transactions are:
- 🔒 Irreversible - Once confirmed, cannot be undone
- 💰 Financial - Directly involve user's assets and money
- 🧩 Complex - Smart contract interactions can have unexpected consequences
- ⚠️ High-stakes - Mistakes can result in total loss of funds
Policy enforcement prevents MCPs and LLMs from:
- 🚫 Transferring more than daily limits
- 🚫 Sending to blocked/suspicious addresses
- 🚫 Paying excessive gas fees
- 🚫 Interacting with unauthorized contracts
- 🚫 Executing transactions during off-hours
- 🚫 Signing malicious messages
Policy Structure
Policies consist of two rule sets:
interface Policy {
allow: Rule[]; // Conditions under which actions are permitted
deny: Rule[]; // Conditions under which actions are forbidden
}
Evaluation Logic:
- ✅ Action must NOT match any
deny
rules - ✅ Action must match at least one
allow
rule (if allow rules exist) - ❌ If no allow rules exist, any action not denied is implicitly allowed
signTransaction Policies
Uniswap Swap Example
Prevent unauthorized or excessive DeFi trading:
const uniswapPolicy = {
allow: [
{
method: "signTransaction",
chain: "evm:eip155:137", // Polygon only
contractAddress: "0xE592427A0AEce92De3Edee1F18E0157C05861564", // Uniswap V3 Router
decoded: {
functionName: "exactInputSingle",
args: {
lengthEq: 1,
"0": { // SwapParams struct
amountIn: {
bigIntMax: "1000000000000000000" // Max 1 ETH per swap
},
amountOutMinimum: {
bigIntMin: "1" // Must have minimum output protection
},
recipient: {
addressEq: "{{walletAddress}}" // Only to user's wallet
}
}
}
}
}
],
deny: [
{
method: "signTransaction",
decoded: {
functionName: "exactInputSingle",
args: {
"0": {
amountIn: {
bigIntMin: "10000000000000000000" // Deny > 10 ETH swaps
}
}
}
}
}
]
};
ERC-20 Approval Policy
Control token approvals to prevent unlimited access:
const approvalPolicy = {
allow: [
{
method: "signTransaction",
decoded: {
functionName: "approve",
args: {
lengthEq: 2,
"0": { // spender address
addressInList: [
"0xE592427A0AEce92De3Edee1F18E0157C05861564", // Uniswap Router
"0x1F98431c8aD98523631AE4a59f267346ea31F984" // Uniswap Factory
]
},
"1": { // amount
bigIntMax: "1000000000000000000000" // Max 1000 tokens
}
}
}
}
],
deny: [
{
method: "signTransaction",
decoded: {
functionName: "approve",
args: {
"1": "115792089237316195423570985008687907853269984665640564039457584007913129639935"
}
}
}
]
};
signMessage Policies
Phishing Protection
Prevent signing dangerous messages that could compromise accounts:
const messagePolicy = {
allow: [
{
method: "signMessage",
payload: {
lengthEq: 1,
"0": {
minLength: 10,
maxLength: 500,
// Allow login messages
pattern: "^(Sign in to|Login to|Authenticate with)"
}
}
}
],
deny: [
{
method: "signMessage",
payload: {
"0": {
anyOf: [
{ stringContains: "private key" },
{ stringContains: "seed phrase" },
{ stringContains: "mnemonic" },
{ stringContains: "transfer ownership" },
{ stringContains: "give permission to drain" }
]
}
}
}
]
};
SIWE (Sign-In With Ethereum) Policy
Allow only legitimate authentication messages:
const siwePolicy = {
allow: [
{
method: "signMessage",
decoded: {
messageType: "siwe",
uri: {
pattern: "^https://(app\\.mysite\\.com|localhost:3000)"
},
chainId: {
enum: [1, 137, 42161] // Mainnet, Polygon, Arbitrum
},
issuedAt: {
// Message must be recent (within 5 minutes)
maxAge: 300
}
}
}
],
deny: []
};
signTypedData Policies
Hyperliquid Trading Example
Control perpetual trading on Hyperliquid with granular limits:
const hyperliquidPolicy = {
allow: [
{
method: "signTypedData",
chain: "evm:eip155:42161", // Arbitrum
decoded: {
verifiedOrder: {
type: "order",
riskLevel: "low",
orders: {
lengthEq: 1,
"0": {
a: 0, // asset
b: true, // isBuy
p: {
lte: 2000.0 // price limit
},
r: false // reduceOnly
}
}
}
}
},
{
method: "signTypedData",
decoded: {
verifiedOrder: {
type: "cancel"
}
}
},
{
method: "signTypedData",
decoded: {
verifiedOrder: {
type: "withdraw",
amount: { numericMax: 500 }
}
}
}
],
deny: [
{
method: "signTypedData",
decoded: {
verifiedOrder: {
riskLevel: "high"
}
}
},
{
method: "signTypedData",
decoded: {
verifiedOrder: {
type: "order",
leverage: { numericGt: 10 }
}
}
}
]
};
EIP-712 Domain Restrictions
Only allow signing for trusted applications:
const eip712Policy = {
allow: [
{
method: "signTypedData",
payload: {
lengthEq: 1,
"0": {
domain: {
name: {
enum: ["MyTrustedApp", "Uniswap", "OpenSea"]
},
verifyingContract: {
addressInList: [
"0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", // Uniswap Router
"0x495f947276749Ce646f68AC8c248420045cb7b5e" // OpenSea Registry
]
},
chainId: {
enum: [1, 137] // Mainnet and Polygon only
}
}
}
}
}
],
deny: [
{
method: "signTypedData",
payload: {
"0": {
domain: {
name: {
stringContains: "Phishing" // Block obvious scams
}
}
}
}
}
]
};
Policy Implementation in MCPs
Setting Up Policy Enforcement
When users connect wallets to your MCP, they can define policies during the connection process:
npx @osiris-ai/cli connect-mcp-auth
# User will be prompted to set wallet policies for their MCP connection
Using PolicyBuilder for Dynamic Policies
Create policies programmatically using the PolicyBuilder:
import { PolicyBuilder } from '@osiris-ai/policy-engine';
const builder = new PolicyBuilder();
// Allow specific token transfers
builder.allowTransfer(
{ addressEq: "0x..." }, // to address constraint
{ bigIntMax: "1000000000000000000" } // max 1 ETH
);
// Allow safe approvals
builder.allowApprove(
{ addressIn: ["0x...", "0x..."] }, // trusted spenders
{ bigIntMax: "1000000000000000000" } // max approval amount
);
// Deny unlimited approvals
builder.denyUnlimitedApprovals();
// Allow message signing with constraints
builder.allowMessageSigning({
maxLength: 200,
stringNotContains: ["private key", "seed phrase"]
});
const policy = builder.build();
Runtime Policy Validation
The policy engine automatically validates transactions before signing:
import { EVMWalletClient } from '@osiris-ai/web3-evm-sdk';
import { getAuthContext } from '@osiris-ai/sdk';
export const executeSwap = tool({
name: 'execute_swap',
description: 'Execute Uniswap token swap',
inputSchema: z.object({
tokenIn: z.string(),
tokenOut: z.string(),
amountIn: z.string()
}),
handler: async ({ tokenIn, tokenOut, amountIn }) => {
const { token, context } = getAuthContext("osiris");
if (!token || !context) throw new Error("Not authenticated");
const walletClient = new EVMWalletClient(
"https://api.osirislabs.xyz/v1",
token.access_token,
context.deploymentId
);
// Prepare Uniswap transaction
const transaction = prepareSwapTransaction({
tokenIn, tokenOut, amountIn
});
try {
// Policy engine automatically validates before signing
const signedTx = await walletClient.signTransaction(
UNISWAP_ABI,
transaction,
"evm:eip155:137", // Polygon
userWalletAddress
);
return { success: true, txHash: signedTx };
} catch (error: any) {
if (error.message.includes('Policy violation')) {
return {
content: [{
type: 'text',
text: `❌ Transaction blocked by policy: ${error.message}`
}]
};
}
throw error;
}
}
});
Advanced Policy Features
Gas Fee Protection
const gasFeePolicy = {
deny: [
{
method: "signTransaction",
anyOf: [
{ gasPrice: { bigIntMin: "100000000000" } }, // > 100 gwei
{ maxFeePerGas: { bigIntMin: "100000000000" } }
]
}
]
};
Best Practices
1. Start Restrictive, Loosen Gradually
// Start with tight controls
const restrictivePolicy = {
allow: [
{
method: "signTransaction",
contractAddress: "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D", // Only one contract
decoded: {
functionName: "deposit", // Only one function
args: {
"0": { bigIntMax: "100000000000000000" } // Small amounts
}
}
}
]
};
2. Use Allowlists for Critical Operations
const allowlistPolicy = {
allow: [
{
method: "signTransaction",
contractAddress: {
addressInList: [
"0xE592427A0AEce92De3Edee1F18E0157C05861564", // Uniswap
"0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9" // Aave
]
}
}
]
};
3. Implement Emergency Stops
const emergencyPolicy = {
deny: [
{
method: "signTransaction",
metadata: {
riskLevel: "high"
}
}
]
};
4. Combine Multiple Policy Types
const comprehensivePolicy = {
allow: [
// Regular trading
uniswapPolicy.allow[0],
// Safe message signing
messagePolicy.allow[0],
// Controlled typed data
hyperliquidPolicy.allow[0]
],
deny: [
// Combine all deny rules
...uniswapPolicy.deny,
...messagePolicy.deny,
...hyperliquidPolicy.deny
]
};
Policy-driven development puts users in control of their wallet security while enabling sophisticated DeFi and Web3 interactions through MCPs. Users define the rules, and the policy engine enforces them automatically.