Skip to content

Oracle systems

Any validation layer can be added as long as it supports validating a payload from the output chain.

In the simplest implementation, a validation layer needs to support the following:

  1. A submission interface where solvers can submit their filled outputs. The submission interface should accept arbitrary packages for validation and then call the associated output settlement contract to check whether the payloads are valid.

    interface IPayloadCreator {
    function arePayloadsValid(
    bytes[] calldata payloads
    ) external view returns (bool);
    }
  2. Implement the associated validation interfaces so that Input Settlement implementations can accurately verify whether outputs have been filled.

    interface IOracle {
    function efficientRequireProven(
    bytes calldata proofSeries
    ) external view;
    }

Importantly, the submission interface does not have to be standardized; it just needs to be accurately documented so solvers can implement it.

There are three types of validation interfaces:

  1. Self-serve: Validation interfaces where submitting the payload generates an off-chain proof that must be collected and submitted on the input chain.
  2. Automatic: Validation interfaces where submitting the payload automatically delivers the associated proof on the input chain.

Currently, all supported oracle systems are self-serve.

Polymer allows validation of emitted events. The target event for validation is the OutputFilled event, emitted when an output has been filled.

event OutputFilled(bytes32 indexed orderId, bytes32 solver, uint32 timestamp, MandateOutput output);

Using the Polymer prove API, a proof of the event can be generated. Once generated, it can be submitted to receivedMessage.

For an example of such an integration, see the lintent.org implementation.

Polymer can only prove one filled output at a time.

The Wormhole implementation is based on the broadcast functionality of Wormhole. Messages must be submitted to the Wormhole Implementation via the submit interface. Messages must be encoded into FillDescriptions and then submitted:

function submit(address source, bytes[] calldata payloads) public payable returns (uint256 refund);

This message is then emitted to the Wormhole guardian set. Once the associated proof becomes available, the solver can submit the proof to receiveMessage on the input chain to validate their intents.

Note: The Wormhole implementation uses a more efficient validation algorithm than Wormhole’s Implementation.sol.

LI.FI Intents has a Bitcoin Simplified Payment Validation (SPV) client implementation. This implementation works both as an Output Settlement implementation and as a validation layer.

The Bitcoin SPV client requires constant upkeep—the blockchain must be updated approximately every 10 minutes, or whenever a transaction needs to be proven—to properly validate transactions.

To generate a transaction proof, refer to the code below:

import mempoolJS from "@catalabs/mempool.js";
const mainnet: boolean;
const {
bitcoin: { transactions, blocks },
} = mempoolJS({
hostname: "mempool.space",
network: mainnet ? undefined : "testnet4",
});
export async function generateProof(
txid: string,
): Promise<{ blockHeader: string; proof: Proof; rawTx: string }> {
const tx = await transactions.getTx({ txid });
const merkleProof = await transactions.getTxMerkleProof({ txid });
// TODO: serialization version 1.
const rawTx = await transactions.getTxHex({ txid });
const blockHash = await blocks.getBlockHeight({
height: merkleProof.block_height,
});
// Most endpoints provide transactions witness encoded.
// The following function serves to strip the witness data.
const rawTxWitnessStripped = removeWitnesses(rawTx);
const blockHeader = await blocks.getBlockHeader({ hash: blockHash });
return {
blockHeader,
proof: {
txId: txid,
txIndex: merkleProof.pos,
siblings: merkleProof.merkle.reduce(
(accumulator, currentValue) => accumulator + currentValue,
),
},
rawTx: rawTxWitnessStripped,
};
}