Rust client package for interacting with a Fiber Network API over gRPC.
cargo add fiber --git https://github.com/chainbound/fiber-rs
fiber-rs
prints traces to the fiber
target. To see them, run your program with RUST_LOG=fiber=info
.
Using default options:
use fiber::Client;
#[tokio::main]
async fn main() {
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
}
Specifying options:
use fiber::{Client, ClientOptions};
#[tokio::main]
async fn main() {
let opts = ClientOptions::default().send_compressed(true);
let mut client = Client::connect_with_options("ENDPOINT_URL", "API_KEY", opts).await.unwrap();
}
With ClientOptions
you can negotiate incoming and outgoing stream compression. This activates gzip compression on the underlying gRPC connections. Compression is not always faster, and here are some general guidelines to follow:
- If your client is close to the target Fiber node and you have enough bandwidth, it's best to disable
accept
compression. - If you client is far from the target Fiber node and you have limited bandwidth, it's best to enable
accept
compression.
All subscriptions return a Stream
(UnboundedReceiverStream
) of the specified type.
If the underlying gRPC stream fails due to connection issues, it will automatically be retried.
Subscribing to transactions will return a Stream
, yielding reth_primitives::TransactionSignedEcRecovered
for every new transaction that's received.
Note on EIP-4844: Blob-carrying type 3 transactions are returned by the stream WITHOUT their blobs. The blobs are returned in a separate stream, subscribe_new_blob_transactions
.
Example:
use fiber::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// No filter in this example
let mut sub = client.subscribe_new_transactions(None).await;
// Use the stream as an async iterator
while let Some(tx) = sub.next().await {
handle_transaction(tx);
}
}
Subscribing to blob transactions will return a Stream
, yielding BlobTransactionSignedEcRecovered
for every new blob transaction that's received. This type is a simple wrapper over reth_primitives::BlobTransaction.
NOTE: This stream will ONLY yield blob-carrying transactions with their blobs. To get all transactions, use subscribe_new_transactions
instead.
Example:
use fiber::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// No filter in this example
let mut sub = client.subscribe_new_blob_transactions().await;
// Use the stream as an async iterator
while let Some(tx) = sub.next().await {
handle_blob_transaction(tx);
}
}
You can apply filters to the transaction stream using fiber::filter::Filter
. The builder pattern is used
for constructing a filter, with a couple of examples below.
use fiber::{Client, filter::FilterBuilder};
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// Construct filter
// example 1: simple receiver filter
let f = FilterBuilder::new()
.to("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");
// example 2: all transactions with either of these addresses as the receiver
let f = FilterBuilder::new()
.or() // creates a new 'OR' level
.to("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
.to("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D");
// example 3: all ERC20 transfers on the 2 tokens below
let f = FilterBuilder::new()
.and()
.method_id("0xa9059cbb")
.or()
.to("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
.to("0xdAC17F958D2ee523a2206206994597C13D831ec7");
// Encode the filter
let mut sub = client.subscribe_new_transactions(f.encode().unwrap()).await;
// Use the stream as an async iterator
while let Some(tx) = sub.next().await {
handle_transaction(tx);
}
}
Subscribing to raw transactions will return a Stream
, yielding raw RLP encoded transactions.
Example:
use fiber::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// No filter in this example
let mut sub = client.subscribe_new_raw_transactions(None).await;
// Use the stream as an async iterator
while let Some(raw_tx) = sub.next().await {
handle_raw_transaction(tx);
}
}
Returns a stream of newly seen execution payloads. This is useful for getting the state updates of a
newly confirmed block. An ExecutionPayload
contains both the block header and the transactions
that were executed in that block.
The type returned by this stream is an alloy-rpc-types::Block
. Since the blocks returned are parsed from consensus-layer payloads, they are missing the following fields, which are set to None
or zero
in all returned stream items:
parent_beacon_block_root
transactions_root
withdrawals_root
Example:
use fiber::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// No filter in this example
let mut sub = client.subscribe_new_execution_payloads().await;
// Use the stream as an async iterator
while let Some(block) = sub.next().await {
handle_block(block);
}
}
Returns a stream of consensus-layer SignedBeaconBlock
objects.
Example:
use fiber::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// No filter in this example
let mut sub = client.subscribe_new_beacon_blocks().await;
// Use the stream as an async iterator
while let Some(block) = sub.next().await {
handle_beacon_block(block);
}
}
Returns a stream of raw SSZ-encoded beacon blocks. Decoding is left to the caller to handle.
Example:
use fiber::Client;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() {
// Client needs to be mutable
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// No filter in this example
let mut sub = client.subscribe_new_raw_beacon_blocks().await;
// Use the stream as an async iterator
while let Some(raw_block) = sub.next().await {
handle_raw_beacon_block(raw_block);
}
}
Allows to broadcast transactions quickly to the Fiber network. It expects a reth_primitives::TransactionSigned
object, and returns the transaction hash and the timestamp of when the first Fiber node received it.
use reth_primitives::{Transaction, TxEip1559, Address, TransactionKind, AccessList, Signature, U256, TransactionSigned};
use fiber::Client;
#[tokio::main]
async fn main() {
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// Create a transaction and a signature for it
let tx = Transaction::Eip1559(TxEip1559 {
chain_id: 1,
nonce: 0x42,
gas_limit: 44386,
to: TransactionKind::Call( Address::from_str("0x6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").unwrap()),
value: 0_u64.into(),
input: hex::decode("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
max_fee_per_gas: 0x4a817c800,
max_priority_fee_per_gas: 0x3b9aca00,
access_list: AccessList::default(),
});
let sig = Signature {
r: U256::from_str("0x840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565")
.unwrap(),
s: U256::from_str("0x25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1")
.unwrap(),
odd_y_parity: false,
};
let signed_tx = TransactionSigned::from_transaction_and_signature(tx, sig);
let res = client.send_transaction(signed_tx).await.unwrap();
println!("{:?}", res);
}
Sends a sequence of transactions to the Fiber network at once.
use reth_primitives::{Transaction, TxEip1559, Address, TransactionKind, AccessList, Signature, U256, TransactionSigned};
use std::str::FromStr;
use fiber::Client;
#[tokio::main]
async fn main() {
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// Provide a transaction with signature as the first transaction of the sequence
let tx = Transaction::Eip1559(TxEip1559 {
chain_id: 1,
nonce: 0x42,
gas_limit: 44386,
to: TransactionKind::Call( Address::from_str("0x6069a6c32cf691f5982febae4faf8a6f3ab2f0f6").unwrap()),
value: 0_u64.into(),
input: hex::decode("a22cb4650000000000000000000000005eee75727d804a2b13038928d36f8b188945a57a0000000000000000000000000000000000000000000000000000000000000000").unwrap().into(),
max_fee_per_gas: 0x4a817c800,
max_priority_fee_per_gas: 0x3b9aca00,
access_list: AccessList::default(),
});
let sig = Signature {
r: U256::from_str("0x840cfc572845f5786e702984c2a582528cad4b49b2a10b9db1be7fca90058565")
.unwrap(),
s: U256::from_str("0x25e7109ceb98168d95b09b18bbf6b685130e0562f233877d492b94eee0c5b6d1")
.unwrap(),
odd_y_parity: false,
};
let signed_tx_1 = TransactionSigned::from_transaction_and_signature(tx, sig);
// Then, provide a second signed transaction from wherever you want
let tx_bytes = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
let signed_tx_2 = TransactionSigned::decode_enveloped(&mut &tx_bytes[..]).unwrap();
let res = client.send_transaction_sequence(vec![signed_tx_1, signed_tx_2]).await.unwrap();
println!("{:?}", res);
}
Sends a raw RLP encoded transaction to the Fiber network.
use fiber::Client;
#[tokio::main]
async fn main() {
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
let raw_tx = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
let res = client.send_raw_transaction(raw_tx).await.unwrap();
println!("{:?}", res);
}
Sends a sequence of RLP encoded transactions, for things like backrunning and other strategies where the explicitly stated order is important.
use fiber::Client;
#[tokio::main]
asyn fn main() {
let mut client = Client::connect("ENDPOINT_URL", "API_KEY").await.unwrap();
// Our transaction, RLP-encoded
let raw_tx_1 = hex::decode("02f872018307910d808507204d2cb1827d0094388c818ca8b9251b393131c08a736a67ccb19297880320d04823e2701c80c001a0cf024f4815304df2867a1a74e9d2707b6abda0337d2d54a4438d453f4160f190a07ac0e6b3bc9395b5b9c8b9e6d77204a236577a5b18467b9175c01de4faa208d9").unwrap();
// The target transaction should be an RLP encoded transaction as well
let target_tx_2: Vec<u8> = vec![...]
let res = client.send_raw_transaction_sequence(vec![raw_tx_1, target_tx_2]).await.unwrap();
println!("{:?}", res);
}