Skip to main content

Write-precompile promises

Yutu's write precompile gives EVM contracts a direct path into the native market engine. The call returns a deterministic ticket immediately. The ticket resolves after the engine has processed the request.

A promise is a Yutu protocol object, not a JavaScript Promise and not a claim that settlement happened inside the original call.

Why the result is asynchronous​

An EVM call runs to completion without yielding. Yutu's matching engine needs the final ordered block and can't interrupt that call later with a fill result.

The promise model makes the boundary explicit:

EVM contract
│ submitOrder(...)
▼
yutu_control · 0x1016
│ validate request, queue order, return ticket
▼
native matching engine
│ execute when the block is installed
▼
promise state
├─ poll orderStatus(ticket)
└─ receive onYutuSettled(...) from 0x1017

Submission​

YutuControl.submitOrder accepts:

  • Market identifier
  • Bid or ask side
  • Quantity in lots
  • Limit price in ticks, or zero for a market order
  • Good-till-cancelled, immediate-or-cancel, or fill-or-kill timing

The precompile rejects errors it can know immediately, including an unknown or paused market, malformed values, or an unregistered caller. A successful call returns a 32-byte ticket and creates a pending promise in chain state.

The ticket is deterministic. Replaying the same block derives the same identifier and state updates.

Resolution​

The engine drains the queued request when the block is installed. It then records one of six states:

StateMeaning
PendingThe engine hasn't resolved the request yet
FilledThe requested quantity filled
Partially filledSome quantity filled and some remains
RestingThe remaining order is on the book
CancelledThe request left no active order
RejectedThe engine couldn't accept the request

orderStatus(ticket) also returns filled lots, remaining lots, average fill price, and the block height at which the promise resolved.

An unknown ticket returns Rejected. A contract never has to treat a missing record as pending forever.

Polling or callback​

A contract can poll YutuControl.status(ticket), or submit the order with a callback address and gas budget.

For callbacks, the chain calls:

onYutuSettled(
bytes32 ticket,
uint8 state,
uint64 filledLots,
uint64 remainingLots,
uint64 avgPxTicks,
uint64 resolvedAtHeight
)

The call comes from 0x1017. Receivers should reject every other sender. YutuSettlementReceiver provides that check and converts the result into a typed Settlement value.

How the callback reaches _onSettled​

submitOrderWithCallback takes a contract address rather than a Solidity function. Passing address(this) tells Yutu which contract to notify.

Once the ticket resolves, Yutu calls the fixed external function onYutuSettled(...) on that address. YutuSettlementReceiver implements that function. It checks that the caller is 0x1017, builds the typed Settlement, and calls its internal virtual function _onSettled. Solidity then dispatches that internal call to the strategy's override.

submitOrderWithCallback(..., address(this), ...)
│ store callback address against the returned ticket
▼
address(this).onYutuSettled(ticket, raw settlement fields)
│ inherited YutuSettlementReceiver checks msg.sender == 0x1017
▼
Strategy._onSettled(ticket, typed settlement)

The strategy never passes _onSettled into the submission call. The external onYutuSettled ABI and the inherited virtual call connect the two stages.

The callback is delivered once and isn't retried. Keep it short and make every valid settlement path complete without reverting.

Settlement can't be rolled back by the callback​

The promise resolves before notification. If the callback reverts or runs out of gas, Yutu rolls back only the callback's writes. The fill and promise result remain final.

Callback gas is capped at 1,000,000. The contract chooses a lower budget when it submits the order.

Solidity example​

pragma solidity ^0.8.26;

import {YutuControl} from "./YutuControl.sol";
import {YutuSettlementReceiver} from "./IYutuSettlementReceiver.sol";

contract Strategy is YutuSettlementReceiver {
struct PendingOrder {
uint64 marketId;
uint64 requestedLots;
bool exists;
}

mapping(bytes32 ticket => PendingOrder) public pendingOrders;
mapping(bytes32 ticket => YutuControl.Settlement) public settlements;

event OrderSubmitted(
bytes32 indexed ticket,
uint64 indexed marketId,
uint64 requestedLots
);

event OrderSettled(
bytes32 indexed ticket,
YutuControl.OrderState state,
uint64 filledLots,
uint64 remainingLots
);

function buy(
uint64 marketId,
uint64 lots
) external returns (bytes32 ticket) {
ticket = YutuControl.submitOrderWithCallback(
marketId,
YutuControl.Side.Bid,
lots,
0,
YutuControl.Tif.Ioc,
address(this),
200_000
);

pendingOrders[ticket] = PendingOrder({
marketId: marketId,
requestedLots: lots,
exists: true
});

emit OrderSubmitted(ticket, marketId, lots);
}

function _onSettled(
bytes32 ticket,
YutuControl.Settlement memory settlement
) internal override {
if (!pendingOrders[ticket].exists) return;

delete pendingOrders[ticket];
settlements[ticket] = settlement;

emit OrderSettled(
ticket,
settlement.state,
settlement.filledLots,
settlement.remainingLots
);
}
}

Each call to buy stores its returned ticket as a separate mapping key. Several orders can therefore remain pending at the same time. When a callback arrives, its ticket selects the exact pending order and the exact settlement record to update.

The callback can't arrive during the original buy call. The order is processed after that EVM transaction completes, and its notification is included in a later block. The mapping entry is stored before Yutu can call the receiver.

Returning for an unknown ticket keeps the one-shot callback from reverting. A production strategy can emit an event there if it wants to monitor unexpected notifications. It should also apply its own access control to buy; the example leaves caller policy to the strategy.

The order submission needs no signed native intent or external relayer. The contract is the authority, the chain is the executor, and the ticket is the record connecting both stages.