Skip to main content
The Local Prover handles same-chain intent fulfillment where both intent creation and execution occur on the same blockchain. Unlike cross-chain provers, it requires no message passing infrastructure.

Architecture

Purpose

Local Prover enables intents to be fulfilled on their origin chain:
  • No cross-chain messaging required
  • Zero additional fees beyond gas
  • Instant proof availability
  • Useful for same-chain swaps, transfers, or operations

Orchestration Mode

Beyond same-chain convenience, the Local Prover enables a fundamentally different execution model: orchestration. In orchestration mode, the user’s own funds move through underlying infrastructure — no solver capital required. This is powered by flashFulfill — a flash-loan-style operation that fulfills an intent using the vault’s own funds within a single atomic transaction. The vault’s contents are used directly to execute the desired action on the destination, with no counterparty and no cross-chain message. Settlement vs. Orchestration:
  • Settlement: A solver provides its own inventory to deliver the user’s requested outcome. Zero slippage.
  • Orchestration: User funds move through infrastructure at the cost of gas alone. Zero solver capital required.
The Local Prover and flashFulfill make the system available even when no solver is online, creating a high-availability fallback:
TierConditionModeCapital Required
1Solver available, better pricingSettlementSolver capital
2Solver available, no pricing advantageOrchestrationGas only
3No solver availableSelf-solve via local intentGas only
All three tiers use the same Portal, vaults, and API. System availability equals chain availability, not solver availability.

Configuration

constructor(address inbox)
Parameters:
  • inbox: Portal contract address (provides Inbox functionality)
Initialization:
  • Stores Portal reference as immutable
  • Records chain ID for proof data validation
  • Validates chain ID fits in uint64

How It Works

Immediate Proof Creation

Unlike cross-chain provers, Local Prover doesn’t create proofs—it reads them:
  1. Intent fulfilled on Portal → claimant stored in Portal.claimants mapping
  2. provenIntents() queries Portal’s claimant mapping directly
  3. Returns proof data with current chain ID and claimant address

Proof Lookup

function provenIntents(bytes32 intentHash) 
    public view returns (ProofData memory)
Returns:
  • claimant: Address that fulfilled the intent (converted from bytes32)
  • destination: Current chain ID
If not fulfilled:
  • Returns ProofData(address(0), 0)

No-Op Functions

prove()

function prove(
    address sender,
    uint64 sourceChainId,
    bytes calldata encodedProofs,
    bytes calldata data
) external payable
Intentionally empty - no action needed because:
  • Claimant already stored by Portal during fulfillment
  • No cross-chain message to send
  • Proof is immediately available
Can be called without reverting to support fulfillAndProve() pattern.

challengeIntentProof()

function challengeIntentProof(
    uint64 destination,
    bytes32 routeHash,
    bytes32 rewardHash
) external pure
Intentionally empty - challenges not applicable because:
  • Same-chain intents cannot be proven on wrong chain
  • No cross-chain discrepancy possible

Usage Patterns

Creating Same-Chain Intent

Intent memory intent = Intent({
    destination: block.chainid,  // Same as current chain
    route: Route({
        portal: address(portal),
        // ... other route params
    }),
    reward: Reward({
        prover: address(localProver),  // Use LocalProver
        // ... other reward params
    })
});

portal.publishAndFund{value: amount}(intent, false);

Fulfilling Same-Chain Intent

// Fulfill without proving
portal.fulfill{value: route.nativeAmount}(
    intentHash,
    route,
    rewardHash,
    claimant
);

// OR fulfill and prove (prove is no-op but doesn't revert)
portal.fulfillAndProve{value: route.nativeAmount}(
    intentHash,
    route,
    rewardHash,
    claimant,
    address(localProver),
    uint64(block.chainid),
    "" // Empty data
);
Both patterns work identically—prove() is a no-op.

Claiming Rewards

// Check if intent is proven (fulfilled)
IProver.ProofData memory proof = localProver.provenIntents(intentHash);
require(proof.claimant != address(0), "Not fulfilled");

// Withdraw rewards
portal.withdraw(
    uint64(block.chainid),
    routeHash,
    reward
);

Fee Structure

Zero additional fees:
  • No cross-chain messaging costs
  • No bridge fees
  • Only standard gas costs for fulfillment and withdrawal

When to Use Local Prover

Ideal Use Cases

Same-Chain Operations:
  • Token swaps on same chain
  • Transfers between accounts
  • DeFi operations on single chain
  • Testing and development
Advantages:
  • Instant settlement (no message delays)
  • Zero bridging costs
  • Simpler integration
  • No external dependencies

Not Suitable For

  • Cross-chain transfers
  • Multi-chain operations
  • Bridging assets between chains
For cross-chain intents, use:

Integration Example

// Deploy Local Prover
LocalProver localProver = new LocalProver(address(portal));

// Create same-chain intent
Intent memory intent = Intent({
    destination: uint64(block.chainid),
    route: Route({
        salt: keccak256(abi.encode(user, nonce)),
        deadline: block.timestamp + 1 hours,
        portal: address(portal),
        nativeAmount: 0,
        tokens: tokenAmounts,
        calls: calls
    }),
    reward: Reward({
        creator: msg.sender,
        prover: address(localProver),
        deadline: block.timestamp + 1 hours,
        nativeAmount: 0.01 ether,
        tokens: rewardTokens
    })
});

// Publish and fund
portal.publishAndFund{value: 0.01 ether}(intent, false);

// Solver fulfills
portal.fulfill{value: route.nativeAmount}(
    intentHash,
    route,
    rewardHash,
    bytes32(uint256(uint160(solverAddress)))
);

// Solver claims rewards immediately
portal.withdraw(
    uint64(block.chainid),
    routeHash,
    reward
);

Comparison with Cross-Chain Provers

FeatureLocalProverCross-Chain Provers
Proof AvailabilityImmediateAfter message delivery
Additional FeesNone$5-50 per message
Message DelaysNoneSeconds to minutes
InfrastructureNone requiredBridge dependencies
ComplexityMinimalDomain IDs, hooks, etc.
Use CaseSame chain onlyCross-chain operations

Best Practices

Intent Creation

Set destination correctly:
destination: uint64(block.chainid)  // Must match current chain
Use Local Prover:
prover: address(localProver)  // Not a cross-chain prover

Fulfillment

Either pattern works:
// Simple: fulfill only
portal.fulfill(intentHash, route, rewardHash, claimant);

// With prove (no-op): works with fulfillAndProve
portal.fulfillAndProve(
    intentHash, route, rewardHash, claimant,
    address(localProver), uint64(block.chainid), ""
);

Validation

Check destination matches:
require(
    intent.destination == block.chainid,
    "Not same-chain intent"
);
Verify prover is LocalProver:
require(
    intent.reward.prover == address(localProver),
    "Wrong prover"
);

Error Handling

  • ChainIdTooLarge(uint256): Chain ID exceeds uint64.max during construction
Note: LocalProver functions (prove, challengeIntentProof) never revert—they are intentionally no-ops.

Technical Notes

Why prove() is Empty

The prove() function is empty because:
  1. Portal stores claimant in claimants mapping during fulfillment
  2. provenIntents() queries this mapping directly
  3. No cross-chain message needed
  4. Proof is instantly available

Why No Fees

No fees because:
  • No cross-chain message to send
  • No bridge infrastructure to pay
  • No external services required
  • Only standard EVM gas costs

Claimant Storage

// Portal stores during fulfill()
claimants[intentHash] = claimant;

// LocalProver reads it
bytes32 claimant = _PORTAL.claimants(intentHash);
Direct storage access eliminates need for proving.

Constant

function getProofType() external pure returns (string memory) {
    return "Same chain";
}
Identifies this prover handles same-chain intents.