Integration patterns
Noeracle supports three integration patterns. Pick by the structure of your consumer contract.
Pattern A — Standalone update + cached read
Two operations in one transaction. The first invokes Noeracle directly to verify and store the price. The second is your own application op, which reads the just-stored price via get_price.
const fresh = await oracle.fetchLatest(["BTC/USD"]);
const tx = new TransactionBuilder(account, { fee, networkPassphrase })
.addOperation(fresh.toUpdateOp(ORACLE)) // verify + store
.addOperation(myContract.call("open_position")) // your app op
.setTimeout(30)
.build();
Your contract reads the verified price:
let entry: PriceEntry = env.invoke_contract(
&oracle_address,
&Symbol::new(&env, "get_price"),
vec![&env, asset_tag.into_val(&env)],
).unwrap();
let price: i128 = entry.price;
When to use. You don't want to change your existing contract surface.
Trade-off. get_price returns the most recently verified price for the asset, which in principle could be from another consumer's pull-mode tx earlier in the same ledger. If exact provenance matters, use Pattern B.
Pattern B — Inline verification inside your contract
Your contract exposes a wrapper that verifies the price and uses it in one call. Use fresh.updateArgs() to pass the six raw arguments through.
const fresh = await oracle.fetchLatest(["BTC/USD"]);
const args = fresh.updateArgs();
// args = [assets, prices, timestamp, round_id, pubkey, sigs]
const tx = new TransactionBuilder(account, { fee, networkPassphrase })
.addOperation(myContract.call("open_position_with_price", ...args, ORACLE_ADDR))
.setTimeout(30)
.build();
Your contract:
pub fn open_position_with_price(
env: Env,
assets: Vec<BytesN<8>>,
prices: Vec<i128>,
timestamp: u64,
round_id: u64,
pubkey: BytesN<32>,
sigs: Vec<BytesN<64>>,
oracle: Address,
) {
env.invoke_contract::<()>(
&oracle,
&Symbol::new(&env, "update_batch_ed25519_args"),
(assets.clone(), prices.clone(), timestamp, round_id, pubkey, sigs)
.into_val(&env),
);
let price = prices.get_unchecked(0);
// ... use price directly ...
}
When to use. You control the consumer contract and want the verified price used inline, with no possibility of another consumer's tx changing the cache between verify and read.
:::warning Replay window
The oracle's 60-second freshness check guarantees the attestation was signed within the last 60 seconds. It does not guarantee the attestation was signed inside your consumer's logical time window. For settlement, liquidation, or auction logic, enforce a stronger temporal check in the consumer body:
if timestamp < my_window_start {
return Err(Error::PriceBeforeWindow);
}
Without that check, an attacker can replay a ≤60 s old attestation to settle an event that was supposed to settle "now".
:::
Pattern C — Live SSE subscription
For long-running clients (UIs, keepers, market-makers) that want to hold a fresh price in memory without polling.
const sub = oracle.subscribe(["BTC/USD", "ETH/USD"], (fresh) => {
console.log(fresh.price("BTC/USD"));
});
// later:
sub.close();
The connection reconnects automatically on transient errors. Each round (every 500 ms on testnet) fires onUpdate with a Fresh for the requested assets.
Failure modes
| Failure | Behavior | Recovery |
|---|---|---|
| Attestation service unreachable | fetchLatest throws AttestationServiceError | Retry with backoff; check https://api.noeracle.org/health |
| Requested asset not in snapshot | fetchLatest throws AssetUnavailableError | Check /v1/latest for the supported asset list |
| Snapshot older than 2 seconds at fetch | fetchLatest throws StalePriceError | Retry; service may be lagging or client clock skewed |
| Signed price >60 s old at on-chain check | Contract returns Error::StalePrice (5) | Re-fetch and resubmit |
| Round older than stored | Silent no-op (does not fail the tx) | Expected; consumer ordering is independent across pulls |
| Unknown publisher key | Contract returns Error::UnknownPublisher (4) | Mismatched contract address or stale SDK; upgrade |
See also
- SDK reference — every method and error type
- Contract reference — entrypoints, errors, signed message layout
- Threat model — what v0 protects against and what it doesn't