Declare, upload, and read API keys inside Newton PolicyData oracles. JSON Schema declaration, HPKE-encrypted upload via SDK or CLI, and the newton:provider/secrets WASM host interface.
PolicyData oracles often need an API key to fetch external data. Newton lets you store those secrets encrypted so they are never exposed on-chain, and decrypts them only inside the operator at evaluation time — your WASM oracle reads them through a host interface without ever handling the ciphertext.This guide covers the full lifecycle: declaring which secrets an oracle needs, uploading them encrypted, and accessing them inside your oracle code. For the underlying encryption design, see Encrypting Secrets and the Privacy Layer.
Each PolicyData oracle declares the secrets it requires as a JSON Schema, stored on-chain as the oracle’s secretsSchemaCid. Uploaded secrets are validated against this schema before being stored.Create secrets_schema.json:
Secrets are encrypted client-side with HPKE before they ever leave your machine. The operator decrypts them in memory at evaluation time; the plaintext is never persisted and never sent over the network. Only the on-chain owner of the PolicyClient can upload secrets.
Secrets are scoped per policy_data_address. If you redeploy a PolicyData contract you get a new address, so you must re-upload its secrets. In a multi-oracle policy, upload each oracle’s secrets to its own PolicyData address.
# 1. Create a secrets file matching the oracle's schemacat > secrets.json <<'EOF'{ "VAULTS_FYI_API_KEY": "sk-prod-xxxxxxxxxxxx" }EOF# 2. Upload — the CLI fetches the HPKE key, encrypts locally, and uploadsnewton-cli --chain-id 11155111 secrets upload \ --secrets-file secrets.json \ --policy-client 0xYourPolicyClient \ --policy-data-address 0xYourPolicyData \ --api-key $NEWTON_API_KEY
Both paths fetch the operator’s HPKE public key via newt_getSecretsPublicKey, seal the envelope locally, and submit it via newt_storeEncryptedSecrets. The operator validates the plaintext against your secretsSchemaCid and stores only the encrypted envelope.
Your WASM oracle reads secrets through the newton:provider/secrets host interface (declared in newton-provider.wit). Calling get() returns the decrypted secrets JSON as raw bytes — the operator has already decrypted them; you only decode and parse:
import { fetch as httpFetch } from "newton:provider/http@0.2.0";import { get as getHostSecrets } from "newton:provider/secrets@0.2.0";let _secrets = {};// Load host-provided secrets once, at the start of run().function loadHostSecrets() { try { const r = getHostSecrets(); const resp = r?.val ?? r; // unwrap result<secret-response, string> const bytes = resp?.value; // value: list<u8> — decrypted secrets JSON if (!bytes || bytes.length === 0) return; const text = new TextDecoder().decode(new Uint8Array(bytes)); const parsed = JSON.parse(text); if (parsed && typeof parsed === "object") { _secrets = { ..._secrets, ...parsed }; } } catch (_) { // Host secrets unavailable (e.g. local sim without uploaded secrets). }}function secret(name) { return _secrets[name];}function getJson(url) { const apiKey = secret("VAULTS_FYI_API_KEY"); const r = httpFetch({ url, method: "GET", headers: [["accept", "application/json"], ["x-api-key", apiKey]], body: null, }); if (r.tag === "err") throw new Error(`http: ${r.val}`); const resp = r.val ?? r; if (resp.status < 200 || resp.status >= 300) { throw new Error(`http ${resp.status}`); } return JSON.parse(new TextDecoder().decode(new Uint8Array(resp.body)));}export function run(input) { try { loadHostSecrets(); const args = JSON.parse(input); const data = getJson(`https://api.example.com/v2/${args.id}`); // wrap output under your pack id so it composes with other oracles return JSON.stringify({ my_oracle: { value: data.value } }); } catch (e) { return JSON.stringify({ my_oracle: { error: String(e) } }); }}
Notes:
get() returns a result<secret-response, string>; secret-response.value is list<u8> — the bytes of the decrypted secrets JSON object. The scope (which PolicyClient + PolicyData) is bound by the operator’s execution context, so you do not pass any identifiers.
The named keys you read (VAULTS_FYI_API_KEY above) must match the keys you uploaded and the properties in secrets_schema.json.
Put credentials in request headers, never in the URL.
Fail closed: if a fetch errors, return a namespaced error so your Rego can deny on it (see Chaining Data Oracles).