Aptos Transaction Stream - gRPC Overview
Stream finalized Aptos transactions over gRPC with aptos.indexer.v1.RawData/GetTransactions. Covers the request and response shapes, server-side filtering, batch semantics, metering, and rate-limit scope.
Early access — not yet in production
The Aptos Transaction Stream is not generally available. Endpoint hostname and API-key handling are assigned during onboarding, and we have not published delivery-lag figures because the service is not yet running in production. Email support@dwellir.com to join the early-access list.
Dwellir's Aptos Transaction Stream is a server-streaming gRPC service that delivers finalized transactions in version order without polling. It is aimed at indexers, analytics backends, trading systems, and monitoring pipelines that need a continuous feed rather than repeated REST requests.
Service Definition
The service is the standard upstream Aptos indexer gRPC API. There is no Dwellir-specific service, wrapper, or message type.
| Service | aptos.indexer.v1.RawData |
| Method | GetTransactions |
| Type | Server-streaming RPC |
| Protobufs | aptos/indexer/v1/raw_data.proto, filter.proto, aptos/transaction/v1/transaction.proto |
Generate your client stubs from the upstream aptos-core protos. If you already
vendor them for another provider, those are the correct definitions and no change
is needed.
service RawData {
rpc GetTransactions(GetTransactionsRequest) returns (stream TransactionsResponse);
}Correction to earlier revisions of this page
Earlier versions of these docs showed a TransactionStreamClient class, a
subscribe() method, and a stream.aptos.dwellir.com host. None of those exist.
The correct client is the generated RawData stub, the correct call is
GetTransactions, and the endpoint is assigned at onboarding.
Request
message GetTransactionsRequest {
optional uint64 starting_version = 1;
optional uint64 transactions_count = 2;
optional uint64 batch_size = 3;
optional BooleanTransactionFilter transaction_filter = 4;
}| Field | Behaviour |
|---|---|
starting_version | Ledger version to start from. Omit to start near the head of chain. |
transactions_count | Stop after this many transactions. Omit for an unbounded stream. |
batch_size | Transactions per TransactionsResponse. Defaults to 1000. Values above 1000 are rejected. |
transaction_filter | Server-side filter. Non-matching transactions are never sent. |
There are no includeEvents, includeChanges, or field-mask options. Every
transaction is delivered in full, and filtering is the only way to reduce what
crosses the wire.
Response
Each message on the stream is a batch, not a single transaction:
message TransactionsResponse {
repeated aptos.transaction.v1.Transaction transactions = 1;
optional uint64 chain_id = 2;
optional ProcessedRange processed_range = 3;
}
message ProcessedRange {
uint64 first_version = 1;
uint64 last_version = 2;
}Your data handler receives a TransactionsResponse and must iterate
response.transactions. Treating the message itself as a transaction is the most
common integration mistake.
processed_range reports the version span the server covered while producing the
message, including versions that your filter removed. Use it — not the last
transaction version — to advance your checkpoint, so a filtered stream does not
appear to stall across long runs of non-matching transactions.
Transaction hashes are bytes
Transaction.info.hash is a bytes field, not a hex string. Hex-encode it before
logging or comparing against explorer output. Useful top-level fields are
version, timestamp, epoch, block_height, type, and info.
Server-Side Filtering
transaction_filter is evaluated on the node. Transactions that do not match are
never serialized, never sent, and never metered. This is the main lever for both
latency and cost.
BooleanTransactionFilter composes api_filter leaves with logical_and,
logical_or, and logical_not:
message APIFilter {
oneof filter {
TransactionRootFilter transaction_root_filter = 1; // success, transaction_type
UserTransactionFilter user_transaction_filter = 2; // sender, entry function
EventFilter event_filter = 3; // struct type, data substring
}
}A filter for successful user transactions calling a specific entry function:
const transactionFilter = {
logical_and: {
filters: [
{ api_filter: { transaction_root_filter: { success: true } } },
{
api_filter: {
user_transaction_filter: {
payload_filter: {
entry_function_filter: {
address: '0x1',
module_name: 'coin',
function: 'transfer',
},
},
},
},
},
],
},
};This differs from Sui
Sui's SubscribeCheckpoints has no server-side filter, so our
Sui gRPC docs tell you to
filter client-side. Aptos is the opposite: filter on the server, because filtering
is what keeps transactions off your bill.
Connection and Authentication
The endpoint hostname is assigned when your account is provisioned for the stream. Connect over TLS on port 443 and pass your existing Dwellir dashboard API key as gRPC metadata:
import { credentials, Metadata } from '@grpc/grpc-js';
const metadata = new Metadata();
metadata.add('x-api-key', process.env.DWELLIR_API_KEY);
const client = new RawDataClient(
process.env.DWELLIR_APTOS_STREAM_ENDPOINT, // assigned at onboarding
credentials.createSsl()
);
const stream = client.GetTransactions(request, metadata);There is no separate streaming credential — the stream uses the same key as your other Dwellir endpoints. See Authentication for the full details, including how gRPC metadata differs from the key-in-path form used by the Aptos REST and GraphQL endpoints.
Metering
The billed unit is the TransactionsResponse message, consistent with how we
meter Sui gRPC per delivered checkpoint.
transaction_filteris applied before metering. Filtered-out transactions are neither delivered nor counted.- A filtered stream still emits progress messages carrying
processed_rangefor the versions it dropped. Those messages are billed. - At the head of chain, batches are far smaller than the 1000 ceiling, because the
server sends roughly one message per block rather than waiting to fill a batch.
Message counts at the tail are therefore driven by block rate, not by
batch_size.
For an unfiltered head-of-chain tail, plan on approximately one message per block. We will provide exact metering in writing before you commit to the stream.
Rate Limits
- No cap on concurrent subscriptions per key.
- Your plan's rate limit applies per delivered message.
- Rate limits are scoped per account, not per key. A stream added to an account already running other workloads draws from the same budget. See Rate Limits.
Plan the account layout before you subscribe
Because the limit is per account, a busy Aptos tail can throttle unrelated production traffic on the same account — including a Sui gRPC subscription. If the two workloads must not compete, separate them onto different accounts or size the plan for their combined message rate before going live.
Region
Aptos is served from Sweden, on the same ingress as our Sui endpoints. If you already run close to that region for Sui, the Aptos stream lands in the same place.
We have not published p50/p95/p99 delivery lag at the head of chain. The service is not in production, so the figures do not exist yet and we would rather say so than publish an estimate.
Choosing an Operating Mode
| Goal | Approach |
|---|---|
| Live tail only | Omit starting_version. See Real-Time Streaming. |
| Backfill or recovery | Set starting_version to your last checkpoint. See Historical Replay. |
| Catch up, then tail | One stream from your stored version; it transitions into the live tail on its own once it reaches the head. |
Implementation Checklist
- Iterate
response.transactions; never treat a message as a single transaction. - Persist progress from
processed_range.last_versionso reconnects resume without gaps. - Make downstream writes idempotent — a reconnect can redeliver a version you already processed.
- Filter server-side wherever possible; it reduces both latency and metered messages.
- Handle backpressure explicitly so a slow consumer does not stall the stream reader.
- Monitor reconnect frequency, version lag, and messages per second.
- Keep REST or GraphQL available for targeted lookups and operator debugging.
Related Guides
- Authentication — API key handling for gRPC, REST, and GraphQL
- Real-Time Streaming — tailing the head of chain
- Historical Replay — backfills and recovery
- Custom Processors — worker architecture on batch semantics
- Rate Limits — plan limits and per-account scope