Native Oracle
Cross-chain data has always been the soft spot of L1 design. Whether it's an asset bridge, a price feed, or any other piece of off-chain truth, the data has to enter the chain somewhere — and where that "somewhere" lives determines who you have to trust.
Most chains push that responsibility outside their consensus. An external oracle network signs the data, or a separate committee of bridge signers attests to deposits. The chain itself stays clean, but a new trust assumption is bolted on the side. If that side-channel breaks, the chain keeps running — but the data is wrong.
Gravity L1 collapses this. The same validators that produce blocks under AptosBFT also observe external data, vote on it, and write it to L1. There is no external oracle network. No multisig committee. The bridge isn't a separate service; it's a contract that receives data already committed by the validator set.
This is what "native" means in Native Oracle: the validator-attestation pipeline is part of the chain's state machine, not a service running next to it. The security of any data that lands on Native Oracle is the security of the chain itself — same validator set, same BFT thresholds, same finality window.
This post walks through the primitive, the one application built on it today (the Ethereum → Gravity L1 asset bridge), and the design space it opens up.
The Core Abstraction
Native Oracle is a single contract deployed at a fixed system address on Gravity L1:
NativeOracle → 0x0000000000000000000000000001625F4000It exposes one core operation, callable only by SYSTEM_CALLER — a privileged consensus-time identity, not a normal account:
function record(
uint32 sourceType, // 0 = BLOCKCHAIN; more reserved
uint256 sourceId, // e.g. chain ID
uint128 nonce, // must equal currentNonce + 1
uint256 blockNumber, // source-side provenance
bytes payload, // opaque blob
uint256 callbackGasLimit
) external;A recordBatch(...) variant exists for delivering multiple events from the same source in one transaction.
Three design choices are worth pulling out:
1. Replay protection is centralized. Native Oracle enforces nonce == currentNonce + 1 per (sourceType, sourceId) — strictly sequential, no gaps. An old message can never be replayed because the contract has already moved past its nonce. Application-level handlers don't need to maintain their own seen-nonce maps; in fact, the bridge receiver explicitly drops that work and leaves only a __deprecated_processedNonces storage slot for layout compatibility.
2. Callbacks are routed, not pulled. Each (sourceType, sourceId) pair can register a callback contract. When data is recorded, Native Oracle calls onOracleEvent(...) on the registered handler with a caller-specified gas limit. Resolution is two-layer: a default handler per source type, optionally overridden by a specialized handler for a specific sourceId. Governance manages the registry.
3. Callbacks are fail-safe. The handler returns shouldStore: bool — a handler that has fully consumed the payload (applied it to its own state) can return false to skip persistence and save gas. If the handler reverts or runs out of gas, Native Oracle catches it, emits CallbackFailed(reason), and stores the payload anyway. The recording succeeds regardless. The data is committed by consensus; the application logic is best-effort.
The result is a clean separation: Native Oracle is responsible for truth (consensus-attested, replay-safe), and the application contract is responsible for meaning (decoding and acting on the payload).
The Consensus Layer
Each Gravity validator runs an oracle observer alongside its block-production duties. For every registered task, the observer:
Polls the source endpoint (today, an Ethereum RPC) for the relevant events.
Restricts itself to finalized state — only events past Ethereum finality are eligible, eliminating reorg surprises.
Tracks a cursor (last scanned block) and a last-processed nonce, persisted to disk for fast restart.
Emits its observed batch into the consensus layer.
These observations then flow through the same AptosBFT machinery that orders normal transactions. Validators sign their observations; a quorum certificate forms; the agreed batch is committed in-line with regular block production. There is no separate consensus instance, no separate signer set, no separate liveness assumption.
When the batch is committed, the system call NativeOracle.recordBatch(...) is constructed and dispatched as a SYSTEM_CALLER transaction, landing the data on L1 in the same block-execution flow as everything else.
This is what gives the bridge its security guarantee: a deposit is finalized on Gravity when ⅔+ of validators have attested to its Ethereum finalization, enforced by the same BFT thresholds the network uses to produce blocks. There is no separate bridge to compromise.
End-to-End Today: ETH → Gravity Asset Bridge
The first application wired end-to-end through Native Oracle is the G token bridge: lock G on Ethereum, receive native G on Gravity. Here's the full path.

Step 1 — Ethereum side
The user calls GBridgeSender.bridgeToGravity(amount, recipient) (or bridgeToGravityWithPermit(...) for gasless approval). The sender:
Escrows
amountG tokens (ERC-20) in itself.Calls
GravityPortal.send(abi.encode(amount, recipient))with an ETH fee.
GravityPortal.send uses a compact 36-byte header format:
This trims the typical 128+ bytes of abi.encode overhead down to 36, charged as:
The portal rejects both under- and over-payment (InsufficientFee / ExcessiveFee) to keep fees deterministic. It then assigns a monotonic uint128 nonce and emits:
Step 2 — Validator observation & attestation
Every Gravity validator's observer polls Ethereum for MessageSent events, filtered on the GravityPortal address, between its cursor and the latest finalized block. New events with nonce > last_processed_nonce are accepted and emitted into AptosBFT.
The BFT layer reaches agreement on the observed batch in the same finality window as normal transactions. There's no delayed settlement — the validators that order blocks also order observations.
Step 3 — On-chain commit
The agreed batch becomes a SYSTEM_CALLER transaction calling:
Native Oracle enforces nonces[i] == currentNonce + 1 for each entry, reverting NonceNotSequential(...) on any gap or duplicate. For each accepted record, it resolves the callback — (sourceType=0, sourceId=1) → GBridgeReceiver — and invokes it.
Step 4 — Callback & native mint
GBridgeReceiver extends BlockchainEventHandler and implements IOracleCallback. On invocation it:
Decodes the PortalMessage:
(sender, messageNonce, message) = decode(payload).Verifies
sourceId == trustedSourceId(Ethereum mainnet) — rejects withInvalidSourceChain(...)otherwise.Verifies
sender == trustedBridge(the deployedGBridgeSender) — defense in depth.Decodes the application message:
(amount, recipient) = abi.decode(message, (uint256, address)).Calls the native mint precompile:
The precompile mints native G tokens directly to the recipient — no wrapper, no synthetic asset. The user holds the L1's native gas asset, just as if it had been minted at genesis.
Total trip from bridgeToGravity call to native G in the recipient's address: one Ethereum-finality window plus one Gravity finality window. No external relayer, no wrapped representation, no separate signer set.
Reliability & Recovery
A few mechanics that matter for operators and integrators:
Finalized-only polling. Observers only ingest events past Ethereum finality. Reorgs on the source chain cannot land on Native Oracle — by definition, they cannot land on a finalized block.
Persisted cursors. Each observer writes
(cursor_block, last_processed_nonce)to disk per source. After a crash or restart, the observer resumes from its checkpoint instead of rescanning from genesis.On-chain reconciliation. On startup, the observer reads
NativeOracle.getLatestNonce(sourceType, sourceId). If the on-chain nonce is ahead of the local checkpoint (e.g., the validator was offline while peers committed), the observer fast-forwards both cursor and last-processed state. No duplicate delivery, no missed events.Exactly-once delivery, structurally. Combining (a) sequential nonces in the contract, (b) strictly monotonic filtering in the observer, and (c) finalized-only polling: each
MessageSentevent maps to at most onerecord(...)call across the whole network. There is no per-handler dedup logic to get wrong.Fail-safe callback semantics. A bug in the callback contract — revert, out-of-gas, wrong logic — does not block the oracle. The record is committed,
CallbackFailed(reason)is emitted, and operators can patch the handler (via governance-managed callback registration) without losing data.
Live Contract Map
Ethereum
GravityPortal
0x76cf8526Fa9461e50B2c6702a7246ce6915f6E53
Ethereum
GBridgeSender
0xE82c61Ac9Ec2041b493118051afa4F18a55dC876
Ethereum
G (ERC-20)
0x9C7BEBa8F6eF6643aBd725e45a4E8387eF260649
Gravity L1
NativeOracle
0x0000000000000000000000000001625F4000 (system)
Gravity L1
NativeMintPrecompile
0x0000000000000000000000000001625F5000 (system)
Source addresses for the system precompiles are fixed; ABIs are available in the gravity_chain_core_contracts repository.
Beyond the Bridge: What the Primitive Enables

Native Oracle isn't the bridge — the bridge is the first application built on Native Oracle. The primitive itself is just (sourceType, sourceId, nonce, payload, callback); everything above that line is policy. The substrate that today routes Ethereum deposits to a mint precompile can route any externally attested event to any callback governance approves. The primitive doesn't change; only the tuples registered on top of it do.
To make that concrete, here are application shapes the primitive supports by design, not by deployment. Each is a different (sourceType, sourceId, callback) registration:
Asset bridges (today). Validators observe MessageSent events on Ethereum; the callback decodes (amount, recipient) and mints native G. More chains under sourceType = 0 are a governance registration, not a code change. Bidirectional flow — burn on Gravity, release on Ethereum — is the symmetric callback on the same primitive.
Perp DEX oracles. Mark price, index price, and funding-rate updates require fresh, consensus-attested data — exactly what most perp DEXes today pay an external oracle network for. A registered price-feed source, observed by validators and routed to the perp protocol's callback, removes that external dependency. Liquidations execute against prices the chain itself agreed to, not prices a side network signed.
Payments and x402. The bridge is one shape of payment; payments in general are another. A payer authorizes on chain A, validators attest it on Gravity, a callback credits the payee — same primitive, different decoder. HTTP-402-style pay-per-call services can use this to verify "the client paid X on chain Y" inside the same finality window that serves the request: no off-chain receipt, no trusted indexer.
On-chain AI agents. Agents acting on-chain need verifiable inputs they can react to — prices, balances on other chains, the result of off-chain computation they didn't perform themselves. Native Oracle is the substrate they read from. An agent does not have to trust an oracle network the chain itself doesn't trust; whatever the chain sees, the agent sees, in the same finality window.
Arbitrary application messaging. The PortalMessage format already carries an opaque message field. Fungible-token transfer is the first decoder; arbitrary cross-chain calls are a small step from there.
The connective thread: every one of these uses the same (sourceType, sourceId, nonce, payload, callback) shape, the same AptosBFT machinery, the same fail-safe callback resolution. Adding a new application doesn't mean adding a new trust assumption — it means writing a callback contract and asking governance to register it. The security envelope doesn't expand; only what lives inside it does.
Last updated
Was this helpful?

