Overview
Many products need to answer one deceptively simple question: “Did assets just land in one of our addresses?” Exchanges, neobanks, on-ramps, and payment apps all maintain a large, changing set of receiving addresses and need to react the instant funds arrive. Doing this with raw RPC polling is inefficient (you poll thousands of addresses on every block), and standing up custom indexing infrastructure is overkill. Turbo sits in between: you get a fast, queryable index that you point at a set of addresses you maintain, without running any indexing infrastructure yourself. This guide shows how to build that with a Turbo pipeline:Maintain an address set
Store the addresses you care about in a dynamic table and add/remove them at any time — no redeploy.
Query the index
Filter the transfer stream down to your addresses and land the matches in Postgres you can query directly.
Get alerted on arrival
Fire a webhook the moment a watched address receives funds.
How it works
- The source is a curated transfer dataset (for example
base.erc20_transfers) — a normalized, cross-chain stream of token transfers. - The dynamic table holds the addresses you’re watching. It’s backed by Postgres, so you can
INSERT/DELETEaddresses live and the pipeline picks up changes within a second or two — no redeploy. - A SQL transform keeps only transfers whose
recipientis in your watched set, usingdynamic_table_check(). - Sinks deliver the result: a Postgres table you can query, a webhook for real-time alerts, or a running-balance table (all three can run in the same pipeline).
Prerequisites
- The Turbo CLI extension installed, and a Goldsky account logged in to your project.
- A Postgres database for the dynamic table and sinks. You have two options:
- Goldsky-hosted Postgres (recommended) — Goldsky provisions and manages it for you. Available on Scale plans and above.
- Bring your own Postgres — Neon, Supabase, RDS, Cloud SQL, or self-hosted.
Step 1: Provision Postgres
The dynamic table and the sinks all connect to Postgres through a Goldsky secret. You can use one secret for everything in the pipeline.- Goldsky-hosted Postgres (recommended)
- Bring your own Postgres
Provision a managed database and register its secret in one step:The command prints the secret’s name, ID, and type. Look up the raw connection string in the web app under Sinks when you need to connect a SQL client. You can also provision from the web app under Sinks → New sink → Hosted Postgres.
Hosted Postgres is a Scale plan feature (and above). Adding a credit card to your account upgrades you to Scale. See pricing.
MY_POSTGRES.
Step 2: Create the pipeline
Create a file namedaddress-watcher.yaml. This pipeline maintains a watched_addresses table, filters the Base ERC-20 transfer stream down to transfers received by one of those addresses, and writes the matches to a Postgres table you can query.
address-watcher.yaml
What each piece does
What each piece does
sources.base_transfers— the curatedbase.erc20_transfersdataset.start_at: latestprocesses only new transfers going forward. Usestart_at: earliestto backfill history first.transforms.watched_addresses— the dynamic table.schema: publiccreatespublic.watched_addresseswith avalue(primary key) column and anupdated_attimestamp. (If you omitschema, the dynamic table defaults to astreamlingschema — setting it explicitly keeps everything in one place.)transforms.incoming_transfers— filters the stream withdynamic_table_check('watched_addresses', recipient), which returnstruewhenrecipientexists in the dynamic table. Changerecipienttosender(or check both) to track outgoing transfers too.sinks.incoming_index— writes matches topublic.incoming_transfers.primary_key: idmakes writes idempotent upserts.
Step 3: Deploy
Validate, then apply:Step 4: Add addresses to watch
The dynamic table is just a Postgres table. Connect any SQL client to the same database and insert the addresses you want to watch. Changes take effect within a second or two — no redeploy.Step 5: Query the index
Once addresses are being watched and matching transfers arrive, query the Postgres table directly:Get a webhook when funds land
To be notified the moment a watched address receives funds, add a webhook sink. It POSTs each matching transfer to your endpoint. You can run it alongside the Postgres sink in the same pipeline. First, create an auth secret for your endpoint:sinks: in address-watcher.yaml:
The webhook sink guarantees at-least-once delivery and retries
5xx/timeout responses with backoff. Your endpoint should be idempotent — dedupe on id (or transaction_hash) in case a batch is retried.Optional: track a running balance per address
The queryable index above logs every transfer that landed. If you also want to answer “which of these addresses currently hold funds?”, you can optionally add a running balance per watched address using thepostgres_aggregate sink, which maintains a live sum in a database trigger.
The example below is a self-contained pipeline you can adapt — add its transform and sink to address-watcher.yaml, or run it on its own. Take the parts you need.
It tracks the net balance each watched address holds of a single token (USDC on Base). It counts credits (incoming) as positive and debits (outgoing) as negative, so the balance reflects deposits minus withdrawals.
balance-watcher.yaml
Each transfer produces two distinct rows — a
-credit entry for the recipient and a -debit entry for the sender — so entry_id (not the raw transfer id) is the primary key. Reusing id would make the two rows collide in the aggregate’s landing table.Coverage and limitations
Multiple chains
Multiple chains
Add more sources (
ethereum.erc20_transfers, arbitrum.erc20_transfers, and so on) and UNION ALL them in the SQL transform, tagging each with a chain column. See multi-chain monitoring. The same watched_addresses table can back all chains.Point-in-time balance datasets
Point-in-time balance datasets
Some chains expose dedicated balance datasets (for example
solana.native_balances, stellar_mainnet.balances). For EVM chains, derive balances from the transfer stream as shown above, or query a single address on demand via Edge RPC eth_getBalance.Related
- Dynamic Tables — full reference for the watched-address table
- EVM Sources — available datasets and fields
- PostgreSQL sink and PostgreSQL aggregation sink
- Webhook sink