DEV Community

Cover image for Building a Pons Copy Trading Bot on Robinhood Chain with Python
BornToWin
BornToWin

Posted on

Building a Pons Copy Trading Bot on Robinhood Chain with Python

A practical implementation architecture for monitoring wallets, decoding trades, applying copy rules, sizing positions, executing trades, and reconciling on-chain state.

A Pons copy trading bot watches a source wallet, detects supported trading activity, decides whether the trade should be copied, and executes a separate position from the follower wallet.

The simple version looks like:

Source Wallet
      ↓
Detect Trade
      ↓
Copy Trade
Enter fullscreen mode Exit fullscreen mode

A useful implementation looks more like:

Source Wallet
      ↓
Block / Event Monitor
      ↓
Trade Decoder
      ↓
Copy Strategy
      ↓
Risk Engine
      ↓
Position Sizing
      ↓
Fresh Quote
      ↓
Pons Execution
      ↓
Transaction Monitor
      ↓
Portfolio
      ↓
Reconciliation
Enter fullscreen mode Exit fullscreen mode

Pons is a launch-and-trade application on Robinhood Chain, and its current documentation describes live token trading through each token's own pool.

The goal of this article is to show how I would structure a Pons copy trading bot as a real automation system rather than a script that blindly mirrors wallet transactions.


What Is a Pons Copy Trading Bot?

A Pons copy trading bot monitors one or more wallets and converts their supported trades into signals.

For example:

Trader Wallet
     ↓
BUY Token A
     ↓
Copy Engine
     ↓
Risk Check
     ↓
Follower Wallet
     ↓
BUY Token A
Enter fullscreen mode Exit fullscreen mode

But the follower does not have to copy the transaction exactly.

It can apply its own:

  • copy ratio
  • maximum position
  • slippage
  • token filters
  • exposure limits
  • signal-age limits
  • gas reserve
  • emergency stop

That is the difference between transaction replication and copy-trading infrastructure.


Why the Architecture Matters

The source wallet controls only the signal.

Your system should control the money.

SOURCE
  ↓
OBSERVATION
  ↓
STRATEGY
  ↓
RISK
  ↓
EXECUTION
Enter fullscreen mode Exit fullscreen mode

That means the source wallet should never directly determine:

how much you buy
how much you risk
which tokens you accept
how much slippage you tolerate
how many positions you can hold
Enter fullscreen mode Exit fullscreen mode

Those are follower-side decisions.


Project Architecture

I would separate the implementation into:

bot/
├── monitor/
│   └── wallet_monitor.py
├── decoder/
│   └── trade_decoder.py
├── strategy/
│   └── copy_strategy.py
├── risk/
│   └── risk_engine.py
├── sizing/
│   └── position_sizer.py
├── execution/
│   └── pons_executor.py
├── portfolio/
│   └── position_manager.py
├── reconciliation/
│   └── reconciler.py
└── engine.py

config/
├── wallets.json
└── default.yaml

tests/
Enter fullscreen mode Exit fullscreen mode

The exact structure can differ, but the separation should remain.

A monitor should monitor.

An executor should execute.

A portfolio manager should track positions.


1. Connect to Robinhood Chain

Robinhood Chain mainnet currently uses chain ID 4663 and ETH as its native gas asset.

A minimal Python connection can start with:

from web3 import Web3
import os

rpc_url = os.environ["RH_RPC_URL"]

web3 = Web3(
    Web3.HTTPProvider(rpc_url)
)

if not web3.is_connected():
    raise RuntimeError("Failed to connect to Robinhood Chain")

print("Connected:", web3.is_connected())
print("Chain ID:", web3.eth.chain_id)
Enter fullscreen mode Exit fullscreen mode

The important production rule is:

Do not assume the RPC is healthy.
Enter fullscreen mode Exit fullscreen mode

Add:

timeouts
retries
backoff
health checks
Enter fullscreen mode Exit fullscreen mode

And distinguish:

RPC error
Enter fullscreen mode Exit fullscreen mode

from:

transaction reverted
Enter fullscreen mode Exit fullscreen mode

They are not the same failure.


2. Configure Source Wallets

The first useful configuration is a list of source wallets.

For example:

{
  "wallets": [
    {
      "address": "0xSourceWallet...",
      "enabled": true,
      "label": "alpha-trader",
      "copy_ratio": 0.10,
      "max_position_eth": 0.02
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This configuration gives the system:

wallet identity
copy ratio
position limit
enable/disable state
Enter fullscreen mode Exit fullscreen mode

A production implementation should also support:

per-wallet risk
priority
score
token filters
daily limits
Enter fullscreen mode Exit fullscreen mode

3. Monitor New Blocks

One practical approach is block polling.

last_block = web3.eth.block_number

while True:
    current_block = web3.eth.block_number

    if current_block > last_block:
        for block_number in range(
            last_block + 1,
            current_block + 1
        ):
            process_block(block_number)

        last_block = current_block
Enter fullscreen mode Exit fullscreen mode

The monitor's job is to identify transactions involving the source wallet.

It should not immediately execute trades.

Instead:

Block
  ↓
Source wallet transaction
  ↓
Candidate transaction
Enter fullscreen mode Exit fullscreen mode

Then hand that transaction to the decoder.


4. Detect Source Wallet Activity

A source wallet may produce many types of transactions:

ETH transfer
Token transfer
Approval
Launch
Buy
Sell
Other contract call
Enter fullscreen mode Exit fullscreen mode

The monitor should only forward transactions that could represent a supported trade.

def is_candidate(tx, source_wallet):
    return (
        tx["from"].lower()
        == source_wallet.lower()
    )
Enter fullscreen mode Exit fullscreen mode

That is only the first filter.

You still need to inspect the receipt and contract interaction.


5. Decode the Actual Trade

This is one of the most important parts of the system.

A transaction alone may not tell you the complete economic action.

The decoder should inspect:

transaction input
receipt
logs
Transfer events
router interaction
token addresses
amounts
Enter fullscreen mode Exit fullscreen mode

The normalized output might look like:

from dataclasses import dataclass
from typing import Literal, Optional


@dataclass
class SourceTrade:
    trade_id: str
    wallet: str
    token: str
    side: Literal["BUY", "SELL"]
    amount_in: int
    amount_out: int
    tx_hash: str
    block_number: int
    timestamp: int
Enter fullscreen mode Exit fullscreen mode

Now the strategy layer doesn't need to know anything about raw blockchain logs.


6. Deduplicate Trades

The same transaction may be observed more than once.

This can happen after:

RPC reconnect
process restart
polling overlap
event + polling
Enter fullscreen mode Exit fullscreen mode

So generate a deterministic ID.

For example:

trade_id = f"{chain_id}:{tx_hash}"
Enter fullscreen mode Exit fullscreen mode

Then:

if registry.exists(trade_id):
    return

registry.add(trade_id)
Enter fullscreen mode Exit fullscreen mode

The workflow becomes:

Trade
 ↓
Registry
 ↓
Seen?
 ├── YES → ignore
 └── NO  → evaluate
Enter fullscreen mode Exit fullscreen mode

Duplicate prevention is one of the most important pieces of copy-trading infrastructure.


7. Copy Strategy

Now we have a real trade.

The next question:

Should the follower copy it?

Create a strategy function:

def should_copy(trade: SourceTrade) -> bool:
    if trade.side not in {"BUY", "SELL"}:
        return False

    if trade.token in BLOCKED_TOKENS:
        return False

    return True
Enter fullscreen mode Exit fullscreen mode

The production version can add:

token allowlist
token denylist
minimum trade size
maximum trade size
source-wallet score
trade age
liquidity
existing position
Enter fullscreen mode Exit fullscreen mode

The important thing is to keep the rules configurable.


8. Copy Ratio

A simple copy strategy might use a percentage of the source trade.

Suppose:

Source:
0.50 ETH

Copy ratio:
10%
Enter fullscreen mode Exit fullscreen mode

Then:

Follower:
0.05 ETH
Enter fullscreen mode Exit fullscreen mode

Using integer arithmetic:

BPS = 10_000

def copy_amount(
    source_amount: int,
    copy_ratio_bps: int,
) -> int:
    return (
        source_amount
        * copy_ratio_bps
        // BPS
    )
Enter fullscreen mode Exit fullscreen mode

This avoids floating-point calculations.


9. Add a Maximum Position

Even if the copy calculation says:

0.08 ETH
Enter fullscreen mode Exit fullscreen mode

the strategy might only allow:

0.02 ETH
Enter fullscreen mode Exit fullscreen mode

So:

amount = min(
    proportional_amount,
    max_position
)
Enter fullscreen mode Exit fullscreen mode

The final process becomes:

Source Trade
    ↓
Copy Ratio
    ↓
Maximum Position
    ↓
Risk Limits
    ↓
Final Position Size
Enter fullscreen mode Exit fullscreen mode

This is much safer than blindly copying source trade size.


10. Signal Freshness

Copy trading has a latency problem.

The source transaction may already be on-chain before the follower detects it.

So every signal should have an age:

trade_age = now - trade.timestamp
Enter fullscreen mode Exit fullscreen mode

Then:

if trade_age > MAX_SIGNAL_AGE_SECONDS:
    return False
Enter fullscreen mode Exit fullscreen mode

The pipeline becomes:

Source Trade
      ↓
Detection
      ↓
Age Check
      ↓
Fresh?
   ├── NO → SKIP
   └── YES
Enter fullscreen mode Exit fullscreen mode

A stale signal can produce a completely different execution outcome from the original trade.


11. Risk Engine

The copy strategy should not have direct permission to spend funds.

Add a risk layer:

class RiskEngine:
    def validate(
        self,
        token: str,
        amount: int,
        wallet_balance: int,
    ) -> tuple[bool, str]:

        if amount <= 0:
            return False, "invalid amount"

        if amount > MAX_POSITION:
            return False, "position too large"

        if wallet_balance < amount + GAS_RESERVE:
            return False, "insufficient balance"

        return True, "approved"
Enter fullscreen mode Exit fullscreen mode

The architecture is:

Source Trade
     ↓
Copy Strategy
     ↓
Risk Engine
     ↓
Execution
Enter fullscreen mode Exit fullscreen mode

That separation is important.


12. Gas Reserve

Never use:

wallet balance = trading capital
Enter fullscreen mode Exit fullscreen mode

Instead:

wallet balance
     ↓
gas reserve
     ↓
spendable balance
Enter fullscreen mode Exit fullscreen mode

For example:

spendable = (
    wallet_balance
    - gas_reserve
)

if spendable <= 0:
    raise RuntimeError(
        "No spendable balance"
    )
Enter fullscreen mode Exit fullscreen mode

The risk engine should reject a trade if the gas reserve would be violated.


13. Fresh Pons Quote

A follower should calculate its own current quote.

Do not simply assume:

source execution price
=
follower execution price
Enter fullscreen mode Exit fullscreen mode

Pons currently documents token trading through each token's live pool, so the follower should evaluate current execution conditions rather than blindly copying the source's historical price.

Conceptually:

Source Trade
      ↓
Current Pons State
      ↓
Fresh Quote
      ↓
Expected Output
      ↓
Slippage
      ↓
Follower Transaction
Enter fullscreen mode Exit fullscreen mode

If you already have a Pons-specific quote implementation, reuse it.

Don't create a second pricing engine with an approximate formula.


14. Minimum Output

A buy should define an acceptable execution boundary.

For example:

expected_out = quote.tokens_out

min_out = (
    expected_out
    * (10_000 - slippage_bps)
    // 10_000
)
Enter fullscreen mode Exit fullscreen mode

Then:

Expected Output
      ↓
Slippage
      ↓
Minimum Output
      ↓
Execution
Enter fullscreen mode Exit fullscreen mode

The exact formula should match the protocol's token decimals and contract behavior.


15. Pons Execution Layer

Keep protocol-specific execution in a dedicated class:

class PonsExecutor:

    def buy(
        self,
        token: str,
        amount_in: int,
        min_tokens_out: int,
    ):
        ...
Enter fullscreen mode Exit fullscreen mode

The strategy layer should never need to understand:

nonce
gas
raw transaction
private-key signing
RPC broadcasting
Enter fullscreen mode Exit fullscreen mode

It should only submit an execution request.


16. Transaction State Machine

A copied trade needs explicit state.

SIGNAL_RECEIVED
      ↓
DECODED
      ↓
STRATEGY_APPROVED
      ↓
RISK_APPROVED
      ↓
QUOTED
      ↓
PREPARED
      ↓
SIGNED
      ↓
SUBMITTED
      ↓
PENDING
      ├── CONFIRMED
      ├── REVERTED
      └── UNKNOWN
Enter fullscreen mode Exit fullscreen mode

This state should be persisted.

For example:

@dataclass
class Execution:
    trade_id: str
    wallet: str
    tx_hash: str | None
    status: str
    amount_in: int
    created_at: int
Enter fullscreen mode Exit fullscreen mode

Now the system can recover after a restart.


17. Never Blindly Retry Timeouts

A dangerous pattern is:

RPC timeout
   ↓
retry transaction
Enter fullscreen mode Exit fullscreen mode

The original transaction might already exist.

Instead:

RPC timeout
   ↓
Check transaction state
   ↓
Reconcile
   ↓
Decide
Enter fullscreen mode Exit fullscreen mode

Possible state:

UNKNOWN
Enter fullscreen mode Exit fullscreen mode

means:

I don't know yet.

It should not mean:

It failed.


18. Nonce Management

For each follower wallet, use a single nonce-management strategy.

For example:

Wallet
  ↓
Nonce Manager
  ↓
Transaction A
Transaction B
Transaction C
Enter fullscreen mode Exit fullscreen mode

Do not let:

copy strategy
retry worker
sell worker
transfer worker
Enter fullscreen mode Exit fullscreen mode

all manage the same wallet nonce independently.

That creates race conditions.


19. Portfolio Tracking

Once a copy trade confirms, create or update a follower position.

@dataclass
class Position:
    token: str
    wallet: str
    quantity: int
    entry_amount: int
    entry_tx: str
    status: str
Enter fullscreen mode Exit fullscreen mode

Then:

Trade
 ↓
Receipt
 ↓
Actual Token Balance
 ↓
Position
Enter fullscreen mode Exit fullscreen mode

Do not simply record:

expected token output
Enter fullscreen mode Exit fullscreen mode

as the actual position.


20. Copying Sells

Buy copying is only half the problem.

Suppose the source does:

BUY Token A
Enter fullscreen mode Exit fullscreen mode

and later:

SELL 50% Token A
Enter fullscreen mode Exit fullscreen mode

The follower needs its own sell policy.

Possible models:

Sell 50% of follower position
Enter fullscreen mode Exit fullscreen mode

or:

Sell the same token quantity
Enter fullscreen mode Exit fullscreen mode

or:

Reduce follower exposure to a target
Enter fullscreen mode Exit fullscreen mode

A good architecture keeps this in the copy strategy.


21. Reconciliation

Reconciliation compares local state with the chain.

LOCAL POSITION
      ↕
RECONCILIATION
      ↕
CHAIN STATE
Enter fullscreen mode Exit fullscreen mode

Verify:

transaction receipt
token balance
native balance
position state
Enter fullscreen mode Exit fullscreen mode

Run reconciliation:

after execution
after restart
after timeout
after reconnect
periodically
Enter fullscreen mode Exit fullscreen mode

This is what turns a long-running bot from an unreliable process into recoverable infrastructure.


22. Restart Recovery

Imagine:

Source trade detected
      ↓
Risk approved
      ↓
Transaction submitted
      ↓
Process crashes
Enter fullscreen mode Exit fullscreen mode

After restart:

Load state
    ↓
Find pending execution
    ↓
Query blockchain
    ↓
Reconcile
    ↓
Continue
Enter fullscreen mode Exit fullscreen mode

The bot should not simply start from an empty in-memory state.


23. Paper Trading

Before enabling live execution, build paper trading.

For example:

if PAPER_TRADING:
    return PaperExecutionResult(...)
Enter fullscreen mode Exit fullscreen mode

The system should still perform:

monitoring
decoding
strategy
risk
position sizing
quote
Enter fullscreen mode Exit fullscreen mode

but not broadcast a transaction.

That allows you to validate the strategy before risking capital.


24. Emergency Stop

A production bot should always have a kill switch.

For example:

if STOP_TRADING:
    raise RuntimeError(
        "Trading disabled by emergency stop"
    )
Enter fullscreen mode Exit fullscreen mode

Possible triggers:

manual stop
daily loss limit
RPC instability
unexpected execution behavior
too many failed transactions
Enter fullscreen mode Exit fullscreen mode

The emergency stop should block new entries while allowing reconciliation and reporting to continue.


25. Observability

Track the complete funnel:

Source Trades
      ↓
Decoded Trades
      ↓
Eligible Trades
      ↓
Risk Approved
      ↓
Execution Submitted
      ↓
Confirmed
Enter fullscreen mode Exit fullscreen mode

Example metrics:

Trades detected:       250
Trades decoded:        231
Signals accepted:       42
Risk rejected:          17
Submitted:              25
Confirmed:              22
Reverted:                2
Pending:                 1
Enter fullscreen mode Exit fullscreen mode

This tells you where the system is actually losing opportunities.


26. Latency Measurements

For copy trading, measure:

source transaction
        ↓
     detection
        ↓
     decoding
        ↓
     strategy
        ↓
       risk
        ↓
      quote
        ↓
    submission
Enter fullscreen mode Exit fullscreen mode

For example:

Detection:       80 ms
Decode:          15 ms
Strategy:         4 ms
Risk:             3 ms
Quote:           20 ms
Preparation:     18 ms
Submission:      30 ms
Enter fullscreen mode Exit fullscreen mode

Now you know which component should be optimized.

Without this instrumentation, latency discussions are mostly guesswork.


27. Example End-to-End Flow

A complete copy event might look like:

1. Source wallet buys Token A
                ↓
2. Block monitor detects transaction
                ↓
3. Decoder identifies Pons trade
                ↓
4. Opportunity registry checks duplication
                ↓
5. Copy strategy approves
                ↓
6. Signal freshness check passes
                ↓
7. Position size calculated
                ↓
8. Risk engine approves
                ↓
9. Fresh quote calculated
                ↓
10. minTokensOut calculated
                ↓
11. Transaction prepared
                ↓
12. Transaction signed
                ↓
13. Transaction submitted
                ↓
14. Receipt monitored
                ↓
15. Position created
                ↓
16. Reconciliation verifies state
Enter fullscreen mode Exit fullscreen mode

That is the complete product.


28. CLI

A simple interface could expose:

python main.py connect
Enter fullscreen mode Exit fullscreen mode
python main.py monitor
Enter fullscreen mode Exit fullscreen mode
python main.py run
Enter fullscreen mode Exit fullscreen mode
python main.py reconcile
Enter fullscreen mode Exit fullscreen mode
python main.py portfolio
Enter fullscreen mode Exit fullscreen mode

And live trading should have explicit safety gates.

A useful default is:

PAPER / DRY RUN
Enter fullscreen mode Exit fullscreen mode

with live execution requiring explicit configuration.


29. Production Project Structure

The final project can evolve into:

bot/
├── monitor/
│   ├── block_monitor.py
│   └── wallet_tracker.py
│
├── decoder/
│   ├── trade_decoder.py
│   └── swap_classifier.py
│
├── strategy/
│   ├── copy_strategy.py
│   ├── filters.py
│   └── sizing.py
│
├── risk/
│   └── risk_engine.py
│
├── execution/
│   ├── pons_executor.py
│   ├── nonce_manager.py
│   └── transaction_manager.py
│
├── portfolio/
│   ├── positions.py
│   └── pnl.py
│
├── reconciliation/
│   └── reconciler.py
│
└── engine.py
Enter fullscreen mode Exit fullscreen mode

This structure also makes future strategy development much easier.


30. From Copy Trading to Multiple Strategies

Once the execution engine exists, copy trading becomes one strategy.

For example:

                  Pons Trading Engine
                          │
           ┌──────────────┼──────────────┐
           ↓              ↓              ↓
        Sniper           Copy         Momentum
        Strategy        Strategy       Strategy
           └──────────────┼──────────────┘
                          ↓
                      Risk Engine
                          ↓
                    Execution Engine
                          ↓
                     Robinhood Chain
Enter fullscreen mode Exit fullscreen mode

This is the architecture I would build toward.

The execution engine should be reusable.


Final Thoughts

A Pons copy trading bot is not:

watch wallet
   ↓
copy transaction
Enter fullscreen mode Exit fullscreen mode

It is:

MONITOR
   ↓
DECODE
   ↓
FILTER
   ↓
SIZE
   ↓
MANAGE RISK
   ↓
QUOTE
   ↓
EXECUTE
   ↓
MONITOR
   ↓
RECONCILE
Enter fullscreen mode Exit fullscreen mode

That difference matters.

The source wallet provides a signal.

The follower system controls the decision.

The blockchain determines the actual result.

And reconciliation keeps the application's state aligned with reality.


The Bigger Product Opportunity

Once this execution layer works, it can support more than copy trading.

Pons Bundler
      ↓
Pons Sniper Bot
      ↓
Pons Copy Trading Bot
      ↓
Pons Launch Monitor
      ↓
Pons Trading Terminal
      ↓
Stock Token Arbitrage Bot
      ↓
Stock Token Trading Bot
Enter fullscreen mode Exit fullscreen mode

The common foundation is:

Data
 ↓
Strategy
 ↓
Risk
 ↓
Execution
 ↓
Monitoring
 ↓
Reconciliation
Enter fullscreen mode Exit fullscreen mode

That is the part I care about most.

Not just making a bot that can send a transaction.

Building reliable trading infrastructure around a specific protocol.


Need a Pons Copy Trading Bot?

I build custom trading automation for Robinhood Chain, including:

Pons Copy Trading
Pons Sniper Bots
Pons Bundlers
Trading Automation
Risk Engines
Execution Systems
Transaction Monitoring
Portfolio / Reconciliation
Enter fullscreen mode Exit fullscreen mode

The implementation should be designed around the client's strategy, wallet configuration, execution requirements, and risk controls rather than a generic one-size-fits-all bot.


Reference

Pons documentation: https://docs.ponsfamily.com/
Pons V2 documentation: https://docs.ponsfamily.com/v2
Pons bundler implementation: https://github.com/wooyang/pons-bundler

Top comments (0)