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.
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