User Client
The fastpay-user-client crate provides a Rust library for wallet interactions with Allegro. It handles transaction construction, certificate collection, QC assembly, and wallet state management.
Overview
The client library coordinates the full payment lifecycle. Users construct transactions with the builder API. The transport layer fans out requests to validators. The certificate manager collects and validates responses. The wallet tracks nonces and pending transactions.
The library compiles to both native Rust and WebAssembly. Native builds use tokio and tonic for async and gRPC. WASM builds use wasm-bindgen-futures for browser compatibility.
Core Components
FastPayClient
The FastPayClient struct is the main entry point. It coordinates transaction submission, certificate collection, and QC assembly.
#![allow(unused)]
fn main() {
let client = FastPayClient::new(
transport,
wallet,
cert_manager,
chain_id,
threshold,
default_nonce_key,
cert_parser,
).with_sender_private_key(sender, private_key)?;
}
The constructor accepts pluggable components for transport, wallet, and certificate management. The with_sender_private_key method registers signing keys for transaction construction.
Key Methods
The send_payment method performs an atomic payment flow.
#![allow(unused)]
fn main() {
let qc = client.send_payment(
sender,
recipient,
amount,
asset,
Expiry::MaxBlockHeight(100),
).await?;
}
This method reserves a nonce, builds the transaction, fans out to validators, collects certificates, and assembles the QC. It returns the complete Quorum Certificate on success.
The send_payment_with_parent method enables chained payments. It accepts a parent QC reference that credits the sender with received funds.
#![allow(unused)]
fn main() {
let qc = client.send_payment_with_parent(
sender,
recipient,
amount,
asset,
expiry,
&parent_qc,
).await?;
}
This method attaches the parent QC hash to the transaction. Validators validate the parent and credit the sender accordingly.
Transaction Builder
The TxBuilder constructs FastPayTx messages with a fluent API.
#![allow(unused)]
fn main() {
let built = TxBuilder::new(chain_id)
.with_payment(sender, recipient, amount, asset)
.with_sender_private_key(private_key)
.with_nonce(nonce_key)
.with_expiry(Expiry::MaxBlockHeight(100))
.build()?;
}
The builder validates inputs and computes hashes. It signs the underlying Tempo transaction using the provided private key. The build method returns a BuiltTx containing the protobuf message, tx_hash, and effects_hash.
Nonce Management
The builder tracks nonce sequences per key. Each call to with_nonce with the same key increments the sequence. This prevents nonce reuse within a session.
#![allow(unused)]
fn main() {
let tx1 = builder.clone().with_nonce(key).build()?; // seq = 0
let tx2 = builder.clone().with_nonce(key).build()?; // seq = 1
}
For cross-session persistence, use WalletState nonce reservation.
Clients should reuse a fixed set of nonce keys rather than generating new keys per transaction. Creating new nonce keys incurs TIP-1000 state creation costs. See Tempo Integration for details on nonce key economics.
Certificate Manager
The CertManager validates and stores certificates from validators.
#![allow(unused)]
fn main() {
let cert_manager = CertManager::<_, _, SimpleAssembler>::new(verify_ctx, threshold);
cert_manager.collect_certificate(tx_hash, effects_hash, cert)?;
let qc = cert_manager.assemble_qc(tx_hash, effects_hash)?;
}
The manager verifies each certificate against the committee verification context. It rejects certificates from unknown validators or mismatched epochs. Duplicate certificates from the same signer are ignored.
Verification Context
The verification context binds certificate validation to a specific committee.
#![allow(unused)]
fn main() {
let verify_ctx = VerificationContext {
chain_id: 1337,
domain_tag: "tempo.fastpay.cert.v1".to_string(),
protocol_version: 1,
epoch: 1,
committee: validator_pubkeys,
};
}
The committee maps validator IDs to public keys. Certificates from validators not in the committee are rejected.
Wallet State
The WalletState struct manages nonces, balances, and pending transactions.
#![allow(unused)]
fn main() {
let mut wallet = WalletState::<MultiCertQC>::new(address, CacheLimits::default());
let seq = wallet.reserve_next_nonce(nonce_key);
wallet.add_pending_tx(pending);
wallet.record_qc(qc);
}
Nonce reservation prevents double-spending across restarts. The wallet journals state changes for crash recovery.
Nonce Reservation
The reserve_next_nonce method atomically claims the next sequence number. Reserved nonces can be released on failure or committed on success.
#![allow(unused)]
fn main() {
let seq = wallet.reserve_next_nonce(key);
// On success:
wallet.commit_reserved_nonce(key, seq);
// On failure:
wallet.release_reserved_nonce(key, seq);
}
This pattern ensures nonces are not reused even if the process crashes between reservation and submission.
Cache Management
The wallet enforces memory bounds through cache limits.
#![allow(unused)]
fn main() {
let limits = CacheLimits {
max_pending_txs: 100,
max_cached_certs: 1000,
max_cached_qcs: 100,
};
}
The prune_caches method removes settled transactions and old certificates when limits are exceeded.
Transport Layer
The transport trait abstracts network communication with sidecars.
#![allow(unused)]
fn main() {
#[async_trait(?Send)]
pub trait SidecarTransport {
async fn submit_fastpay(&self, request: Request, meta: RequestMeta) -> Result<Response>;
async fn get_bulletin_board(&self, request: Request) -> Result<Response>;
async fn get_validator_info(&self) -> Result<Response>;
async fn get_chain_head(&self) -> Result<Response>;
}
}
The ?Send bound enables WASM compatibility where futures are not Send.
Implementations
Three transport implementations are provided.
The MockTransport wraps an in-memory MockSidecar for testing. No network calls are made.
The GrpcTransport uses tonic for native gRPC calls. It supports automatic retry with exponential backoff.
The MultiValidatorTransport wraps multiple transports for parallel fan-out.
#![allow(unused)]
fn main() {
let transport = MultiValidatorTransport::new(vec![
MockTransport::new(sidecar_dave),
MockTransport::new(sidecar_edgar),
]);
}
Fan-out requests are sent in parallel. All responses are collected before returning.
Error Handling
The FastPayClientError enum covers all error cases.
| Variant | Cause |
|---|---|
Transport | Network or sidecar communication failure |
Builder | Invalid transaction parameters |
CertManager | Certificate validation failure |
Wallet | Nonce or state error |
Rejected | Validator rejected the transaction |
ThresholdNotMet | Insufficient certificates for QC |
Errors include context for debugging. Transport errors distinguish between timeouts, unavailable services, and validation failures.
WASM Compatibility
The library uses feature flags for platform-specific dependencies.
[features]
default = ["native"]
native = ["tokio", "tonic"]
wasm = ["wasm-bindgen-futures", "getrandom/js"]
WASM builds exclude native-only transports. The MockTransport works in both environments for testing.
Related Documentation
See System Architecture for the overall system design.
See Validator Sidecar for the gRPC service the client communicates with.
See Tempo Integration for transaction format details.