SteemVM Oracle Client Protocol: Building a Reliable Multi-Language Validator Oracle
As SteemVM continues to evolve, one of the most important pieces of infrastructure is the validator oracle client.
The oracle is responsible for more than simply publishing prices. It participates in bridge attestations, withdrawal confirmations, name-registration attestations, and the commit-reveal price-feed mechanism.
That means every validator implementation must follow exactly the same cryptographic and serialization rules.
A small difference in key derivation, protobuf serialization, signature formatting, hashing, or decimal formatting can result in a transaction being rejected by the chain.
This document summarizes the current SteemVM Oracle Client Protocol, including the signing procedure, bridge routing, price-feed commit/reveal mechanism, state management, test vectors, and live-broadcast validation across the Python and JavaScript implementations.
The protocol is designed so that the Go, Python, and JavaScript oracle clients can produce byte-for-byte compatible transactions and independently verify the same results.
The .proto definitions under proto/steemvm/ define the wire format, while this protocol defines the exact procedure that implementations must follow.
1. Validator Keys and Addresses
SteemVM uses eth_secp256k1 for validator signing.
The private key is a standard 32-byte secp256k1 private key.
The public key used on the wire is the 33-byte compressed secp256k1 public key.
The account address is derived using the standard Ethereum address procedure:
- Generate the uncompressed public key.
- Remove the
0x04prefix. - Keccak256-hash the remaining 64 bytes containing X and Y.
- Take the final 20 bytes.
- Encode those 20 bytes using Bech32 with the
steemHRP.
In other words, SteemVM does not introduce a custom Ethereum address derivation scheme.
The resulting address is the validator's account address:
steem1...
An important distinction exists here.
The oracle messages must use the account address, not the validator operator address:
steem1...
and not:
steemvaloper1...
The steemvaloper address is used for staking-related queries, but oracle duty messages are signed and identified by the bonded account key.
HD Wallet Derivation
Mnemonic-based keys use the standard Ethereum derivation path:
m/44'/60'/0'/0/0
This means existing Ethereum-compatible HD wallet implementations can be used without custom derivation logic.
The public key is embedded in protobuf using:
/cosmos.evm.crypto.v1.ethsecp256k1.PubKey
with the compressed 33-byte public key stored in the protobuf key field.
2. How SteemVM Oracle Transactions Are Signed
The oracle clients use Cosmos SDK's:
SIGN_MODE_DIRECT
signing mode.
The signing process is deterministic and must be reproduced exactly.
Step 1 — Build TxBody
The transaction body contains the oracle message:
TxBody {
messages: [...]
memo: ""
timeout_height: 0
}
The message is wrapped inside a protobuf Any.
Step 2 — Build AuthInfo
AuthInfo contains:
- the validator public key
SIGN_MODE_DIRECT- account sequence
- transaction fee
- gas limit
Step 3 — Serialize
Both objects are serialized independently:
body_bytes
auth_info_bytes
Step 4 — Create SignDoc
The signing document contains:
body_bytes
auth_info_bytes
chain_id = "steemvm"
account_number
The resulting protobuf is then serialized.
Step 5 — Hash
The final digest is:
Keccak256(serialized SignDoc)
Step 6 — Sign
The digest is signed using secp256k1.
The signature must be:
R (32 bytes) || S (32 bytes) || V (1 byte)
for a total of:
65 bytes
The recovery byte V must be the raw recovery ID:
0 or 1
It must not use Ethereum's historical:
27 / 28
format.
The signature must also use canonical low-S formatting.
Critical Serialization Rule
One of the easiest mistakes to make is re-marshaling the transaction after signing.
The exact same body_bytes and auth_info_bytes used to construct the SignDoc must be placed into the final TxRaw.
They should not be serialized again after the signature is generated.
Even if two protobuf messages are logically identical, their serialized bytes are what actually matter to the cryptographic signature.
Finally, the complete TxRaw is protobuf serialized and Base64 encoded for broadcasting.
3. Oracle Message Types
The current SteemVM oracle protocol covers five important message types:
| Message | Type URL |
|---|---|
MsgAttestDeposit | /steemvm.steembridge.v1.MsgAttestDeposit |
MsgAttestWithdrawalPayout | /steemvm.steembridge.v1.MsgAttestWithdrawalPayout |
MsgSubmitNameRegistration | /steemvm.steembridge.v1.MsgSubmitNameRegistration |
MsgAggregateExchangeRatePrevote | /steemvm.oracle.data.v1.MsgAggregateExchangeRatePrevote |
MsgAggregateExchangeRateVote | /steemvm.oracle.data.v1.MsgAggregateExchangeRateVote |
These messages cover the bridge-attestation and price-oracle responsibilities of validators.
4. Fees and Gas
Not every oracle message is treated the same way.
Bridge Attestations
Bridge-attestation messages are fee-exempt for bonded validators, subject to the configured per-block limits.
The reference gas calculation is:
gasBase = 200,000
gasPerMsg = 400,000
with:
MaxMsgsPerTx = 50
A fee amount can therefore be empty for these validator bridge-attestation transactions, but the gas limit still needs to be supplied.
Price Oracle Transactions
Price-feed transactions are different.
MsgAggregateExchangeRatePrevote and MsgAggregateExchangeRateVote are not fee-exempt.
The validator account must therefore have a real asteem balance.
The default oracle gas price is:
1000000000asteem
This matches the existing manual oracle transaction configuration.
5. Account Sequence and Transaction Broadcasting
Python and JavaScript implementations can use the REST API without requiring a full gRPC client.
The account information can be retrieved through:
/cosmos/auth/v1beta1/accounts/{address}
This provides the validator's:
account_number
sequence
The transaction can then be broadcast through:
/cosmos/tx/v1beta1/txs
using:
BROADCAST_MODE_SYNC
The important point is that broadcast acceptance is not the same as transaction delivery.
The oracle client should continue polling:
/cosmos/tx/v1beta1/txs/{hash}
until the transaction is found and:
tx_response.code == 0
The reference cadence is approximately:
2 seconds between polls
45 seconds maximum timeout
Local state should only advance after the transaction has actually been confirmed on-chain.
6. Steem Bridge Memo Routing
The SteemVM bridge uses a fixed gateway account:
svm.bank
This value is hardcoded into the oracle implementations.
It should not be dynamically obtained from the chain's gateway parameter because that parameter exists primarily for backwards-compatible display purposes and is not the authoritative consensus routing value.
The oracle determines which attestation to submit based on Steem transfer direction and memo.
Name Registration
An inbound transfer to the gateway with a memo beginning with:
svm-register
is treated as a name-registration attestation.
Deposit
Other inbound transfers to the gateway are treated as deposits.
Examples include:
svm-deposit <address>
or even an unparseable/bare-address memo.
Ultimately, the chain determines whether the deposit is claimable.
SBD
SBD has special routing:
SBD transfers always route as deposits.
There is no SBD name-registration path.
Withdrawal
An outbound gateway transfer containing:
svm-withdrawal <id>
is treated as a withdrawal payout attestation.
The <id> represents the withdrawal record ID.
7. Deduplication and Idempotency
Oracle infrastructure must be able to recover safely from retries, restarts, and network failures.
For deposits and name registrations, the deduplication key is:
(txid, op_index[, validator])
Before submitting an attestation, the validator should check whether the event has already been attested.
This prevents the same Steem operation from being unnecessarily submitted multiple times.
Withdrawal payouts are chain-side idempotent, meaning duplicate attestations become no-ops.
Price-feed deduplication works differently because the commit-reveal protocol itself restricts each validator to one prevote per period.
8. The SteemVM Price Oracle Commit-Reveal System
The price oracle uses a commit-reveal mechanism.
This is important because validators should not simply publish their prices directly and allow other validators to copy them.
Instead:
- The validator commits to a hash.
- The next voting period reveals the actual values.
- The chain verifies that the revealed values match the original commitment.
The current vote period is:
period = current_height / VotePeriod
The configured period is currently:
600 blocks
which is approximately one hour.
The reveal must occur in the immediately following period. A late reveal is abandoned rather than accepted.
9. Oracle Commit Hash
The commit hash is generated from:
salt
exchangeRates
validator
using:
sha256(
f"{salt}:{exchangeRates}:{validator}"
)
The SHA-256 result is represented as hexadecimal and truncated to the first:
40 hexadecimal characters
which represents 20 bytes.
The validator included in this calculation is the Bech32 account address:
steem1...
not the operator address.
10. Exchange Rate Formatting — The Most Dangerous Detail
One of the most important implementation details is decimal formatting.
Rates must use the canonical LegacyDec.String() representation:
18 decimal places
with trailing zeros preserved.
For example:
1.23
must become:
1.230000000000000000
and:
0.5
must become:
0.500000000000000000
A generic decimal formatter is therefore unsafe.
For example, implementations must avoid automatically normalizing values using approaches equivalent to:
Decimal.normalize()
or JavaScript number-to-string conversion.
The exact formatted string is part of the hash.
If the validator formats the value differently during commit and reveal, the chain will reject the reveal.
This is arguably the single highest-risk formatting detail in the entire oracle protocol.
11. Supported Price Pairs
The current whitelist contains four values:
STEEM/USD_External
STEEM/SBD_Internal
SBD/USD_External
Price_Feed
The order matters.
Pairs are sorted lexicographically, which currently results in:
Price_Feed
SBD/USD_External
STEEM/SBD_Internal
STEEM/USD_External
The final string is formatted as:
PAIR:rate,PAIR:rate,PAIR:rate
with:
- no spaces
- exact decimal formatting
- deterministic ordering.
Price_Feed is intentionally different from the other values.
It represents Steem's blockchain-native witness-median feed rather than a conventional market pair.
12. Where the Prices Come From
The oracle combines several data sources.
| Oracle Value | Source |
|---|---|
STEEM/USD_External | CoinMarketCap |
SBD/USD_External | CoinMarketCap |
STEEM/SBD_Internal | Steem condenser_api.get_ticker |
Price_Feed | Steem condenser_api.get_feed_history |
For CoinMarketCap, STEEM and SBD should be requested together in a single:
quotes/latest?symbol=STEEM,SBD&convert=USD
request rather than making separate requests.
If an individual price source fails, the missing pair can be dropped rather than causing the entire oracle cycle to crash.
Even an empty price map simply results in no vote for that period, which the unified slashing mechanism can count as a missed price duty.
13. Persistent Oracle State
A validator may eventually switch between Go, Python, and JavaScript implementations.
The protocol therefore defines persistent state files so that changing implementation language does not mean losing operational progress.
The Steem scanner maintains:
{
"last_scanned_block": 12345678
}
while the price feeder maintains its commit/reveal state separately:
{
"prevote_period": 4821,
"salt": "a1b2c3...",
"exchange_rates": "Price_Feed:0.520000000000000000,..."
}
The separation is intentional.
The Steem relayer and price feeder have different failure domains, so their state should not depend on one another.
State files should also be written atomically by writing to a temporary file and then renaming it.
14. Cryptographic Test Vector
To ensure that independent implementations produce identical results, a fixed test vector was created.
The test uses the secp256k1 private key:
0000000000000000000000000000000000000000000000000000000000000001
which produces the compressed public key:
0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
and the SteemVM account address:
steem10e0525sfrf53yh2aljmm3sn9jq5njk7lj48rdt
These values provide a deterministic way for different implementations to verify their key derivation and address generation.
15. Signing Vector
A fixed MsgAttestDeposit was used to validate the entire signing pipeline.
The resulting Keccak256 digest was:
6e0e34f350dc69f8436809664007e133499a17d6137aa36ee1981b8e78afa656
The resulting signature was 65 bytes and used:
R || S || V
with:
V = 0
and canonical low-S formatting.
This gives implementations a concrete cryptographic target rather than relying only on theoretical protocol descriptions.
The complete Base64-encoded TxRaw is also included in the protocol specification as a reproducible serialization vector.
However, it is intentionally not a transaction to broadcast directly, because its account number, sequence, and gateway values are test-vector values rather than current production account state.
16. Price Oracle Test Vector
The fixed test uses:
STEEM/USD_External = 0.523
SBD/USD_External = 1.01
STEEM/SBD_Internal = 0.518
Price_Feed = 0.52
The resulting canonical exchange-rate string is:
Price_Feed:0.520000000000000000,SBD/USD_External:1.010000000000000000,STEEM/SBD_Internal:0.518000000000000000,STEEM/USD_External:0.523000000000000000
The resulting commit hash is:
aa83a62ac7bebf100aa5d5690d2cbd6af8dbf679
This provides another cross-language verification point for oracle implementations.
17. Python Oracle: Live Broadcast Confirmed
The Python client reached an important milestone.
A real MsgAttestDeposit was signed and broadcast against a fresh development network.
The transaction returned:
tx_response.code == 0
at block height:
12
with:
gas_used = 171697
gas_wanted = 600000
The complete bridge flow was observed:
deposit_created
↓
deposit_confirmed
↓
deposit_minted
The single-validator devnet naturally resulted in:
confirmed_ratio = 1.000000000000000000
The resulting minted amount also correctly reflected the configured bridge fee.
This confirmed that the Python implementation's signing and broadcasting path works against a live SteemVM development network.
18. JavaScript Oracle: Live Broadcast Confirmed
The JavaScript implementation subsequently completed its own live-broadcast validation.
Unlike the earlier Python test, this test was performed against the current post-migration steemvmd build.
The JavaScript client used the actual:
signer.ts
broadcast.ts
implementation without replacing the signing logic with a hand-written test implementation.
The transaction again returned:
tx_response.code == 0
at block height:
25
with:
gas_used = 168015
gas_wanted = 600000
The bridge events again progressed successfully:
deposit_created
↓
deposit_confirmed
↓
deposit_minted
The resulting minted amount matched the configured 25-basis-point bridge fee exactly.
This is a significant milestone because it validates not just offline cryptographic vectors, but the complete JavaScript signing → broadcast → chain verification → bridge resolution path.
19. Current Implementation Status
At this stage, the oracle protocol has been validated across multiple implementation layers.
Python
Live broadcast confirmed.
The Python client successfully signed and submitted a real bridge attestation to a development network and received:
tx_response.code == 0
JavaScript
Live broadcast confirmed.
The current post-migration JavaScript implementation successfully completed the same live transaction path.
Go
The Go implementation has the protocol and signing path defined, but its post-migration live-broadcast validation still needs to be re-run.
That is currently the remaining gap in the live-broadcast verification matrix.
Why This Matters for SteemVM
A blockchain oracle is only as reliable as the agreement between the validators operating it.
If one validator signs a slightly different byte representation than another, or formats a price differently, or uses the wrong address type, the result can be rejected.
That is why this protocol goes beyond simply saying:
"Sign the transaction with secp256k1."
It defines the exact:
- key derivation
- public-key format
- address derivation
- Bech32 encoding
- protobuf serialization
- signing mode
- Keccak digest
- ECDSA signature format
- low-S requirement
- gas configuration
- bridge routing
- deduplication
- price formatting
- price ordering
- commit hash
- state persistence
- transaction confirmation process
Every one of these details can affect consensus-facing behavior.
The goal is straightforward:
A Go validator, Python validator, and JavaScript validator should all produce transactions that SteemVM can verify identically.
Final Thoughts
The SteemVM Oracle is becoming a critical piece of infrastructure connecting the Steem blockchain with the EVM environment.
The bridge requires validators to independently observe Steem activity and attest to deposits and withdrawals.
The price oracle requires validators to independently collect market information and participate in a commit-reveal process.
Both systems therefore require strong cryptographic guarantees and deterministic behavior.
The work completed so far provides a common protocol that can be implemented across multiple programming languages while maintaining the same wire-level behavior.
The Python and JavaScript implementations have now demonstrated successful live transaction broadcasting on development networks, while the deterministic test vectors provide a reproducible foundation for additional implementations and regression testing.
The remaining major validation task is to perform the equivalent post-migration live-broadcast test for the Go client.
Once all three implementations have completed that validation, SteemVM will have a much stronger foundation for a multi-language, validator-operated bridge and price oracle infrastructure.
The important part is not simply that the oracle works.
It is that independent validators can implement it, verify it, and arrive at exactly the same result.
That is the foundation required for reliable decentralized infrastructure.
SteemVM Oracle Client Protocol
Go • Python • JavaScript
Deterministic signing.
Deterministic hashing.
Deterministic price formatting.
Independent validator verification.
#SVM #Steem #SteemVM #Oracle #Blockchain #EVM #DeFi #Crypto #Development
Technical specification and implementation status based on the current SteemVM Oracle Client Protocol and its validation work.