Skip to main content

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

FailureBehaviorRecovery
Attestation service unreachablefetchLatest throws AttestationServiceErrorRetry with backoff; check https://api.noeracle.org/health
Requested asset not in snapshotfetchLatest throws AssetUnavailableErrorCheck /v1/latest for the supported asset list
Snapshot older than 2 seconds at fetchfetchLatest throws StalePriceErrorRetry; service may be lagging or client clock skewed
Signed price >60 s old at on-chain checkContract returns Error::StalePrice (5)Re-fetch and resubmit
Round older than storedSilent no-op (does not fail the tx)Expected; consumer ordering is independent across pulls
Unknown publisher keyContract returns Error::UnknownPublisher (4)Mismatched contract address or stale SDK; upgrade

See also