OsirisOsiris
Core Concepts

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:

  1. ✅ Action must NOT match any deny rules
  2. ✅ Action must match at least one allow rule (if allow rules exist)
  3. ❌ 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.