A practical implementation architecture for detecting launches, indexing token activity, tracking liquidity and volume, filtering markets, and exposing real-time alerts.
A token launches on Pons.
A basic scanner can print:
```text id="cw3b4t"
NEW TOKEN
MEMESTOCK
0x...
That is not enough for a useful trading product.
A production **Pons memestock scanner** should turn raw Robinhood Chain activity into structured market information:
```text id="x6z2w0"
Robinhood Chain
↓
Pons Events
↓
Launch Detection
↓
Token Registry
↓
Market State
↓
Liquidity / Volume
↓
Filters
↓
Alerts / API / Dashboard
Pons's current documentation describes Pons as a launch-and-trading protocol on Robinhood Chain and recommends indexing factory and market events directly from the chain as the authoritative source of truth.
The Pons ecosystem also uses memestock as a market-facing term for some projects. For example, the current MEMESTOCK project describes itself as a memecoin on Pons / Robinhood Chain paired against GME. This article uses “memestock” in that ecosystem sense; it is not presented as an official protocol classification.
1. Why build the scanner around blockchain events?
A weak implementation might work like:
```text id="15io52"
Every 5 seconds
↓
Request website data
↓
Compare
↓
Refresh
That creates several problems.
You can miss events.
You depend on a frontend.
You have no clean block cursor.
You make repeated requests.
You cannot easily reconstruct history.
A better architecture is:
```text id="w9xx9t"
Robinhood Chain
↓
Event Logs
↓
Indexer
↓
Database
↓
Scanner Engine
↓
API / Alerts
Pons's current integration documentation explicitly recommends indexing onchain launch and trading events as the source of truth.
That makes the blockchain the foundation of the scanner.
2. Pons versioning matters
One mistake I would avoid is hard-coding a single Pons market model.
The current Pons documentation describes an active protocol where tokens launch directly into a WETH pool, while the separate v2 documentation describes a launch lifecycle that begins on a bonding curve and later graduates into a Uniswap v4 pool.
So the scanner should have a version-aware layer:
```text id="4q71yl"
Launch
↓
Resolve Protocol Version
↓
Resolve Launch State
↓
Resolve Market
↓
Resolve Price Source
Do not write:
```typescript id="v2g1ws"
const pool = knownPoolAddress;
and assume every token uses it.
Instead:
```typescript id="x30kn9"
interface MarketResolver {
resolve(
token: string
): Promise;
}
That gives the scanner room to support multiple generations of Pons launches.
---
## 3. The TypeScript architecture
I would split the project into these components:
```text id="r6o6p4"
src/
├── chain/
├── indexer/
├── tokens/
├── markets/
├── scoring/
├── alerts/
├── api/
└── monitoring/
The data flow is:
```text id="8uxi5h"
CHAIN
↓
INDEXER
↓
TOKEN REGISTRY
↓
MARKET ENGINE
↓
FILTER ENGINE
↓
ALERT ROUTER
↓
API / DASHBOARD / BOTS
Each layer has one responsibility.
---
## 4. Connect to Robinhood Chain with viem
Robinhood Chain uses chain ID `4663` in the current Pons documentation. The current Pons integration docs also provide the network and deployed protocol contracts.
A basic client:
```typescript id="6d6s3l"
import {
createPublicClient,
http,
} from "viem";
export const client =
createPublicClient({
chain: {
id: 4663,
name: "Robinhood Chain",
nativeCurrency: {
name: "Ether",
symbol: "ETH",
decimals: 18,
},
rpcUrls: {
default: {
http: [
"https://rpc.mainnet.chain.robinhood.com",
],
},
},
},
transport: http(),
});
For production, keep RPC configuration external:
```typescript id="hd95w8"
const rpcUrl =
process.env.RH_RPC_URL;
if (!rpcUrl) {
throw new Error(
"RH_RPC_URL is required"
);
}
Do not put credentials or private configuration into the repository.
---
## 5. Start with the launch event
For the current Pons integration surface, the factory emits `TokenLaunched`.
The official docs provide the event definition and recommend indexing the factory event, then registering the emitted market for further event indexing.
A viem event definition:
```typescript id="cf09oh"
import {
parseAbiItem,
} from "viem";
export const tokenLaunchedEvent =
parseAbiItem(
"event TokenLaunched(address indexed token, address indexed deployer, address indexed dexFactory, address pairToken, address pool, uint256 dexId, uint256 launchConfigId, uint256 positionId, uint256 restrictionsEndBlock, uint256 initialBuyAmount)"
);
Then query events:
```typescript id="suzh5w"
const logs =
await client.getLogs({
address:
"0xA5aAb3F0c6EeadF30Ef1D3Eb997108E976351feB",
event:
tokenLaunchedEvent,
fromBlock: startBlock,
toBlock: "latest",
});
The important part is not the query itself.
It is turning each event into durable application state.
---
## 6. Normalize the launch event
Create an application model:
```typescript id="8j6zzj"
export interface PonsLaunch {
tokenAddress: string;
deployer: string;
pairToken?: string;
poolAddress?: string;
launchConfigId?: bigint;
launchBlock: bigint;
transactionHash: string;
version:
| "V1"
| "V2"
| "UNKNOWN";
createdAt: number;
}
Now the rest of the application does not need to understand raw ABI arguments.
It receives:
```text id="64b2t2"
token
deployer
pair
market
block
transaction
version
---
## 7. Use a durable block cursor
Do not query from the beginning of the chain on every restart.
Store:
```typescript id="p7z8p1"
interface IndexerCursor {
contractAddress: string;
lastProcessedBlock: bigint;
updatedAt: number;
}
The worker can then do:
```text id="zv2y8g"
Saved Block
↓
Fetch Next Range
↓
Process Events
↓
Commit Database
↓
Save New Block
This makes the indexer restartable.
A crash should not mean:
```text
"Start scanning the entire blockchain again."
8. Make event processing idempotent
Blockchain indexers can encounter the same event more than once during retries or reprocessing.
Use:
```text id="k4g6ec"
transactionHash
+
logIndex
as an event identity.
```typescript id="qt4qz4"
interface ChainEvent {
transactionHash: string;
logIndex: number;
blockNumber: bigint;
eventName: string;
contractAddress: string;
payload: unknown;
}
Database constraint:
```text id="6p6a3o"
UNIQUE(
transaction_hash,
log_index
)
Now this is safe:
```text id="w3m3fc"
Retry
↓
Same event
↓
Already processed
↓
Ignore
9. Build the token registry
The scanner needs a durable record for each discovered token.
```typescript id="n0vtdm"
export interface PonsToken {
address: string;
name?: string;
symbol?: string;
deployer: string;
pairToken?: string;
poolAddress?: string;
version:
| "V1"
| "V2"
| "UNKNOWN";
firstSeenBlock: bigint;
firstSeenAt: number;
}
Database table:
```text id="4fiz5o"
pons_tokens
------------------------
address
name
symbol
deployer
pair_token
pool_address
version
first_seen_block
first_seen_at
updated_at
Use the normalized contract address as the unique key.
10. Never trust the ticker alone
Memestock-style projects make token identity especially important.
A name can be copied.
A ticker can be copied.
An image can be copied.
The contract address is what your scanner should treat as the primary identifier.
Pons's current documentation explicitly warns that names and symbols can be copied and tells users to verify the token address.
So the scanner should display:
```text id="3dd2xn"
MEMESTOCK
Contract
0x41a2...
rather than:
```text id="l2gyrx"
MEMESTOCK
alone.
11. Read token metadata
After detecting a launch, query the token contract.
For example:
```typescript id="utw0e0"
const tokenAbi = [
{
name: "name",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [
{ type: "string" },
],
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [
{ type: "string" },
],
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [
{ type: "uint8" },
],
},
] as const;
Then:
```typescript id="p1g2m8"
const [
name,
symbol,
decimals,
] = await Promise.all([
client.readContract({
address: tokenAddress,
abi: tokenAbi,
functionName: "name",
}),
client.readContract({
address: tokenAddress,
abi: tokenAbi,
functionName: "symbol",
}),
client.readContract({
address: tokenAddress,
abi: tokenAbi,
functionName: "decimals",
}),
]);
Now the scanner has structured token metadata.
12. Resolve the market instead of assuming it
The current Pons launch architecture can expose the pair token and market/pool information directly from launch state. The integration docs recommend registering the emitted pool for current launches.
Model that:
```typescript id="lw6s3s"
export interface TokenMarket {
tokenAddress: string;
pairToken: string;
poolAddress?: string;
marketType:
| "POOL"
| "CURVE"
| "UNISWAP_V4"
| "UNKNOWN";
}
The scanner can now resolve:
```text id="4u82jf"
Token
↓
Launch Record
↓
Pair
↓
Current Market
instead of hard-coding a quote asset.
13. Track live market data
Once the market is known, create a snapshot:
```typescript id="v2g0q4"
export interface MarketSnapshot {
tokenAddress: string;
pairToken: string;
price: number;
liquidity?: number;
volume24h?: number;
buys?: number;
sells?: number;
updatedAt: number;
}
The scanner can expose:
```text id="m5y0ym"
MEMESTOCK
Price
0.00042 GME
Liquidity
$125K
24h Volume
$890K
Buys / Sells
1,241 / 934
The exact calculations depend on the market model and pair.
14. Current Pons v1-style pool monitoring
The current Pons documentation says current launches trade against WETH in their pool, with trading beginning immediately at launch; the integration surface recommends indexing the pool's Swap events.
That means a current pool scanner can do:
```text id="wymk6e"
TokenLaunched
↓
Pool Address
↓
Swap Events
↓
Trade History
↓
Price / Volume
A swap event can become:
```typescript id="0w2j6s"
interface PoolTrade {
tokenAddress: string;
trader: string;
amount0: bigint;
amount1: bigint;
blockNumber: bigint;
transactionHash: string;
}
The scanner then calculates the market metrics from actual chain activity.
15. V2 requires a different state engine
The separate Pons v2 documentation describes another lifecycle:
```text id="9fy19k"
CREATE
↓
CURVE TRADING
↓
CURVE COMPLETED
↓
GRADUATION
↓
UNISWAP V4 POOL
It documents events such as `TokenLaunched`, `CurveBuy`, `CurveSell`, `CurveBuyRefunded`, `CurveCompleted`, and `LaunchSwept`.
So your scanner should have a market-state abstraction:
```typescript id="p1fs2y"
type MarketPhase =
| "LAUNCHED"
| "CURVE"
| "GRADUATING"
| "POOL"
| "COMPLETED"
| "UNKNOWN";
Then:
```typescript id="f3s0c6"
interface LaunchState {
tokenAddress: string;
phase: MarketPhase;
pairToken: string;
marketAddress?: string;
updatedAt: number;
}
This keeps version-specific logic out of the dashboard.
---
## 16. Index trades into a common format
Regardless of market type, normalize trades:
```typescript id="6w4c72"
export interface NormalizedTrade {
tokenAddress: string;
side: "BUY" | "SELL";
trader: string;
quoteAmount: bigint;
tokenAmount: bigint;
blockNumber: bigint;
transactionHash: string;
timestamp: number;
}
Now the scanner doesn't care whether the trade came from:
```text id="6m79un"
Pool Swap
or:
```text id="h6qzbi"
CurveBuy / CurveSell
Both become:
```text id="d2m7co"
NormalizedTrade
That is a major simplification for analytics.
---
## 17. Calculate activity metrics
Once trades are normalized, compute metrics.
For example:
```typescript id="i2of9b"
interface TokenMetrics {
tokenAddress: string;
tradeCount: number;
buyCount: number;
sellCount: number;
volumeQuote: bigint;
uniqueTraders: number;
updatedAt: number;
}
Now your scanner can display:
```text id="jel3vy"
ACTIVITY
Trades
2,175
Buys
1,241
Sells
934
Unique Traders
608
24h Volume
$890K
These are measurements, not predictions.
---
## 18. Liquidity monitoring
Liquidity should be treated as a time series.
```typescript id="tmr1s5"
interface LiquiditySnapshot {
tokenAddress: string;
liquidityQuote: bigint;
timestamp: number;
}
Then detect large changes:
```typescript id="ag2q1e"
function liquidityChanged(
previous: bigint,
current: bigint,
thresholdBps: number
): boolean {
if (previous === 0n) {
return false;
}
const difference =
previous > current
? previous - current
: current - previous;
return (
difference * 10_000n >=
previous * BigInt(thresholdBps)
);
}
An alert might say:
```text id="5g5v5g"
LIQUIDITY CHANGE
MEMESTOCK
Previous:
$125,000
Current:
$101,000
Change:
-19.2%
The scanner reports what happened.
It does not need to assign a simplistic “good/bad” label.
19. Volume-spike detection
A simple activity detector:
```typescript id="4rv8q6"
function isVolumeSpike(
current: number,
baseline: number,
multiple: number
): boolean {
if (baseline <= 0) {
return false;
}
return (
current >=
baseline * multiple
);
}
For example:
```text id="rkx9wx"
Baseline:
$100K/day
Current:
$450K/day
Activity:
4.5× baseline
The alert engine can turn that into:
```text id="y6ltc1"
VOLUME SPIKE
$MEMESTOCK
4.5× baseline activity
This is much more useful than simply sorting tokens by price change.
---
## 20. Deployer analysis
The scanner can maintain a deployer profile:
```typescript id="fe0v4l"
interface DeployerProfile {
address: string;
launchCount: number;
firstSeenAt: number;
lastLaunchAt: number;
}
When a new launch is detected:
```text id="nkiw2o"
Deployer
0x123...
Launches
18
Last Launch
2 minutes ago
This gives users additional context without pretending that the deployer history alone determines token quality.
---
## 21. Build a filter engine
The scanner should allow configurable filters.
```typescript id="d5idbg"
interface ScannerFilters {
minLiquidity?: number;
minVolume24h?: number;
minTradeCount?: number;
maxTokenAgeMinutes?: number;
quoteTokens?: string[];
requireActiveMarket?: boolean;
}
Then:
```typescript id="32nkkc"
function matchesFilters(
token: TokenMetrics,
filters: ScannerFilters
): boolean {
if (
filters.minVolume24h !== undefined &&
Number(token.volumeQuote) <
filters.minVolume24h
) {
return false;
}
if (
filters.minTradeCount !== undefined &&
token.tradeCount <
filters.minTradeCount
) {
return false;
}
return true;
}
The user can then create different scanner views:
```text id="d2fgcy"
New Launches
High Activity
High Liquidity
Volume Spikes
Recent Graduations
My Watchlist
22. Risk signals should be explainable
Rather than:
```text id="0u7x49"
Score: 87
show the evidence:
```text id="1tn65u"
MEMESTOCK
Liquidity
$125K
24h Volume
$890K
Trades
2,175
Unique Traders
608
Deployer Launches
18
Market
ACTIVE
Contract
0x41...
Pons itself warns that launches can be volatile, illiquid, or lose all value, and its v2 documentation warns that names, symbols, and images are not unique identifiers.
A scanner should expose those facts rather than turn them into an unexplained recommendation.
23. Alert router
The scanner should produce normalized alerts:
```typescript id="7d0sly"
export type AlertType =
| "NEW_LAUNCH"
| "PRICE_MOVE"
| "VOLUME_SPIKE"
| "LIQUIDITY_CHANGE"
| "GRADUATION"
| "MARKET_STATE_CHANGE";
Model:
```typescript id="ksjv6z"
interface ScannerAlert {
type: AlertType;
tokenAddress: string;
title: string;
message: string;
createdAt: number;
}
Then route to:
```text id="xix72x"
Alert
│
├── Telegram
├── Discord
├── Webhook
├── Email
└── Dashboard
The scanner doesn't need to know how Telegram works.
It only creates alerts.
---
## 24. WebSocket updates
A real-time dashboard can subscribe to scanner events.
```text id="z4s6vs"
Blockchain
↓
Indexer
↓
Scanner Engine
↓
WebSocket
↓
Browser
When a new launch arrives:
```json id="v2qz1s"
{
"type": "NEW_LAUNCH",
"token": "0x41...",
"symbol": "MEMESTOCK"
}
The dashboard updates immediately.
No full-page refresh is required.
---
## 25. Build an API for bots
This is where the scanner becomes commercially useful.
A Pons sniper bot could call:
```http id="w7gr0e"
GET /api/launches/recent
A terminal could call:
```http id="s9w8qs"
GET /api/tokens/trending
An alert client could call:
```http id="w5d4xz"
GET /api/alerts
A portfolio system could call:
```http id="5axl58"
GET /api/tokens/:address
The same scanner backend powers multiple products.
---
## 26. Suggested API
```http id="cf5gcu"
GET /api/tokens
GET /api/tokens/:address
GET /api/tokens/recent
GET /api/tokens/trending
GET /api/launches
GET /api/launches/recent
GET /api/tokens/:address/metrics
GET /api/tokens/:address/trades
GET /api/deployers/:address
GET /api/alerts
For a client-facing system, add pagination and server-side filters from the beginning.
27. Database design
A practical PostgreSQL schema:
```text id="v7j6xx"
pons_tokens
address
symbol
name
deployer
pair_token
market_address
version
phase
first_seen_block
first_seen_at
pons_launches
token_address
launch_block
tx_hash
deployer
pair_token
pons_trades
token_address
tx_hash
log_index
trader
side
quote_amount
token_amount
block_number
timestamp
token_metrics
token_address
liquidity
volume_24h
trade_count
buy_count
sell_count
unique_traders
updated_at
scanner_alerts
id
type
token_address
payload
created_at
This allows the scanner to provide both live state and historical analytics.
---
## 28. Redis for live state
Use Redis for information that changes rapidly:
```text id="76o6sh"
latest market snapshot
alert cooldown
token cache
live metrics
scanner locks
API cache
For example:
``typescript id="5sd7v6"pons:token:${address}`,
await redis.set(
JSON.stringify(snapshot),
{
EX: 5,
}
);
PostgreSQL remains the durable source for history.
---
## 29. Health monitoring
The scanner itself needs observability.
Track:
```text id="ye8te6"
Current block
Last indexed block
Indexing lag
Events processed
Processing errors
RPC latency
Database latency
Active alerts
A health endpoint:
```http id="jz9w9f"
GET /health
might return:
```json id="t67joc"
{
"status": "healthy",
"chain": 4663,
"latestBlock": "9100012",
"indexedBlock": "9100009",
"lag": 3
}
Now you know whether the scanner itself is operating normally.
30. Reorganization and confirmations
A production indexer also needs to think about chain reorganizations.
A simple strategy is:
```text id="0m97up"
New Block
↓
Index Events
↓
Wait Confirmation Window
↓
Mark Final
For example:
```typescript id="7y2k04"
interface IndexedBlock {
number: bigint;
hash: string;
parentHash: string;
finalized: boolean;
}
The exact confirmation policy should match your application's risk tolerance.
For an alert-only scanner, a short provisional state may be acceptable.
For an execution-triggering bot, stronger confirmation logic may be appropriate.
31. Scanner → sniper architecture
Once the scanner has normalized events:
```text id="f0j5bi"
Pons Scanner
↓
New Launch
↓
Token Validation
↓
Strategy Filter
↓
Risk
↓
Execution
The scanner should not contain the sniper's trading rules.
This allows:
```text id="1i9z09"
Same Scanner
│
├── Manual Alert
├── Sniper Strategy
├── Copy Strategy
└── Trading Terminal
That is much easier to maintain.
32. Scanner → trading terminal
The same API can feed a Pons trading terminal:
```text id="vfwg96"
Pons Terminal
│
├── New Launches
├── Trending
├── Watchlist
├── Token Details
└── Alerts
↓
Order Panel
↓
Execution
This gives you a natural upgrade path from:
```text id="pe4r5y"
Scanner
to:
```text id="h4z0uo"
Trading Platform
without throwing away the original architecture.
---
## 33. Mobile-safe interface
For a mobile dashboard, avoid large data tables.
Use stacked cards:
```text id="5l9s16"
MEMESTOCK
──────────────
Price
0.00042 GME
Liquidity
$125K
24h Volume
$890K
Buys / Sells
1,241 / 934
Market
ACTIVE
Deployer
0x41...
[ View Token ]
This is easier to consume on a phone and also makes alert interfaces more usable.
34. Suggested repository
```text id="9p7ciw"
pons-memestock-scanner/
│
├── src/
│ ├── chain/
│ │ ├── client.ts
│ │ ├── contracts.ts
│ │ └── events.ts
│ │
│ ├── indexer/
│ │ ├── worker.ts
│ │ ├── cursor.ts
│ │ └── processor.ts
│ │
│ ├── tokens/
│ │ ├── registry.ts
│ │ └── metadata.ts
│ │
│ ├── markets/
│ │ ├── resolver.ts
│ │ ├── pricing.ts
│ │ └── metrics.ts
│ │
│ ├── filters/
│ │ └── scanner.ts
│ │
│ ├── alerts/
│ │ ├── rules.ts
│ │ └── router.ts
│ │
│ ├── api/
│ │ ├── tokens.ts
│ │ ├── launches.ts
│ │ └── alerts.ts
│ │
│ └── monitoring/
│ └── health.ts
│
├── database/
├── tests/
├── .env.example
├── package.json
└── README.md
This is enough structure to turn the project into a real service instead of a single script.
---
## 35. End-to-end scanner flow
The complete process becomes:
```text id="q14g6u"
ROBINHOOD CHAIN
│
▼
PONS FACTORY
│
▼
LAUNCH EVENT
│
▼
TOKEN REGISTRY
│
┌────────┴────────┐
▼ ▼
METADATA DEPLOYER
│ │
└────────┬────────┘
▼
MARKET STATE
│
┌─────────┼─────────┐
▼ ▼ ▼
PRICE LIQUIDITY VOLUME
│ │ │
└─────────┼─────────┘
▼
FILTER ENGINE
│
┌─────────┼─────────┐
▼ ▼ ▼
ALERTS API DASHBOARD
│ │ │
└─────────┼─────────┘
▼
TRADING SYSTEMS
This is the core architecture I would use for a client-ready Pons scanner.
Conclusion
A Pons memestock scanner on Robinhood Chain should not be built as a web scraper or a list of token names.
The stronger architecture is:
```text id="7rj9zv"
Onchain Events
↓
Indexer
↓
Token Registry
↓
Market Resolver
↓
Price / Liquidity / Volume
↓
Filters
↓
Alerts / API / Dashboard
↓
Trading Systems
Pons's current documentation provides the integration surface for this design: Robinhood Chain support, launch contracts, onchain events, token state, market information, and version-specific trading flows. The current docs explicitly recommend indexing events as the source of truth.
The key engineering principle is:
> **Detect onchain activity first. Normalize it into a clean market model. Then let strategies decide what to do.**
That keeps the scanner reusable.
The same data engine can power a **Pons memestock scanner**, a launch monitor, a trading terminal, a sniper strategy, an alert service, or a broader Robinhood Chain trading platform.
## Building a custom Pons scanner?
I build custom **Pons and Robinhood Chain trading infrastructure**, including:
* Pons memestock scanners
* Pons launch monitors
* Pons token trackers
* Pons trading terminals
* Pons sniper bots
* Pons copy-trading systems
* real-time market-data APIs
* alerting infrastructure
* automated execution systems
The scanner can be built as a standalone dashboard, an API for an existing application, or the market-data layer behind an automated trading system.
Top comments (0)