Substreams module for indexing Request Network ERC20FeeProxy payment events across multiple blockchain networks. Currently supports TRON with a multi-chain architecture ready for additional networks.
- Rust with
wasm32-unknown-unknowntarget - Substreams CLI
- Docker and Docker Compose
- Buf CLI (for protobuf generation)
- Streamingfast API Token
rustup target add wasm32-unknown-unknown# macOS
brew install streamingfast/tap/substreams
# Linux
curl -sSL https://github.com/streamingfast/substreams/releases/latest/download/substreams_linux_x86_64.tar.gz | tar xz
sudo mv substreams /usr/local/bin/brew install bufbuild/buf/bufpayments-substream/
├── tron/ # TRON substream module
│ ├── src/
│ │ ├── lib.rs # Main substream logic
│ │ └── pb/ # Generated protobuf code
│ ├── proto/
│ │ └── request/tron/v1/
│ │ └── payments.proto # Payment message definitions
│ ├── schema.sql # PostgreSQL schema for SQL sink
│ ├── schema.graphql # GraphQL schema (for future use)
│ ├── substreams.yaml # Substream manifest
│ ├── Cargo.toml # Rust dependencies
│ └── Makefile # Build commands
├── docker-compose.yml # Local development setup
├── docker-compose.prod.yml # Production deployment (Easypanel)
├── Dockerfile.sink # SQL sink Docker image
└── .env.example # Environment variables template
Edit tron/src/lib.rs to change payment parsing logic:
// Example: Add new field extraction
fn parse_transfer_with_reference_and_fee(...) -> Option<Payment> {
// Your parsing logic here
}Edit tron/proto/request/tron/v1/payments.proto:
message Payment {
string token_address = 1;
// Add new fields here
string new_field = 13;
}After changing .proto files, regenerate the Rust code:
cd tron
make protogenEdit tron/schema.sql to add new columns:
ALTER TABLE payments ADD COLUMN new_field TEXT;cd tron
# Build WASM module
make build
# Run unit tests
make test
# Package into .spkg file
make packageTest the substream against live blockchain data without a database:
cd tron
# Set your API token
export SUBSTREAMS_API_TOKEN="your-token-here"
# Run against TRON mainnet (100 blocks from deployment)
substreams run ./request-network-tron-v0.1.0.spkg \
map_erc20_fee_proxy_payments \
-e mainnet.tron.streamingfast.io:443 \
--start-block 79216121 \
--stop-block +100# JSON output (for debugging)
substreams run ... -o json
# Protobuf output (default)
substreams run ... -o protoTest the full pipeline with a local PostgreSQL database:
# Create .env file
cp .env.example .env
# Edit .env with your values
# SUBSTREAMS_API_TOKEN=your-token
# POSTGRES_PASSWORD=your-password
# Start PostgreSQL and sink
docker compose up -d
# View logs
docker compose logs -f sink# Connect to PostgreSQL
docker exec -it tron-payments-db psql -U postgres -d tron_payments
# Query payments
SELECT * FROM payments LIMIT 10;
SELECT chain, COUNT(*) FROM payments GROUP BY chain;docker compose down
# Remove data volumes (fresh start)
docker compose down -vParameters are configured in tron/substreams.yaml:
params:
map_erc20_fee_proxy_payments: |
mainnet_proxy_address=TCUDPYnS9dH3WvFEaE7wN7vnDa51J4R4fd
chain=tron| Parameter | Description | Example |
|---|---|---|
mainnet_proxy_address |
ERC20FeeProxy contract address | TCUDPYnS9dH3WvFEaE7wN7vnDa51J4R4fd |
chain |
Chain identifier for multi-chain support | tron, ethereum, polygon |
| Variable | Description |
|---|---|
SUBSTREAMS_API_TOKEN |
Streamingfast API authentication token |
DSN |
PostgreSQL connection string |
POSTGRES_PASSWORD |
PostgreSQL password (local dev) |
In Easypanel:
- Create a new PostgreSQL service
- Note the internal hostname (e.g.,
shared_payments-substream-postgres)
- Create a new Docker Compose app pointing to this repository
- Set compose file path:
docker-compose.prod.yml - Configure environment variables:
DSN=postgres://postgres:PASSWORD@shared_payments-substream-postgres:5432/shared?sslmode=disable
SUBSTREAMS_API_TOKEN=your-streamingfast-token
- Enable "Create .env file" checkbox
- Deploy
Check logs in Easypanel to verify:
- Connection to Streamingfast endpoint
- Database writes (
db_flush_rate) - Block processing (
progress_total_processed_blocks)
# Build the Docker image
docker build -f Dockerfile.sink -t payments-sink .
# Run with environment variables
docker run -d \
-e DSN="postgres://user:pass@host:5432/db?sslmode=disable" \
-e SUBSTREAMS_API_TOKEN="your-token" \
payments-sinkThe PostgreSQL database is exposed via Hasura, providing a GraphQL API for the Request Network SDK to query payments.
┌─────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ TRON │────▶│ Substream │────▶│ PostgreSQL │────▶│ Hasura │
│ Blockchain │ │ Sink │ │ (payments) │ │ GraphQL │
└─────────────┘ └──────────────┘ └──────────────┘ └──────┬───────┘
│
▼
┌──────────────┐
│ Request SDK │
│ (payment │
│ detection) │
└──────────────┘
- Staging:
https://graphql.stage.request.network/v1/graphql - Production:
https://graphql.request.network/v1/graphql
query GetPayments($reference: String!, $chain: String) {
payments(
where: {
payment_reference: { _eq: $reference }
chain: { _eq: $chain }
}
order_by: { block_number: asc }
) {
id
chain
tx_hash
block_number
timestamp
token_address
from_address
to_address
amount
fee_amount
fee_address
payment_reference
contract_address
}
}curl -X POST https://graphql.stage.request.network/v1/graphql \
-H "Content-Type: application/json" \
-d '{
"query": "query { payments(where: { payment_reference: { _eq: \"0xabc123...\" } }) { tx_hash amount chain } }"
}'The Request Network SDK uses a custom info retriever to query the Hasura endpoint for TRON payments. See the @requestnetwork/payment-detection package for implementation details.
The payments table includes a chain field to support multiple networks:
SELECT chain, COUNT(*) as payments FROM payments GROUP BY chain;- Create a new substream module (e.g.,
ethereum/) - Configure the appropriate Streamingfast endpoint
- Set the
chainparameter insubstreams.yaml - Deploy an additional sink pointing to the same database
Example for Ethereum:
params:
map_erc20_fee_proxy_payments: |
mainnet_proxy_address=0x...
chain=ethereumIf you see cursor module hash mismatch:
-- Connect to PostgreSQL and reset
DROP TABLE IF EXISTS payments;
DROP TABLE IF EXISTS cursors;Then redeploy the sink.
Schema changes require dropping and recreating tables:
DROP TABLE IF EXISTS payments;
DROP TABLE IF EXISTS cursors;Ensure services are on the same Docker network. In docker-compose.prod.yml:
networks:
easypanel:
external: true| Network | Endpoint |
|---|---|
| TRON Mainnet | mainnet.tron.streamingfast.io:443 |
| TRON Shasta (Testnet) | shasta.tron.streamingfast.io:443 |
| Ethereum Mainnet | mainnet.eth.streamingfast.io:443 |
MIT