OnchainTempo

Hex.pm HexDocs

Tempo blockchain primitives for Elixir — 0x76 transaction handling, TIP-20 token encoding, RPC broadcasting, and TransferWithMemo event parsing.

Built on onchain.

Installation

def deps do
[
{:onchain_tempo, "~> 0.13"}
]
end

Documentation: hexdocs.pm/onchain_tempo.

Modules

Module Purpose
Onchain.Tempo.TIP20 TIP-20 function selectors, calldata encoders, Tempo constants
Onchain.Tempo.Transaction 0x76 transaction struct, deserialize, payment matching, fee payer co-signing
Onchain.Tempo.Transaction.Builder Build and sign 0x76 transactions from scratch
Onchain.Tempo.RPC Tempo JSON-RPC operations (broadcast async/sync, fetch receipt, pre-broadcast eth_simulateV1)
Onchain.Tempo.Transfer TransferWithMemo event log parsing
Onchain.Tempo.Faucet Moderato testnet faucet — tempo_fundAddress wrapper (testing only)

Quick Start

Deserialize a Tempo transaction

{:ok, tx} = Onchain.Tempo.Transaction.deserialize("0x76...")
tx.chain_id #=> 42431
tx.calls #=> [%{to: <<...>>, value: 0, input: <<...>>}]

Version 0.13 removes tx.fields. Read and update named struct fields, then call Transaction.serialize/1. raw retains the original broadcast hex; serialize/1 returns hex for the current fields. signing_hash/1, hash/1, and sender/1 return {:ok, binary}. Decode, serialization, hashing, fee-payer cosigning and recovery support Secp256k1, P-256, WebAuthn, and keychain V1/V2. Our builders sign with Secp256k1 keys. Keychain recovery verifies the inner signature, but callers must separately check access-key authorization on-chain.

Signatures are {:secp256k1, %{r: integer, s: integer, y_parity: 0 | 1}}, {:p256, %{r: binary, s: binary, pub_key_x: binary, pub_key_y: binary, pre_hash: boolean}}, {:webauthn, %{r: binary, s: binary, pub_key_x: binary, pub_key_y: binary, webauthn_data: binary}}, or {:keychain, 1 | 2, user_address_binary, primitive_signature}. Key authorizations are typed maps with atom keys; see t:Onchain.Tempo.Transaction.key_authorization/0. Multicall builders accept %{to: address_binary, value: integer, input: binary} calls.

Migration from 0.11 and 0.12

0.12 changed fields from positional RLP to an internal serde map without documenting the break, and restricted signatures to Secp256k1. 0.13 replaces both representations with named fields and restores all supported signatures. The 0.12 keys below lived under fields["transaction"] unless stated otherwise. Addresses/data now use binaries, quantities use integers, and absent optionals use nil. A fee-payer placeholder is :placeholder.

0.11 RLP index (zero-based) 0.12 serde key 0.13 field
0 chainId chain_id
1 maxPriorityFeePerGas max_priority_fee_per_gas
2 maxFeePerGas max_fee_per_gas
3 gas gas_limit
4 calls calls
5 accessList access_list
6 nonceKey nonce_key
7 nonce nonce
8 validBefore valid_before
9 validAfter valid_after
10 feeToken fee_token
11 feePayerSignature / fields["placeholder"] fee_payer_signature
12 aaAuthorizationList tempo_authorization_list
13 when present, before signature keyAuthorization key_authorization
Last fields["signature"] signature
Original envelope raw raw

Transaction.sender/1 normalizes high-s Secp256k1 signatures to low-s before recovery, so equivalent complement-s encodings recover the same sender. It preserves the original envelope; successful recovery does not imply broadcast acceptance.

Find a payment call

{:ok, match} = Onchain.Tempo.Transaction.find_payment_call(tx, token_address,
amount: "1000000",
recipient: "0x70997970..."
)
match.amount #=> 1000000

Build and sign a transfer

{:ok, tx_hex} = Onchain.Tempo.Transaction.Builder.build_signed_transfer(
private_key: "0xac09...",
token: "0x20c0...",
recipient: "0x7099...",
amount: 1_000_000,
chain_id: 42_431,
rpc_url: "https://rpc.moderato.tempo.xyz"
)

Broadcast

# Async (returns tx hash immediately)
{:ok, tx_hash} = Onchain.Tempo.RPC.broadcast_async(tx_hex, rpc_url)
# Sync (waits for block inclusion, returns receipt)
{:ok, tx_hash, receipt} = Onchain.Tempo.RPC.broadcast_sync(tx_hex, rpc_url)

Fund a Moderato testnet wallet

For integration tests against Moderato (testnet 42_431), Onchain.Tempo.Faucet wraps the non-standard tempo_fundAddress JSON-RPC:

# Fund an existing address.
{:ok, [tx_hash | _]} = Onchain.Tempo.Faucet.fund_address("0xabc...")
# Generate + fund a fresh keypair (polls for confirmation before returning).
{:ok, %{private_key: priv, address_hex: hex, address_bin: bin}} =
Onchain.Tempo.Faucet.fresh_funded_wallet()

Defaults to https://rpc.moderato.tempo.xyz; overridable via TEMPO_RPC_URL or by passing rpc_url: in the opts (e.g. fund_address("0xabc...", rpc_url: "https://my-mirror")). Mainnet does not support tempo_fundAddress.

Discovery

All modules use descripex:

OnchainTempo.describe() # Module overview
OnchainTempo.describe(:transaction) # Function list
OnchainTempo.describe(:transaction, :deserialize) # Full details

Tempo Networks

Network Chain ID RPC URL
Mainnet 4217 https://rpc.tempo.xyz
Moderato (testnet) 42431 https://rpc.moderato.tempo.xyz

Which endpoint serves what

This package is Tempo-specific by design — it is not portable to an arbitrary Ethereum provider, and that is the point rather than an oversight. Type-0x76 transactions, TIP-20 encoding, and the synchronous broadcast path are Tempo protocol features; a generic Ethereum endpoint has no notion of them. What you can swap is which Tempo-compatible endpoint you point at — your own node, or a provider serving the Tempo chain — not the chain itself.

Surface Works against Notes
Onchain.Tempo.Transaction, .Builder, .TIP20, .Transfer no node at all Pure encode/decode/sign — offline, no RPC
Onchain.Tempo.RPC.broadcast_async/3, fetch_receipt/3 any Tempo endpoint (mainnet or Moderato) Standard eth_sendRawTransaction / eth_getTransactionReceipt shapes
Onchain.Tempo.RPC.broadcast_sync/3 any Tempo endpoint Uses eth_sendRawTransactionSync, a Tempo extension — a generic Ethereum node answers -32601 Method not found
Onchain.Tempo.Faucet Moderato only Wraps tempo_fundAddress, which mainnet does not expose

Every RPC function takes an rpc_url so you can target either network per call. The faucet additionally reads a TEMPO_RPC_URL environment variable as its default, falling back to https://rpc.moderato.tempo.xyz — set it to point the faucet at a different Moderato endpoint without threading a URL through every call (Onchain.Tempo.Faucet.rpc_url/0 returns the resolved value).

0x76 verification

Signing and canonical encoding are checked against the provider-owned Tempo transaction spec and the current ox TypeScript SDK (TxEnvelopeTempo), plus live Moderato broadcast success and a relevant decode error. Evidence lives in priv/verification/0x76/ (ledger, ox vectors, live observation). Unit tests under test/onchain/tempo/verification/ rerun the properties, differential checks and mutation campaign; live checks are the :integration suite.

License

MIT