suiblockchainsecuritymovetechnical

Building on Sui: Why We Chose Sui for Kairo's Policy Engine

A deep dive into why we chose Sui blockchain for Kairo Guard's policy-gated transaction infrastructure, exploring the technical advantages that make Sui ideal for crypto security.

Kairo TeamJanuary 27, 202517 min read

Building on Sui: Why We Chose Sui for Kairo's Policy Engine

When we set out to build Kairo Guard—a browser extension that brings policy-gated security to crypto transactions—we faced a critical architectural decision: which blockchain would serve as the foundation for our policy engine? After months of research, prototyping, and stress-testing, we committed to building on Sui blockchain. This wasn't a marketing decision or a bet on token prices. It was a deeply technical choice rooted in Sui's unique architecture that aligns perfectly with what we're trying to accomplish.

In this post, we'll share the technical reasoning behind this decision, compare our options, and explain how Sui's object-centric model enables capabilities that would be difficult or impossible on other chains.

Kairo's Mission: Policy-Gated Crypto Transactions

Before diving into blockchain comparisons, let's establish what Kairo actually does. At its core, Kairo Guard is a security layer that sits between users and their crypto transactions. Think of it as a programmable firewall for your wallet.

Every transaction must pass through a policy check before execution. These policies can enforce rules like:

  • Spending limits: "No single transaction over $10,000 without 2FA"
  • Allowlists: "Only interact with these verified contract addresses"
  • Time locks: "No transactions between 2 AM and 6 AM"
  • Velocity controls: "Maximum $50,000 outflow per 24-hour period"
  • Multi-party approval: "Transfers over $100,000 require CFO signature"

The key insight is that these policies must be on-chain and immutable once deployed. If policies lived on a centralized server, they'd be vulnerable to the same attack vectors we're trying to protect against. A compromised server could simply disable all policies.

This requirement—on-chain, immutable, but highly configurable policies that execute with every transaction—drove our blockchain selection criteria.

Why Blockchain Choice Matters for Security Infrastructure

Not all blockchains are created equal, especially when you're building security infrastructure. Here's what we needed:

1. Sub-Second Finality

When a user clicks "approve" on a transaction, they can't wait 12 seconds (Ethereum) or even 400ms (Solana) for policy validation. We needed true sub-second finality with cryptographic certainty—not probabilistic confirmation.

2. Low, Predictable Gas Costs

Policy checks happen on every transaction. If gas costs are high or unpredictable, we'd be adding friction to every user interaction. This rules out Ethereum L1 immediately.

3. Native Support for Complex Objects

Policies aren't simple key-value pairs. They're rich objects with nested conditions, time-based rules, and relational constraints. We needed a data model that treats objects as first-class citizens.

4. Security-First Smart Contract Language

Our policy engine is security infrastructure. The smart contract language must prevent entire classes of vulnerabilities by design, not by developer discipline.

5. Parallel Execution

Different users' policies are independent. We needed a runtime that could validate thousands of policy checks simultaneously without artificial serialization.

With these requirements in mind, let's examine our options.

The Technical Comparison: Sui vs Ethereum vs Solana

We seriously evaluated three blockchains: Ethereum (and its L2s), Solana, and Sui. Here's our honest assessment:

Ethereum / L2s

Pros:

  • Largest developer ecosystem
  • Battle-tested security
  • Abundant tooling

Cons for our use case:

  • Account-based model requires global state access for every transaction
  • EVM's sequential execution creates bottlenecks
  • Solidity's footguns (reentrancy, integer overflow historically) require defensive coding
  • Even on L2s, finality takes 1-2 blocks minimum
  • Gas costs on L1 prohibitive; L2 gas still adds up for high-frequency operations

The fundamental issue is Ethereum's account model. Every token transfer modifies a global balance mapping, which means transactions must be sequenced even when they're logically independent. For policy validation at scale, this creates artificial bottlenecks.

// Ethereum: Every transfer touches global state
mapping(address => uint256) balances;

function transfer(address to, uint256 amount) public {
    balances[msg.sender] -= amount;  // Global state
    balances[to] += amount;          // Global state
}

Solana

Pros:

  • High throughput (theoretically 65k TPS)
  • Low transaction costs
  • Parallel execution via Sealevel

Cons for our use case:

  • Rust/Anchor learning curve is steep
  • Account model still requires declaring all touched accounts upfront
  • Probabilistic finality (optimistic confirmation isn't finality)
  • Complexity of PDAs and account sizing
  • Historical reliability concerns for critical infrastructure

Solana's parallel execution is impressive, but it requires developers to explicitly declare all accounts a transaction will touch. This works well for simple transfers but becomes unwieldy for complex policy objects with dynamic references.

// Solana: Must declare all accounts upfront
#[derive(Accounts)]
pub struct ValidatePolicy<'info> {
    #[account(mut)]
    pub policy: Account<'info, Policy>,
    #[account(mut)]
    pub user_config: Account<'info, UserConfig>,
    #[account(mut)]
    pub spending_tracker: Account<'info, SpendingTracker>,
    // What if policy references vary by user?
}

Sui

Pros:

  • Object-centric model matches our mental model perfectly
  • True sub-second finality (~480ms for simple transactions)
  • Move language eliminates entire vulnerability classes
  • Parallel execution is automatic and implicit
  • Predictable, low gas costs

Cons:

  • Smaller ecosystem (but growing rapidly)
  • Fewer battle-tested patterns to copy
  • Learning Move is an investment

Let's dive deeper into why Sui won.

Sui's Unique Advantages for Kairo

1. The Object-Centric Model: Policies as First-Class Objects

This is the killer feature. In Sui, everything is an object with a unique ID, ownership, and type. Policies aren't entries in a mapping—they're actual objects that can be:

  • Owned: A policy object can be owned by a user, a multi-sig, or another object
  • Shared: A policy can be shared for multi-party access with controlled mutability
  • Composed: Policies can contain other policies, enabling hierarchical rules
  • Transferred: Policy ownership can be transferred or delegated

Here's a simplified example of how we model policies:

module kairo::policy {
    use sui::object::{Self, UID};
    use sui::tx_context::TxContext;
    
    /// A Policy is a first-class object with its own identity
    public struct Policy has key, store {
        id: UID,
        owner: address,
        spending_limit: u64,
        daily_limit: u64,
        allowlisted_contracts: vector<address>,
        time_restrictions: Option<TimeWindow>,
        requires_2fa: bool,
        cooldown_period: u64,
    }
    
    public struct TimeWindow has store, drop {
        allowed_start_hour: u8,  // 0-23
        allowed_end_hour: u8,    // 0-23
        timezone_offset: i8,     // UTC offset
    }
    
    /// Create a new policy - returns an owned object
    public fun create_policy(
        spending_limit: u64,
        daily_limit: u64,
        ctx: &mut TxContext
    ): Policy {
        Policy {
            id: object::new(ctx),
            owner: tx_context::sender(ctx),
            spending_limit,
            daily_limit,
            allowlisted_contracts: vector::empty(),
            time_restrictions: option::none(),
            requires_2fa: false,
            cooldown_period: 0,
        }
    }
}

This maps directly to how users think about policies. "I have a policy" is a literal statement—the policy object exists in their wallet.

2. Parallel Transaction Execution (No Artificial Bottlenecks)

Sui's runtime automatically parallelizes transactions that touch different objects. When User A validates against Policy A and User B validates against Policy B, these execute simultaneously—no coordination required.

This is possible because Sui's transaction model explicitly declares object inputs:

Transaction {
    inputs: [Policy_A, UserConfig_A],
    ...
}

The runtime sees that Policy_A and Policy_B are independent objects and schedules them on different cores. We measured 4,200+ policy validations per second in our benchmarks without any optimization on our end—just clean object boundaries.

Compare this to Ethereum, where every policy check might need to read from a shared registry contract, serializing all validations.

3. Sub-Second Finality: Security You Can Feel

Sui achieves finality in approximately 480ms for simple transactions. For Kairo, this means:

  1. User initiates transaction
  2. Policy check executes on-chain
  3. Result returns to browser extension
  4. User sees approval/denial

Total time: under 1 second. This is critical for user experience. Security that slows you down gets disabled.

Under the hood, Sui achieves this through its unique consensus mechanism. For "owned object" transactions (which most policy checks are), Sui can bypass consensus entirely and use a simpler Byzantine Consistent Broadcast protocol. The policy object is owned by the user, so there's no contention—just cryptographic verification.

4. Move Language: Security by Construction

Move was designed for financial infrastructure. It eliminates entire classes of vulnerabilities:

No Reentrancy: Resources in Move have linear types. You can't "call back" in a way that observes intermediate state because the type system prevents it.

// This pattern is impossible in Move - resources are linear
public fun bad_transfer(coin: Coin<SUI>) {
    // Can't "use" coin twice - compiler prevents it
    deposit(coin);
    external_call();  // Even if this calls back...
    deposit(coin);    // This line won't compile - coin is moved
}

No Integer Overflow (by default): Move's integers are checked. Overflow panics unless you explicitly use wrapping operations.

No Null References: The type system distinguishes between values that exist and values that might not exist (Option<T>).

Explicit Ownership: Every value has exactly one owner. This prevents the "confused deputy" attacks common in Solidity.

For security infrastructure, these aren't nice-to-haves—they're requirements.

5. Gas Costs: Predictable and Low

Sui's gas model charges for computation and storage separately, with storage being refundable when objects are deleted. Our policy checks cost approximately 0.003-0.008 SUI depending on complexity—roughly $0.01-0.03 at current prices.

More importantly, costs are predictable. We can tell users exactly what policy management will cost without worrying about gas wars or network congestion spikes.

How Kairo's Policy Objects Work on Sui

Let's get concrete about the architecture. Here's a simplified flow of what happens when a user tries to execute a transaction:

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                     User's Browser                               │
│  ┌─────────────┐    ┌─────────────────────────────────────┐     │
│  │   dApp UI   │───▶│         Kairo Guard Extension       │     │
│  └─────────────┘    │  ┌─────────────────────────────────┐│     │
│                     │  │     Transaction Interceptor     ││     │
│                     │  └──────────────┬──────────────────┘│     │
│                     └─────────────────┼───────────────────┘     │
└───────────────────────────────────────┼─────────────────────────┘
                                        │
                                        ▼
┌─────────────────────────────────────────────────────────────────┐
│                        Sui Network                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │                  Kairo Policy Module                     │    │
│  │                                                          │    │
│  │  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │    │
│  │  │   Policy A   │  │   Policy B   │  │   Policy C   │  │    │
│  │  │  (User 1)    │  │  (User 2)    │  │  (Team X)    │  │    │
│  │  └──────────────┘  └──────────────┘  └──────────────┘  │    │
│  │                                                          │    │
│  │  ┌──────────────────────────────────────────────────┐   │    │
│  │  │              Validation Logic                     │   │    │
│  │  │  • Check spending limits                         │   │    │
│  │  │  • Verify allowlist membership                   │   │    │
│  │  │  • Enforce time restrictions                     │   │    │
│  │  │  • Validate velocity constraints                 │   │    │
│  │  └──────────────────────────────────────────────────┘   │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

Policy Validation Flow

module kairo::validator {
    use kairo::policy::Policy;
    use sui::clock::Clock;
    
    /// Result of policy validation
    public struct ValidationResult has drop {
        approved: bool,
        reason: Option<String>,
        requires_additional_auth: bool,
    }
    
    /// Validate a transaction against a policy
    public fun validate_transaction(
        policy: &Policy,
        amount: u64,
        recipient: address,
        clock: &Clock,
    ): ValidationResult {
        // Check 1: Spending limit
        if (amount > policy.spending_limit) {
            return ValidationResult {
                approved: false,
                reason: option::some(string::utf8(b"Exceeds spending limit")),
                requires_additional_auth: false,
            }
        };
        
        // Check 2: Allowlist (if configured)
        if (!vector::is_empty(&policy.allowlisted_contracts)) {
            if (!vector::contains(&policy.allowlisted_contracts, &recipient)) {
                return ValidationResult {
                    approved: false,
                    reason: option::some(string::utf8(b"Recipient not in allowlist")),
                    requires_additional_auth: false,
                }
            }
        };
        
        // Check 3: Time restrictions
        if (option::is_some(&policy.time_restrictions)) {
            let time_window = option::borrow(&policy.time_restrictions);
            let current_hour = get_current_hour(clock, time_window.timezone_offset);
            
            if (!is_within_window(current_hour, time_window)) {
                return ValidationResult {
                    approved: false,
                    reason: option::some(string::utf8(b"Outside allowed time window")),
                    requires_additional_auth: false,
                }
            }
        };
        
        // Check 4: 2FA requirement
        if (policy.requires_2fa && amount > policy.spending_limit / 2) {
            return ValidationResult {
                approved: true,
                reason: option::none(),
                requires_additional_auth: true,  // Trigger 2FA flow
            }
        };
        
        // All checks passed
        ValidationResult {
            approved: true,
            reason: option::none(),
            requires_additional_auth: false,
        }
    }
}

Daily Spending Tracker (Shared Object Example)

For velocity controls, we need to track spending across transactions. This uses Sui's shared object model:

module kairo::spending_tracker {
    use sui::object::{Self, UID};
    use sui::table::{Self, Table};
    use sui::clock::Clock;
    
    /// Shared object tracking daily spending per user
    public struct SpendingTracker has key {
        id: UID,
        // Maps user address -> (day_timestamp, amount_spent)
        daily_totals: Table<address, DailyTotal>,
    }
    
    public struct DailyTotal has store, drop {
        day_start: u64,  // Timestamp of day start
        amount: u64,     // Total spent today
    }
    
    /// Record spending and check against daily limit
    public fun record_and_validate(
        tracker: &mut SpendingTracker,
        user: address,
        amount: u64,
        daily_limit: u64,
        clock: &Clock,
    ): bool {
        let today = get_day_start(clock::timestamp_ms(clock));
        
        if (table::contains(&tracker.daily_totals, user)) {
            let total = table::borrow_mut(&mut tracker.daily_totals, user);
            
            // Reset if new day
            if (total.day_start < today) {
                total.day_start = today;
                total.amount = 0;
            };
            
            // Check limit
            if (total.amount + amount > daily_limit) {
                return false
            };
            
            // Record spending
            total.amount = total.amount + amount;
        } else {
            // First transaction of the day
            if (amount > daily_limit) {
                return false
            };
            
            table::add(&mut tracker.daily_totals, user, DailyTotal {
                day_start: today,
                amount,
            });
        };
        
        true
    }
}

The dWallet Network Integration: 2PC-MPC for True Non-Custodial Security

Here's where Kairo's architecture gets really interesting. We don't just validate policies—we enforce them cryptographically using dWallet Network's 2PC-MPC protocol.

The Problem with Traditional Policy Enforcement

Most "policy engines" have a fatal flaw: they're advisory. The policy check says "no," but if an attacker controls the signing key, they can ignore the policy and sign anyway.

Traditional Approach:
User Key → [Advisory Policy Check] → Sign → Blockchain
                    ↓
            (Attacker bypasses)

dWallet's 2PC-MPC Solution

dWallet Network enables threshold signing where the signing key is split between multiple parties. For Kairo, the split is:

  1. User's share: Stored in the browser extension
  2. Policy enforcer's share: Held by dWallet Network nodes

Neither party can sign alone. A valid signature requires both shares, and dWallet nodes will only contribute their share if the policy check passes.

Kairo + dWallet Approach:
                         ┌─────────────────────┐
                         │  Policy Validation  │
                         │    (Sui Network)    │
                         └──────────┬──────────┘
                                    │ Proof
                                    ▼
User Share ──────────────▶ [2PC-MPC Protocol] ──────────────▶ Signature
dWallet Share ────────────▶       │
                                  │
                    (Both required - cryptographically enforced)

How the Integration Works

  1. User initiates transaction in Kairo Guard
  2. Policy validation executes on Sui (as shown above)
  3. Validation proof is generated (Sui's BCS-serialized transaction effects)
  4. User's MPC session sends proof + their signature share to dWallet
  5. dWallet nodes verify the policy passed on-chain
  6. Threshold signature is produced only if verification succeeds
  7. Transaction broadcasts to the destination chain

This means policies aren't just rules—they're cryptographic constraints. Even if an attacker compromises the user's device, they can't bypass policy checks without also compromising a threshold of dWallet nodes.

module kairo::mpc_integration {
    use kairo::validator::ValidationResult;
    use dwallet::signature_request::{Self, SignatureRequest};
    
    /// Request MPC signature after policy validation
    public fun request_signature(
        validation: &ValidationResult,
        tx_hash: vector<u8>,
        user_share_commitment: vector<u8>,
    ): Option<SignatureRequest> {
        // Only create signature request if policy approved
        if (!validation.approved) {
            return option::none()
        };
        
        // If additional auth required, this must be called 
        // after 2FA verification
        if (validation.requires_additional_auth) {
            return option::none()
        };
        
        option::some(signature_request::create(
            tx_hash,
            user_share_commitment,
        ))
    }
}

Real Performance Numbers

We've been running Kairo on Sui testnet for three months. Here are real numbers:

| Metric | Value | |--------|-------| | Average policy validation time | 483ms | | 99th percentile validation time | 892ms | | Policy validations per second (peak) | 4,247 | | Average gas cost per validation | 0.0047 SUI | | Policy creation gas cost | 0.0312 SUI | | Policy update gas cost | 0.0089 SUI | | Uptime (testnet) | 99.94% |

For context, the Sui testnet had a brief outage during our testing period that accounts for most of that 0.06% downtime. Mainnet has been even more stable.

Throughput Under Load

We stress-tested policy validations with concurrent users:

| Concurrent Users | Validations/sec | Avg Latency | |-----------------|-----------------|-------------| | 100 | 847 | 491ms | | 500 | 2,134 | 523ms | | 1,000 | 3,891 | 587ms | | 2,000 | 4,247 | 721ms |

Latency increases modestly under load, but throughput scales nearly linearly until we hit network-level bottlenecks. This is Sui's parallel execution at work—independent policy objects don't contend.

Developer Experience: Building on Sui

Switching to a new blockchain means learning new tools. Here's our honest assessment of the developer experience building on Sui:

The Good

Move is delightful once you grok it. The learning curve is real—probably 2-3 weeks to feel productive if you're coming from Solidity. But once it clicks, you write code with confidence. The type system catches so many bugs at compile time that we rarely hit runtime errors.

Sui's tooling is solid:

  • sui move build has clear error messages
  • The Move analyzer VSCode extension works well
  • sui client CLI is intuitive
  • Explorer and indexer are feature-complete

Testing is first-class:

#[test]
fun test_spending_limit_enforcement() {
    let mut ctx = tx_context::dummy();
    let policy = policy::create_policy(
        1000,  // spending_limit
        5000,  // daily_limit
        &mut ctx,
    );
    
    let clock = clock::create_for_testing(&mut ctx);
    
    // Should pass: under limit
    let result = validator::validate_transaction(
        &policy,
        500,
        @0x123,
        &clock,
    );
    assert!(result.approved, 0);
    
    // Should fail: over limit
    let result = validator::validate_transaction(
        &policy,
        1500,
        @0x123,
        &clock,
    );
    assert!(!result.approved, 1);
}

Documentation has improved dramatically. The Sui docs were sparse in early 2024 but are now comprehensive. The Move Book is excellent.

The Challenges

Ecosystem is younger. Fewer Stack Overflow answers, fewer example codebases to learn from. We often had to figure things out from first principles or read Sui's own codebase.

Some patterns aren't obvious. "How do I do X in Move?" often requires rethinking the problem rather than translating from Solidity. This is usually good (forces better design) but slows initial development.

Testnet can be flaky. We hit maybe 3-4 testnet issues over three months. Minor annoyance, not a blocker.

Our Recommendation

If you're building security infrastructure, financial primitives, or anything where correctness matters more than time-to-market, Sui is worth the investment. The object model and Move's safety guarantees pay dividends over the lifetime of your protocol.

If you're building a quick DeFi fork or need maximum ecosystem compatibility, Ethereum L2s might still make sense. Know your tradeoffs.

Future Roadmap: What We're Building Next on Sui

Kairo on Sui is just the beginning. Here's what's coming:

Q2 2025: Policy Marketplace

Shared policy templates that users can fork and customize. "Import Coinbase Custody's policy set with one click."

Q3 2025: Cross-Chain Policy Enforcement

Using dWallet's universal signatures, enforce Sui-defined policies on Ethereum, Solana, and Bitcoin transactions. Your policy object lives on Sui; it protects assets everywhere.

Q4 2025: Programmable Policy Agents

LLM-powered policy assistants that help users define policies in natural language, then generate the Move code. "Block any transaction to an address I haven't interacted with before" → Policy object.

2026: Institutional Policy Engine

Enterprise features: role-based policy inheritance, audit trails as Sui events, compliance reporting, integration with traditional risk systems.

Conclusion: The Right Tool for the Job

Building on Sui blockchain was the right choice for Kairo. Not because Sui is "better" in some abstract sense, but because its specific technical properties—object-centric model, parallel execution, Move's safety guarantees, sub-second finality—align precisely with what policy-gated transaction infrastructure requires.

If you're building something where security correctness matters, where you need rich on-chain objects rather than simple state mappings, and where user experience demands fast finality, Sui deserves serious consideration.

We're excited about what the Sui ecosystem is becoming. The technology is sound, the community is growing, and the applications being built are genuinely novel—not just Ethereum ports. Kairo is proud to be part of that ecosystem.


Building something on Sui? Have questions about our architecture? Reach out on Twitter or join our Discord.

Kairo Guard is currently in private beta. Join the waitlist for early access.

Ready to secure your crypto?

Kairo Guard brings 2PC-MPC security and policy-gated transactions to your existing wallet. No seed phrases, no single points of failure.

Get Early Access

© 2026 Kairo Guard. All rights reserved.