Skip to main content

Updates

Contracts That Read The Future

Anthony //4 min read
technical

Smart contracts can’t see the future. A flash loan needs liquidity in a pool. A payment gate needs msg.value. An exit needs… patience.

Unless the contract can demand that the future happen.

Signet’s Orders system inverts this. Contracts specify what they need to receive, and fillers compete to provide it. The contract doesn’t care where the liquidity comes from—only that it arrives, atomically, in the same block.

The Pattern

Every example follows the same structure: specify outputs (what you need), specify inputs (what you’ll give), let the market solve it.

solidity
ORDERS.initiate(
    block.timestamp,  // deadline (same block = now)
    inputs,           // what you're giving
    outputs           // what you need to receive
);

If outputs aren’t filled, the transaction reverts. No partial execution. No stranded assets.

The signet-sol repository provides base contracts that auto-configure across networks:

solidity
contract SignetL2 {
    constructor() {
        if (block.chainid == 88888) { // Parmigiana
            HOST_CHAIN_ID = 3151908;
            PASSAGE = 0x0000000000007369676e65742d70617373616765;
            ORDERS = 0x000000000000007369676e65742d6f7264657273;
        }
    }
}

Deploy identical bytecode everywhere. Chain detection handles the rest.


Flash Loans Without Pools

Traditional flash loans require liquidity pools—capital sitting idle, waiting to be borrowed. Signet’s approach: demand the capital, let fillers provide it.

solidity
abstract contract Flash is SignetL2 {
    modifier flash(address asset, uint256 amount) {
        _;  // Your logic executes here

        // Output: what we received at start of tx
        RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
        outputs[0] = makeRollupOutput(asset, amount, address(this));

        // Input: what we return at end of tx
        RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
        inputs[0] = makeInput(asset, amount);

        ORDERS.initiate(block.timestamp, inputs, outputs);
    }
}

The flash modifier receives assets before your function executes, then returns them after. Fillers provide just-in-time liquidity from their own inventory—no pool required.

Why this matters: Capital efficiency. Traditional pools lock capital that earns nothing until borrowed. Fillers deploy capital only when profitable, then immediately redeploy elsewhere.


Payment Gates Without msg.value

What if you could require payment without handling funds directly? Demand that someone pays you, and refuse to execute unless they do.

solidity
abstract contract PayMe is SignetL2 {
    modifier payMe(uint256 amount) {
        _;
        demandPayment(NATIVE_ASSET, amount);
    }

    function demandPayment(address asset, uint256 amount) internal {
        RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
        outputs[0] = makeRollupOutput(asset, amount, address(this));

        ORDERS.initiate(
            block.timestamp,
            new RollupOrders.Input[](0),  // no inputs—pure demand
            outputs
        );
    }
}

The payMe modifier demands payment as an output. No inputs means you’re not offering anything in exchange—just requiring that funds arrive. The filler pays you to unlock whatever value your function provides.

Gas-subsidized variant: Calculate gas cost and subtract from demanded payment:

solidity
modifier payMeSubsidizedGas(uint256 amount) {
    uint256 pre = gasleft();
    uint256 gp = tx.gasprice;
    _;
    uint256 post = gasleft();
    uint256 loot = amount - (gp * (pre - post));
    demandPayment(NATIVE_ASSET, loot);
}

Execution Bounties

The inverse of payment gates: offer rewards for executing functions.

solidity
abstract contract PayYou is SignetL2 {
    modifier paysYou(uint256 tip) {
        _;
        providePayment(NATIVE_ASSET, tip);
    }

    function providePayment(address asset, uint256 amount) internal {
        RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
        inputs[0] = makeInput(asset, amount);

        ORDERS.initiate{value: amount}(
            block.timestamp,
            inputs,
            new RollupOrders.Output[](0)  // no outputs—pure bounty
        );
    }
}

No outputs means you’re offering payment without requiring anything in return. Fillers compete to call your function and claim the bounty.

Use case: Automated execution. Post a bounty on a keeper function. Fillers call it when profitable, creating a decentralized scheduling system.


Instant Exits

Most rollups make you wait days to exit. Signet: post an order, get L1 liquidity instantly.

solidity
contract GetOut is SignetL2 {
    receive() external payable {
        getOut();
    }

    function getOut() public payable {
        require(msg.value > 0);

        uint256 desired = msg.value * 995 / 1000; // 0.5% fee

        RollupOrders.Input[] memory inputs = new RollupOrders.Input[](1);
        inputs[0] = makeInput(NATIVE_ASSET, msg.value);

        RollupOrders.Output[] memory outputs = new RollupOrders.Output[](1);
        outputs[0] = hostUsdcOutput(desired, msg.sender);

        ORDERS.initiate{value: msg.value}(block.timestamp, inputs, outputs);
    }
}

User sends USD on Signet, receives USDC on Ethereum in the same block. The 0.5% spread goes to the filler who provides L1 liquidity.

The economics: Fillers hold inventory on both chains. They accept your Signet USD (which they can use or exit later) and send you their L1 USDC immediately. Market competition drives spreads toward equilibrium.


Why This Works

Traditional cross-chain execution is imperative: call this contract, wait for this bridge, claim from this pool. Signet’s Orders are declarative: specify what you need, let the market provide it.

This inverts MEV dynamics. Instead of extractors front-running your trades, fillers compete to serve you. The same actors, different incentives.

Synchronous composability: All execution happens in one block. Flash loans that span chains. Arbitrage that settles atomically. Applications that treat cross-chain like local calls.

Application-directed MEV: Your contracts define the terms. Fillers accept them or don’t. Value capture becomes intentional, not leaked.


Get Started

Questions? Get in touch.

ESC

Start typing to search documentation...

Navigate Select ⌘K Open