A Rust CLI that fetches token prices from multiple external sources, validates them through cross-source agreement, and builds a ConversionTable compatible with the Unyt DNA. It can optionally submit the table to a running Holochain conductor via the create_conversion_table zome call.
# From the pricing_oracle/ directory
cp .env.example .env # edit as needed
cargo run # fetch prices, print table
cargo run -- --dry-run # preview the ConversionTable JSON (no Holochain connection)
cargo run -- --submit # fetch prices, resolve GlobalDefinition from Holochain, submit| Flag | Description |
|---|---|
-c, --config <PATH> |
Path to the YAML config file (default: config.yaml) |
-o, --output <FORMAT> |
Output format: table (default) or json |
-u, --unit <INDEX> |
Only process a single unit by its index |
--dry-run |
Build the ConversionTable and print it as JSON without connecting to Holochain. Uses a zeroed placeholder for global_definition. Mutually exclusive with --submit. |
--submit |
Connect to Holochain, fetch the current GlobalDefinition, build the ConversionTable with it, and call create_conversion_table. Mutually exclusive with --dry-run. |
-V, --version |
Print the version and exit. This is the release tag without its leading v. |
Defines the units the oracle tracks (each with a unit_index, name, chain, and contract) and optionally price references — tokens that are fetched for pricing but have no unit_index and do not appear in the ConversionTable.
- units — Entries that appear in the ConversionTable. Each has a unique
unit_index. Units withoutprice_proxyare fetched from price sources; units withprice_proxyinherit price from another unit or from a price reference. - price_references (optional) — Tokens used only as price sources. They have an
id,name,chain, andcontract(nounit_index). They are fetched and aggregated like real units, but never get a row in the ConversionTable. Use them when a unit should proxy from a token that is not part of the network’s unit list. - forex (optional) — Fiat currencies to include in
ConversionTable.forex_rates. Rates are stored as foreign units per 1 USD (for example,EUR=0.93means1 USD = 0.93 EUR).max_symbols_per_run— symbols per batch (default8). The oracle fetches all symbols in a loop, one batch at a time.delay_between_batches_secs— seconds to wait between batches (default0). Set to e.g.65for Twelve Data free-tier per-minute limit so each batch gets a fresh credit window.
price_proxy must have exactly one of:
- use_unit — Unit index in the same
unitslist (same config as before). - use_reference — Id of an entry in
price_references.
# Tokens fetched for price only; not in ConversionTable
price_references:
- id: "HOT"
name: "HOT"
chain: "ethereum"
contract: "0x6c6ee5e31d828de241282b9606c8e98ea48526e2"
forex:
max_symbols_per_run: 8
delay_between_batches_secs: 65 # optional; 65s for Twelve Data free tier
use_twelve_data: true
use_coinapi: false
symbols:
- "USD"
- "EUR"
- "GBP"
- "JPY"
units:
- unit_index: 0
name: "HOTMOCK"
chain: "sepolia"
contract: "0xeaC8eEEE9f84F3E3F592e9D8604100eA1b788749"
price_proxy:
use_reference: "HOT"You can still proxy from another unit in the list: use price_proxy: { use_unit: 0 } instead of use_reference.
If forex.symbols is empty or omitted, no forex API calls are made.
| Variable | Required | Default | Description |
|---|---|---|---|
COINGECKO_API_KEY |
No | — | Free demo key from coingecko.com. If unset, only GeckoTerminal is used. |
COINMARKETCAP_API_KEY |
No | — | CoinMarketCap Pro API key. Enables CoinMarketCap token source. |
TWELVE_DATA_API_KEY |
No | — | Twelve Data key for forex rates (USD/<SYMBOL>) |
COINAPI_API_KEY |
No | — | CoinAPI key for forex rates (USD/<SYMBOL>) |
HOLOCHAIN_ADMIN_PORT |
For --submit |
30000 |
Holochain conductor admin port |
HOLOCHAIN_APP_PORT |
For --submit |
30001 |
Holochain conductor app port |
HOLOCHAIN_APP_ID |
For --submit |
bridging-app |
Installed app ID |
HOLOCHAIN_ROLE_NAME |
For --submit |
alliance |
DNA role name |
RUST_LOG |
No | info |
Log level filter |
The GlobalDefinition is fetched automatically from the conductor via get_current_global_definition -- no manual ActionHash configuration is needed.
Sources are compiled into the binary. Adding a new source means adding a new module that implements the PriceSource trait.
| Source | API key required | Data provided |
|---|---|---|
| GeckoTerminal | No | price, volume, market cap, liquidity |
| CoinGecko | Yes (free demo key) | price, volume, market cap, 24h change |
| CoinMarketCap | Yes (Pro API key) | price, volume, market cap, 24h change |
All enabled token sources are queried for each real unit. If only one source returns data, the single-source result is accepted without cross-checking.
| Source | API key required | Data provided |
|---|---|---|
| Twelve Data | Yes | forex rate for USD/<SYMBOL> |
| CoinAPI | Yes | forex rate for USD/<SYMBOL> |
For each configured forex symbol, providers are queried when available. If both return valid rates, the oracle stores their average. If one source fails or quota is exhausted, partial results from the other source are still used.
For each unit, the oracle computes the average price across all successful sources. If any single source deviates by more than 1% from the average, the unit is marked invalid and excluded from the final ConversionTable.
When only one source returns data, the cross-check is skipped and the result is accepted.
The output is structured as a ConversionTable (mirroring the rave_engine type):
ConversionTable
├── reference_unit: { symbol: "USD", name: "US Dollar" }
├── data: HashMap<unit_index, ConversionData>
│ └── ConversionData
│ ├── current_price: ZFuel
│ ├── volume: String
│ ├── net_change: String (24h % change)
│ ├── sources: Vec<String>
│ └── contract: Option<String>
├── forex_rates: Vec<ForexRate>
│ └── ForexRate
│ ├── symbol: String
│ ├── name: String
│ └── rate: ZFuel (foreign units per 1 USD)
├── additional_data: None
└── global_definition: ActionHash
Invalid units are omitted from the data map.
When --submit is used, the CLI:
- Reads Holochain connection settings from env.
- Connects to the conductor using the HAM (Holochain Agent Manager) pattern.
- Calls
transactor/get_current_global_definitionto obtain the currentGlobalDefinitionExt.id. - Builds the
ConversionTablewith the realglobal_definitionActionHash. - Prints the table as JSON for visibility.
- Calls
transactor/create_conversion_tableand prints the resulting ActionHash.
The agent running the CLI must be the pricing_oracle agent defined in the active GlobalDefinition.
Releases are cut by pushing a semver tag:
git tag v0.1.0 && git push origin v0.1.0The workflow validates the tag, checks it against the crate version in Cargo.toml, builds with --locked, and publishes. The tag and Cargo.toml must agree — bump and commit the crate version before tagging.
Assets have fixed names, so a provisioning script can hardcode the URL:
| Asset | Description |
|---|---|
pricing-oracle |
Stripped release binary, dynamically linked against glibc |
pricing-oracle.sha256 |
Digest of the binary, bare filename inside |
config.yaml |
The in-repo default config — override it for your deployment |
config.yaml.sha256 |
Digest of the config |
https://github.com/unytco/pricing_oracle/releases/download/v0.1.0/pricing-oracle
https://github.com/unytco/pricing_oracle/releases/latest/download/pricing-oracle
releases/latest/download/ resolves to the newest non-prerelease, so a node pointed at latest will not pick up an -rc tag.
VERSION=v0.1.0
INSTALL_DIR=/opt/pricing-oracle
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
for asset in pricing-oracle pricing-oracle.sha256; do
curl -fsSL -o "$asset" \
"https://github.com/unytco/pricing_oracle/releases/download/${VERSION}/${asset}"
done
sha256sum -c pricing-oracle.sha256
chmod 755 pricing-oracleThe binary is built on the same Ubuntu release the fleet droplets run, so it needs no toolchain on the target — only ca-certificates, since outbound HTTPS verifies against the host CA store.
config.yaml is deployment-specific (unit list, contracts, forex symbols). Fetch it the same way for a starting point, but expect to replace it. .env is not a release asset; it carries API keys and is generated per deployment.
pricing_oracle/
├── Cargo.toml
├── config.yaml
├── .env.example
└── src/
├── main.rs # CLI entry point, argument parsing, orchestration
├── config.rs # YAML config loading and validation
├── types.rs # TokenData, AggregatedResult, ConversionTable mirrors
├── forex_aggregate.rs # Forex symbol merge/fallback + validation
├── sources/
│ ├── mod.rs # PriceSource trait and SourceRegistry
│ ├── geckoterminal.rs # GeckoTerminal API implementation
│ └── coingecko.rs # CoinGecko API implementation
├── forex/
│ ├── mod.rs # ForexSource trait and ForexSourceRegistry
│ ├── twelve_data.rs # Twelve Data USD/<SYMBOL> implementation
│ └── coinapi.rs # CoinAPI USD/<SYMBOL> implementation
├── aggregate.rs # Average calculation and 1% deviation check
├── output.rs # ConversionTable builder and print formatters
├── ham.rs # Holochain Agent Manager (admin/app websocket)
└── zome.rs # fetch_global_definition + submit_conversion_table