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:
| State | Meaning |
|---|---|
| Pending | The engine hasn't resolved the request yet |
| Filled | The requested quantity filled |
| Partially filled | Some quantity filled and some remains |
| Resting | The remaining order is on the book |
| Cancelled | The request left no active order |
| Rejected | The 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.