<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: hamssog</title>
    <description>The latest articles on DEV Community by hamssog (@hamssog).</description>
    <link>https://dev.to/hamssog</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2177649%2Ff9d5f6f9-e1dd-47d8-a345-45fe5a54384b.jpg</url>
      <title>DEV Community: hamssog</title>
      <link>https://dev.to/hamssog</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hamssog"/>
    <language>en</language>
    <item>
      <title>Building a Robinhood Trading MCP Risk Gateway with TypeScript</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Wed, 07 Oct 2026 05:39:35 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-robinhood-trading-mcp-risk-gateway-with-typescript-37bl</link>
      <guid>https://dev.to/hamssog/building-a-robinhood-trading-mcp-risk-gateway-with-typescript-37bl</guid>
      <description>&lt;p&gt;AI agents can now do more than answer questions.&lt;/p&gt;

&lt;p&gt;They can interact with external applications through tools.&lt;/p&gt;

&lt;p&gt;Robinhood's Trading MCP is one example: an external AI agent can connect to Robinhood and use supported account, portfolio, market-data, watchlist, equities, options, crypto, scanner, alert, and order-related tools. Robinhood also provides trade-approval controls for agentic trading.&lt;/p&gt;

&lt;p&gt;That creates a new engineering problem.&lt;/p&gt;

&lt;p&gt;The difficult part is not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI
 ↓
Place Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difficult part is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
   ↓
Trading MCP
   ↓
Permission Layer
   ↓
Risk Gateway
   ↓
Approval / Policy
   ↓
Order Execution
   ↓
Audit
   ↓
State Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this project, I am building a reusable &lt;strong&gt;Robinhood Trading MCP Risk Gateway&lt;/strong&gt; with TypeScript.&lt;/p&gt;

&lt;p&gt;The goal is to demonstrate how I would put deterministic controls between an AI agent and trading execution.&lt;/p&gt;

&lt;p&gt;This is not an LLM stock-prediction system.&lt;/p&gt;

&lt;p&gt;It is an execution-control system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why put a risk gateway between the agent and the broker?
&lt;/h2&gt;

&lt;p&gt;An AI model can interpret instructions and select tools.&lt;/p&gt;

&lt;p&gt;It should not automatically become the final authority over trading limits.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User:
"Buy $5,000 of this stock."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent might generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;symbol = XYZ
side = buy
amount = $5,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the application's risk policy might say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum order = $1,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway should reject the request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI Agent
   ↓
Trade Intent
   ↓
Risk Gateway
   ↓
REJECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the central design principle:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The agent can propose an action, but deterministic application code decides whether that action is allowed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;The initial architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| AI Agent             |
+----------+-----------+
           |
           v
+----------------------+
| Robinhood Trading    |
| MCP                  |
+----------+-----------+
           |
           v
+----------------------+
| Tool Permission      |
| Layer                |
+----------+-----------+
           |
           v
+----------------------+
| Trade Intent         |
| Validation           |
+----------+-----------+
           |
           v
+----------------------+
| Risk Gateway         |
+----------+-----------+
           |
           v
+----------------------+
| Approval / Policy    |
+----------+-----------+
           |
           v
+----------------------+
| Execution            |
+----------+-----------+
           |
           v
+----------------------+
| Audit / State        |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway sits in the middle.&lt;/p&gt;

&lt;p&gt;That makes the execution boundary explicit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Robinhood's external-agent model
&lt;/h2&gt;

&lt;p&gt;Robinhood's current Trading MCP documentation describes MCP as a mechanism for connecting an external AI agent to Robinhood so the agent can access information and take supported actions. Robinhood documents connections for platforms including Claude, ChatGPT, Codex, Cursor, Grok, Perplexity, OpenClaw, Replit, and others.&lt;/p&gt;

&lt;p&gt;The external agent uses a dedicated MCP account for trading.&lt;/p&gt;

&lt;p&gt;The agent can read information from the user's Robinhood accounts, while trading through the dedicated Agentic/MCP account.&lt;/p&gt;

&lt;p&gt;That distinction is important when designing an application around the integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Read tools versus write tools
&lt;/h2&gt;

&lt;p&gt;The first permission boundary I want is the separation between reading and changing state.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read
----
get_accounts
get_portfolio
get_watchlists
get_market_data
get_positions
get_history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Write
-----
place order
cancel order
modify order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Robinhood's current tool documentation exposes separate categories of account, portfolio, market-data, trading and advanced-order capabilities.&lt;/p&gt;

&lt;p&gt;That allows the application to define permissions independently.&lt;/p&gt;

&lt;p&gt;A research agent might receive only read permissions.&lt;/p&gt;

&lt;p&gt;A portfolio assistant might receive read plus proposal permissions.&lt;/p&gt;

&lt;p&gt;A trading agent might receive execution permissions only after additional policy checks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The project structure
&lt;/h2&gt;

&lt;p&gt;I want the repository to remain simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;robinhood-mcp-risk-gateway/
├── src/
│   ├── agent/
│   │   └── agent-types.ts
│   │
│   ├── mcp/
│   │   ├── mcp-client.ts
│   │   └── tool-registry.ts
│   │
│   ├── permissions/
│   │   ├── permission.ts
│   │   └── permission-engine.ts
│   │
│   ├── trades/
│   │   ├── trade-intent.ts
│   │   ├── trade-validator.ts
│   │   └── trade-service.ts
│   │
│   ├── risk/
│   │   ├── risk-policy.ts
│   │   └── risk-engine.ts
│   │
│   ├── approvals/
│   │   └── approval-service.ts
│   │
│   ├── audit/
│   │   └── audit-log.ts
│   │
│   ├── state/
│   │   └── execution-state.ts
│   │
│   └── index.ts
│
├── test/
│   ├── unit/
│   └── integration/
│
├── examples/
├── docs/
├── package.json
├── tsconfig.json
├── .env.example
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core business rules should not be coupled directly to the AI model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Structured trade intent
&lt;/h2&gt;

&lt;p&gt;I don't want the rest of the application to consume free-form model output.&lt;/p&gt;

&lt;p&gt;Instead, convert it into a typed intent.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TradeIntent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;buy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;orderType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;market&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;notional&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;limitPrice&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;rationale&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then validate the object before it reaches execution.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Natural Language
      ↓
AI Agent
      ↓
Structured Trade Intent
      ↓
Schema Validation
      ↓
Risk Validation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AI's output becomes an input to the software system.&lt;/p&gt;

&lt;p&gt;It is not the software system itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why structured output matters
&lt;/h2&gt;

&lt;p&gt;Compare these two outputs.&lt;/p&gt;

&lt;p&gt;Free-form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Let's buy a meaningful amount of XYZ because momentum looks strong."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structured:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  symbol: "XYZ",
  side: "buy",
  orderType: "market",
  notional: 500
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second can be validated.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max order = $1,000
requested = $500
result = ALLOWED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max order = $1,000
requested = $5,000
result = REJECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much easier to test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Risk policy
&lt;/h2&gt;

&lt;p&gt;The risk policy should be deterministic.&lt;/p&gt;

&lt;p&gt;A basic model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RiskPolicy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;maxOrderNotional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxPositionNotional&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxPortfolioExposure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxDailyLoss&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxTradesPerDay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;allowedSymbols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The risk engine can evaluate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order size
position size
portfolio exposure
daily trading count
daily loss
symbol restrictions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact rules can be extended later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Risk evaluation
&lt;/h2&gt;

&lt;p&gt;The core API can be small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;RiskDecision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;failedChecks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;evaluateTrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TradeIntent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RiskPolicy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;RiskDecision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// deterministic checks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should explain why a trade was accepted or rejected.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example risk checks
&lt;/h2&gt;

&lt;p&gt;A trade might go through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade Intent
    |
    +--&amp;gt; symbol allowed?
    |
    +--&amp;gt; order size allowed?
    |
    +--&amp;gt; position limit allowed?
    |
    +--&amp;gt; portfolio exposure allowed?
    |
    +--&amp;gt; daily loss limit allowed?
    |
    +--&amp;gt; trading limit allowed?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only when the required checks pass does the trade continue.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All checks pass
      ↓
Execution allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Symbol allowlists
&lt;/h2&gt;

&lt;p&gt;One simple control is restricting which assets the agent can trade.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedSymbols&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AAPL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NVDA&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MSFT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowedSymbols&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SYMBOL_NOT_ALLOWED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;failedChecks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allowedSymbols&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally boring.&lt;/p&gt;

&lt;p&gt;Risk controls should be boring.&lt;/p&gt;




&lt;h2&gt;
  
  
  Position limits
&lt;/h2&gt;

&lt;p&gt;Suppose the application has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum position = $2,500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the current position is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$2,200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A new:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;order should fail because it would push exposure beyond the configured limit.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Current position
    $2,200
       +
New order
      $500
       =
    $2,700

Limit = $2,500

Result = REJECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the decision is deterministic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Portfolio exposure
&lt;/h2&gt;

&lt;p&gt;The gateway can also enforce portfolio-level constraints.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Technology exposure: 48%
Maximum:             40%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An agent might still propose another technology trade.&lt;/p&gt;

&lt;p&gt;The risk engine rejects it.&lt;/p&gt;

&lt;p&gt;This creates a portfolio-aware execution boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ↓
Trade
 ↓
Portfolio State
 ↓
Risk Policy
 ↓
Decision
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Daily loss controls
&lt;/h2&gt;

&lt;p&gt;Another useful guardrail is a daily loss limit.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum daily loss = $500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the application detects that the threshold has already been reached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade Intent
     ↓
Risk Engine
     ↓
DAILY_LOSS_LIMIT
     ↓
Rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent does not get to override the policy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tool permissions
&lt;/h2&gt;

&lt;p&gt;Risk is only one layer.&lt;/p&gt;

&lt;p&gt;The agent also needs permissions.&lt;/p&gt;

&lt;p&gt;I model capabilities such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READ_ACCOUNT
READ_PORTFOLIO
READ_MARKET_DATA
READ_WATCHLIST
PROPOSE_TRADE
PLACE_ORDER
CANCEL_ORDER
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then define which agent has which capability.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AgentPermissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;readAccount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;readPortfolio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;readMarketData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;proposeTrade&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;placeOrder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cancelOrder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A research agent can have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;readAccount = true
readPortfolio = true
readMarketData = true
placeOrder = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An execution agent can be given a narrower set of explicitly required write permissions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Human approval
&lt;/h2&gt;

&lt;p&gt;Robinhood currently supports trade approvals for agentic trading. With approvals enabled, the agent can propose trades but the user must review and manually place them in Robinhood; with approvals disabled, eligible trades can be placed by the agent without manual confirmation of each order. Robinhood notes that certain trades may still require approval.&lt;/p&gt;

&lt;p&gt;That maps naturally to two application modes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assisted mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
  ↓
Trade Intent
  ↓
Risk
  ↓
Approval Request
  ↓
Human
  ↓
Robinhood
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Automated mode
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
  ↓
Trade Intent
  ↓
Risk
  ↓
Policy
  ↓
Robinhood
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would build assisted mode first.&lt;/p&gt;




&lt;h2&gt;
  
  
  Approval state
&lt;/h2&gt;

&lt;p&gt;The application can represent approval explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ApprovalStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;NOT_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;APPROVED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade lifecycle then becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PROPOSED
   ↓
VALIDATED
   ↓
RISK_CHECKED
   ↓
APPROVAL_PENDING
   ↓
APPROVED
   ↓
SUBMITTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RISK_CHECKED
   ↓
REJECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the execution path easy to audit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Order state
&lt;/h2&gt;

&lt;p&gt;Once an order is submitted, the application still needs to track it.&lt;/p&gt;

&lt;p&gt;A useful state machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id="robinhood-order-state"
PROPOSED
   ↓
VALIDATED
   ↓
APPROVED
   ↓
SUBMITTED
   ↓
OPEN
   ↓
FILLED
   ↓
RECONCILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ├── REJECTED
   ├── CANCELED
   └── FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important distinction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order submitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;order filled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second requires actual execution state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Audit logging
&lt;/h2&gt;

&lt;p&gt;Every meaningful agent action should produce an audit record.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;AuditRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;inputHash&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;riskDecision&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;approvalStatus&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;error&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why did this order happen?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An audit trail should let us reconstruct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Request
     ↓
Agent Decision
     ↓
Tool Call
     ↓
Risk Decision
     ↓
Approval
     ↓
Order
     ↓
Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The AI should not bypass the gateway
&lt;/h2&gt;

&lt;p&gt;The architecture should enforce one execution route.&lt;/p&gt;

&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
 ├── MCP
 ├── Direct API
 └── Another execution path
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent
   ↓
Trading MCP
   ↓
Risk Gateway
   ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There should be one clearly defined path to a trading action.&lt;/p&gt;

&lt;p&gt;That makes permissions, logging, testing, and monitoring much easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Market data and account data
&lt;/h2&gt;

&lt;p&gt;Robinhood's current agent tooling includes account, portfolio, watchlist, market-data, equities, options, crypto, scanner, alerts, and advanced order capabilities.&lt;/p&gt;

&lt;p&gt;That means the agent can potentially build decisions from multiple sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Portfolio
     +
Market Data
     +
Watchlist
     +
Trading Rules
     ↓
Trade Proposal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important engineering boundary remains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Data
 ↓
Analysis
 ↓
Trade Intent
 ↓
Risk
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Portfolio-aware execution
&lt;/h2&gt;

&lt;p&gt;A simple trading bot might know only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;symbol
price
signal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A portfolio-aware agent can incorporate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current positions
buying power
existing exposure
trade history
P&amp;amp;L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent:
"Buy $500 of XYZ."

Portfolio:
Existing XYZ = $2,200

Risk:
Maximum XYZ position = $2,500

Result:
REJECT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent does not need to be trusted to remember the limit.&lt;/p&gt;

&lt;p&gt;The policy engine enforces it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example workflow
&lt;/h2&gt;

&lt;p&gt;Here is an example from start to finish:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 |
 | "Find a momentum opportunity
 |  and propose a $500 trade."
 |
 v
AI Agent
 |
 | market-data tools
 v
Robinhood MCP
 |
 v
Trade Intent
 |
 v
Risk Gateway
 |
 +--&amp;gt; symbol check
 +--&amp;gt; order-size check
 +--&amp;gt; position check
 +--&amp;gt; portfolio check
 |
 v
Approval
 |
 v
Order
 |
 v
Robinhood
 |
 v
Execution State
 |
 v
Audit Log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the architecture I want the GitHub project to demonstrate.&lt;/p&gt;




&lt;h2&gt;
  
  
  TypeScript service boundary
&lt;/h2&gt;

&lt;p&gt;The core service can have one main operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processTradeIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TradeIntent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TradeDecision&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;validateTradeIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;riskDecision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;riskEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;riskDecision&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;riskDecision&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;approvalService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the AI model does not call the broker directly.&lt;/p&gt;

&lt;p&gt;It submits a typed request into the application's controlled pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing risk rules
&lt;/h2&gt;

&lt;p&gt;Risk logic should be highly testable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max order = $1,000

$500  → allowed
$1,000 → allowed
$1,001 → rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Position limits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;current = $2,000
limit   = $2,500

order = $400 → allowed
order = $500 → allowed
order = $501 → rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Symbol restrictions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AAPL → allowed
XYZ  → rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Daily loss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;daily loss = $490
limit      = $500

new trade allowed if other constraints pass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests should cover boundary values.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing permissions
&lt;/h2&gt;

&lt;p&gt;Permission tests should verify that an agent cannot use tools it does not have.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Research Agent
    ↓
get_portfolio
    ↓
ALLOW
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Research Agent
    ↓
place_order
    ↓
DENY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the permission layer a simple and deterministic contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing approval flows
&lt;/h2&gt;

&lt;p&gt;Test both modes.&lt;/p&gt;

&lt;p&gt;Assisted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade
 ↓
Risk passes
 ↓
Approval pending
 ↓
User approves
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rejected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade
 ↓
Risk passes
 ↓
Approval pending
 ↓
User rejects
 ↓
No execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Automated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade
 ↓
Risk passes
 ↓
Policy permits automation
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests should be independent of the actual AI model.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing failure paths
&lt;/h2&gt;

&lt;p&gt;The system should also handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MCP unavailable
tool timeout
invalid tool result
risk rejection
approval timeout
order rejected
order canceled
execution failure
state synchronization failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to make the AI appear perfect.&lt;/p&gt;

&lt;p&gt;The goal is to make the surrounding system behave predictably when the AI or external infrastructure is imperfect.&lt;/p&gt;




&lt;h2&gt;
  
  
  Secrets and account security
&lt;/h2&gt;

&lt;p&gt;The gateway must not become a place where secrets are casually stored.&lt;/p&gt;

&lt;p&gt;Do not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;log credentials
commit .env files
store private secrets in audit records
print authentication tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use environment variables or an appropriate secret-management layer.&lt;/p&gt;

&lt;p&gt;The repository should contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but never actual credentials.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this architecture is reusable
&lt;/h2&gt;

&lt;p&gt;The risk gateway does not have to be limited to one strategy.&lt;/p&gt;

&lt;p&gt;It can sit underneath:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Momentum Agent
     ↓
Risk Gateway

Rebalancing Agent
     ↓
Risk Gateway

Portfolio Agent
     ↓
Risk Gateway

Research + Execution Agent
     ↓
Risk Gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent changes.&lt;/p&gt;

&lt;p&gt;The controls remain.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger architecture
&lt;/h2&gt;

&lt;p&gt;The project fits into a larger trading system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         USER
                           |
                           v
                       AI AGENT
                           |
                           v
                 ROBINHOOD TRADING MCP
                           |
                           v
                 +--------------------+
                 | Permission Layer   |
                 +----------+---------+
                            |
                            v
                 +--------------------+
                 | Trade Intent       |
                 | Validation         |
                 +----------+---------+
                            |
                            v
                 +--------------------+
                 | Risk Gateway       |
                 +----------+---------+
                            |
                            v
                 +--------------------+
                 | Approval / Policy  |
                 +----------+---------+
                            |
                            v
                 +--------------------+
                 | Robinhood Order    |
                 +----------+---------+
                            |
                            v
                      ORDER STATE
                            |
                            v
                       AUDIT LOG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The gateway is the control point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this is different from a traditional trading bot
&lt;/h2&gt;

&lt;p&gt;A traditional bot often looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signal
 ↓
Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A controlled agentic trading system looks more like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User Intent
     ↓
AI Agent
     ↓
Tools
     ↓
Structured Intent
     ↓
Permission
     ↓
Risk
     ↓
Approval / Policy
     ↓
Execution
     ↓
Audit
     ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  That is a fundamentally different engineering problem.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What this proves to a client
&lt;/h2&gt;

&lt;p&gt;The valuable statement is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I can connect ChatGPT to Robinhood."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The stronger statement is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I can build the controlled infrastructure around an AI trading agent.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That includes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI integration
+
MCP
+
Tool permissions
+
Structured outputs
+
Risk controls
+
Approval workflows
+
Execution
+
Auditability
+
State management
+
Failure handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much closer to what a real trading product requires.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                       ROBINHOOD AI AGENT
                               |
                               v
                       TRADING MCP
                               |
                               v
                      TOOL PERMISSIONS
                               |
                               v
                     STRUCTURED INTENT
                               |
                               v
                        RISK ENGINE
                               |
                     +---------+---------+
                     |                   |
                     v                   v
                  REJECT              APPROVE
                                         |
                                         v
                                  TRADE APPROVAL
                                         |
                                         v
                                    ROBINHOOD
                                         |
                                         v
                                   ORDER STATE
                                         |
                                         v
                                    AUDIT LOG
                                         |
                                         v
                                  RECONCILIATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core principle is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI proposes. Software validates. Policy controls. Robinhood executes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the architecture I am using to explore the new generation of &lt;strong&gt;Robinhood agentic trading infrastructure&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Building an EVM Event Indexer with TypeScript and viem</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Mon, 05 Oct 2026 11:16:29 +0000</pubDate>
      <link>https://dev.to/hamssog/building-an-evm-event-indexer-with-typescript-and-viem-2lb7</link>
      <guid>https://dev.to/hamssog/building-an-evm-event-indexer-with-typescript-and-viem-2lb7</guid>
      <description>&lt;p&gt;A trading system needs to know more than whether a transaction was submitted.&lt;/p&gt;

&lt;p&gt;It needs to know what happened on-chain.&lt;/p&gt;

&lt;p&gt;A wallet traded.&lt;/p&gt;

&lt;p&gt;A token was launched.&lt;/p&gt;

&lt;p&gt;A position changed.&lt;/p&gt;

&lt;p&gt;A protocol emitted an event.&lt;/p&gt;

&lt;p&gt;A transaction was reverted.&lt;/p&gt;

&lt;p&gt;A pool graduated.&lt;/p&gt;

&lt;p&gt;For an automated trading application, these events eventually need to become structured application data.&lt;/p&gt;

&lt;p&gt;That is the problem I am solving with this project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Build a reusable EVM Event Indexer that turns blockchain logs into application-ready trading state.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The implementation uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;viem&lt;/li&gt;
&lt;li&gt;SQLite&lt;/li&gt;
&lt;li&gt;Vitest&lt;/li&gt;
&lt;li&gt;EVM-compatible networks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The indexer is designed to support applications such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pons monitoring&lt;/li&gt;
&lt;li&gt;wallet tracking&lt;/li&gt;
&lt;li&gt;copy trading&lt;/li&gt;
&lt;li&gt;arbitrage&lt;/li&gt;
&lt;li&gt;stock-token systems&lt;/li&gt;
&lt;li&gt;trading terminals&lt;/li&gt;
&lt;li&gt;portfolio tracking&lt;/li&gt;
&lt;li&gt;custom EVM applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain
    ↓
Block Scanner
    ↓
Event Decoder
    ↓
Event Normalizer
    ↓
Deduplication
    ↓
Persistent Store
    ↓
State Processor
    ↓
API / Dashboard / Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why build an event indexer?
&lt;/h2&gt;

&lt;p&gt;Reading blockchain data directly from every application quickly becomes repetitive.&lt;/p&gt;

&lt;p&gt;Without a central indexing layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Monitor
 └── reads logs

Wallet Tracker
 └── reads logs

Copy Trader
 └── reads logs

Trading Terminal
 └── reads logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each application ends up implementing its own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;block scanning&lt;/li&gt;
&lt;li&gt;ABI decoding&lt;/li&gt;
&lt;li&gt;event filtering&lt;/li&gt;
&lt;li&gt;retry logic&lt;/li&gt;
&lt;li&gt;deduplication&lt;/li&gt;
&lt;li&gt;checkpointing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                EVM Blockchain
                       |
                       v
                 Event Indexer
                       |
        +--------------+--------------+
        |              |              |
        v              v              v
     Pons App      Wallet App     Trading App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One indexing layer.&lt;/p&gt;

&lt;p&gt;Multiple consumers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Raw EVM logs are not application state
&lt;/h2&gt;

&lt;p&gt;A blockchain gives us low-level data.&lt;/p&gt;

&lt;p&gt;A trading application usually wants high-level objects.&lt;/p&gt;

&lt;p&gt;For example, the chain may provide an event containing encoded values.&lt;/p&gt;

&lt;p&gt;The application may actually need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TradeEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;trader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;buy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indexer performs the transformation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw Log
   ↓
Decode
   ↓
Normalize
   ↓
Typed Domain Event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now downstream code does not need to understand low-level log encoding.&lt;/p&gt;




&lt;h2&gt;
  
  
  Overall architecture
&lt;/h2&gt;

&lt;p&gt;The project is split into several responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| EVM Blockchain       |
+----------+-----------+
           |
           v
+----------------------+
| Block Scanner        |
+----------+-----------+
           |
           v
+----------------------+
| Log Fetcher          |
+----------+-----------+
           |
           v
+----------------------+
| Event Decoder        |
+----------+-----------+
           |
           v
+----------------------+
| Event Normalizer     |
+----------+-----------+
           |
           v
+----------------------+
| Deduplication        |
+----------+-----------+
           |
           v
+----------------------+
| SQLite               |
+----------+-----------+
           |
           v
+----------------------+
| State Processors     |
+----------+-----------+
           |
           v
+----------------------+
| API / Alerts / UI    |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protocol-specific logic stays separate from the generic indexing engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;A simple repository structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;evm-trading-event-indexer/
├── src/
│   ├── scanner/
│   │   ├── block-scanner.ts
│   │   └── log-fetcher.ts
│   │
│   ├── decoder/
│   │   ├── event-decoder.ts
│   │   └── abi-registry.ts
│   │
│   ├── normalizer/
│   │   └── event-normalizer.ts
│   │
│   ├── checkpoint/
│   │   └── checkpoint-store.ts
│   │
│   ├── persistence/
│   │   ├── database.ts
│   │   └── event-repository.ts
│   │
│   ├── deduplication/
│   │   └── event-deduplicator.ts
│   │
│   ├── reconciliation/
│   │   └── chain-reconciler.ts
│   │
│   ├── protocols/
│   │   ├── generic/
│   │   └── pons/
│   │
│   ├── state/
│   │   ├── trade-state.ts
│   │   ├── wallet-state.ts
│   │   └── position-state.ts
│   │
│   └── index.ts
│
├── test/
│   ├── unit/
│   ├── integration/
│   └── fixtures/
│
├── examples/
├── docs/
├── package.json
├── tsconfig.json
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Keep protocol-specific event decoding separate from generic scanning and persistence.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Block scanning
&lt;/h2&gt;

&lt;p&gt;The first job is discovering blocks that need to be processed.&lt;/p&gt;

&lt;p&gt;For historical indexing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Start Block
    ↓
Block Range 1
    ↓
Block Range 2
    ↓
Block Range 3
    ↓
Latest Block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For live indexing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Last Indexed Block
        ↓
New Blocks
        ↓
Fetch Logs
        ↓
Process
        ↓
Advance Checkpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same processing pipeline should ideally handle both cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Block ranges
&lt;/h2&gt;

&lt;p&gt;I prefer processing configurable block chunks rather than requesting an enormous historical range in one operation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fromBlock = 1,000,000
toBlock   = 1,010,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,010,001 → 1,020,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and so on.&lt;/p&gt;

&lt;p&gt;The chunk size should be configuration rather than hard-coded.&lt;/p&gt;

&lt;p&gt;That makes the indexer easier to adapt to different RPC environments and workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fetching logs with viem
&lt;/h2&gt;

&lt;p&gt;With viem, event log retrieval can be performed through the public client.&lt;/p&gt;

&lt;p&gt;For a known contract and event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tradeEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is filtering as early as possible.&lt;/p&gt;

&lt;p&gt;Do not retrieve unrelated data and decode everything in application code when the RPC layer can filter it.&lt;/p&gt;

&lt;p&gt;Useful filters include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract address
event signature
indexed parameters
block range
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Event definitions
&lt;/h2&gt;

&lt;p&gt;The indexer needs ABI definitions for the events it understands.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tradeEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TradeExecuted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;trader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amountIn&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uint256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;indexed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amountOut&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uint256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The decoder can then convert matching logs into typed objects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Decoding events
&lt;/h2&gt;

&lt;p&gt;The decoder should have one job:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Convert an on-chain log into a domain event.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;DecodedTrade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRADE_EXECUTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;trader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;logIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A good decoder should preserve the original blockchain identifiers.&lt;/p&gt;

&lt;p&gt;At minimum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction hash
block number
block hash
log index
contract address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values are useful later for debugging and reconciliation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Normalize events
&lt;/h2&gt;

&lt;p&gt;Different protocols often represent similar actions differently.&lt;/p&gt;

&lt;p&gt;One contract may emit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TradeExecuted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Swap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Buy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application may want one normalized concept:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRADE_EXECUTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I use a normalization layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Protocol Event
      ↓
Protocol Decoder
      ↓
Normalized Event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NormalizedEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TRADE_EXECUTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;trader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;amountOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TOKEN_LAUNCHED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;creator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the rest of the application can consume a consistent interface.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deduplication
&lt;/h2&gt;

&lt;p&gt;An indexer should assume that an event may be seen more than once.&lt;/p&gt;

&lt;p&gt;This can happen because of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;process restarts&lt;/li&gt;
&lt;li&gt;retries&lt;/li&gt;
&lt;li&gt;overlapping scans&lt;/li&gt;
&lt;li&gt;checkpoint rollback&lt;/li&gt;
&lt;li&gt;manual replay&lt;/li&gt;
&lt;li&gt;reprocessing after an error&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the processing pipeline should be idempotent.&lt;/p&gt;

&lt;p&gt;A log identity can be represented using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chainId
transactionHash
logIndex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also keep block metadata for reconciliation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blockHash
blockNumber
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important behavior is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process event
     ↓
Process same event again
     ↓
No duplicate application event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Unique event constraints
&lt;/h2&gt;

&lt;p&gt;The database should help enforce deduplication.&lt;/p&gt;

&lt;p&gt;A table can contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events
------
id
chain_id
block_number
block_hash
transaction_hash
log_index
contract_address
event_name
event_data
indexed_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then define a unique constraint over the event identity.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(chain_id, transaction_hash, log_index)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then safely retry processing without creating duplicate event records.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checkpointing
&lt;/h2&gt;

&lt;p&gt;The indexer needs to know where it stopped.&lt;/p&gt;

&lt;p&gt;A checkpoint record can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;checkpoints
-----------
chain_id
stream
last_block
updated_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The processing flow should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetch block range
       ↓
Decode logs
       ↓
Normalize
       ↓
Persist
       ↓
Advance checkpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not reverse the last two steps.&lt;/p&gt;

&lt;p&gt;This is dangerous:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fetch
  ↓
Checkpoint
  ↓
Persist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the process crashes between checkpointing and persistence, the indexer may permanently skip events.&lt;/p&gt;




&lt;h2&gt;
  
  
  Crash recovery
&lt;/h2&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blocks 10,000 - 10,100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are being processed.&lt;/p&gt;

&lt;p&gt;The application crashes after processing through block 10,060.&lt;/p&gt;

&lt;p&gt;After restarting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;read checkpoint
      ↓
resume from safe position
      ↓
reprocess overlapping events if necessary
      ↓
deduplication prevents duplicates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This combination is powerful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Checkpointing
+
Idempotent Processing
+
Persistent Events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It makes recovery much safer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-time indexing
&lt;/h2&gt;

&lt;p&gt;After historical backfill is complete, the indexer can continue watching new blocks.&lt;/p&gt;

&lt;p&gt;A simple loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;while running

    latestBlock = getBlockNumber()

    if latestBlock &amp;gt; checkpoint
        process range

    wait

end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the implementation should avoid a naive busy loop.&lt;/p&gt;

&lt;p&gt;Use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;configurable polling&lt;/li&gt;
&lt;li&gt;controlled sleep intervals&lt;/li&gt;
&lt;li&gt;error backoff&lt;/li&gt;
&lt;li&gt;cancellation support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The indexer should be able to shut down cleanly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Historical backfill and live mode
&lt;/h2&gt;

&lt;p&gt;I think of these as two operating modes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------+
| Historical Backfill |
+----------+----------+
           |
           v
     Indexed State
           |
           v
+---------------------+
| Real-Time Indexing  |
+---------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that they use the same decoder, normalizer, repository, and state-processing logic.&lt;/p&gt;

&lt;p&gt;That reduces the risk of having:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backfill behavior ≠ live behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reorganization awareness
&lt;/h2&gt;

&lt;p&gt;One of the assumptions an indexer should avoid is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A processed block can never change.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Chains can experience reorganizations.&lt;/p&gt;

&lt;p&gt;That means the indexer should keep enough metadata to recognize block identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;block number
block hash
parent hash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the canonical chain changes, affected indexed data may need to be rolled back and replayed.&lt;/p&gt;

&lt;p&gt;The exact policy depends on the chain and application.&lt;/p&gt;

&lt;p&gt;For a trading application, the important thing is to make this a deliberate part of the architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  Events versus contract reads
&lt;/h2&gt;

&lt;p&gt;Events and current contract state serve different purposes.&lt;/p&gt;

&lt;p&gt;Events tell us:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Something happened.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A contract read tells us:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This is the state now.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Events:
Trade A
Trade B
Trade C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;do not necessarily mean that simply summing those events gives the complete current state.&lt;/p&gt;

&lt;p&gt;Some systems have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cancellations&lt;/li&gt;
&lt;li&gt;transfers&lt;/li&gt;
&lt;li&gt;fees&lt;/li&gt;
&lt;li&gt;refunds&lt;/li&gt;
&lt;li&gt;partial executions&lt;/li&gt;
&lt;li&gt;migrations&lt;/li&gt;
&lt;li&gt;state resets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The indexer therefore needs to know when event history is enough and when state must be queried directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  State processors
&lt;/h2&gt;

&lt;p&gt;I do not want the indexer itself to become the complete trading engine.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Indexed Event
      ↓
State Processor
      ↓
Position / Wallet / Protocol State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TradeState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tradeCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indexer produces reliable input.&lt;/p&gt;

&lt;p&gt;The state processor converts that input into a product-specific view.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wallet tracking
&lt;/h2&gt;

&lt;p&gt;A wallet tracker is a natural consumer.&lt;/p&gt;

&lt;p&gt;The flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain
   ↓
Event Indexer
   ↓
Trade Events
   ↓
Wallet Filter
   ↓
Wallet Activity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;recent trades
positions
tokens
transaction history
PnL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indexer itself remains generic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Copy trading
&lt;/h2&gt;

&lt;p&gt;The same indexed event can trigger another system.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet Event
     ↓
Indexer
     ↓
Copy Strategy
     ↓
Risk Check
     ↓
Transaction Manager
     ↓
Blockchain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a closed trading loop.&lt;/p&gt;

&lt;p&gt;The indexer observes.&lt;/p&gt;

&lt;p&gt;The strategy decides.&lt;/p&gt;

&lt;p&gt;The transaction manager executes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pons event indexing
&lt;/h2&gt;

&lt;p&gt;Pons is a useful example because its application layer can benefit from indexed launch and trading activity.&lt;/p&gt;

&lt;p&gt;The current Pons V2 documentation describes launch, bonding-curve trading, graduation, and subsequent pool trading, with protocol events that can be indexed to reconstruct activity. (&lt;a href="https://docs.ponsfamily.com/v2" rel="noopener noreferrer"&gt;docs.ponsfamily.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;A Pons-focused application can therefore use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Contracts
      ↓
Pons Event Decoder
      ↓
Normalized Pons Events
      ↓
Pons State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That state can power:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launch monitor
wallet tracker
trading dashboard
alerts
copy trading
analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Example Pons launch pipeline
&lt;/h2&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TOKEN_LAUNCHED
       ↓
Create Token Record
       ↓
Monitor Trading Events
       ↓
Update Curve State
       ↓
Detect Graduation
       ↓
Update Venue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The protocol adapter handles Pons-specific decoding.&lt;/p&gt;

&lt;p&gt;The rest of the indexing infrastructure stays generic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building a generic protocol adapter
&lt;/h2&gt;

&lt;p&gt;A protocol adapter can expose something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ProtocolAdapter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getSources&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;IndexerSource&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NormalizedEvent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;NormalizedEvent&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the engine can register:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Generic EVM Adapter
Pons Adapter
Future Protocol Adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without changing the core scanner.&lt;/p&gt;




&lt;h2&gt;
  
  
  Persistence
&lt;/h2&gt;

&lt;p&gt;For the reference implementation, SQLite is enough to demonstrate the architecture.&lt;/p&gt;

&lt;p&gt;The database can store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events
checkpoints
trades
tokens
wallet activity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important distinction is between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw indexed data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;derived application state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping those layers separate makes rebuilding state easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rebuilding state
&lt;/h2&gt;

&lt;p&gt;Suppose the position-state logic changes.&lt;/p&gt;

&lt;p&gt;If the raw events are still stored, the state can be rebuilt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Raw Events
    ↓
Replay
    ↓
New State Processor
    ↓
New State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a powerful property.&lt;/p&gt;

&lt;p&gt;It prevents the system from depending entirely on whatever derived state happened to exist at one point in time.&lt;/p&gt;




&lt;h2&gt;
  
  
  API layer
&lt;/h2&gt;

&lt;p&gt;The indexer can expose application-ready data through an API.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /events
GET /trades
GET /wallets/:address
GET /tokens/:address
GET /positions/:address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A WebSocket layer can later provide real-time updates.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Trade
   ↓
Indexer
   ↓
WebSocket
   ↓
Trading Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API should consume indexed state rather than performing raw blockchain scans on every request.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitoring
&lt;/h2&gt;

&lt;p&gt;A production-style indexer needs its own monitoring.&lt;/p&gt;

&lt;p&gt;Useful metrics include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;latest indexed block
indexing lag
events processed
processing errors
RPC failures
processing latency
reorg events
database latency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain height: 2,000,100
Indexed height:    2,000,096

Indexing lag:      4 blocks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells the operator whether the indexer is healthy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Failure handling
&lt;/h2&gt;

&lt;p&gt;The indexer should expect failures.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC timeout
RPC rate limit
malformed event
decoder error
database error
process crash
network interruption
chain reorganization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right response is not always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;retry forever
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some errors should be retried.&lt;/p&gt;

&lt;p&gt;Some should be isolated.&lt;/p&gt;

&lt;p&gt;Some should stop the affected stream and require investigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error isolation
&lt;/h2&gt;

&lt;p&gt;Suppose one malformed event causes a decoder error.&lt;/p&gt;

&lt;p&gt;I do not want the entire indexer to lose all future blocks.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Block
 ├── Event A → Processed
 ├── Event B → Processed
 ├── Event C → Decoder Error
 └── Event D → Processed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on the application's requirements, the problematic event can be recorded for investigation while the system continues processing safely.&lt;/p&gt;

&lt;p&gt;The exact policy should be explicit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idempotent processing
&lt;/h2&gt;

&lt;p&gt;A retry should be safe.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First attempt:
block 200 → events A, B, C

Crash after B

Retry:
block 200 → events A, B, C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A correct idempotent pipeline produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → one record
B → one record
C → one record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → duplicate
B → duplicate
C → duplicate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is why event identity and database constraints are part of the architecture rather than an afterthought.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing strategy
&lt;/h2&gt;

&lt;p&gt;The project uses two categories of tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vitest
  ↓
Indexer / TypeScript logic

Integration tests
  ↓
RPC + local EVM + contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important tests include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event decoding
event normalization
block-range scanning
checkpointing
deduplication
historical replay
restart recovery
RPC failure
database failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Decoder tests
&lt;/h2&gt;

&lt;p&gt;Given a known event fixture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;raw log
  ↓
decoder
  ↓
expected normalized event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test should verify every important field:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trader
token
amount
transaction hash
block number
log index
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests protect against ABI or mapping mistakes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Checkpoint tests
&lt;/h2&gt;

&lt;p&gt;A useful scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process blocks 100-110
Crash after block 105
Restart
Resume safely
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result is that the event stream is complete and duplicates are eliminated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reprocessing tests
&lt;/h2&gt;

&lt;p&gt;Another important test is deliberate replay:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Process block 500
Process block 500 again
Process block 500 again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;one event
one derived trade
one state update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This demonstrates idempotency.&lt;/p&gt;




&lt;h2&gt;
  
  
  Integration test
&lt;/h2&gt;

&lt;p&gt;A simple end-to-end local test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Mock Contract
      ↓
Emit Event
      ↓
Local EVM
      ↓
Indexer
      ↓
Decode
      ↓
Normalize
      ↓
Persist
      ↓
Query Indexed Event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proves that the complete pipeline works rather than only testing isolated functions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting the indexer to a trading system
&lt;/h2&gt;

&lt;p&gt;The indexer becomes especially useful when connected to the rest of the trading stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   Blockchain
                       |
                       v
                 Event Indexer
                       |
                       v
                  Trading State
                       |
                       v
                    Strategy
                       |
                       v
                  Risk Engine
                       |
                       v
              Transaction Manager
                       |
                       v
                  Blockchain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a feedback loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Observe
   ↓
Decide
   ↓
Execute
   ↓
Observe Again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the architecture behind many automated trading systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting it to the transaction manager
&lt;/h2&gt;

&lt;p&gt;The event indexer and transaction manager solve different problems.&lt;/p&gt;

&lt;p&gt;The transaction manager answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened to the transaction I submitted?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The event indexer answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What happened on-chain?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy
   ↓
Transaction Manager
   ↓
Blockchain
   ↓
Event Indexer
   ↓
Updated State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much more robust than treating a transaction hash as the final result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this can be used
&lt;/h2&gt;

&lt;p&gt;The same indexing layer can support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Launch Monitor
Pons Wallet Tracker
Pons Copy Trading
Stock Token Monitoring
Stock Token Trading
Arbitrage Detection
Trading Terminals
Portfolio Analytics
Wallet Intelligence
Custom EVM Applications
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The indexer does not need to become a product-specific monolith.&lt;/p&gt;

&lt;p&gt;It provides the data foundation underneath those products.&lt;/p&gt;




&lt;h2&gt;
  
  
  The engineering principles
&lt;/h2&gt;

&lt;p&gt;The project is built around a few rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain is the source of truth.
Processing should be idempotent.
Checkpoints must be persistent.
Raw events should be retained.
Protocol logic should be isolated.
Uncertain state should remain explicit.
Recovery must be possible after restart.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These principles are more important than the particular TypeScript classes used to implement them.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I am trying to prove with this project
&lt;/h2&gt;

&lt;p&gt;The important capability is not simply knowing how to call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogs&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The real engineering problem is building everything around it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Block scanning
+
Event decoding
+
Normalization
+
Historical backfill
+
Real-time indexing
+
Checkpointing
+
Deduplication
+
Persistence
+
Reorg awareness
+
Failure recovery
+
State reconstruction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is what turns blockchain logs into usable infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         EVM Blockchain
                               |
                               v
                         Block Scanner
                               |
                               v
                          Log Fetcher
                               |
                               v
                         Event Decoder
                               |
                               v
                       Event Normalizer
                               |
                               v
                         Deduplication
                               |
                               v
                       Persistent Store
                               |
                               v
                        State Processor
                               |
             +-----------------+-----------------+
             |                 |                 |
             v                 v                 v
          Positions           PnL             Alerts
             |                 |                 |
             +-----------------+-----------------+
                               |
                               v
                         API / Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for an automated trading application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Indexed Events
      ↓
Strategy
      ↓
Risk Engine
      ↓
Transaction Manager
      ↓
Solidity / EVM Executor
      ↓
Blockchain
      ↓
Event Indexer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The loop closes.&lt;/p&gt;

&lt;p&gt;The application observes the blockchain, makes a decision, executes the decision, observes the result, and updates its state.&lt;/p&gt;

&lt;p&gt;That is the infrastructure I am building for reusable &lt;strong&gt;EVM trading systems, Pons applications, Stock Token systems, arbitrage, copy trading, trading terminals, and custom blockchain products&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ethereum</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>Building an EVM Transaction Manager for Automated Trading Bots</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Sat, 03 Oct 2026 13:46:09 +0000</pubDate>
      <link>https://dev.to/hamssog/building-an-evm-transaction-manager-for-automated-trading-bots-39j2</link>
      <guid>https://dev.to/hamssog/building-an-evm-transaction-manager-for-automated-trading-bots-39j2</guid>
      <description>&lt;p&gt;A trading bot can detect an opportunity correctly and still execute incorrectly.&lt;/p&gt;

&lt;p&gt;The strategy may be fine.&lt;/p&gt;

&lt;p&gt;The transaction infrastructure may not be.&lt;/p&gt;

&lt;p&gt;For automated EVM trading, I treat transaction management as its own engineering layer.&lt;/p&gt;

&lt;p&gt;The system has to handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signal
  ↓
Transaction Request
  ↓
Nonce Allocation
  ↓
Simulation
  ↓
Submission
  ↓
Pending
  ↓
Confirmation
  ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the failure paths matter just as much:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pending
  ├── Reverted
  ├── Timed Out
  ├── Replaced
  ├── RPC Failure
  └── Unknown Nonce Consumption
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For this project, I am building an &lt;strong&gt;EVM Transaction Manager&lt;/strong&gt; with TypeScript, viem, SQLite, Vitest, and Foundry.&lt;/p&gt;

&lt;p&gt;The goal is to create reusable infrastructure that can sit underneath:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated trading bots&lt;/li&gt;
&lt;li&gt;arbitrage systems&lt;/li&gt;
&lt;li&gt;copy-trading systems&lt;/li&gt;
&lt;li&gt;Pons applications&lt;/li&gt;
&lt;li&gt;stock-token trading systems&lt;/li&gt;
&lt;li&gt;trading terminals&lt;/li&gt;
&lt;li&gt;custom EVM applications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a trading strategy.&lt;/p&gt;

&lt;p&gt;It is the transaction lifecycle underneath the strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;The basic architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| Trading Strategy     |
+----------+-----------+
           |
           v
+----------------------+
| Risk / Execution     |
+----------+-----------+
           |
           v
+----------------------+
| EVM Transaction      |
| Manager              |
+----------------------+
| Nonce Manager        |
| Persistence          |
| Submission           |
| Monitoring           |
| Retry Policy         |
| Replacement          |
| Reconciliation       |
+----------+-----------+
           |
           v
+----------------------+
| EVM RPC              |
+----------+-----------+
           |
           v
+----------------------+
| Blockchain            |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy should not own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;nonce allocation&lt;/li&gt;
&lt;li&gt;transaction persistence&lt;/li&gt;
&lt;li&gt;receipt polling&lt;/li&gt;
&lt;li&gt;replacement logic&lt;/li&gt;
&lt;li&gt;recovery after restart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those responsibilities belong inside the transaction manager.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why transaction management needs its own layer
&lt;/h2&gt;

&lt;p&gt;A naive bot often looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;walletClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendTransaction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That produces a transaction hash.&lt;/p&gt;

&lt;p&gt;It does not provide a complete execution lifecycle.&lt;/p&gt;

&lt;p&gt;A more useful abstraction is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submit()
   ↓
transaction ID
   ↓
pending
   ↓
confirmed / reverted
   ↓
reconciled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the rest of the application work with a logical transaction rather than directly managing every RPC operation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;The repository is organized around clear responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── config/
│   └── env.ts
│
├── domain/
│   ├── transaction.ts
│   ├── transaction-status.ts
│   └── transaction-error.ts
│
├── clients/
│   └── evm-client.ts
│
├── nonce/
│   ├── nonce-manager.ts
│   └── nonce-lock.ts
│
├── persistence/
│   ├── database.ts
│   └── transaction-repository.ts
│
├── submission/
│   ├── transaction-submitter.ts
│   ├── fee-policy.ts
│   └── replacement-policy.ts
│
├── monitoring/
│   ├── transaction-monitor.ts
│   ├── receipt-monitor.ts
│   └── confirmation-monitor.ts
│
├── reconciliation/
│   └── transaction-reconciler.ts
│
└── services/
    └── transaction-manager.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is not to create dozens of abstractions.&lt;/p&gt;

&lt;p&gt;The goal is to keep blockchain access, persistence, lifecycle state, and business logic from becoming one giant class.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transaction domain model
&lt;/h2&gt;

&lt;p&gt;I use an explicit transaction record.&lt;/p&gt;

&lt;p&gt;A simplified TypeScript version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransactionStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUEUED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIMULATING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REVERTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACEMENT_PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TIMED_OUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransactionRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;currentHash&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;replacementHashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionStatus&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;attemptCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;submittedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;confirmedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reconciledAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;lastCheckedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;blockHash&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;errorCode&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One important implementation detail is avoiding JavaScript floating-point numbers for EVM amounts and fee values.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;bigint&lt;/code&gt; for values that represent on-chain integers.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explicit state transitions
&lt;/h2&gt;

&lt;p&gt;A transaction should not be able to jump randomly between states.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
QUEUED
   ↓
SIMULATING
   ↓
SUBMITTING
   ↓
SUBMITTED
   ↓
PENDING
   ↓
CONFIRMED
   ↓
RECONCILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ├── REVERTED
   ├── TIMED_OUT
   └── REPLACEMENT_PENDING
                    ↓
                REPLACED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a transition validator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedTransitions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
  &lt;span class="nx"&gt;TransactionStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;TransactionStatus&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;CREATED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUEUED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;QUEUED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIMULATING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;SIMULATING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;SUBMITTING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;SUBMITTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REVERTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACEMENT_PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TIMED_OUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;PENDING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REVERTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACEMENT_PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TIMED_OUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;CONFIRMED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;REPLACEMENT_PENDING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REVERTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;REPLACED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;TIMED_OUT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REPLACEMENT_PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RECONCILIATION_REQUIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;RECONCILED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;REVERTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;FAILED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact state model can evolve, but the important part is that transitions are explicit and validated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nonce management
&lt;/h2&gt;

&lt;p&gt;Nonce management becomes difficult as soon as multiple transactions use the same account.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy A ──┐
Strategy B ──┼──&amp;gt; Same wallet
Strategy C ──┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If each service independently requests the pending nonce, they can race.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request A → nonce 100
Request B → nonce 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction manager should allocate nonces centrally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pending nonce = 100

Request A → 100
Request B → 101
Request C → 102
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The allocation itself needs concurrency control.&lt;/p&gt;




&lt;h2&gt;
  
  
  Nonce manager API
&lt;/h2&gt;

&lt;p&gt;A clean interface might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NonceManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getNextNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;allocateNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;reconcileNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The implementation should:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;query chain state&lt;/li&gt;
&lt;li&gt;establish the starting nonce&lt;/li&gt;
&lt;li&gt;allocate locally&lt;/li&gt;
&lt;li&gt;serialize concurrent allocation&lt;/li&gt;
&lt;li&gt;periodically reconcile&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important rule is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never assume two concurrent calls will see different chain nonces.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The local allocator is what guarantees uniqueness inside the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concurrent nonce allocation
&lt;/h2&gt;

&lt;p&gt;This should be tested explicitly.&lt;/p&gt;

&lt;p&gt;Pseudo-scenario:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nonceManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allocateNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nonces&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test should verify:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 requests
      ↓
50 unique nonces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 requests
      ↓
same nonce repeated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not make this reliable through arbitrary delays.&lt;/p&gt;

&lt;p&gt;Use an actual mutex or serialized queue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keeping chain state and local state synchronized
&lt;/h2&gt;

&lt;p&gt;Local nonce tracking can become stale.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local state:
next nonce = 120

Blockchain:
nonce usage has moved forward
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction manager needs reconciliation logic.&lt;/p&gt;

&lt;p&gt;A useful mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain
    ↑
    |
Local Nonce State
    ↑
    |
Transaction Records
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The local state is an optimization and coordination mechanism.&lt;/p&gt;

&lt;p&gt;The blockchain remains the source of truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  Submission flow
&lt;/h2&gt;

&lt;p&gt;A transaction submission should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction Request
        ↓
Validate
        ↓
Allocate Nonce
        ↓
Simulate
        ↓
Prepare Fees
        ↓
Submit
        ↓
Persist Hash
        ↓
Monitor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simplified public API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return a logical transaction ID as well as the initial transaction hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;hash&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important because one logical transaction may later have multiple attempts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Simulation before submission
&lt;/h2&gt;

&lt;p&gt;For contract calls, the manager can optionally simulate before sending.&lt;/p&gt;

&lt;p&gt;The flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Request
  ↓
Simulation
  |
  +-- fail → don't submit
  |
  +-- success
        ↓
      submit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With viem, the application can use simulation before writing when the request supports it.&lt;/p&gt;

&lt;p&gt;The important limitation is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Simulation is a check against one observed state, not a guarantee of future mining success.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The state can change between simulation and execution.&lt;/p&gt;

&lt;p&gt;So simulation improves validation but does not replace monitoring and reconciliation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fee management
&lt;/h2&gt;

&lt;p&gt;Transaction replacement depends on fee policy.&lt;/p&gt;

&lt;p&gt;I keep fee logic separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FeePolicy&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;getInitialFees&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxFeePerGas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;maxPriorityFeePerGas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;getReplacementFees&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nl"&gt;maxFeePerGas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nl"&gt;maxPriorityFeePerGas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;maxFeePerGas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;maxPriorityFeePerGas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The replacement policy can then be configured with values such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;replacementBumpPercent
minimumFeeBump
maxFeePerGasCap
maxPriorityFeePerGasCap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Avoid assuming one universal replacement percentage works across every EVM network.&lt;/p&gt;




&lt;h2&gt;
  
  
  Retry policy
&lt;/h2&gt;

&lt;p&gt;Retries should be based on failure classification.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC timeout
      ↓
possibly retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;versus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract reverted
      ↓
do not blindly retry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A simple classification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Retryable
---------
temporary RPC failure
transport failure
pending timeout

Not automatically retryable
---------------------------
contract revert
invalid calldata
authorization failure
insufficient funds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final policy depends on the application.&lt;/p&gt;

&lt;p&gt;The important engineering principle is that retry behavior must be intentional.&lt;/p&gt;




&lt;h2&gt;
  
  
  Replacement transactions
&lt;/h2&gt;

&lt;p&gt;A stuck transaction may need a replacement.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce = 200
hash = 0xAAA
status = PENDING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The manager can submit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce = 200
hash = 0xBBB
higher fee
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nonce must stay the same.&lt;/p&gt;

&lt;p&gt;That gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nonce 200
   |
   +--&amp;gt; 0xAAA
   |
   +--&amp;gt; 0xBBB
   |
   +--&amp;gt; final result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database should preserve the whole attempt history.&lt;/p&gt;




&lt;h2&gt;
  
  
  Attempt history
&lt;/h2&gt;

&lt;p&gt;Instead of replacing one hash with another, keep each attempt.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction
-----------
id: tx-123
nonce: 200
currentHash: 0xCCC

attempts
--------
1 → 0xAAA
2 → 0xBBB
3 → 0xCCC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for debugging.&lt;/p&gt;

&lt;p&gt;It also prevents the database from losing important historical information.&lt;/p&gt;




&lt;h2&gt;
  
  
  Detecting unknown nonce consumption
&lt;/h2&gt;

&lt;p&gt;One subtle case is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original:
nonce = 200
hash = 0xAAA

Later:
account nonce advanced
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not automatically prove:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0xAAA was replaced by 0xBBB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another transaction may have used nonce 200.&lt;/p&gt;

&lt;p&gt;The manager should therefore be conservative:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;known replacement
      ↓
REPLACED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unknown nonce consumption
      ↓
RECONCILIATION_REQUIRED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prevents the application from creating false certainty.&lt;/p&gt;




&lt;h2&gt;
  
  
  Monitoring pending transactions
&lt;/h2&gt;

&lt;p&gt;The monitor periodically checks active transactions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------+
| Transaction Monitor|
+---------+----------+
          |
          +--&amp;gt; TX A
          +--&amp;gt; TX B
          +--&amp;gt; TX C
          +--&amp;gt; TX D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For each transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;receipt exists?
     |
     +-- no
     |    |
     |    +--&amp;gt; still pending
     |    +--&amp;gt; timeout policy
     |
     +-- yes
          |
          +--&amp;gt; success
          |
          +--&amp;gt; reverted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a configurable polling interval.&lt;/p&gt;

&lt;p&gt;Avoid creating a permanent uncontrolled timer for every transaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  Confirmation tracking
&lt;/h2&gt;

&lt;p&gt;A transaction may be mined but still require additional confirmations.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Receipt
  ↓
1 confirmation
  ↓
2 confirmations
  ↓
3 confirmations
  ↓
Application accepts final state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make the required confirmation count configurable.&lt;/p&gt;

&lt;p&gt;Persist useful blockchain metadata:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;blockNumber
blockHash
transactionIndex
confirmationCount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Reconciliation
&lt;/h2&gt;

&lt;p&gt;Receipt monitoring answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did the transaction get mined?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reconciliation answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What actually happened?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reconciler can inspect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction
receipt
status
logs
nonce
block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then update application state.&lt;/p&gt;

&lt;p&gt;The lifecycle becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
    ↓
PENDING
    ↓
CONFIRMED
    ↓
RECONCILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last step is important for trading systems because the requested action and the actual blockchain result can differ.&lt;/p&gt;




&lt;h2&gt;
  
  
  Process restart
&lt;/h2&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09:31:00 transaction submitted
09:31:01 process crashes
09:31:03 transaction mined
09:31:10 service restarts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A memory-only application loses context.&lt;/p&gt;

&lt;p&gt;A persistent transaction manager can recover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SQLite
  ↓
find active transactions
  ↓
fetch blockchain state
  ↓
reconcile
  ↓
restore correct status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the main reasons I store transaction state rather than keeping it only in memory.&lt;/p&gt;




&lt;h2&gt;
  
  
  SQLite persistence
&lt;/h2&gt;

&lt;p&gt;For a small reusable reference implementation, SQLite is enough.&lt;/p&gt;

&lt;p&gt;The main table can contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transactions
------------
id
chain_id
account
nonce
to_address
data
value
current_hash
status
attempt_count
created_at
submitted_at
confirmed_at
reconciled_at
block_number
block_hash
error_code
error_message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A second table tracks attempts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction_attempts
--------------------
id
transaction_id
hash
nonce
attempt_number
submitted_at
max_fee_per_gas
max_priority_fee_per_gas
status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep SQL inside a repository layer.&lt;/p&gt;

&lt;p&gt;Do not spread SQL statements throughout the transaction manager.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository interface
&lt;/h2&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TransactionRepository&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionRecord&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TransactionRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;findById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TransactionRecord&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;findByHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TransactionRecord&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;findPending&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TransactionRecord&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;findByAccountAndNonce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TransactionRecord&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;addAttempt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionAttempt&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That allows the domain logic to remain independent of SQLite.&lt;/p&gt;




&lt;h2&gt;
  
  
  Error handling
&lt;/h2&gt;

&lt;p&gt;Transaction infrastructure benefits from typed errors.&lt;/p&gt;

&lt;p&gt;Examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NonceAllocationError
NonceMismatchError
SimulationError
SubmissionError
TransactionRevertedError
ReplacementError
ReconciliationError
ConfigurationError
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;something went wrong&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;preserve useful information.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SimulationError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;transactionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cause&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application can then decide whether the error is retryable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security boundaries
&lt;/h2&gt;

&lt;p&gt;The transaction manager should never become a secret-management system.&lt;/p&gt;

&lt;p&gt;Do not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;store private keys in SQLite&lt;/li&gt;
&lt;li&gt;log private keys&lt;/li&gt;
&lt;li&gt;log seed phrases&lt;/li&gt;
&lt;li&gt;include &lt;code&gt;.env&lt;/code&gt; in Git&lt;/li&gt;
&lt;li&gt;print credentials during debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The wallet/account layer should be injectable.&lt;/p&gt;

&lt;p&gt;That makes local tests easier and avoids coupling the system to one key-management approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testing strategy
&lt;/h2&gt;

&lt;p&gt;I split testing into two layers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Foundry
  ↓
Solidity / local contracts

Vitest
  ↓
TypeScript transaction infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That keeps the test responsibilities clear.&lt;/p&gt;




&lt;h2&gt;
  
  
  Unit tests
&lt;/h2&gt;

&lt;p&gt;The TypeScript test suite should cover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Nonce Manager
----------------
initial nonce
sequential allocation
concurrent allocation
reconciliation


State Machine
----------------
valid transition
invalid transition
terminal state


Retry Policy
----------------
retryable failure
non-retryable failure
maximum attempts


Replacement
----------------
same nonce
higher fee
attempt history


Persistence
----------------
create
update
reload
pending lookup
attempt lookup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Failure-path tests
&lt;/h2&gt;

&lt;p&gt;The failure paths are particularly valuable.&lt;/p&gt;

&lt;p&gt;Test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC unavailable
RPC timeout
simulation revert
submission error
duplicate nonce
stale nonce
pending timeout
replacement
reverted receipt
missing receipt
process restart
unknown nonce consumption
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A transaction manager that only tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;send → success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not demonstrate much about reliability.&lt;/p&gt;




&lt;h2&gt;
  
  
  Foundry integration contracts
&lt;/h2&gt;

&lt;p&gt;For local integration testing, create simple contracts.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract MockTarget {
    uint256 public executions;

    function execute() external {
        executions++;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a reverting contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract MockRevertingTarget {
    function execute() external pure {
        revert("MOCK_REVERT");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These give the TypeScript integration suite deterministic targets.&lt;/p&gt;




&lt;h2&gt;
  
  
  End-to-end flow
&lt;/h2&gt;

&lt;p&gt;A complete successful test should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
Transaction Manager
    ↓
Nonce Allocation
    ↓
Simulation
    ↓
Transaction Submission
    ↓
Transaction Hash
    ↓
Persistence
    ↓
Receipt Monitoring
    ↓
Confirmation
    ↓
Reconciliation
    ↓
RECONCILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the failure path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
Transaction Manager
    ↓
Submission
    ↓
PENDING
    ↓
Receipt
    ↓
REVERTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No blind retry.&lt;/p&gt;




&lt;h2&gt;
  
  
  Using the manager from a trading system
&lt;/h2&gt;

&lt;p&gt;The advantage of this architecture is that the strategy code stays simple.&lt;/p&gt;

&lt;p&gt;A Pons strategy could produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildTradeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An arbitrage strategy could produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildArbitrageRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A copy-trading strategy could produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;buildCopyTradeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;trade&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three can submit through the same transaction manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transactionManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The infrastructure is reusable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Example application architecture
&lt;/h2&gt;

&lt;p&gt;A larger trading product can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------+
| Trading UI            |
+-----------+-----------+
            |
            v
+-----------------------+
| API                   |
+-----------+-----------+
            |
            v
+-----------------------+
| Strategy / Risk       |
+-----------+-----------+
            |
            v
+-----------------------+
| EVM Transaction       |
| Manager               |
+-----------+-----------+
            |
            v
+-----------------------+
| Blockchain            |
+-----------+-----------+
            |
            v
+-----------------------+
| Indexer / Reconciler  |
+-----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the architecture I prefer over putting transaction logic directly inside every strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters for Pons and Stock Token systems
&lt;/h2&gt;

&lt;p&gt;The transaction manager does not need to understand one specific protocol.&lt;/p&gt;

&lt;p&gt;That is a feature.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Bot
    |
    v
Execution Request
    |
    v
EVM Transaction Manager
    |
    v
Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stock Token Strategy
    |
    v
Execution Request
    |
    v
EVM Transaction Manager
    |
    v
EVM Protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Arbitrage Strategy
    |
    v
Execution Request
    |
    v
EVM Transaction Manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same lifecycle infrastructure can serve all three.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I am trying to prove
&lt;/h2&gt;

&lt;p&gt;This repository is not primarily about sending an EVM transaction.&lt;/p&gt;

&lt;p&gt;It demonstrates the engineering around that transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity / EVM
      +
TypeScript
      +
Nonce Management
      +
Persistence
      +
Retries
      +
Replacement
      +
Receipt Monitoring
      +
Reconciliation
      +
Failure Handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the part of automated trading infrastructure that often determines whether a prototype can evolve into a usable application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The project is:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;evm-transaction-manager&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The repository is intended as a reusable reference implementation rather than a claim of audited or production-certified infrastructure.&lt;/p&gt;

&lt;p&gt;The README documents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;architecture&lt;/li&gt;
&lt;li&gt;state transitions&lt;/li&gt;
&lt;li&gt;nonce management&lt;/li&gt;
&lt;li&gt;replacement logic&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;li&gt;local setup&lt;/li&gt;
&lt;li&gt;testing&lt;/li&gt;
&lt;li&gt;failure scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is designed so another EVM application can use the transaction manager without embedding transaction lifecycle logic inside its trading strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Trading Strategy
                       |
                       v
                 Risk / Validation
                       |
                       v
             +----------------------+
             | EVM Transaction      |
             | Manager              |
             +----------------------+
             | Nonce Management     |
             | Persistence          |
             | Simulation           |
             | Submission           |
             | Monitoring           |
             | Retry Policy         |
             | Replacement          |
             | Reconciliation       |
             +----------+-----------+
                        |
                        v
                     EVM RPC
                        |
                        v
                   Blockchain
                        |
                        v
                Receipt / Events
                        |
                        v
                 Reconciliation
                        |
                        v
                Position / PnL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy determines &lt;strong&gt;what transaction should be attempted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The transaction manager determines &lt;strong&gt;how that transaction is submitted, tracked, recovered, and reconciled&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That separation is the foundation for building reliable automated EVM trading systems.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>ethereum</category>
      <category>solidity</category>
      <category>web3</category>
    </item>
    <item>
      <title>Building a Solidity Trading Executor with Foundry and TypeScript</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Fri, 02 Oct 2026 06:48:18 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-solidity-trading-executor-with-foundry-and-typescript-4ld5</link>
      <guid>https://dev.to/hamssog/building-a-solidity-trading-executor-with-foundry-and-typescript-4ld5</guid>
      <description>&lt;p&gt;Most trading bot examples stop at the strategy layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price update
   ↓
Signal
   ↓
Buy / Sell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough to demonstrate an idea.&lt;/p&gt;

&lt;p&gt;It is not enough to build reusable trading infrastructure.&lt;/p&gt;

&lt;p&gt;Once a strategy starts executing real transactions, the system needs to deal with authorization, approved contracts, slippage, deadlines, transaction state, events, failed execution, and reconciliation.&lt;/p&gt;

&lt;p&gt;For this project, I am building a reusable &lt;strong&gt;EVM Trading Executor&lt;/strong&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solidity&lt;/li&gt;
&lt;li&gt;Foundry&lt;/li&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;EVM-compatible protocols&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The executor is designed to sit underneath different applications rather than being tied to one trading strategy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy
   ↓
Risk Checks
   ↓
Execution Request
   ↓
Solidity Trading Executor
   ↓
Protocol
   ↓
On-chain Result
   ↓
Events
   ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why build an execution layer?
&lt;/h2&gt;

&lt;p&gt;A strategy answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should I trade?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An execution system answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can I execute that trade under the required constraints?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those are different responsibilities.&lt;/p&gt;

&lt;p&gt;For example, a strategy may produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenIn
tokenOut
amountIn
minimumAmountOut
deadline
router
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The execution layer should validate those parameters before interacting with an external contract.&lt;/p&gt;

&lt;p&gt;That gives the system a clear boundary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------------------+
| Strategy             |
|                      |
| Signal / Opportunity |
+----------+-----------+
           |
           v
+----------------------+
| Trading Executor     |
|                      |
| Validation           |
| Security             |
| Execution            |
+----------+-----------+
           |
           v
+----------------------+
| EVM Protocol         |
+----------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;A simple project structure can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;evm-trading-executor/
├── src/
│   ├── TradingExecutor.sol
│   ├── interfaces/
│   │   └── ITradingExecutor.sol
│   └── libraries/
├── test/
│   ├── TradingExecutor.t.sol
│   ├── TradingExecutorFuzz.t.sol
│   └── TradingExecutorInvariant.t.sol
├── script/
│   └── Deploy.s.sol
├── lib/
├── foundry.toml
└── ts/
    ├── executor.ts
    └── types.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Solidity side owns the execution rules.&lt;/p&gt;

&lt;p&gt;The TypeScript side handles application-level orchestration.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solidity executor
&lt;/h2&gt;

&lt;p&gt;The first version should keep the core interface small.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface ITradingExecutor {
    function executeTrade(
        address router,
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOutMinimum,
        uint256 deadline
    ) external returns (uint256 amountOut);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is the separation of responsibilities.&lt;/p&gt;

&lt;p&gt;The executor receives a trade request.&lt;/p&gt;

&lt;p&gt;It does not need to know why the strategy generated it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access control
&lt;/h2&gt;

&lt;p&gt;The executor should not allow arbitrary addresses to submit trades.&lt;/p&gt;

&lt;p&gt;A basic implementation can use an executor role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bytes32 public constant EXECUTOR_ROLE =
    keccak256("EXECUTOR_ROLE");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the execution function can enforce the role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function executeTrade(
    address router,
    address tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 amountOutMinimum,
    uint256 deadline
) external onlyRole(EXECUTOR_ROLE) returns (uint256 amountOut) {
    // validation and execution
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an explicit trust boundary.&lt;/p&gt;

&lt;p&gt;A production architecture can separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin
 ├── configuration
 └── role management

Executor
 └── trade execution

Emergency Operator
 └── pause / recovery
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping these responsibilities separate makes the system easier to operate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Token allowlists
&lt;/h2&gt;

&lt;p&gt;A reusable executor should not blindly interact with arbitrary tokens.&lt;/p&gt;

&lt;p&gt;A simple allowlist is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; bool) public allowedTokens;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then validate both sides of a trade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    allowedTokens[tokenIn],
    "TOKEN_IN_NOT_ALLOWED"
);

require(
    allowedTokens[tokenOut],
    "TOKEN_OUT_NOT_ALLOWED"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small feature with an important purpose.&lt;/p&gt;

&lt;p&gt;If a configuration error or compromised service generates an unexpected token address, the executor can reject the request.&lt;/p&gt;




&lt;h2&gt;
  
  
  Router allowlists
&lt;/h2&gt;

&lt;p&gt;The same approach applies to protocol routers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mapping(address =&amp;gt; bool) public allowedRouters;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    allowedRouters[router],
    "ROUTER_NOT_ALLOWED"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute arbitrary target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Executor
   ↓
Approved Router
   ↓
Approved Protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much easier to reason about from a security perspective.&lt;/p&gt;




&lt;h2&gt;
  
  
  Slippage protection
&lt;/h2&gt;

&lt;p&gt;Automated trading should not assume that a quote remains valid indefinitely.&lt;/p&gt;

&lt;p&gt;A trade request should include a minimum acceptable output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 amountOutMinimum;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The executor can then pass the constraint into the underlying protocol.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected output:     100 tokens
Minimum accepted:    98 tokens
Actual output:      101 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade can proceed.&lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected output:     100 tokens
Minimum accepted:    98 tokens
Actual output:       94 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;should fail.&lt;/p&gt;

&lt;p&gt;This is one of the most important differences between a trading executor and a simple transaction sender.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deadline protection
&lt;/h2&gt;

&lt;p&gt;Execution parameters can also become stale.&lt;/p&gt;

&lt;p&gt;The executor should therefore validate the deadline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require(
    block.timestamp &amp;lt;= deadline,
    "DEADLINE_EXPIRED"
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes time part of the execution contract.&lt;/p&gt;

&lt;p&gt;An old request should not remain valid forever.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validation before external calls
&lt;/h2&gt;

&lt;p&gt;I prefer validating everything possible before making an external call.&lt;/p&gt;

&lt;p&gt;A simplified execution flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Check caller
2. Check amount
3. Check token allowlist
4. Check router allowlist
5. Check deadline
6. Prepare execution
7. Call protocol
8. Emit event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This has two advantages:&lt;/p&gt;

&lt;p&gt;First, invalid requests fail early.&lt;/p&gt;

&lt;p&gt;Second, the external protocol is only called after the executor has verified its own rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Events
&lt;/h2&gt;

&lt;p&gt;The execution contract should provide a useful event stream.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event TradeExecuted(
    address indexed executor,
    address indexed router,
    address indexed tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 amountOut
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The off-chain system can consume this event and update application state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity Executor
      ↓
TradeExecuted
      ↓
  Indexer
      ↓
Position Engine
      ↓
     PnL
      ↓
Dashboard / Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much cleaner than trying to reconstruct every execution entirely from application logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  TypeScript execution layer
&lt;/h2&gt;

&lt;p&gt;The TypeScript service sits above the contract.&lt;/p&gt;

&lt;p&gt;Its job is to prepare and submit execution requests.&lt;/p&gt;

&lt;p&gt;A simplified structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TradeRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amountOutMinimum&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service can then construct the transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;walletClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;executorAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;executorAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;executeTrade&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tokenOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;amountOutMinimum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact client library can vary.&lt;/p&gt;

&lt;p&gt;The important architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript
   ↓
Build execution request
   ↓
Validate application state
   ↓
Call Solidity executor
   ↓
Monitor transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Type safety matters
&lt;/h2&gt;

&lt;p&gt;Trading systems move a lot of numeric values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amountIn
amountOut
price
slippage
gas
fees
balances
reserves
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using JavaScript floating-point numbers for on-chain amounts is dangerous.&lt;/p&gt;

&lt;p&gt;I keep token amounts in integer form:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amountIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000000000000000000&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and convert only at presentation boundaries.&lt;/p&gt;

&lt;p&gt;That keeps calculations aligned with EVM integer arithmetic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transaction state
&lt;/h2&gt;

&lt;p&gt;Calling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;writeContract&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;does not mean the trade is completed.&lt;/p&gt;

&lt;p&gt;A useful state machine is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;READY
  ↓
SUBMITTED
  ↓
CONFIRMED
  ↓
RECONCILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With failure paths:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ├── REVERTED
   ├── DROPPED
   ├── REPLACED
   └── CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application should distinguish these states.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction submitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is not equivalent to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;position updated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The latter requires reconciliation with on-chain state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reconciliation
&lt;/h2&gt;

&lt;p&gt;After a transaction is confirmed, the off-chain application should verify what actually happened.&lt;/p&gt;

&lt;p&gt;A simplified process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction receipt
       ↓
Execution event
       ↓
Actual token amounts
       ↓
Position update
       ↓
PnL update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters particularly for partial fills, refunds, fee deductions, or protocol-specific execution behavior.&lt;/p&gt;

&lt;p&gt;A reliable system should use actual on-chain results rather than trusting only the parameters that were originally requested.&lt;/p&gt;




&lt;h2&gt;
  
  
  Foundry tests
&lt;/h2&gt;

&lt;p&gt;The contract layer needs more than one successful test.&lt;/p&gt;

&lt;p&gt;My testing structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tests
├── Unit
├── Fuzz
├── Invariant
└── Fork
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Unit testing
&lt;/h2&gt;

&lt;p&gt;Start with deterministic behavior.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function test_RejectsUnauthorizedCaller() public {
    vm.prank(attacker);

    vm.expectRevert();
    executor.executeTrade(
        router,
        tokenIn,
        tokenOut,
        amountIn,
        minimumOut,
        deadline
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other unit tests should cover:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorized execution
blocked token
blocked router
expired deadline
invalid amount
successful execution
reverted execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Fuzz testing
&lt;/h2&gt;

&lt;p&gt;Trading values have a huge input space.&lt;/p&gt;

&lt;p&gt;Fuzz testing allows Foundry to generate different values automatically.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testFuzz_AmountValidation(
    uint256 amountIn
) public {
    // validate execution behavior
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps find edge cases around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;zero values&lt;/li&gt;
&lt;li&gt;very large amounts&lt;/li&gt;
&lt;li&gt;boundary conditions&lt;/li&gt;
&lt;li&gt;unexpected combinations of parameters&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Invariant testing
&lt;/h2&gt;

&lt;p&gt;Some properties should always remain true.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unauthorized accounts cannot execute trades.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blocked routers cannot be used.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expired execution requests cannot execute.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These properties are stronger than testing one specific transaction.&lt;/p&gt;

&lt;p&gt;They define the rules the executor is supposed to preserve across many state transitions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fork testing
&lt;/h2&gt;

&lt;p&gt;Mocks are useful for isolated contract tests.&lt;/p&gt;

&lt;p&gt;Fork tests provide another layer.&lt;/p&gt;

&lt;p&gt;The idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Real EVM state
      ↓
   Fork
      ↓
Trading Executor
      ↓
Real protocol contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps expose integration problems that do not appear in fully mocked environments.&lt;/p&gt;

&lt;p&gt;For a trading system, integration behavior is critical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Protocol adapters
&lt;/h2&gt;

&lt;p&gt;One executor may eventually need to support multiple protocols.&lt;/p&gt;

&lt;p&gt;I prefer keeping protocol-specific code separate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Trading Executor
                        |
        +---------------+---------------+
        |               |               |
        v               v               v
     Adapter A       Adapter B       Adapter C
        |               |               |
        v               v               v
     Protocol A      Protocol B      Protocol C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The executor owns shared rules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorization
allowlists
slippage
deadlines
events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter owns protocol-specific behavior.&lt;/p&gt;

&lt;p&gt;That keeps the core contract from becoming one enormous protocol-specific implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where the executor fits in a real trading system
&lt;/h2&gt;

&lt;p&gt;The smart contract is only one layer.&lt;/p&gt;

&lt;p&gt;A larger application might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+---------------------------+
| Frontend / Trading UI     |
+-------------+-------------+
              |
              v
+---------------------------+
| API / Trading Service     |
+-------------+-------------+
              |
              v
+---------------------------+
| Strategy + Risk Engine    |
+-------------+-------------+
              |
              v
+---------------------------+
| Transaction Manager       |
+-------------+-------------+
              |
              v
+---------------------------+
| Solidity Trading Executor |
+-------------+-------------+
              |
              v
+---------------------------+
| EVM Protocol              |
+---------------------------+
              |
              v
+---------------------------+
| Indexer / Reconciliation  |
+---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has a different responsibility.&lt;/p&gt;

&lt;p&gt;That separation makes debugging much easier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reusing the same infrastructure
&lt;/h2&gt;

&lt;p&gt;Once the execution layer is isolated, different applications can use it.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    EVM Trading Executor
                            |
        +-------------------+-------------------+
        |                   |                   |
        v                   v                   v
   Sniper Bot         Copy Trading         Arbitrage
        |                   |                   |
        +-------------------+-------------------+
                            |
                            v
                      EVM Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful because the strategy can evolve without rewriting the fundamental execution controls.&lt;/p&gt;

&lt;p&gt;The same pattern can also support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pons trading systems&lt;/li&gt;
&lt;li&gt;Stock Token applications&lt;/li&gt;
&lt;li&gt;automated arbitrage&lt;/li&gt;
&lt;li&gt;copy trading&lt;/li&gt;
&lt;li&gt;trading terminals&lt;/li&gt;
&lt;li&gt;custom EVM products&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What I am actually proving with this project
&lt;/h2&gt;

&lt;p&gt;The main purpose of this repository is not to show that I can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contract TradingExecutor {}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The goal is to demonstrate the complete engineering boundary around execution.&lt;/p&gt;

&lt;p&gt;That means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity
+
EVM architecture
+
TypeScript integration
+
Access control
+
Protocol validation
+
Slippage protection
+
Transaction lifecycle
+
Event design
+
Foundry testing
+
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the infrastructure I would reuse when turning a trading idea into an actual application.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub
&lt;/h2&gt;

&lt;p&gt;The project is being developed as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;evm-trading-executor&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recommended repository layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
test/
script/
ts/
foundry.toml
README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The README should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Architecture
Installation
Environment variables
Deployment
Contract interface
TypeScript usage
Testing
Fork testing
Failure handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A repository is much more useful to a potential client when it explains both &lt;strong&gt;what was built&lt;/strong&gt; and &lt;strong&gt;how the system is intended to be extended&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final architecture
&lt;/h2&gt;

&lt;p&gt;The complete flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market / On-chain Data
          ↓
       Strategy
          ↓
      Risk Checks
          ↓
   Execution Request
          ↓
+-----------------------+
| Solidity Executor     |
|                       |
| Access Control        |
| Allowlists            |
| Slippage              |
| Deadline              |
| Validation            |
| Execution             |
| Events                |
+-----------+-----------+
            |
            v
      EVM Protocol
            |
            v
     Transaction Result
            |
            v
       Reconciliation
            |
            v
     Position / PnL State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy decides &lt;strong&gt;what should happen&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The execution layer makes sure the requested action follows the application's rules.&lt;/p&gt;

&lt;p&gt;That distinction is the foundation I use when building reusable EVM trading infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Next extensions
&lt;/h2&gt;

&lt;p&gt;The next iterations can add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Protocol adapters
Nonce management
Gas policies
Pause / emergency controls
Multi-wallet execution
Execution simulation
MEV-aware routing
Advanced monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same executor architecture can then become part of a larger custom trading product rather than remaining an isolated Solidity demo.&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>ethereum</category>
      <category>foundry</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Testing Solidity Trading Contracts with Foundry: Unit, Fork, and Failure-Path Testing</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Thu, 01 Oct 2026 06:59:56 +0000</pubDate>
      <link>https://dev.to/hamssog/testing-solidity-trading-contracts-with-foundry-unit-fork-and-failure-path-testing-4pjb</link>
      <guid>https://dev.to/hamssog/testing-solidity-trading-contracts-with-foundry-unit-fork-and-failure-path-testing-4pjb</guid>
      <description>&lt;p&gt;A Solidity contract can compile successfully and still be the wrong contract to put behind a trading system.&lt;/p&gt;

&lt;p&gt;For trading infrastructure, I care about a different question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happens when execution goes wrong?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A useful test suite should prove more than this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valid input
    ↓
trade succeeds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should also prove:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unauthorized caller
    ↓
revert

unsupported router
    ↓
revert

invalid token
    ↓
revert

bad slippage
    ↓
revert

expired transaction
    ↓
revert

external protocol failure
    ↓
revert

valid transaction
    ↓
execute
    ↓
emit event
    ↓
backend reconciles
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the approach I use when thinking about &lt;strong&gt;Solidity and EVM engineering for trading systems&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Robinhood Chain is EVM-compatible, so standard Solidity development and Foundry testing workflows apply to contracts deployed there. The official Robinhood Chain documentation also recommends testing on testnet before deploying to mainnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why testing matters more for trading contracts
&lt;/h2&gt;

&lt;p&gt;A normal application can sometimes recover from a bug with a new deployment.&lt;/p&gt;

&lt;p&gt;A smart contract may control assets directly.&lt;/p&gt;

&lt;p&gt;That changes the engineering priorities.&lt;/p&gt;

&lt;p&gt;For a trading contract, I want explicit guarantees around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access
Authorization
Assets
Routers
Amounts
Slippage
Deadlines
State
Events
External Calls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract should fail predictably when an assumption is violated.&lt;/p&gt;

&lt;p&gt;The test suite should prove those assumptions.&lt;/p&gt;




&lt;h2&gt;
  
  
  The testing pyramid
&lt;/h2&gt;

&lt;p&gt;I generally think about contract testing in layers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Production
                     ↑
                  Testnet
                     ↑
                 Fork Tests
                     ↑
              Integration Tests
                     ↑
            Invariant / Fuzz Tests
                     ↑
                  Unit Tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer answers a different question.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit tests
&lt;/h3&gt;

&lt;p&gt;Does the contract behave correctly for a known case?&lt;/p&gt;

&lt;h3&gt;
  
  
  Fuzz tests
&lt;/h3&gt;

&lt;p&gt;Does it behave correctly across many unexpected inputs?&lt;/p&gt;

&lt;h3&gt;
  
  
  Invariant tests
&lt;/h3&gt;

&lt;p&gt;Does an important property remain true after many actions?&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration tests
&lt;/h3&gt;

&lt;p&gt;Does the contract interact correctly with the surrounding protocol?&lt;/p&gt;

&lt;h3&gt;
  
  
  Fork tests
&lt;/h3&gt;

&lt;p&gt;Does it behave correctly against realistic chain state?&lt;/p&gt;

&lt;h3&gt;
  
  
  Testnet
&lt;/h3&gt;

&lt;p&gt;Does the deployed artifact behave correctly in a real network environment?&lt;/p&gt;




&lt;h2&gt;
  
  
  Example: a trading executor
&lt;/h2&gt;

&lt;p&gt;Consider a simplified execution contract:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IERC20 {
    function approve(
        address spender,
        uint256 amount
    ) external returns (bool);

    function balanceOf(
        address account
    ) external view returns (uint256);
}

interface IRouter {
    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut,
        address recipient
    ) external returns (uint256 amountOut);
}

contract TradingExecutor {
    address public immutable owner;

    mapping(address =&amp;gt; bool) public allowedRouter;
    mapping(address =&amp;gt; bool) public allowedToken;

    bool public paused;

    error Unauthorized();
    error Paused();
    error RouterNotAllowed();
    error TokenNotAllowed();
    error InvalidAmount();
    error SlippageExceeded();
    error DeadlineExpired();

    event RouterUpdated(
        address indexed router,
        bool allowed
    );

    event TokenUpdated(
        address indexed token,
        bool allowed
    );

    event TradeExecuted(
        bytes32 indexed operationId,
        address indexed router,
        address indexed tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOut
    );

    constructor(address initialOwner) {
        owner = initialOwner;
    }

    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert Unauthorized();
        }

        _;
    }

    function setRouter(
        address router,
        bool allowed
    ) external onlyOwner {
        allowedRouter[router] = allowed;

        emit RouterUpdated(
            router,
            allowed
        );
    }

    function setToken(
        address token,
        bool allowed
    ) external onlyOwner {
        allowedToken[token] = allowed;

        emit TokenUpdated(
            token,
            allowed
        );
    }

    function executeTrade(
        bytes32 operationId,
        address router,
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut,
        uint256 deadline,
        address recipient
    ) external onlyOwner returns (uint256 amountOut) {
        if (paused) {
            revert Paused();
        }

        if (!allowedRouter[router]) {
            revert RouterNotAllowed();
        }

        if (!allowedToken[tokenIn]) {
            revert TokenNotAllowed();
        }

        if (!allowedToken[tokenOut]) {
            revert TokenNotAllowed();
        }

        if (amountIn == 0) {
            revert InvalidAmount();
        }

        if (block.timestamp &amp;gt; deadline) {
            revert DeadlineExpired();
        }

        amountOut = IRouter(router).swap(
            tokenIn,
            tokenOut,
            amountIn,
            minAmountOut,
            recipient
        );

        if (amountOut &amp;lt; minAmountOut) {
            revert SlippageExceeded();
        }

        emit TradeExecuted(
            operationId,
            router,
            tokenIn,
            tokenOut,
            amountIn,
            amountOut
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally simplified.&lt;/p&gt;

&lt;p&gt;The important part is the number of assumptions it exposes.&lt;/p&gt;

&lt;p&gt;That gives us something concrete to test.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Test the happy path
&lt;/h2&gt;

&lt;p&gt;The first test should prove that the intended operation works.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testExecuteTrade() public {
    mockRouter.setAmountOut(100 ether);

    vm.prank(owner);

    uint256 amountOut = executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );

    assertEq(amountOut, 100 ether);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This proves:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorized caller
+
allowed router
+
allowed tokens
+
valid amount
+
valid deadline
+
acceptable output
=
successful execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that is only one path.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Unauthorized caller
&lt;/h2&gt;

&lt;p&gt;The next test should prove that another address cannot execute the contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsUnauthorizedCaller() public {
    vm.prank(attacker);

    vm.expectRevert(
        TradingExecutor.Unauthorized.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The property is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;attacker
   ↓
executeTrade()
   ↓
REVERT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the first tests I would expect to see in a serious trading contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Wrong router
&lt;/h2&gt;

&lt;p&gt;The execution contract should not call arbitrary addresses.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsUnknownRouter() public {
    address unknownRouter =
        address(0x1234);

    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.RouterNotAllowed.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        unknownRouter,
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the contract guarantees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;configured router
    ↓
allowed

unknown router
    ↓
rejected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much stronger security boundary than relying on the frontend to select the correct router.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Unsupported token
&lt;/h2&gt;

&lt;p&gt;Do the same for assets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsUnsupportedToken() public {
    address unsupportedToken =
        address(0x5678);

    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.TokenNotAllowed.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        unsupportedToken,
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This protects against accidental or malicious execution using an unexpected asset.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Zero amount
&lt;/h2&gt;

&lt;p&gt;Never assume callers will provide sensible amounts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsZeroAmount() public {
    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.InvalidAmount.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        0,
        0,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks trivial.&lt;/p&gt;

&lt;p&gt;It is still worth testing explicitly.&lt;/p&gt;

&lt;p&gt;The goal is to make the contract's assumptions visible.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Slippage failure
&lt;/h2&gt;

&lt;p&gt;Trading systems need an execution boundary.&lt;/p&gt;

&lt;p&gt;Suppose the application expects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected output: 100
Minimum output: 95
Actual output:   90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade must fail.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsBadSlippage() public {
    mockRouter.setAmountOut(90 ether);

    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.SlippageExceeded.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The property is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amountOut &amp;lt; minAmountOut
        ↓
REVERT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is far more meaningful than simply testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;swap works
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Deadline failure
&lt;/h2&gt;

&lt;p&gt;A quote can become stale.&lt;/p&gt;

&lt;p&gt;So test expired execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsExpiredTrade() public {
    uint256 deadline =
        block.timestamp + 10;

    vm.warp(block.timestamp + 11);

    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.DeadlineExpired.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        deadline,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foundry's cheatcodes make time-dependent testing deterministic.&lt;/p&gt;

&lt;p&gt;The test does not need to wait eleven real seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Paused execution
&lt;/h2&gt;

&lt;p&gt;If the contract has an emergency control, test it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsPausedExecution() public {
    vm.prank(owner);

    executor.setPaused(true);

    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.Paused.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The desired property is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paused = true
      ↓
no new execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An emergency control that isn't tested is not much of an emergency control.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Event testing
&lt;/h2&gt;

&lt;p&gt;A contract should make important state transitions observable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event TradeExecuted(
    bytes32 indexed operationId,
    address indexed router,
    address indexed tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 amountOut
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test the event directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testEmitsTradeExecuted() public {
    mockRouter.setAmountOut(100 ether);

    vm.expectEmit(
        true,
        true,
        true,
        true
    );

    emit TradeExecuted(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        100 ether
    );

    vm.prank(owner);

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters because the backend may depend on this event.&lt;/p&gt;

&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity
   ↓
TradeExecuted
   ↓
viem
   ↓
TypeScript
   ↓
Trade State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Fuzz testing
&lt;/h2&gt;

&lt;p&gt;Fixed test values cover known cases.&lt;/p&gt;

&lt;p&gt;Fuzzing explores a much larger input space.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testFuzzRejectsZeroAmount(
    uint256 amountIn
) public {
    vm.assume(amountIn == 0);

    vm.prank(owner);

    vm.expectRevert(
        TradingExecutor.InvalidAmount.selector
    );

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        amountIn,
        0,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The useful part of fuzzing is not merely generating random numbers.&lt;/p&gt;

&lt;p&gt;It is expressing a property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;for every input satisfying condition X,
property Y must remain true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For trading contracts, useful fuzzing targets include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amounts
minimum outputs
deadlines
addresses
limits
configuration boundaries
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foundry provides fuzz testing as part of its Solidity testing workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Invariant testing
&lt;/h2&gt;

&lt;p&gt;Fuzzing tests inputs.&lt;/p&gt;

&lt;p&gt;Invariant testing tests system properties across sequences of actions.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;paused
   ↓
no trade execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unauthorized user
   ↓
cannot modify router configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unsupported router
   ↓
never becomes executable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are stronger than a single test case because they describe what should &lt;strong&gt;always&lt;/strong&gt; remain true.&lt;/p&gt;

&lt;p&gt;For trading infrastructure, I would define invariants before writing a large amount of contract code.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Integration testing
&lt;/h2&gt;

&lt;p&gt;Mocks are useful.&lt;/p&gt;

&lt;p&gt;They are not enough.&lt;/p&gt;

&lt;p&gt;Suppose the executor calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Router
   ↓
Token
   ↓
Trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A perfect mock may hide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allowance problems
different revert behavior
unexpected return data
event differences
real token decimals
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Integration tests should therefore validate contract boundaries.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TradingExecutor
      ↓
Router Mock
      ↓
ERC-20 Mock
      ↓
Assertions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test should prove the contracts behave correctly together.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. External protocol failures
&lt;/h2&gt;

&lt;p&gt;One of the most important tests is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your contract
      ↓
External protocol
      ↓
REVERT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external failure
      ↓
transaction reverts
      ↓
no partial state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mock router can simulate this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testProtocolFailureReverts() public {
    mockRouter.setShouldRevert(true);

    vm.prank(owner);

    vm.expectRevert();

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the first failure paths I would add to a trading contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. State rollback
&lt;/h2&gt;

&lt;p&gt;Suppose a transaction performs several operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;update state
   ↓
transfer
   ↓
external call
   ↓
failure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ethereum transaction atomicity means a reverted transaction should not leave those earlier state changes committed.&lt;/p&gt;

&lt;p&gt;A useful test should verify the state before and after.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testFailedExecutionDoesNotChangeState() public {
    uint256 beforeValue =
        executor.someState();

    mockRouter.setShouldRevert(true);

    vm.prank(owner);

    vm.expectRevert();

    executor.executeTrade(
        OPERATION_ID,
        address(mockRouter),
        address(tokenIn),
        address(tokenOut),
        100 ether,
        95 ether,
        block.timestamp + 60,
        recipient
    );

    uint256 afterValue =
        executor.someState();

    assertEq(
        afterValue,
        beforeValue
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This verifies a property rather than only checking the revert itself.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Fork testing
&lt;/h2&gt;

&lt;p&gt;This is where the test becomes much more realistic.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Your Contract
   ↓
Mock Protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the environment becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Robinhood Chain State
       ↓
Fork
       ↓
Your Contract
       ↓
Real Protocol Contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Foundry supports fork testing against live chain state.&lt;/p&gt;

&lt;p&gt;This is useful for testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;real token behavior
real router behavior
real balances
real allowances
real contract state
real event behavior
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without sending a production transaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Fork testing a trading path
&lt;/h2&gt;

&lt;p&gt;A simplified example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testForkedExecution() public {
    vm.createSelectFork(
        vm.envString("RH_RPC_URL")
    );

    // Load actual deployed contracts.
    // Configure test accounts.
    // Provide test balances.
    // Execute the real protocol path.
    // Verify balances and events.
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The RPC URL should come from environment configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RH_RPC_URL=...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Never commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private keys
API keys
RPC credentials
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to the repository.&lt;/p&gt;

&lt;p&gt;Robinhood's current deployment guidance also explicitly warns against committing real private keys and recommends testnet-first deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Pons integration testing
&lt;/h2&gt;

&lt;p&gt;The same methodology applies to Pons.&lt;/p&gt;

&lt;p&gt;Pons V2 has a distinct bonding-curve lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch
   ↓
Curve
   ↓
Trading
   ↓
Graduation
   ↓
Uniswap V4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Its documented event model includes launch and curve-trading events that can be used for integration and indexing workflows.&lt;/p&gt;

&lt;p&gt;A Pons integration test should therefore validate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launch detection
curve state
quote inputs
trade event
actual tokens out
market transition
graduation state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test should use the &lt;strong&gt;current verified ABI&lt;/strong&gt;, not a hand-created approximation.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Testing the scanner and contract together
&lt;/h2&gt;

&lt;p&gt;This is where the smart-contract and TypeScript layers connect.&lt;/p&gt;

&lt;p&gt;A complete test might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Contract
      ↓
Event
      ↓
TypeScript Decoder
      ↓
Normalized Trade
      ↓
Database
      ↓
Position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CurveBuy
   ↓
decode
   ↓
quoteIn
tokensOut
fee
tax
   ↓
Trade Record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That prevents a common mistake:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requested amount
≠
actual execution amount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend should consume the actual onchain result.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Transaction-state testing
&lt;/h2&gt;

&lt;p&gt;A trading system also needs tests outside Solidity.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
SIGNED
   ↓
SUBMITTED
   ↓
PENDING
   ↓
CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ↓
RPC TIMEOUT
   ↓
UNKNOWN
   ↓
RECONCILIATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important test is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC timeout
   ↓
do NOT blindly submit another transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lookup transaction
   ↓
check receipt
   ↓
check events
   ↓
update state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where EVM testing becomes trading-infrastructure testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. Idempotency testing
&lt;/h2&gt;

&lt;p&gt;Suppose the same event is delivered twice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TradeExecuted
TradeExecuted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database should still contain one logical event.&lt;/p&gt;

&lt;p&gt;A good test is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;process event
   ↓
process same event again
   ↓
no duplicate record
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chainId
+
transactionHash
+
logIndex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as the event identity.&lt;/p&gt;

&lt;p&gt;This is especially important for applications that use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket listeners
backfills
RPC retries
reconnects
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  21. Testing the reconciliation layer
&lt;/h2&gt;

&lt;p&gt;Suppose the application believes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but the onchain event says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokensOut = 63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reconciliation test should prove that the final position is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete flow becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction
    ↓
Receipt
    ↓
Events
    ↓
Actual Fill
    ↓
Position
    ↓
Database Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the strongest places to demonstrate that the project understands real trading infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  22. A practical test matrix
&lt;/h2&gt;

&lt;p&gt;For a trading executor, I would start with:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authorization&lt;/td&gt;
&lt;td&gt;Valid caller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authorization&lt;/td&gt;
&lt;td&gt;Invalid caller&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;Allowed router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;Unknown router&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token&lt;/td&gt;
&lt;td&gt;Allowed token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token&lt;/td&gt;
&lt;td&gt;Unknown token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amount&lt;/td&gt;
&lt;td&gt;Zero amount&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slippage&lt;/td&gt;
&lt;td&gt;Valid output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slippage&lt;/td&gt;
&lt;td&gt;Output below minimum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadline&lt;/td&gt;
&lt;td&gt;Valid deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deadline&lt;/td&gt;
&lt;td&gt;Expired deadline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pause&lt;/td&gt;
&lt;td&gt;Trading enabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pause&lt;/td&gt;
&lt;td&gt;Trading paused&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;Successful call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;Reverted call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events&lt;/td&gt;
&lt;td&gt;Correct event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;No partial state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fuzzing&lt;/td&gt;
&lt;td&gt;Random boundary inputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Invariants&lt;/td&gt;
&lt;td&gt;Security properties&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fork&lt;/td&gt;
&lt;td&gt;Real protocol state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration&lt;/td&gt;
&lt;td&gt;TypeScript decoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconciliation&lt;/td&gt;
&lt;td&gt;Correct final position&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That test matrix tells a much more convincing story than simply saying:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The contract has tests."&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  23. Recommended Foundry project structure
&lt;/h2&gt;

&lt;p&gt;For a contract project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contracts/
├── src/
│   ├── executors/
│   ├── adapters/
│   ├── interfaces/
│   └── libraries/
│
├── test/
│   ├── unit/
│   ├── fuzz/
│   ├── invariant/
│   ├── integration/
│   └── fork/
│
├── script/
│   └── Deploy.s.sol
│
└── foundry.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then keep the TypeScript application separately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── strategy/
├── risk/
├── execution/
├── monitoring/
├── portfolio/
└── reconciliation/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity
   ↓
  EVM
   ↓
Robinhood Chain
   ↓
TypeScript
   ↓
Trading Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  24. What I want the test suite to prove
&lt;/h2&gt;

&lt;p&gt;A strong repository should make these properties obvious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Only authorized accounts can execute.

Unsupported routers are rejected.

Unsupported tokens are rejected.

Invalid amounts are rejected.

Bad slippage is rejected.

Expired transactions are rejected.

Paused execution is blocked.

External failures revert cleanly.

Important state transitions emit events.

Events can be decoded reliably.

Duplicate events do not create duplicate state.

Actual execution results become the canonical position state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are engineering properties.&lt;/p&gt;

&lt;p&gt;They are much more valuable than a list of technologies.&lt;/p&gt;




&lt;h2&gt;
  
  
  25. From contract testing to client work
&lt;/h2&gt;

&lt;p&gt;This is also why I am adding Solidity/EVM work to my Robinhood Chain development portfolio.&lt;/p&gt;

&lt;p&gt;A client may need only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity contract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity
+
TypeScript
+
EVM integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smart Contract
+
Trading Engine
+
Risk
+
Wallets
+
Database
+
Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The testing approach scales with the product.&lt;/p&gt;




&lt;h2&gt;
  
  
  26. Public implementation direction
&lt;/h2&gt;

&lt;p&gt;My current Robinhood Chain work already covers the application side:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons Sniper Bot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons Bundler&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons Token Scanner&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons Trading Terminal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock Token Trading Bot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock Token Arbitrage Bot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Solidity/EVM layer adds another dimension:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smart Contracts
       ↓
Protocol Integration
       ↓
Trading Engine
       ↓
Execution
       ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes the portfolio demonstrate more than frontend or TypeScript development.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;For a trading contract, the most useful question isn't:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does the swap work?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Can the wrong person execute it?

Can the wrong asset be used?

Can the wrong router be called?

What happens when the output is too low?

What happens when the transaction expires?

What happens when the external protocol reverts?

What happens when the RPC disappears?

What does the backend record?

What is the actual position afterward?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why I treat testing as part of the architecture.&lt;/p&gt;

&lt;p&gt;The workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity
   ↓
Unit Tests
   ↓
Fuzz / Invariants
   ↓
Integration
   ↓
Fork
   ↓
TypeScript
   ↓
Testnet
   ↓
Production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The objective is not simply to make a contract compile.&lt;/p&gt;

&lt;p&gt;The objective is to make the &lt;strong&gt;contract, EVM integration, trading engine, and state-management system behave predictably under both normal and failure conditions&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is the level of engineering I am targeting with my Robinhood Chain and Pons work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Need Solidity / EVM trading infrastructure?
&lt;/h3&gt;

&lt;p&gt;I build &lt;strong&gt;Solidity contracts, EVM integrations, trading executors, Pons integrations, Stock Token systems, TypeScript trading engines, and full-stack trading applications&lt;/strong&gt;, with testing and failure handling designed into the development process.&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>testing</category>
    </item>
    <item>
      <title>Building a Pons Token Scanner on Robinhood Chain with TypeScript</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Wed, 30 Sep 2026 12:35:17 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-pons-token-scanner-on-robinhood-chain-with-typescript-cm2</link>
      <guid>https://dev.to/hamssog/building-a-pons-token-scanner-on-robinhood-chain-with-typescript-cm2</guid>
      <description>&lt;p&gt;A token scanner is often treated as a simple script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;listen for new token
→ print address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is enough for a demo.&lt;/p&gt;

&lt;p&gt;It is not enough for a useful trading product.&lt;/p&gt;

&lt;p&gt;A production &lt;strong&gt;Pons Token Scanner&lt;/strong&gt; needs to turn raw blockchain activity into structured state that other systems can consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Robinhood Chain
      ↓
Pons Events
      ↓
Event Decoder
      ↓
Token Registry
      ↓
 Market State
      ↓
   Filters
      ↓
Opportunity State
      ↓
API / Alerts / Dashboard
      ↓
Sniper / Trading Bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important architectural distinction is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The scanner discovers and understands what happened. The trading bot decides what to do.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That makes the scanner useful on its own and reusable as the data layer underneath other trading products.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why build the scanner onchain?
&lt;/h2&gt;

&lt;p&gt;The current Pons V2 documentation explicitly describes factory and curve events as the integration source of truth: indexing the factory gives you launches, while indexing each curve gives you the launch's trading history. It states that this is enough to reconstruct protocol state without depending on a Pons service.&lt;/p&gt;

&lt;p&gt;That gives us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Contracts
      ↓
   Events
      ↓
Own Indexer
      ↓
Own Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   Pons UI
      ↓
Third-Party API
      ↓ 
   Scanner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building the indexer yourself gives control over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;data model&lt;/li&gt;
&lt;li&gt;filtering&lt;/li&gt;
&lt;li&gt;historical backfill&lt;/li&gt;
&lt;li&gt;latency&lt;/li&gt;
&lt;li&gt;alerts&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;downstream automation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Robinhood Chain connection
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain is an EVM-compatible Arbitrum Layer-2 on Ethereum. Mainnet currently uses chain ID &lt;strong&gt;4663&lt;/strong&gt; and ETH as the native gas token. The official deployment documentation supports standard Solidity tooling and EVM libraries.&lt;/p&gt;

&lt;p&gt;For a TypeScript scanner, a familiar stack works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript
    ↓
  viem
    ↓
   RPC
    ↓
Robinhood Chain
    ↓
Pons Contracts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A basic client can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;createPublicClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;robinhoodChain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4663&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Robinhood Chain&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;nativeCurrency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Ether&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ETH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;rpcUrls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://rpc.mainnet.chain.robinhood.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPublicClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;robinhoodChain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;http&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For production infrastructure, Robinhood currently recommends using a dedicated infrastructure provider such as Alchemy rather than relying on the public RPC endpoint for everything.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Separate Pons V1 and V2
&lt;/h2&gt;

&lt;p&gt;A scanner should be version-aware from the beginning.&lt;/p&gt;

&lt;p&gt;Pons V2 has a bonding-curve lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create
  ↓
Trade Curve
  ↓
Graduate
  ↓
Uniswap V4 Pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current V2 documentation describes the curve as the initial trading venue, with graduation occurring when the curve is bought out and the resulting liquidity moving into a permanently locked Uniswap V4 pool.&lt;/p&gt;

&lt;p&gt;That is different from the earlier Pons launch architecture.&lt;/p&gt;

&lt;p&gt;So I would structure the scanner like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Scanner
     ↓
Protocol Adapter
     ├── V1
     └── V2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the application should consume a normalized internal representation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Detect Pons V2 launches
&lt;/h2&gt;

&lt;p&gt;The current V2 documentation provides the event signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TokenLaunched(
    token,
    curve,
    deployer,
    pairToken,
    launchConfigId,
    graduationThreshold
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and explicitly shows using &lt;code&gt;getLogs()&lt;/code&gt; to index every launch.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;viem&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;parseAbiItem&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;viem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tokenLaunchedEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseAbiItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event TokenLaunched(address indexed token, address indexed curve, address indexed deployer, address pairToken, uint256 launchConfigId, uint256 graduationThreshold)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PONS_V2_FACTORY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tokenLaunchedEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;deploymentBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner has now answered:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A new Pons V2 launch exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But we still know very little about it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Normalize the launch
&lt;/h2&gt;

&lt;p&gt;I would immediately convert the raw event into an application object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PonsVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;V1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;V2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PonsLaunch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;deployer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PonsVersion&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;pairToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;launchConfigId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;graduationThreshold&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;launchBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;launchTxHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain Event
      ↓
V2 Adapter
      ↓
PonsLaunch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is important because application code should not have to understand the raw ABI of every protocol version.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Create a canonical token ID
&lt;/h2&gt;

&lt;p&gt;A ticker is not a unique blockchain identity.&lt;/p&gt;

&lt;p&gt;The scanner should identify a token using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chain ID
+
contract address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;tokenId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4663:0x1234...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes the canonical application identifier.&lt;/p&gt;

&lt;p&gt;This also prevents duplicate records caused by address casing differences.&lt;/p&gt;

&lt;p&gt;For Pons V2 specifically, the documentation warns that token names and symbols can be copied, so the address should be treated as the actual identity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 5: Enrich token metadata
&lt;/h2&gt;

&lt;p&gt;After the launch event, the scanner should load the token's metadata.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TokenMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;decimals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;totalSupply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;deployer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;image&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;website&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;twitter&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;telegram&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pipeline becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TokenLaunched
      ↓
Token Address
      ↓
ERC-20 Metadata
      ↓
Pons-Specific Metadata
      ↓
Token Registry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the scanner can answer more useful questions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What is this token?
Who launched it?
Which Pons version?
Which pair?
Which curve?
When was it launched?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 6: Track the market state
&lt;/h2&gt;

&lt;p&gt;For V2, a new token begins on a bonding curve.&lt;/p&gt;

&lt;p&gt;The current documentation describes the curve as the initial market, with trading continuing there until graduation and then continuing in a Uniswap V4 pool.&lt;/p&gt;

&lt;p&gt;I would represent market state explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MarketPhase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CURVE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRADUATING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POOL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RESCUED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner can then maintain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MarketState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;phase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MarketPhase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;quoteAsset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;lastBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much more useful than keeping only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;token = 0x...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 7: Index the curve
&lt;/h2&gt;

&lt;p&gt;The current Pons V2 docs explicitly recommend indexing the curve for trade history.&lt;/p&gt;

&lt;p&gt;The documented events include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CurveBuy
CurveSell
CurveBuyRefunded
CurveCompleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the factory also emits &lt;code&gt;LaunchSwept&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;curveBuyEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseAbiItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event CurveBuy(address indexed buyer, address indexed recipient, uint256 quoteIn, uint256 tokensOut, uint256 fee, uint256 tax)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;curveSellEvent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseAbiItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;event CurveSell(address indexed seller, address indexed recipient, uint256 tokensIn, uint256 quoteOut, uint256 fee, uint256 tax)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tradeLogs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;curveAddress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;curveBuyEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;curveSellEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;fromBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;launchBlock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;toBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latest&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the scanner is no longer just a launch detector.&lt;/p&gt;

&lt;p&gt;It has become a market activity indexer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 8: Store actual execution values
&lt;/h2&gt;

&lt;p&gt;This is an important implementation detail.&lt;/p&gt;

&lt;p&gt;The current Pons V2 documentation notes that a buy which finishes a curve can be partially filled. It specifically recommends reading &lt;code&gt;tokensOut&lt;/code&gt; and &lt;code&gt;quoteIn&lt;/code&gt; from the event rather than assuming the requested amount is what was executed.&lt;/p&gt;

&lt;p&gt;So the scanner should store:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;CurveTrade&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;logIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;trader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;quoteAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;fee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;blockNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not reconstruct the history from UI assumptions.&lt;/p&gt;

&lt;p&gt;Use the onchain event.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9: Build a token registry
&lt;/h2&gt;

&lt;p&gt;Once launch and trade events are normalized, the database becomes the core of the scanner.&lt;/p&gt;

&lt;p&gt;A simple schema:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokens
-------------------
id
address
version
name
symbol
deployer
created_at

pons_launches
-------------------
token_id
curve_address
pool_address
pair_token
launch_config_id
launch_block
launch_tx_hash

market_states
-------------------
token_id
phase
quote_asset
last_block
updated_at

curve_trades
-------------------
token_id
tx_hash
log_index
side
trader
recipient
quote_amount
token_amount
fee
tax
block_number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The advantage is that downstream applications can query a normalized database instead of decoding raw blockchain logs themselves.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 10: Add event idempotency
&lt;/h2&gt;

&lt;p&gt;A scanner will reconnect.&lt;/p&gt;

&lt;p&gt;It will backfill.&lt;/p&gt;

&lt;p&gt;It may process overlapping block ranges.&lt;/p&gt;

&lt;p&gt;That means the same event can appear more than once.&lt;/p&gt;

&lt;p&gt;A useful event identity is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;logIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;logIndex&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event
 ↓
Already processed?
 ├── yes → ignore
 └── no  → store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database should enforce uniqueness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNIQUE(chain_id, transaction_hash, log_index)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small feature that prevents large state problems later.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 11: Real-time listener and historical backfill should be separate
&lt;/h2&gt;

&lt;p&gt;There are really two jobs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Historical Indexer
       ↓
Backfill missing blocks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Live Listener
       ↓
New events
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I would not combine them into one giant process.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Pons Contracts
                       │
              ┌────────┴────────┐
              ↓                 ↓
         Backfill Worker    Live Worker
              ↓                 ↓
              └────────┬────────┘
                       ↓
                    Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes restarts and catch-up much easier.&lt;/p&gt;

&lt;p&gt;Robinhood's documentation currently recommends dedicated infrastructure providers for production use, while its network documentation also exposes the public RPC endpoints.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 12: Bounded block processing
&lt;/h2&gt;

&lt;p&gt;A common indexing mistake is requesting an enormous block range in one call.&lt;/p&gt;

&lt;p&gt;I prefer bounded chunks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="nx"&gt;_000n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;backfill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;startBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;endBlock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startBlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;endBlock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;endBlock&lt;/span&gt;
        &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;endBlock&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;CHUNK_SIZE&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;indexRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the indexer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smaller requests
+
retryable work
+
clear progress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;instead of one enormous RPC request that can fail and force the entire operation to restart.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 13: Filters
&lt;/h2&gt;

&lt;p&gt;Once the token registry exists, we can build a filter engine.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TokenFilter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TokenRecord&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FilterResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FilterResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;passed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token
  ↓
Contract Filter
  ↓
Metadata Filter
  ↓
Deployer Filter
  ↓
Market Filter
  ↓
Liquidity Filter
  ↓
Custom Rules
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much cleaner than putting everything into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;shouldTrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with hundreds of conditions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 14: Opportunity state
&lt;/h2&gt;

&lt;p&gt;A scanner should distinguish detection from eligibility.&lt;/p&gt;

&lt;p&gt;I would use something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OpportunityState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DETECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WATCHING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ELIGIBLE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EXPIRED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TokenLaunched
      ↓
DETECTED
      ↓
Filters
      ↓
WATCHING
      ↓
ELIGIBLE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the scanner can feed different systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scanner → Sniper Bot
&lt;/h2&gt;

&lt;p&gt;The Pons Sniper Bot can consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ELIGIBLE TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then perform its own:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quote
↓
risk
↓
execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Scanner
      ↓
Eligible Opportunity
      ↓
Pons Sniper
      ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is cleaner than implementing launch discovery separately inside the sniper.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scanner → Trading Terminal
&lt;/h2&gt;

&lt;p&gt;A Pons Trading Terminal can consume the scanner's API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Launches
Market Phase
Recent Trades
Liquidity
Token Metadata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend can then show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NEW
WATCHING
ELIGIBLE
GRADUATED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without knowing anything about raw contract logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scanner → Alerts
&lt;/h2&gt;

&lt;p&gt;A simpler product might only need alerts.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Token Scanner
        ↓
Filter
        ↓
Telegram / Discord
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alert can contain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Pons Launch

Token: XYZ
Version: V2
Pair: WETH
Curve: 0x...
Deployer: 0x...
Launch Block: 123456
Status: Watching
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can already be a useful standalone application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scanner → Copy Trading
&lt;/h2&gt;

&lt;p&gt;The same data layer can support wallet intelligence.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;New Token
   ↓
Trade Events
   ↓
Identify Active Wallets
   ↓
Track Wallet
   ↓
Generate Signal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That turns the scanner into the first stage of a copy-trading system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 15: Market state and graduation
&lt;/h2&gt;

&lt;p&gt;The scanner must understand when a Pons V2 launch leaves the bonding curve.&lt;/p&gt;

&lt;p&gt;The current Pons documentation describes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CurveBuy
     ↓
CurveCompleted
     ↓
Graduation
     ↓
PoolCreated
     ↓
Uniswap V4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and documents the relevant factory/curve events for reconstruction.&lt;/p&gt;

&lt;p&gt;So the scanner should update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;market&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POOL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when the protocol state confirms graduation.&lt;/p&gt;

&lt;p&gt;Do not infer this only from token balances.&lt;/p&gt;

&lt;p&gt;Use protocol state and events.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 16: Reconciliation
&lt;/h2&gt;

&lt;p&gt;Even a good indexer can fall behind or miss an event.&lt;/p&gt;

&lt;p&gt;So I would build reconciliation.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
Stored Market State
   ↓
Read Contract State
   ↓
Compare
   ↓
Repair
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner can periodically check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launch exists
curve exists
phase
pool state
latest processed block
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and detect inconsistencies.&lt;/p&gt;

&lt;p&gt;This gives the system a recovery mechanism instead of assuming the indexer is permanently correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 17: Keep scanner state separate from trading state
&lt;/h2&gt;

&lt;p&gt;This is important.&lt;/p&gt;

&lt;p&gt;The scanner should record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;what happened
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trading engine should decide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;what to do
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanner
   ↓
Market State
   ↓
Strategy
   ↓
Risk
   ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner itself should not secretly submit trades.&lt;/p&gt;

&lt;p&gt;That separation makes the system much easier to reuse.&lt;/p&gt;




&lt;h2&gt;
  
  
  A production TypeScript structure
&lt;/h2&gt;

&lt;p&gt;I would organize the project roughly like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── config/
│   └── pons.ts
│
├── clients/
│   └── chain.ts
│
├── protocols/
│   ├── v1/
│   │   └── adapter.ts
│   └── v2/
│       └── adapter.ts
│
├── indexer/
│   ├── backfill.ts
│   ├── live.ts
│   └── decoder.ts
│
├── tokens/
│   ├── registry.ts
│   └── metadata.ts
│
├── markets/
│   └── marketState.ts
│
├── filters/
│   ├── creator.ts
│   ├── liquidity.ts
│   └── engine.ts
│
├── opportunities/
│   └── state.ts
│
├── alerts/
│   ├── telegram.ts
│   └── discord.ts
│
├── reconciliation/
│   └── reconcile.ts
│
├── api/
│   └── server.ts
│
└── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives every subsystem one responsibility.&lt;/p&gt;




&lt;h2&gt;
  
  
  Database-backed scanning
&lt;/h2&gt;

&lt;p&gt;A production scanner should persist its progress.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;indexer_state
-----------------
network
contract
last_block
updated_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then after a restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
Last processed block
   ↓
Backfill
   ↓
Live listener
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No need to start from zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observability
&lt;/h2&gt;

&lt;p&gt;The scanner should expose operational metrics.&lt;/p&gt;

&lt;p&gt;I would monitor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;latest indexed block
RPC latency
RPC errors
events processed
duplicate events
tokens discovered
filters rejected
eligible tokens
backfill progress
reconciliation mismatches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Latest block:             2,145,892
Launches indexed:             8,421
Trades indexed:             91,420
Duplicate events:              117
Eligible opportunities:         38
RPC errors:                     4
Reconciliation mismatches:     0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives a much better view of system health than simply checking whether the process is running.&lt;/p&gt;




&lt;h2&gt;
  
  
  A scanner is not necessarily an automated trader
&lt;/h2&gt;

&lt;p&gt;This distinction matters from a product perspective.&lt;/p&gt;

&lt;p&gt;A scanner can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token Discovery Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without ever executing a trade.&lt;/p&gt;

&lt;p&gt;It can provide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alerts
API
dashboard
historical data
analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or it can become the first stage of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanner
   ↓
Sniper
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanner
   ↓
Trading Terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanner
   ↓
Copy Trading
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the scanner a reusable infrastructure component.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would build for a client
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Starter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch Detection
+
Metadata
+
Telegram Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  MVP
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch Detection
+
Metadata
+
Filters
+
Database
+
API
+
Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Advanced
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;V1 + V2
+
Historical Backfill
+
Market State
+
Liquidity
+
Wallet Intelligence
+
Alerts
+
API
+
Sniper Integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full Trading Platform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanner
+
Strategy
+
Risk
+
Execution
+
Portfolio
+
Reconciliation
+
Terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the client start with exactly the part they need.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connecting the scanner to Solidity and EVM engineering
&lt;/h2&gt;

&lt;p&gt;This is also where the scanner connects with the Solidity/EVM work I have been publishing.&lt;/p&gt;

&lt;p&gt;The scanner is mostly an offchain data system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC
+
viem
+
TypeScript
+
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the data originates from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity Contracts
      ↓
EVM Events
      ↓
Indexer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So understanding the contract layer matters.&lt;/p&gt;

&lt;p&gt;You need to know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event signatures
indexed parameters
state-changing functions
contract addresses
protocol versions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is one reason I see &lt;strong&gt;Solidity + EVM + TypeScript&lt;/strong&gt; as one connected engineering skillset rather than three unrelated topics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Public implementation work
&lt;/h2&gt;

&lt;p&gt;The same development approach is already being applied to other Robinhood Chain products.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pons Sniper Bot
&lt;/h3&gt;

&lt;p&gt;Launch detection, V2 curve execution, risk controls, transaction monitoring, and reconciliation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0xhamssog/pons-sniper-bot" rel="noopener noreferrer"&gt;https://github.com/0xhamssog/pons-sniper-bot&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pons Bundler
&lt;/h3&gt;

&lt;p&gt;Pons V2 launch-and-buy and multi-wallet execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0xhamssog/pons-bundler" rel="noopener noreferrer"&gt;https://github.com/0xhamssog/pons-bundler&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stock Token Arbitrage Bot
&lt;/h3&gt;

&lt;p&gt;Reference/onchain pricing and trading infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot" rel="noopener noreferrer"&gt;https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The scanner fits underneath these products as the &lt;strong&gt;discovery and market-data layer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The bigger architecture
&lt;/h2&gt;

&lt;p&gt;Eventually, several products can share the same scanner:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         ROBINHOOD CHAIN
                                ↓
                         PONS CONTRACTS
                                ↓
                         EVENT INDEXER
                                ↓
                       NORMALIZED STATE
                                ↓
                         PONS SCANNER
                                ↓
          ┌─────────────────────┼─────────────────────┐
          ↓                     ↓                     ↓
       Alerts                API                  Analytics
          ↓                     ↓                     ↓
      Telegram             Terminal             Dashboard
                                ↓
                           Trading Engine
                                ↓
                         ┌──────┼──────┐
                         ↓      ↓      ↓
                       Sniper  Copy  Manual
                         ↓      ↓      ↓
                         └──────┼──────┘
                                ↓
                             Risk
                                ↓
                           Execution
                                ↓
                         Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the scanner is not a standalone script.&lt;/p&gt;

&lt;p&gt;It is the data foundation underneath a trading ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A Pons Token Scanner may begin with one event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TokenLaunched
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a useful production implementation becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Onchain Events
      ↓
Protocol Version
      ↓
Token Registry
      ↓
Metadata
      ↓
Market State
      ↓
Trade History
      ↓
Filtering
      ↓
Opportunity State
      ↓
Alerts / API / Dashboard
      ↓
Trading Automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the difference between an event listener and a real discovery system.&lt;/p&gt;

&lt;p&gt;The scanner can power a standalone application.&lt;/p&gt;

&lt;p&gt;It can feed a Pons Sniper Bot.&lt;/p&gt;

&lt;p&gt;It can become the market-data layer of a Trading Terminal.&lt;/p&gt;

&lt;p&gt;It can support wallet intelligence and copy trading.&lt;/p&gt;

&lt;p&gt;And because it is built directly from EVM events, it becomes reusable infrastructure rather than a one-off script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is the type of Robinhood Chain engineering I want to demonstrate: Solidity/EVM understanding underneath, TypeScript infrastructure in the middle, and real trading products on top.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Need a custom Pons token scanner?
&lt;/h3&gt;

&lt;p&gt;I build custom &lt;strong&gt;Pons token scanners, launch monitors, event indexers, APIs, dashboards, sniper integrations, and trading infrastructure&lt;/strong&gt; on Robinhood Chain.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Trading Executor in Solidity for Robinhood Chain</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Tue, 29 Sep 2026 12:48:50 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-trading-executor-in-solidity-for-robinhood-chain-2no1</link>
      <guid>https://dev.to/hamssog/building-a-trading-executor-in-solidity-for-robinhood-chain-2no1</guid>
      <description>&lt;p&gt;A trading bot can be written almost entirely in TypeScript.&lt;/p&gt;

&lt;p&gt;For a simple application, that can be enough:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy
   ↓
TypeScript
   ↓
Router
   ↓
Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But as the system becomes more complex, there is another engineering layer worth designing deliberately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;the Solidity execution layer.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A custom smart contract can provide an onchain boundary for authorized execution, token handling, protocol adapters, validation, and event emission.&lt;/p&gt;

&lt;p&gt;The application above it can still handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;market data
strategy
risk
wallet orchestration
monitoring
reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives us a complete architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trading Strategy
       ↓
Risk Engine
       ↓
TypeScript Backend
       ↓
Solidity Executor
       ↓
EVM
       ↓
Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Robinhood Chain is fully EVM-compatible. Its current documentation states that Solidity and Vyper contracts can be deployed using standard Ethereum tooling, including Foundry and Hardhat. Robinhood Chain mainnet currently uses chain ID &lt;strong&gt;4663&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article focuses on how I would build the Solidity layer for a trading system.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why put part of a trading system onchain?
&lt;/h2&gt;

&lt;p&gt;Not every trading bot needs a custom contract.&lt;/p&gt;

&lt;p&gt;For some systems:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript
   ↓
Existing Protocol Contract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is perfectly reasonable.&lt;/p&gt;

&lt;p&gt;A custom Solidity layer becomes more interesting when the application needs an explicit execution boundary.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript
   ↓
Trade Intent
   ↓
Solidity Executor
   ↓
Protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Solidity contract can enforce rules such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorized executor
allowed assets
allowed routers
maximum trade amount
minimum output
deadline
pause state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, the application layer handles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;market discovery
strategy
pricing
risk scoring
wallet management
transaction monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract is therefore not the trading strategy.&lt;/p&gt;

&lt;p&gt;It is the &lt;strong&gt;onchain enforcement and execution layer&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;A production-oriented implementation can be split into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    TRADING APPLICATION
                           |
            +--------------+--------------+
            |                             |
        Strategy                       Risk Engine
            |                             |
            +--------------+--------------+
                           |
                      Trade Intent
                           |
                    TypeScript Backend
                           |
                      Solidity Executor
                           |
                    Protocol Adapter
                           |
                         EVM
                           |
                   Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Robinhood Chain
       ↓
Events / Receipt
       ↓
Transaction Monitor
       ↓
Reconciliation
       ↓
Position State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a complete round trip.&lt;/p&gt;




&lt;h2&gt;
  
  
  Start with a constrained executor
&lt;/h2&gt;

&lt;p&gt;I would avoid designing a contract that accepts arbitrary calldata from an untrusted caller.&lt;/p&gt;

&lt;p&gt;A better starting point is a constrained executor.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IERC20 {
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

interface IRouter {
    function swap(
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut,
        address recipient
    ) external returns (uint256 amountOut);
}

contract TradingExecutor {
    address public immutable owner;

    mapping(address =&amp;gt; bool) public allowedRouter;
    mapping(address =&amp;gt; bool) public allowedToken;

    bool public paused;

    error Unauthorized();
    error Paused();
    error RouterNotAllowed();
    error TokenNotAllowed();
    error InvalidAmount();
    error SlippageExceeded();

    event RouterUpdated(address indexed router, bool allowed);
    event TokenUpdated(address indexed token, bool allowed);
    event Paused(bool value);

    event TradeExecuted(
        bytes32 indexed operationId,
        address indexed router,
        address indexed tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 amountOut
    );

    modifier onlyOwner() {
        if (msg.sender != owner) revert Unauthorized();
        _;
    }

    constructor(address initialOwner) {
        owner = initialOwner;
    }

    function setRouter(
        address router,
        bool allowed
    ) external onlyOwner {
        allowedRouter[router] = allowed;
        emit RouterUpdated(router, allowed);
    }

    function setToken(
        address token,
        bool allowed
    ) external onlyOwner {
        allowedToken[token] = allowed;
        emit TokenUpdated(token, allowed);
    }

    function setPaused(
        bool value
    ) external onlyOwner {
        paused = value;
        emit Paused(value);
    }

    function executeTrade(
        bytes32 operationId,
        address router,
        address tokenIn,
        address tokenOut,
        uint256 amountIn,
        uint256 minAmountOut,
        address recipient
    ) external onlyOwner returns (uint256 amountOut) {
        if (paused) revert Paused();
        if (!allowedRouter[router]) revert RouterNotAllowed();
        if (!allowedToken[tokenIn]) revert TokenNotAllowed();
        if (!allowedToken[tokenOut]) revert TokenNotAllowed();
        if (amountIn == 0) revert InvalidAmount();

        IERC20(tokenIn).transferFrom(
            msg.sender,
            address(this),
            amountIn
        );

        IERC20(tokenIn).approve(
            router,
            amountIn
        );

        amountOut = IRouter(router).swap(
            tokenIn,
            tokenOut,
            amountIn,
            minAmountOut,
            recipient
        );

        if (amountOut &amp;lt; minAmountOut) {
            revert SlippageExceeded();
        }

        emit TradeExecuted(
            operationId,
            router,
            tokenIn,
            tokenOut,
            amountIn,
            amountOut
        );
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is still only a simplified example.&lt;/p&gt;

&lt;p&gt;The important design principle is the constraint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Caller
 ↓
Authorization
 ↓
Allowed Router
 ↓
Allowed Token
 ↓
Trade Parameters
 ↓
Execution
 ↓
Event
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract does not accept unlimited arbitrary execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Access control comes first
&lt;/h2&gt;

&lt;p&gt;Trading contracts can potentially move valuable assets.&lt;/p&gt;

&lt;p&gt;So the first question should be:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Who is allowed to call the execution function?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The simplest model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Owner
   ↓
Executor
   ↓
Trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But larger systems may need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin
   ↓
Configuration

Operator
   ↓
Trading Execution

Guardian
   ↓
Emergency Pause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right model depends on custody and product requirements.&lt;/p&gt;

&lt;p&gt;A multi-wallet system will usually need a different signer architecture from a single-wallet application.&lt;/p&gt;




&lt;h2&gt;
  
  
  The contract should validate what matters onchain
&lt;/h2&gt;

&lt;p&gt;Some rules belong in TypeScript.&lt;/p&gt;

&lt;p&gt;Some rules are safer when enforced directly by Solidity.&lt;/p&gt;

&lt;p&gt;For example, market scanning belongs naturally offchain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;price feeds
technical indicators
historical analysis
strategy logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But an onchain executor can enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;allowed router
allowed token
maximum amount
minimum output
authorized caller
deadline
pause state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offchain Strategy
       ↓
Onchain Enforcement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than trying to put the entire trading strategy inside a contract.&lt;/p&gt;




&lt;h2&gt;
  
  
  Slippage should be explicit
&lt;/h2&gt;

&lt;p&gt;Suppose a strategy wants to buy a token.&lt;/p&gt;

&lt;p&gt;The application can calculate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected Output = 1,250
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but the contract should not simply accept whatever comes back.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expected Output
       ↓
Minimum Acceptable Output
       ↓
Solidity
       ↓
Execute
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uint256 amountOut = router.swap(
    tokenIn,
    tokenOut,
    amountIn,
    minAmountOut,
    recipient
);

if (amountOut &amp;lt; minAmountOut) {
    revert SlippageExceeded();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact router interface will depend on the protocol being integrated.&lt;/p&gt;

&lt;p&gt;The important concept is that &lt;strong&gt;the acceptable execution boundary is explicit&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deadlines are another useful boundary
&lt;/h2&gt;

&lt;p&gt;A transaction that was valid when a strategy generated it may no longer be desirable several minutes later.&lt;/p&gt;

&lt;p&gt;The TypeScript layer can calculate a deadline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signal generated
      ↓
quote generated
      ↓
deadline = now + 15 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract can then reject execution after the deadline.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error DeadlineExpired();

if (block.timestamp &amp;gt; deadline) {
    revert DeadlineExpired();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially useful for trading systems where quotes can become stale quickly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Token approvals need careful handling
&lt;/h2&gt;

&lt;p&gt;A common EVM interaction looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token
 ↓
approve
 ↓
Router
 ↓
swap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But approvals are part of the security model.&lt;/p&gt;

&lt;p&gt;A trading contract should avoid giving an arbitrary address unlimited approval.&lt;/p&gt;

&lt;p&gt;A better architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Approved Router
      ↓
Token Approval
      ↓
Specific Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The allowed router list can be controlled by the contract administrator.&lt;/p&gt;

&lt;p&gt;For sensitive applications, token approvals should also be considered part of the deployment and operational review.&lt;/p&gt;




&lt;h2&gt;
  
  
  Events connect Solidity to the backend
&lt;/h2&gt;

&lt;p&gt;The smart contract should emit structured events.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;event TradeExecuted(
    bytes32 indexed operationId,
    address indexed router,
    address indexed tokenIn,
    address tokenOut,
    uint256 amountIn,
    uint256 amountOut
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TypeScript backend can then consume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TradeExecuted
      ↓
Event Decoder
      ↓
Trade Record
      ↓
Position Engine
      ↓
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the backend a reliable link between the requested operation and the onchain result.&lt;/p&gt;




&lt;h2&gt;
  
  
  Operation IDs
&lt;/h2&gt;

&lt;p&gt;I like giving each trade a deterministic application-level operation ID.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;strategy
+
wallet
+
nonce
+
timestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can produce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;operationId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract records it in the event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TradeExecuted(operationId, ...)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend can then connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy Signal
      ↓
Trade Intent
      ↓
operationId
      ↓
Transaction
      ↓
Event
      ↓
Position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes extremely useful when debugging execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Idempotent event processing
&lt;/h2&gt;

&lt;p&gt;A blockchain indexer can receive the same event again.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC connection
    ↓
disconnect
    ↓
reconnect
    ↓
event replay
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the backend simply processes every event as a new trade, the position may be doubled.&lt;/p&gt;

&lt;p&gt;So I would construct an event identity from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chainId
+
transactionHash
+
logIndex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;transactionHash&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;logIndex&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then store that value with a database uniqueness constraint.&lt;/p&gt;

&lt;p&gt;The logic becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event
 ↓
Already processed?
 ├── yes → ignore
 └── no  → process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a small implementation detail with a large operational impact.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transaction state still belongs in TypeScript
&lt;/h2&gt;

&lt;p&gt;The Solidity contract can emit the final execution event.&lt;/p&gt;

&lt;p&gt;The application still needs to track transaction state before that event exists.&lt;/p&gt;

&lt;p&gt;I would use something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransactionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGNED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normal execution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
SIGNED
   ↓
SUBMITTED
   ↓
PENDING
   ↓
CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ↓
RPC TIMEOUT
   ↓
UNKNOWN
   ↓
RECONCILIATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where smart-contract engineering meets backend engineering.&lt;/p&gt;




&lt;h2&gt;
  
  
  Never equate RPC failure with transaction failure
&lt;/h2&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
   ↓
broadcast transaction
   ↓
RPC timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The timeout only tells us that the application did not receive the expected response.&lt;/p&gt;

&lt;p&gt;It does not necessarily tell us:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;transaction failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction may already exist onchain.&lt;/p&gt;

&lt;p&gt;So the correct flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNKNOWN
   ↓
Find transaction
   ↓
Check receipt
   ↓
Check events
   ↓
Update state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only then should the application decide what to do next.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reconciliation
&lt;/h2&gt;

&lt;p&gt;Suppose the application expected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY 100 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The transaction completes.&lt;/p&gt;

&lt;p&gt;The contract emits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amountOut = 63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The position engine must record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reconciliation system can compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
Transaction Receipt
   ↓
Trade Events
   ↓
Token Balance
   ↓
Canonical Position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the system a recovery mechanism after:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC failures
missed events
process crashes
partial execution
delayed confirmations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Pons is a good example of why protocol adapters matter
&lt;/h2&gt;

&lt;p&gt;The same architecture applies to Pons, but the protocol adapter needs to understand which Pons version is being used.&lt;/p&gt;

&lt;p&gt;The current Pons documentation describes V2 as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Create
  ↓
Bonding Curve
  ↓
Trade
  ↓
Graduate
  ↓
Uniswap V4 Pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pons V2 also has protocol-specific launch and trading behavior such as its bonding-curve reserves, snipe-tax rules, and launch lifecycle.&lt;/p&gt;

&lt;p&gt;That means a trading executor should not bury Pons-specific assumptions everywhere.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trading Engine
      ↓
Pons Adapter
      ↓
Pons Contract
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps protocol-specific logic isolated.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stock Tokens show another side of EVM integration
&lt;/h2&gt;

&lt;p&gt;Robinhood's current documentation describes Stock Tokens as standard ERC-20 contracts with 18 decimals and onchain Chainlink price feeds. (&lt;a href="https://docs.robinhood.com/chain/stock-tokens/?utm_source=chatgpt.com" rel="noopener noreferrer"&gt;docs.robinhood.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;That means the same application architecture can be reused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stock Token
     ↓
ERC-20 Adapter
     ↓
Trading Executor
     ↓
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy can remain separate from the contract implementation.&lt;/p&gt;

&lt;p&gt;That allows the same backend to support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;arbitrage
momentum
rebalancing
signal-based trading
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without rebuilding the entire execution layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contract testing with Foundry
&lt;/h2&gt;

&lt;p&gt;A contract that compiles is not proof that it is safe.&lt;/p&gt;

&lt;p&gt;I would test at multiple levels.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Unit Tests
    ↓
Integration Tests
    ↓
Fork Tests
    ↓
Testnet
    ↓
Mainnet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a trading executor, the minimum test surface should include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorized caller
unauthorized caller
allowed router
blocked router
allowed token
blocked token
zero amount
slippage failure
deadline failure
paused state
successful execution
event emission
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function testRejectsUnauthorizedCaller() public {
    vm.prank(attacker);

    vm.expectRevert(
        TradingExecutor.Unauthorized.selector
    );

    executor.executeTrade(
        operationId,
        router,
        tokenIn,
        tokenOut,
        amountIn,
        minAmountOut,
        recipient
    );
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact test setup will depend on the project.&lt;/p&gt;

&lt;p&gt;The principle is more important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;test the rejection paths, not only the happy path.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Robinhood's current deployment guide recommends testing on testnet before deploying to mainnet.&lt;/p&gt;




&lt;h2&gt;
  
  
  Fork testing
&lt;/h2&gt;

&lt;p&gt;Fork tests are particularly useful for protocol integrations.&lt;/p&gt;

&lt;p&gt;Instead of mocking every dependency, a fork can reproduce a realistic EVM environment.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Robinhood Chain State
        ↓
Fork
        ↓
Your Contract
        ↓
Real Protocol Contracts
        ↓
Test Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful when validating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router behavior
token behavior
balances
allowances
events
execution paths
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before sending transactions against the live network.&lt;/p&gt;




&lt;h2&gt;
  
  
  Gas optimization
&lt;/h2&gt;

&lt;p&gt;Gas matters.&lt;/p&gt;

&lt;p&gt;But I would optimize in this order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Correctness
2. Security
3. Test coverage
4. Observability
5. Gas optimization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Useful optimization techniques may include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom errors
efficient storage
avoiding unnecessary storage reads
careful calldata usage
efficient event design
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But I would never sacrifice a clear security boundary just to save a small amount of gas.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contract structure
&lt;/h2&gt;

&lt;p&gt;A larger project could separate the Solidity code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contracts/
├── executors/
│   └── TradingExecutor.sol
│
├── adapters/
│   ├── TokenAdapter.sol
│   ├── PonsAdapter.sol
│   └── RouterAdapter.sol
│
├── interfaces/
│   ├── IERC20.sol
│   └── IRouter.sol
│
├── libraries/
│   ├── Validation.sol
│   └── Types.sol
│
└── mocks/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The TypeScript application can then sit beside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── strategy/
├── risk/
├── execution/
├── monitoring/
├── portfolio/
├── reconciliation/
└── api/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;contracts/
     +
TypeScript
     =
Trading Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Multi-wallet systems
&lt;/h2&gt;

&lt;p&gt;Multi-wallet trading adds another layer.&lt;/p&gt;

&lt;p&gt;Imagine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A
Wallet B
Wallet C
Wallet D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend might coordinate all four.&lt;/p&gt;

&lt;p&gt;But the contract should have an explicit authorization model.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Admin
  ↓
Authorized Executor
  ↓
Allowed Operation
  ↓
Protocol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact architecture depends on whether the wallets are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user-owned
operator-owned
custodial
non-custodial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That decision should happen before contract design.&lt;/p&gt;




&lt;h2&gt;
  
  
  Emergency controls
&lt;/h2&gt;

&lt;p&gt;Trading infrastructure can benefit from a pause mechanism.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function setPaused(
    bool value
) external onlyOwner {
    paused = value;
    emit Paused(value);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Normal
 ↓
Trading Enabled

Emergency
 ↓
Pause

Investigation
 ↓
Reconciliation

Recovery
 ↓
Unpause
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A pause mechanism is not a replacement for security.&lt;/p&gt;

&lt;p&gt;It is an additional operational control.&lt;/p&gt;




&lt;h2&gt;
  
  
  The complete execution lifecycle
&lt;/h2&gt;

&lt;p&gt;Putting everything together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MARKET DATA
     ↓
STRATEGY
     ↓
TRADE INTENT
     ↓
RISK ENGINE
     ↓
QUOTE
     ↓
TYPE SCRIPT
     ↓
SOLIDITY EXECUTOR
     ↓
PROTOCOL
     ↓
ROBINHOOD CHAIN
     ↓
TRANSACTION
     ↓
EVENTS
     ↓
RECONCILIATION
     ↓
POSITION
     ↓
PORTFOLIO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the architecture I want to demonstrate when I say I build trading systems.&lt;/p&gt;

&lt;p&gt;The smart contract is one layer.&lt;/p&gt;

&lt;p&gt;The TypeScript engine is another.&lt;/p&gt;

&lt;p&gt;The important part is how the two work together.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where this fits into Pons and Stock Token products
&lt;/h2&gt;

&lt;p&gt;The same engineering model can support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Sniper Bot
Pons Bundler
Pons Copy Trading Bot
Pons Trading Terminal
Stock Token Trading Bot
Stock Token Arbitrage Bot
Multi-Wallet Trading
Custom Execution Infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Sniper
    ↓
Launch Detection
    ↓
Strategy
    ↓
Risk
    ↓
Pons Adapter
    ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stock Token Arbitrage
    ↓
Price Normalization
    ↓
Opportunity
    ↓
Risk
    ↓
Execution Contract
    ↓
Onchain Trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application changes.&lt;/p&gt;

&lt;p&gt;The underlying engineering principles stay similar.&lt;/p&gt;




&lt;h2&gt;
  
  
  My preferred development stack
&lt;/h2&gt;

&lt;p&gt;For Robinhood Chain trading systems, the stack can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Solidity
    ↓
Foundry
    ↓
EVM
    ↓
Robinhood Chain
    ↓
viem
    ↓
TypeScript
    ↓
PostgreSQL
    ↓
API / Dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That allows work across several levels:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Smart Contract
      ↓
Protocol Integration
      ↓
Trading Engine
      ↓
Backend
      ↓
Frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the engineering surface I want my public projects to demonstrate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Public implementation proof
&lt;/h2&gt;

&lt;p&gt;I already work with this broader architecture across Pons and Stock Token projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pons Sniper Bot
&lt;/h3&gt;

&lt;p&gt;Pons V2 launch detection, curve execution, risk controls, transaction monitoring, and reconciliation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;github.com/0xhamssog/pons-sniper-bot&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pons Bundler
&lt;/h3&gt;

&lt;p&gt;Pons V2 launch-and-buy and multi-wallet execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;github.com/0xhamssog/pons-bundler&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stock Token Arbitrage Bot
&lt;/h3&gt;

&lt;p&gt;Stock Token pricing, opportunity detection, and automated trading infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;github.com/0xhamssog/robinhood-stock-token-arbitrage-bot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These projects provide a practical foundation for adding more protocol integrations and custom execution modules.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Smart Contract Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Custom Solidity contracts, adapters, execution contracts, access control, events, and testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trading Bot Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Strategy engines, risk systems, execution, transaction monitoring, and reconciliation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sniper bots, bundlers, copy trading, wallet tracking, launch monitoring, and trading terminals.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock Token Development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Trading bots, arbitrage systems, portfolio tools, and automated execution.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Trading Applications&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Frontend + API + trading engine + Solidity + wallet infrastructure + monitoring.&lt;/p&gt;

&lt;p&gt;The starting point does not have to be a complete platform.&lt;/p&gt;

&lt;p&gt;It can be one contract, one execution module, or one trading strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;A trading bot is often described as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
+
Strategy
+
Transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a more complete system is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Strategy
     ↓
Risk
     ↓
Execution
     ↓
Solidity
     ↓
    EVM
     ↓
Transaction
     ↓
Events
     ↓
Reconciliation
     ↓
Position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is why I am expanding my Robinhood Chain work beyond application-level bots.&lt;/p&gt;

&lt;p&gt;I want the public work to demonstrate the full engineering stack:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solidity.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EVM.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TypeScript.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protocol integration.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trading infrastructure.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk management.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction state.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reconciliation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For clients building on Robinhood Chain and Pons, that means I can work not only on the bot interface, but on the smart-contract and execution infrastructure underneath it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Need a custom Robinhood Chain trading system?
&lt;/h3&gt;

&lt;p&gt;I build custom &lt;strong&gt;Solidity/EVM contracts, Pons integrations, Stock Token trading bots, execution engines, multi-wallet systems, trading terminals, and full-stack trading applications&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>solidity</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Robinhood Chain Development with TypeScript: Building Pons Trading Systems</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Mon, 28 Sep 2026 09:37:06 +0000</pubDate>
      <link>https://dev.to/hamssog/robinhood-chain-development-with-typescript-building-pons-trading-systems-3j60</link>
      <guid>https://dev.to/hamssog/robinhood-chain-development-with-typescript-building-pons-trading-systems-3j60</guid>
      <description>&lt;p&gt;When people search for a &lt;strong&gt;Robinhood Chain developer&lt;/strong&gt;, they usually are not looking for another generic blockchain application.&lt;/p&gt;

&lt;p&gt;They have a specific product in mind.&lt;/p&gt;

&lt;p&gt;Maybe it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Pons Sniper Bot&lt;/li&gt;
&lt;li&gt;a Pons Bundler&lt;/li&gt;
&lt;li&gt;a Pons Copy Trading Bot&lt;/li&gt;
&lt;li&gt;a Pons Trading Terminal&lt;/li&gt;
&lt;li&gt;a Stock Token Trading Bot&lt;/li&gt;
&lt;li&gt;a Stock Token Arbitrage Bot&lt;/li&gt;
&lt;li&gt;a wallet tracker&lt;/li&gt;
&lt;li&gt;a launch monitor&lt;/li&gt;
&lt;li&gt;a multi-wallet trading system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The difficult part is rarely making one blockchain call.&lt;/p&gt;

&lt;p&gt;The difficult part is connecting market data, protocol logic, risk management, transaction execution, persistence, and reconciliation into one system that can actually operate.&lt;/p&gt;

&lt;p&gt;That is the area I focus on.&lt;/p&gt;




&lt;h2&gt;
  
  
  The architecture I use
&lt;/h2&gt;

&lt;p&gt;For trading automation, I prefer this separation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 ROBINHOOD CHAIN
                        ↓
                PROTOCOL / DATA
                        ↓
                 STRATEGY ENGINE
                        ↓
                   RISK ENGINE
                        ↓
                EXECUTION ENGINE
                        ↓
              TRANSACTION STATE
                        ↓
                POSITION STATE
                        ↓
                RECONCILIATION
                        ↓
             API / DASHBOARD / ALERTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy decides what should happen.&lt;/p&gt;

&lt;p&gt;Risk decides whether it is allowed.&lt;/p&gt;

&lt;p&gt;Execution handles the transaction.&lt;/p&gt;

&lt;p&gt;Reconciliation determines what actually happened onchain.&lt;/p&gt;

&lt;p&gt;That makes the system much easier to test and extend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Robinhood Chain is an EVM development environment
&lt;/h2&gt;

&lt;p&gt;Robinhood's current documentation describes Robinhood Chain as an Arbitrum Layer-2 built on Ethereum, with ETH as the native gas token. Mainnet uses chain ID &lt;strong&gt;4663&lt;/strong&gt;, while the testnet uses &lt;strong&gt;46630&lt;/strong&gt;. The network supports standard Ethereum development tooling and EVM wallet/RPC integrations.&lt;/p&gt;

&lt;p&gt;For a TypeScript application, that means the familiar stack can be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript
   ↓
viem / ethers
   ↓
RPC
   ↓
Smart Contracts
   ↓
Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is useful because the application architecture does not need to be reinvented around a proprietary SDK.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pons development is protocol-specific
&lt;/h2&gt;

&lt;p&gt;Pons is where generic EVM knowledge stops being enough.&lt;/p&gt;

&lt;p&gt;The official Pons contracts repository currently documents two generations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;V1
→ fixed-supply ERC-20
→ Uniswap V3 launch position

V2
→ bonding curve
→ curve trading
→ graduation
→ locked Uniswap V4 pool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both generations are live, and the official repository lists separate factories for V1 and V2.&lt;/p&gt;

&lt;p&gt;That distinction affects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;events
pricing
execution
liquidity
launch lifecycle
quote calculation
contract addresses
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So I would not build a "Pons integration" and assume it works across both versions.&lt;/p&gt;

&lt;p&gt;The implementation needs to know which generation it is integrating.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pons V2 changes the trading lifecycle
&lt;/h2&gt;

&lt;p&gt;Pons V2 starts a token on a bonding curve.&lt;/p&gt;

&lt;p&gt;The lifecycle is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE
   ↓
BONDING CURVE
   ↓
TRADING
   ↓
CURVE COMPLETION
   ↓
UNISWAP V4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The official V2 documentation describes the curve as holding the token supply and pricing trades according to the current curve state. After graduation, liquidity moves into a permanently locked Uniswap V4 pool.&lt;/p&gt;

&lt;p&gt;That means a Pons V2 application needs to understand &lt;strong&gt;phase&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A token being newly launched is not the same execution environment as a graduated token.&lt;/p&gt;




&lt;h2&gt;
  
  
  Building a Pons Sniper Bot
&lt;/h2&gt;

&lt;p&gt;One concrete application is a &lt;strong&gt;Pons Sniper Bot&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The basic workflow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TokenLaunched
      ↓
Decode Launch
      ↓
Apply Filters
      ↓
Read Curve State
      ↓
Calculate Quote
      ↓
Risk Check
      ↓
Submit Buy
      ↓
Monitor Transaction
      ↓
Reconcile Position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that launch detection is only the beginning.&lt;/p&gt;

&lt;p&gt;A useful sniper needs to understand the current curve and its trading rules.&lt;/p&gt;

&lt;p&gt;Pons V2 documents launch protection through a decaying buy-side snipe tax. The documentation says the tax starts at 99% and decays toward zero over the first five seconds. The tax is recipient-specific, and additional exemption addresses can be defined when the launch is created.&lt;/p&gt;

&lt;p&gt;So a quote engine should not simply calculate curve mathematics.&lt;/p&gt;

&lt;p&gt;It should also account for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curve state
+
trade fee
+
creator tax
+
snipe tax
+
slippage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository I maintain for the Pons sniper demonstrates the V2 launch-detection and execution flow, including dry-run support and risk controls.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;strong&gt;0xhamssog/pons-sniper-bot&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Pons V2 quote calculation
&lt;/h2&gt;

&lt;p&gt;The quote is one of the places where a superficial implementation can break.&lt;/p&gt;

&lt;p&gt;The current Pons V2 documentation distinguishes between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getReserves()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the curve's physical quote reserve.&lt;/p&gt;

&lt;p&gt;The documentation recommends using the pricing reserves for quote calculations rather than treating arbitrary contract balances as the pricing state.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Curve Reserves
      ↓
Fee Calculation
      ↓
Tax Calculation
      ↓
Trade Size
      ↓
Expected Tokens
      ↓
Minimum Tokens Out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot should calculate &lt;code&gt;minTokensOut&lt;/code&gt; from the actual expected execution rather than from a generic percentage.&lt;/p&gt;

&lt;p&gt;That becomes especially important when several transactions are moving the curve.&lt;/p&gt;




&lt;h2&gt;
  
  
  Partial fills near graduation
&lt;/h2&gt;

&lt;p&gt;Another important V2 edge case occurs near the end of the bonding curve.&lt;/p&gt;

&lt;p&gt;The current documentation describes a final buy that may be partially filled when the requested amount would exceed the remaining sellable token inventory. Unused quote can be refunded in the same transaction.&lt;/p&gt;

&lt;p&gt;That means:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requested Buy
     ↓
Available Curve Inventory
     ↓
Maximum Fill
     ↓
Partial Execution
     ↓
Refund
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A bot that assumes:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text requested amount = filled amount&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


can produce incorrect portfolio state.

The transaction layer therefore needs actual fill information.

---

## Building a Pons Bundler

A bundler solves a different problem from a sniper.

The current public Pons V2 bundler implementation I work with launches a token using `launchAndBuy`, registers additional buyer wallets as snipe-tax exemptions, and then submits those additional buys separately. The repository explicitly describes this as a launch bundler rather than an ERC-4337 UserOperation bundler.

The workflow is:



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Launch Configuration&lt;br&gt;
        ↓&lt;br&gt;
launchAndBuy&lt;br&gt;
        ↓&lt;br&gt;
Master Buy&lt;br&gt;
        ↓&lt;br&gt;
Buyer Wallets&lt;br&gt;
        ↓&lt;br&gt;
Parallel buy() Transactions&lt;br&gt;
        ↓&lt;br&gt;
Transaction Tracking&lt;br&gt;
        ↓&lt;br&gt;
Wallet State&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
markdown

An important protocol detail is that the extra wallets do **not** share the same atomic transaction.

The public implementation documents up to 32 additional buyer wallets as `snipeTaxExemptions`.

That distinction matters when designing the user experience.

The UI should not promise atomic multi-signer execution if the underlying protocol does not provide it.

GitHub: **0xhamssog/pons-bundler**

---

## Pons Copy Trading Bot

Copy trading is another product where the infrastructure changes.

Instead of watching launches, the system watches wallets.



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Tracked Wallet&lt;br&gt;
      ↓&lt;br&gt;
Onchain Activity&lt;br&gt;
      ↓&lt;br&gt;
Trade Decoder&lt;br&gt;
      ↓&lt;br&gt;
Normalized Signal&lt;br&gt;
      ↓&lt;br&gt;
Copy Rules&lt;br&gt;
      ↓&lt;br&gt;
Risk Engine&lt;br&gt;
      ↓&lt;br&gt;
Execution&lt;br&gt;
      ↓&lt;br&gt;
Position&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
plaintext

The important part is:



```text
detected transaction ≠ automatic copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;A client may want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;copy 10%
max trade $500
max position $2,000
only selected tokens
minimum liquidity
maximum slippage
wallet allowlist
cooldown
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the copy-trading system should transform wallet activity into a configurable strategy signal.&lt;/p&gt;

&lt;p&gt;That lets the same tracking engine serve several different copy strategies.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pons Trading Terminal
&lt;/h2&gt;

&lt;p&gt;Not every trading product needs to be fully automated.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Pons Trading Terminal&lt;/strong&gt; can provide a human-facing interface over the same trading infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Discovery
Token Detail
Wallet Management
Trading
Multi-Wallet Controls
Positions
P&amp;amp;L
Transaction History
Alerts
Automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The architecture becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             WEB TERMINAL
                  ↓
                 API
                  ↓
            TRADING ENGINE
                  ↓
             RISK ENGINE
                  ↓
              EXECUTOR
                  ↓
           ROBINHOOD CHAIN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful because manual execution and automated strategies can share the same backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stock Token development
&lt;/h2&gt;

&lt;p&gt;Robinhood's current Stock Token documentation describes Stock Tokens as standard ERC-20 contracts with 18 decimals, corresponding to specific underlying equities or ETFs. It also provides onchain Chainlink pricing and asset metadata.&lt;/p&gt;

&lt;p&gt;That makes several applications possible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stock Token Trading Bot
Stock Token Arbitrage Bot
Portfolio Tracker
Trading Dashboard
Risk System
Lending Application
Analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The development pattern is similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Asset Registry
      ↓
Price Layer
      ↓
Strategy
      ↓
   Risk
      ↓
Execution
      ↓
Position
      ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Stock Token pricing is not just one number
&lt;/h2&gt;

&lt;p&gt;Robinhood's current Stock Token APIs expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/assets
/prices/{symbol}
/corporate-actions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/assets&lt;/code&gt; response includes deployments, &lt;code&gt;currentMultiplier&lt;/code&gt;, and trading capabilities.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/prices/{symbol}&lt;/code&gt; endpoint returns the underlying-equity bid/ask without multiplier adjustment, while the onchain Chainlink price is multiplier-adjusted. The &lt;code&gt;/prices&lt;/code&gt; endpoint is currently documented with a 15-second cache and a 60 requests/second rate limit.&lt;/p&gt;

&lt;p&gt;A robust price adapter therefore needs to normalize the sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NormalizedPrice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CHAINLINK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;priceUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;multiplierAdjusted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the strategy layer can consume normalized data without caring which provider produced it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Corporate actions belong in the data layer
&lt;/h2&gt;

&lt;p&gt;Stock Token applications also need to account for corporate actions.&lt;/p&gt;

&lt;p&gt;The current API exposes &lt;code&gt;currentMultiplier&lt;/code&gt; and corporate-action information, including events that explain changes in the multiplier.&lt;/p&gt;

&lt;p&gt;That suggests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Corporate Action
      ↓
Multiplier
      ↓
Normalized Price
      ↓
Portfolio Valuation
      ↓
Strategy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application should not hard-code assumptions about a token's economic representation into the strategy.&lt;/p&gt;




&lt;h2&gt;
  
  
  Risk engine
&lt;/h2&gt;

&lt;p&gt;Across these products, the risk engine can remain reusable.&lt;/p&gt;

&lt;p&gt;Typical rules include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max trade size
max position
max exposure
minimum edge
maximum slippage
maximum gas
maximum quote age
maximum concurrent trades
daily loss limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A strategy produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TradeIntent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;strategyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The risk engine produces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RiskDecision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps strategy code independent from safety rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Execution state machine
&lt;/h2&gt;

&lt;p&gt;The execution engine is probably the most reusable component across all of these products.&lt;/p&gt;

&lt;p&gt;I would model transaction state explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransactionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGNED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Normal flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
SIGNED
   ↓
SUBMITTED
   ↓
PENDING
   ↓
CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Failure/recovery flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ↓
RPC TIMEOUT
   ↓
UNKNOWN
   ↓
RECONCILE
   ↓
CONFIRMED / FAILED / PENDING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is particularly important for automated trading.&lt;/p&gt;

&lt;p&gt;An RPC timeout does not automatically prove that the transaction failed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reconciliation
&lt;/h2&gt;

&lt;p&gt;The blockchain is the final source of truth for execution state.&lt;/p&gt;

&lt;p&gt;So I would separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application State
        ↓
Transaction Receipt
        ↓
Onchain Events
        ↓
Wallet Balances
        ↓
Canonical Position State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose local state says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but only 63 tokens were actually received.&lt;/p&gt;

&lt;p&gt;The final position must become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reconciliation protects the application from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;missed events
RPC failures
application restarts
partial fills
delayed confirmations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Suggested TypeScript structure
&lt;/h2&gt;

&lt;p&gt;A reusable Robinhood Chain trading codebase can be organized like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── protocols/
│   ├── pons/
│   │   ├── v1/
│   │   └── v2/
│   └── stockTokens/
│
├── market-data/
│   ├── robinhoodApi.ts
│   ├── chainlink.ts
│   └── dex.ts
│
├── strategies/
│   ├── sniper.ts
│   ├── copyTrading.ts
│   └── arbitrage.ts
│
├── risk/
│   └── riskEngine.ts
│
├── execution/
│   ├── executor.ts
│   ├── stateMachine.ts
│   └── transactions.ts
│
├── portfolio/
│   ├── positions.ts
│   └── pnl.ts
│
├── reconciliation/
│   └── reconcile.ts
│
├── api/
│
└── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important architectural decision here is keeping &lt;strong&gt;protocol adapters separate from strategies&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means a strategy can consume normalized data without embedding Pons-specific contract calls throughout the application.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I would build for different requirements
&lt;/h2&gt;

&lt;p&gt;A client may need only one part of this stack.&lt;/p&gt;

&lt;h3&gt;
  
  
  Small integration
&lt;/h3&gt;



&lt;p&gt;```text id="devrc29"&lt;br&gt;
Pons contract integration&lt;br&gt;
Event decoder&lt;br&gt;
Quote calculation&lt;br&gt;
Execution module&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


### Trading MVP



```plaintext
One strategy
One wallet
Risk controls
Dry-run
Execution
Trade history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Production bot
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multiple assets
Persistent state
Transaction monitoring
Reconciliation
Alerts
Monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Complete product
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
API
Trading Engine
Wallet Management
Risk
Portfolio
Analytics
Monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Advanced infrastructure
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multi-wallet
Multiple strategies
Backtesting
Historical data
Custom APIs
Advanced risk
Execution orchestration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Starting smaller also makes it possible to validate a trading idea before building the complete product.&lt;/p&gt;


&lt;h2&gt;
  
  
  Public implementation proof
&lt;/h2&gt;

&lt;p&gt;I prefer showing code rather than simply describing protocol knowledge.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pons Sniper Bot
&lt;/h3&gt;

&lt;p&gt;Pons V2 launch detection, curve quoting, dry-run execution, filtering, and risk controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;0xhamssog/pons-sniper-bot&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Pons Bundler
&lt;/h3&gt;

&lt;p&gt;Pons V2 &lt;code&gt;launchAndBuy&lt;/code&gt;, additional wallet exemptions, and parallel curve buys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;0xhamssog/pons-bundler&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Stock Token Arbitrage
&lt;/h3&gt;

&lt;p&gt;Robinhood Chain Stock Token arbitrage infrastructure around external/reference pricing and onchain markets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;code&gt;0xhamssog/robinhood-stock-token-arbitrage-bot&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These projects are useful as starting points for custom systems rather than being limited to one fixed implementation.&lt;/p&gt;


&lt;h2&gt;
  
  
  What can be built
&lt;/h2&gt;

&lt;p&gt;The product surface I focus on is specific:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Product&lt;/th&gt;
&lt;th&gt;Main Engineering Problem&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pons Sniper Bot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Launch detection + curve execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pons Bundler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-wallet launch execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pons Copy Trading Bot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Wallet signals + configurable copying&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pons Trading Terminal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trading UI + execution backend&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stock Token Trading Bot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automated strategy execution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stock Token Arbitrage Bot&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pricing + executable spread&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wallet Tracker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Onchain activity + position state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Launch Monitor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Event detection + alerts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-Wallet Trading&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Coordinated execution + risk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Custom Trading Infrastructure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reusable backend + execution systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The exact implementation depends on the strategy, wallet model, execution requirements, and product scope.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why I focus on exact products
&lt;/h2&gt;

&lt;p&gt;There is a big difference between searching for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"blockchain developer"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and searching for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"Pons sniper bot developer"

"Pons bundler"

"Pons copy trading bot"

"Pons trading terminal"

"Stock Token trading bot"

"Robinhood Chain trading bot"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second group represents a much more specific engineering requirement.&lt;/p&gt;

&lt;p&gt;That is also how I approach development.&lt;/p&gt;

&lt;p&gt;Instead of starting with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"What blockchain technology can I build?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I start with:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;"What trading product needs to exist?"&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then I build the protocol integration underneath it.&lt;/p&gt;




&lt;h2&gt;
  
  
  From protocol integration to product
&lt;/h2&gt;

&lt;p&gt;The long-term architecture can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    ROBINHOOD CHAIN
                           │
             ┌─────────────┴─────────────┐
             ↓                           ↓
           PONS                      STOCK TOKENS
             │                           │
             └──────────┬────────────────┘
                        ↓
                  DATA LAYER
                        ↓
                 STRATEGY LAYER
                        ↓
                   RISK LAYER
                        ↓
                EXECUTION LAYER
                        ↓
                  STATE LAYER
                        ↓
              RECONCILIATION
                        ↓
          ┌─────────────┼─────────────┐
          ↓             ↓             ↓
       Terminal        API          Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the underlying infrastructure reusable.&lt;/p&gt;

&lt;p&gt;A new client does not necessarily need an entirely new trading engine.&lt;/p&gt;

&lt;p&gt;A new product can reuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallet management
risk
execution
transaction state
portfolio
reconciliation
monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and replace only the protocol-specific and strategy-specific components.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain development is most interesting to me when it moves beyond a single contract call and becomes a real trading application.&lt;/p&gt;

&lt;p&gt;That means building:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Protocol Integration
      +
Market Data
      +
Strategy
      +
   Risk
      +
Execution
      +
Persistent State
      +
Reconciliation
      =
Trading Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Pons, that can become a &lt;strong&gt;Sniper Bot, Bundler, Copy Trading Bot, or Trading Terminal&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For Stock Tokens, it can become a &lt;strong&gt;Trading Bot, Arbitrage Bot, Portfolio System, or Trading Interface&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;For a larger project, those components can become a complete Robinhood Chain trading platform.&lt;/p&gt;

&lt;p&gt;I focus on that middle layer between &lt;strong&gt;trading idea and working software&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom Robinhood Chain and Pons development:&lt;/strong&gt; trading bots, multi-wallet systems, trading terminals, Stock Token automation, execution engines, APIs, and supporting infrastructure.&lt;/p&gt;

&lt;p&gt;The starting point can be a small integration or MVP and expand as the product requirements become clearer.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>trading</category>
    </item>
    <item>
      <title>Building a Stock Token Trading Bot on Robinhood Chain with TypeScript</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Sun, 27 Sep 2026 05:46:46 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-stock-token-trading-bot-on-robinhood-chain-with-typescript-1fnf</link>
      <guid>https://dev.to/hamssog/building-a-stock-token-trading-bot-on-robinhood-chain-with-typescript-1fnf</guid>
      <description>&lt;p&gt;A &lt;strong&gt;Stock Token trading bot&lt;/strong&gt; can start as a simple price-monitoring script.&lt;/p&gt;

&lt;p&gt;But turning it into a useful automated trading system requires much more than detecting a price.&lt;/p&gt;

&lt;p&gt;The bot has to understand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Asset
→ Price
→ Strategy
→ Risk
→ Execution
→ Transaction State
→ Position
→ Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the architecture I would use for building automated Stock Token trading on &lt;strong&gt;Robinhood Chain&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Robinhood Chain is EVM-compatible and currently uses chain ID &lt;strong&gt;4663&lt;/strong&gt;. Stock Tokens are ERC-20 tokens with 18 decimals, and Robinhood provides onchain Chainlink price feeds for them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The architecture
&lt;/h2&gt;

&lt;p&gt;Instead of putting everything into one trading loop, I would separate the system into services or modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    STOCK TOKEN DATA
                           ↓
                  ┌────────────────┐
                  │ Asset Registry │
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │  Price Layer   │
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │ Strategy Engine│
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │   Risk Engine  │
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │    Executor    │
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │ Tx State       │
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │ Position State │
                  └───────┬────────┘
                          ↓
                  ┌────────────────┐
                  │ Reconciliation │
                  └────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation gives each component a clear job.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Asset discovery
&lt;/h2&gt;

&lt;p&gt;The first step is knowing what Stock Tokens are available.&lt;/p&gt;

&lt;p&gt;Robinhood's current &lt;code&gt;/assets&lt;/code&gt; API exposes metadata including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenSymbol
tokenName
deployments
chainId
currentMultiplier
pendingMultiplier
tradingCapabilities
status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API also exposes the canonical contract deployment for Robinhood Chain.&lt;/p&gt;

&lt;p&gt;I would normalize that into an internal TypeScript type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StockTokenAsset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;currentMultiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ACTIVE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INACTIVE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;tradingCapabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The asset registry can then become the source of truth for the rest of the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API
 ↓
Asset Registry
 ↓
Trading System
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of every strategy trying to rediscover the asset independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Price ingestion
&lt;/h2&gt;

&lt;p&gt;Robinhood provides a REST price endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET /rhj/prices/{symbol}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current API documentation says this endpoint is cached for &lt;strong&gt;15 seconds&lt;/strong&gt; and rate-limited to &lt;strong&gt;60 requests/second&lt;/strong&gt;. The response includes bid, ask, generated timestamp, trading-halt information, and other fields.&lt;/p&gt;

&lt;p&gt;That means a bot should retain quote metadata instead of treating the returned number as timeless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ReferenceQuote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;isTradingHalt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isFresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReferenceQuote&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxAgeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generatedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxAgeMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A trading strategy should be able to reject stale data before it generates a trade intent.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Price normalization
&lt;/h2&gt;

&lt;p&gt;This is one of the most important implementation details.&lt;/p&gt;

&lt;p&gt;Robinhood documents that the REST &lt;code&gt;/prices&lt;/code&gt; endpoint returns the raw underlying-equity bid/ask and is &lt;strong&gt;not multiplier-adjusted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The onchain Chainlink price is multiplier-adjusted.&lt;/p&gt;

&lt;p&gt;When combining those surfaces, the application needs to apply &lt;code&gt;currentMultiplier&lt;/code&gt; appropriately.&lt;/p&gt;

&lt;p&gt;So I would make normalization explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PricePoint&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CHAINLINK&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;DEX&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;priceUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;multiplierAdjusted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the price adapter handles the transformation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REST
 ↓
Raw underlying price
 ↓
Multiplier
 ↓
Normalized value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chainlink
 ↓
Already multiplier-adjusted
 ↓
Normalized value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy receives normalized values rather than worrying about where they came from.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Corporate actions
&lt;/h2&gt;

&lt;p&gt;The multiplier is not just a technical field.&lt;/p&gt;

&lt;p&gt;It is part of the economic representation of the Stock Token.&lt;/p&gt;

&lt;p&gt;Robinhood's API exposes &lt;code&gt;currentMultiplier&lt;/code&gt;, &lt;code&gt;pendingMultiplier&lt;/code&gt;, and a corporate-actions endpoint. The documentation describes events including forward splits, reverse splits, dividends, mergers, spin-offs, redemptions, and other actions.&lt;/p&gt;

&lt;p&gt;That means the system can model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Corporate Action
       ↓
Multiplier Change
       ↓
Price Normalization
       ↓
Portfolio Valuation
       ↓
Strategy Calculation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should be handled at the asset/data layer instead of hidden inside individual strategies.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Strategy engine
&lt;/h2&gt;

&lt;p&gt;The strategy should not know how to send transactions.&lt;/p&gt;

&lt;p&gt;It should answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Given the current market state, should I create a trade intent?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TradeIntent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;maxSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;strategyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A strategy might generate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AAPL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;strategyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mean-reversion-v1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy has now expressed what it wants.&lt;/p&gt;

&lt;p&gt;It has not executed anything.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Different strategies can use the same infrastructure
&lt;/h2&gt;

&lt;p&gt;That separation becomes valuable quickly.&lt;/p&gt;

&lt;p&gt;The execution system can support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Momentum
Mean reversion
Arbitrage
Scheduled trading
Rebalancing
Signal-based trading
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;without creating a completely different transaction stack for every strategy.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌─ Momentum
                  │
Strategies ───────┼─ Arbitrage
                  │
                  ├─ Mean Reversion
                  │
                  └─ Rebalancing
                         ↓
                    Risk Engine
                         ↓
                    Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The strategy changes.&lt;/p&gt;

&lt;p&gt;The execution infrastructure does not have to.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Risk engine
&lt;/h2&gt;

&lt;p&gt;Before anything is signed, the trade should pass through a separate risk layer.&lt;/p&gt;

&lt;p&gt;Typical rules include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;max trade size
max position size
max exposure
min expected edge
max slippage
max gas cost
max quote age
max concurrent positions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RiskLimits&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;maxTradeUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxPositionUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minEdgeBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxQuoteAgeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RiskDecision&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;approved&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rejected trade should be treated as a normal state of the system, not an exception.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRADE_INTENT
     ↓
RISK_CHECK
     ↓
REJECTED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with a reason such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Position limit exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  9. Arbitrage is a strategy, not the entire architecture
&lt;/h2&gt;

&lt;p&gt;A Stock Token arbitrage strategy might compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reference Price
        ↓
Normalized
        ↓
Onchain / DEX Price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The naive calculation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;grossSpread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sellPrice&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;buyPrice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But a more useful model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross Spread
- Trading Fees
- Slippage
- Gas
- Execution Costs
- Safety Buffer
-----------------
Expected Net Edge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expectedNetEdgeBps&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minEdgeBps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Insufficient edge&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This distinction is important because a visible spread does not necessarily represent an executable profit opportunity.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Liquidity checks
&lt;/h2&gt;

&lt;p&gt;The bot also needs to consider trade size.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quoted spread = 1.2%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does not mean a $1,000 trade and a $100,000 trade can both execute at the same effective price.&lt;/p&gt;

&lt;p&gt;The strategy should request an executable quote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ExecutionQuote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;amountIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expectedAmountOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minimumAmountOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;priceImpactBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then risk can evaluate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requested amount
      ↓
Liquidity
      ↓
Price impact
      ↓
Expected output
      ↓
Minimum output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much closer to an actual trading decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Trading capabilities
&lt;/h2&gt;

&lt;p&gt;Trading availability also belongs in the decision process.&lt;/p&gt;

&lt;p&gt;The current Stock Token asset API exposes &lt;code&gt;tradingCapabilities&lt;/code&gt;, while Robinhood's documentation notes that different assets can have different availability across market, extended, and overnight sessions. Developers should check the asset's capabilities before execution.&lt;/p&gt;

&lt;p&gt;The execution gate therefore becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ASSET ACTIVE
     ↓
SESSION ALLOWED
     ↓
TRADE ALLOWED
     ↓
QUOTE FRESH
     ↓
LIQUIDITY OK
     ↓
RISK APPROVED
     ↓
EXECUTE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is much safer than assuming every asset is always tradable.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Transaction execution
&lt;/h2&gt;

&lt;p&gt;Once a trade passes risk checks, the executor builds the transaction.&lt;/p&gt;

&lt;p&gt;But execution should also be stateful.&lt;/p&gt;

&lt;p&gt;I would define:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TransactionState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGNED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The happy path is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATED
   ↓
SIGNED
   ↓
SUBMITTED
   ↓
PENDING
   ↓
CONFIRMED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But production systems need another branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ↓
RPC TIMEOUT
   ↓
UNKNOWN
   ↓
RECONCILIATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An RPC timeout is not equivalent to a failed transaction.&lt;/p&gt;

&lt;p&gt;The transaction may already have been broadcast or confirmed.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Never blindly retry an unknown transaction
&lt;/h2&gt;

&lt;p&gt;This is one of the most important rules in automated execution.&lt;/p&gt;

&lt;p&gt;Consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bot submits transaction
        ↓
RPC request times out
        ↓
Bot assumes failure
        ↓
Bot submits another transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now there may be two transactions.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUBMITTED
   ↓
UNKNOWN
   ↓
Find transaction
   ↓
Check receipt
   ↓
Check state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after reconciliation should the system determine whether another action is necessary.&lt;/p&gt;

&lt;p&gt;This is why transaction state should be part of the architecture from day one.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Persistent state
&lt;/h2&gt;

&lt;p&gt;Important trading state should not live only in memory.&lt;/p&gt;

&lt;p&gt;I would persist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trade intent
transaction hash
asset
side
requested quantity
filled quantity
execution price
status
timestamps
strategy ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TradeRecord&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;requestedQuantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;filledQuantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;executionPrice&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TransactionState&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A PostgreSQL database is a reasonable choice for this state layer.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Position management
&lt;/h2&gt;

&lt;p&gt;Trade state and position state are related, but they are not the same thing.&lt;/p&gt;

&lt;p&gt;A position might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;averageEntryPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;realizedPnl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;unrealizedPnl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trades
  ↓
Fills
  ↓
Position Engine
  ↓
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows multiple executions to contribute to the same position.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Reconciliation
&lt;/h2&gt;

&lt;p&gt;The blockchain ultimately determines what happened.&lt;/p&gt;

&lt;p&gt;Suppose the local database records:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but the onchain result is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;63 tokens received
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The portfolio should eventually reflect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reconciliation can compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              DATABASE
                  │
        ┌─────────┼─────────┐
        ↓         ↓         ↓
   Tx Receipt  Balances  Events
        └─────────┼─────────┘
                  ↓
          Canonical State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This process can run periodically and after important execution events.&lt;/p&gt;

&lt;p&gt;It provides recovery from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC errors
missed events
application crashes
partial execution
delayed transaction updates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  17. Recommended TypeScript project structure
&lt;/h2&gt;

&lt;p&gt;I would organize the project roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── assets/
│   ├── assetRegistry.ts
│   └── capabilities.ts
│
├── market-data/
│   ├── robinhoodApi.ts
│   ├── chainlink.ts
│   └── dex.ts
│
├── pricing/
│   ├── normalize.ts
│   ├── quotes.ts
│   └── liquidity.ts
│
├── strategies/
│   ├── arbitrage.ts
│   ├── momentum.ts
│   └── index.ts
│
├── risk/
│   └── riskEngine.ts
│
├── execution/
│   ├── executor.ts
│   ├── transaction.ts
│   └── stateMachine.ts
│
├── portfolio/
│   ├── positions.ts
│   └── pnl.ts
│
├── reconciliation/
│   └── reconcile.ts
│
└── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The benefit is that every layer can be tested independently.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Dry-run mode
&lt;/h2&gt;

&lt;p&gt;Before enabling live execution, I would make dry-run a first-class feature.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DRY RUN
   ↓
Market Data
   ↓
Strategy
   ↓
Risk
   ↓
Simulated Execution
   ↓
Trade Journal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No transaction is broadcast.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"side"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BUY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quantity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expectedEdgeBps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"slippageBps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"riskApproved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"execution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SIMULATED"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the operator visibility into what the bot would do before connecting a funded wallet.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Monitoring
&lt;/h2&gt;

&lt;p&gt;The dashboard should expose more than P&amp;amp;L.&lt;/p&gt;

&lt;p&gt;Useful metrics include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;signals detected
signals rejected
risk rejection rate
average expected edge
executed trades
failed transactions
unknown transactions
transaction latency
stale quotes
reconciliation mismatches
current exposure
realized P&amp;amp;L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Signals                    1,482
Risk Approved                 87
Executed                      36
Confirmed                     34
Failed                         1
Unknown                        1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes the trading system observable.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. Extending the bot into a complete product
&lt;/h2&gt;

&lt;p&gt;The same infrastructure can support several different product scopes.&lt;/p&gt;

&lt;h3&gt;
  
  
  MVP
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;single wallet
single strategy
price monitoring
basic risk
automated execution
trade history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-strategy bot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multiple strategies
strategy configuration
portfolio limits
persistent state
reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Trading application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;web dashboard
wallet management
portfolio
P&amp;amp;L
alerts
execution history
API
monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Larger trading infrastructure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multi-wallet execution
multiple strategies
historical data
backtesting
strategy APIs
advanced risk
analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core execution engine can remain reusable across these versions.&lt;/p&gt;




&lt;h2&gt;
  
  
  21. Robinhood Chain gives the system an EVM-based foundation
&lt;/h2&gt;

&lt;p&gt;One reason this architecture is practical is that Robinhood Chain is EVM-compatible.&lt;/p&gt;

&lt;p&gt;Robinhood's current documentation supports standard Ethereum tooling for smart-contract development, including Foundry and Hardhat, and provides both mainnet and testnet environments. Mainnet uses chain ID &lt;strong&gt;4663&lt;/strong&gt;, while testnet uses &lt;strong&gt;46630&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That means a TypeScript application can use the normal EVM ecosystem for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallets
RPC
viem
ethers
contract calls
transaction signing
event processing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than requiring an entirely different application architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  22. Onchain data can also become more real-time
&lt;/h2&gt;

&lt;p&gt;For applications that require faster market-data reactions, Robinhood's current documentation also provides Chainlink Data Streams support on Robinhood Chain.&lt;/p&gt;

&lt;p&gt;The documentation describes Data Streams as a pull-based oracle system designed for high-frequency applications, with TypeScript SDK support and sub-second data delivery.&lt;/p&gt;

&lt;p&gt;This creates an interesting progression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Basic Bot
    ↓
REST API
    ↓
Onchain Oracle
    ↓
Event Monitoring
    ↓
Real-Time Data Streams
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The appropriate data architecture depends on the strategy's actual latency requirements.&lt;/p&gt;




&lt;h2&gt;
  
  
  23. My implementation approach
&lt;/h2&gt;

&lt;p&gt;I have been working with a TypeScript implementation around this type of architecture for Robinhood Chain Stock Tokens.&lt;/p&gt;

&lt;p&gt;The important objective is not just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detect price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATA
 ↓
STRATEGY
 ↓
RISK
 ↓
EXECUTION
 ↓
STATE
 ↓
RECONCILIATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That architecture can then be reused for other trading products.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot" rel="noopener noreferrer"&gt;https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The public project is listed as a Robinhood Chain Stock Token arbitrage bot using Uniswap, Chainlink, and REST data sources.&lt;/p&gt;




&lt;h2&gt;
  
  
  24. What can be built on top of it?
&lt;/h2&gt;

&lt;p&gt;Once the core system is working, the same infrastructure can support:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock Token Trading Bot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Automated trading based on configurable strategies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock Token Arbitrage Bot&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compare normalized external/reference prices with executable onchain prices.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stock Token Portfolio Tracker&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Track balances, positions, exposure, and P&amp;amp;L.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Robinhood Chain Trading Terminal&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Provide market discovery, wallet management, charts, orders, and portfolio views.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-Wallet Trading System&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Coordinate multiple execution wallets under centralized strategy and risk controls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trading API&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Expose the execution engine to external applications.&lt;/p&gt;

&lt;p&gt;The bot becomes the foundation rather than the final product.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building a Stock Token trading bot is not primarily a matter of writing a &lt;code&gt;buy()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The harder problems are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How fresh is the data?

Is the price normalized?

Is the asset currently tradable?

Is there enough liquidity?

What does the strategy actually want to do?

Does the trade pass risk?

What was submitted?

What actually happened?

What is the resulting position?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A robust architecture answers those questions independently.&lt;/p&gt;

&lt;p&gt;The resulting system looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stock Token Data
      ↓
Price Normalization
      ↓
  Strategy
      ↓
     Risk
      ↓
Execution
      ↓
Transaction State
      ↓
  Position
      ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That same foundation can grow from a small trading-bot MVP into a multi-wallet automated trading platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That is the type of trading infrastructure I build on Robinhood Chain: strategy-specific automation with real execution, risk controls, persistent state, and reconciliation-not just a script that watches prices.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Need a custom Stock Token trading bot?
&lt;/h3&gt;

&lt;p&gt;I build custom Robinhood Chain trading systems ranging from focused trading-bot MVPs to larger automated trading applications.&lt;/p&gt;

&lt;p&gt;GitHub:&lt;br&gt;
&lt;a href="https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot" rel="noopener noreferrer"&gt;https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>blockchain</category>
      <category>trading</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Stock Token Arbitrage Bot on Robinhood Chain with TypeScript</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Sat, 26 Sep 2026 06:52:02 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-stock-token-arbitrage-bot-on-robinhood-chain-with-typescript-37g8</link>
      <guid>https://dev.to/hamssog/building-a-stock-token-arbitrage-bot-on-robinhood-chain-with-typescript-37g8</guid>
      <description>&lt;p&gt;A useful arbitrage bot is not just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;priceA&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;priceB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;buy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That logic can detect a price difference.&lt;/p&gt;

&lt;p&gt;It cannot tell you whether the trade is actually executable.&lt;/p&gt;

&lt;p&gt;A production &lt;strong&gt;Stock Token arbitrage bot&lt;/strong&gt; on Robinhood Chain needs to solve several problems at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reference pricing&lt;/li&gt;
&lt;li&gt;onchain pricing&lt;/li&gt;
&lt;li&gt;corporate-action multipliers&lt;/li&gt;
&lt;li&gt;liquidity&lt;/li&gt;
&lt;li&gt;trading availability&lt;/li&gt;
&lt;li&gt;fees&lt;/li&gt;
&lt;li&gt;gas&lt;/li&gt;
&lt;li&gt;slippage&lt;/li&gt;
&lt;li&gt;stale data&lt;/li&gt;
&lt;li&gt;transaction state&lt;/li&gt;
&lt;li&gt;partial execution&lt;/li&gt;
&lt;li&gt;reconciliation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important engineering question is therefore not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is there a spread?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is there a tradeable spread after every relevant cost and constraint?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article shows how I would structure that system in TypeScript.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What the arbitrage bot actually does
&lt;/h2&gt;

&lt;p&gt;The basic system is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stock Token Asset Data
        ↓
Reference Price
        ↓
Onchain / DEX Price
        ↓
Price Normalization
        ↓
Spread Calculation
        ↓
Liquidity Check
        ↓
Risk Engine
        ↓
Execution
        ↓
Transaction Monitoring
        ↓
Position State
        ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each layer has one responsibility.&lt;/p&gt;

&lt;p&gt;The arbitrage scanner discovers possible opportunities.&lt;/p&gt;

&lt;p&gt;The risk engine decides whether the opportunity is acceptable.&lt;/p&gt;

&lt;p&gt;The execution engine turns the opportunity into a transaction.&lt;/p&gt;

&lt;p&gt;The reconciliation layer determines what actually happened.&lt;/p&gt;

&lt;p&gt;That separation becomes important as soon as the bot starts handling real transactions.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Stock Tokens give us several pricing surfaces
&lt;/h2&gt;

&lt;p&gt;Robinhood Chain documentation exposes Stock Token metadata and market information through read-only REST APIs.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/assets&lt;/code&gt; endpoint provides asset metadata, deployments, the current corporate-action multiplier, and trading capabilities.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/prices/{symbol}&lt;/code&gt; endpoint provides bid/ask information.&lt;/p&gt;

&lt;p&gt;There is also onchain price information through Chainlink feeds. Robinhood notes that the REST price is the raw underlying-equity bid/ask and is &lt;strong&gt;not multiplier-adjusted&lt;/strong&gt;, while the onchain Chainlink value incorporates the multiplier.&lt;/p&gt;

&lt;p&gt;That means a pricing engine should never blindly compare two raw numbers from different sources.&lt;/p&gt;

&lt;p&gt;Instead, create a normalized internal representation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PriceSource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reference&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chainlink&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;NormalizedPrice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PriceSource&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;priceUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every downstream component receives a predictable data structure.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Start with the Stock Token asset registry
&lt;/h2&gt;

&lt;p&gt;A useful first step is loading &lt;code&gt;/assets&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;StockTokenAsset&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenSymbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tokenName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;currentMultiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;deployments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;contractAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;chainId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;tradingCapabilities&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important fields for an arbitrage system are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenSymbol
contractAddress
chainId
currentMultiplier
tradingCapabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The contract address matters because a matching ticker or name alone is not sufficient to identify the canonical Stock Token. Robinhood's contract documentation explicitly points developers to the canonical deployed contract addresses.&lt;/p&gt;

&lt;p&gt;I would store the registry locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL
    ↓
asset symbol
    ↓
canonical contract
    ↓
multiplier
    ↓
trading capabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot should not repeatedly discover the same asset metadata during every trading decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Normalize the multiplier before looking for arbitrage
&lt;/h2&gt;

&lt;p&gt;This is one of the easiest places to build an incorrect arbitrage engine.&lt;/p&gt;

&lt;p&gt;Suppose the data layer receives:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Reference price = 100
Multiplier      = 0.25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If another pricing source already reflects the multiplier, directly comparing the two values can produce a false spread.&lt;/p&gt;

&lt;p&gt;The internal pricing layer should therefore explicitly track whether a source is adjusted.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RawPrice&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;multiplierAdjusted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then normalize:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;normalizePrice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RawPrice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multiplierAdjusted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact transformation depends on what economic unit your strategy is using, but the important design principle is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never let the spread engine guess how a price was produced.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The pricing adapter should make that explicit.&lt;/p&gt;

&lt;p&gt;Robinhood also exposes corporate-action information separately, allowing applications to reconcile multiplier changes with events such as forward or reverse splits.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. The spread is not the profit
&lt;/h2&gt;

&lt;p&gt;A naive calculation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;spread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sellPrice&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;buyPrice&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful arbitrage engine needs to calculate something closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Gross Spread
- DEX fee
- execution slippage
- gas
- protocol costs
- expected execution loss
- safety buffer
= Expected Net Edge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Opportunity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;buyPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sellPrice&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;grossSpreadUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;estimatedFeesUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;estimatedGasUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expectedSlippageUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;netEdgeUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;calculateNetEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;grossSpreadUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;feesUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;gasUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;slippageUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;grossSpreadUsd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="nx"&gt;feesUsd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="nx"&gt;gasUsd&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;
    &lt;span class="nx"&gt;slippageUsd&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This changes the bot's decision from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;spread&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;netEdge&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;minimumEdge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a much more useful trading rule.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Liquidity changes the answer
&lt;/h2&gt;

&lt;p&gt;A price difference is meaningless if the available liquidity cannot support the intended trade size.&lt;/p&gt;

&lt;p&gt;For a small trade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quoted price = executable price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a larger trade:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quoted price ≠ actual execution price
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The arbitrage engine should therefore calculate the opportunity for the actual order size.&lt;/p&gt;

&lt;p&gt;A simple structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;LiquidityCheck&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;requestedSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;estimatedOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;priceImpactBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;sufficientLiquidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner can then reject opportunities such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spread:             1.10%
Expected slippage:  0.85%
Fees:               0.20%
Gas:                0.15%
--------------------------------
Net edge:          -0.10%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A scanner that reports only the first line would incorrectly call this an opportunity.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Trading availability needs to be part of the strategy
&lt;/h2&gt;

&lt;p&gt;Another common mistake is treating every Stock Token as continuously tradable in exactly the same way.&lt;/p&gt;

&lt;p&gt;Robinhood documents per-asset trading capabilities for market, extended, and overnight sessions. Applications are expected to check those capabilities before execution.&lt;/p&gt;

&lt;p&gt;So the arbitrage engine should have a gate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;canTrade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;asset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;StockTokenAsset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Evaluate the current session against&lt;/span&gt;
  &lt;span class="c1"&gt;// the asset's trading capabilities.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a production system I would make this more explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ASSET_ACTIVE
      ↓
SESSION_ALLOWED
      ↓
TRADE_ALLOWED
      ↓
OPPORTUNITY_VALID
      ↓
EXECUTION_ALLOWED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents the bot from treating a data discrepancy as an executable opportunity.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Separate scanning from execution
&lt;/h2&gt;

&lt;p&gt;I prefer a strict separation between these components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Data
    ↓
Opportunity Scanner
    ↓
Risk Engine
    ↓
Execution Engine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner should never directly submit transactions.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TradeIntent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;side&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SELL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minNetEdgeBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The scanner produces the intent.&lt;/p&gt;

&lt;p&gt;The risk engine validates it.&lt;/p&gt;

&lt;p&gt;Only then does the executor receive it.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. A simple risk engine
&lt;/h2&gt;

&lt;p&gt;A useful risk engine can enforce:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum trade size
maximum position size
maximum daily loss
maximum gas cost
minimum net edge
maximum slippage
stale-data threshold
maximum concurrent trades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;RiskLimits&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;maxTradeUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxPositionUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minNetEdgeBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxSlippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxDataAgeMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;validateOpportunity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Opportunity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RiskLimits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;dataTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;dataTimestamp&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;maxDataAgeMs&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netEdgeUsd&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;edgeBps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;netEdgeUsd&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;
      &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buyPrice&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;opportunity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
    &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;edgeBps&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minNetEdgeBps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that &lt;strong&gt;risk is a separate component&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This makes the strategy easier to test and modify.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Transaction execution needs states
&lt;/h2&gt;

&lt;p&gt;This is where trading bots often become unreliable.&lt;/p&gt;

&lt;p&gt;A transaction is not simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;submit()
→ success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real execution can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SIGNAL
  ↓
RISK_APPROVED
  ↓
TX_PREPARED
  ↓
TX_SUBMITTED
  ↓
PENDING
  ↓
CONFIRMED
  ↓
POSITION_UPDATED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But there is another branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TX_SUBMITTED
      ↓
RPC TIMEOUT
      ↓
UNKNOWN
      ↓
RECONCILE
      ↓
CONFIRMED / FAILED / STILL_PENDING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;UNKNOWN&lt;/code&gt; state is important.&lt;/p&gt;

&lt;p&gt;If an RPC request times out after the transaction was broadcast, submitting another transaction blindly can create a duplicate execution.&lt;/p&gt;

&lt;p&gt;A basic state machine might look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TxState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SIGNED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the executor can transition states explicitly.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Position tracking should not depend only on local memory
&lt;/h2&gt;

&lt;p&gt;A restart-safe bot needs durable state.&lt;/p&gt;

&lt;p&gt;I would store something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;trade_intent
transaction_hash
symbol
side
requested_amount
filled_amount
average_execution_price
status
created_at
updated_at
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot can reconstruct the state after a restart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
Open transactions
   ↓
Chain verification
   ↓
Actual balances
   ↓
Actual positions
   ↓
Recovered state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is much safer than keeping the position state only inside a running Node.js process.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Reconciliation is part of execution
&lt;/h2&gt;

&lt;p&gt;Suppose the bot believes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY 100 units
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the chain shows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BUY 63 units
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bot's local position must eventually become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filledAmount = 63
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;filledAmount = 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reconciliation loop can periodically compare:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local Trade State
        │
        ├── transaction receipt
        │
        ├── wallet balance
        │
        ├── token balance
        │
        └── onchain events
        │
        ▼
Canonical Position State
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the system a recovery path after crashes, delayed RPC responses, missed events, or partial execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Data freshness matters more than most people expect
&lt;/h2&gt;

&lt;p&gt;Robinhood's Stock Token API is cached and rate-limited. The current documentation states a 60 requests/second limit across the APIs and a 15-second cache for &lt;code&gt;/prices/{symbol}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That means a bot should not interpret every API response as an instantaneous market tick.&lt;/p&gt;

&lt;p&gt;Store the timestamp:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Quote&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;bid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;ask&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then reject stale data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;quote&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generatedAt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;MAX_QUOTE_AGE_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This becomes especially important when the expected arbitrage margin is small.&lt;/p&gt;

&lt;p&gt;A stale quote can turn a theoretical edge into a real loss.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Project structure
&lt;/h2&gt;

&lt;p&gt;For a TypeScript implementation, I would keep the system modular:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── assets/
│   ├── assetRegistry.ts
│   └── tradingCapabilities.ts
│
├── market-data/
│   ├── robinhoodApi.ts
│   ├── chainlink.ts
│   └── dex.ts
│
├── pricing/
│   ├── normalization.ts
│   ├── spread.ts
│   └── liquidity.ts
│
├── strategy/
│   └── arbitrage.ts
│
├── risk/
│   └── riskEngine.ts
│
├── execution/
│   ├── executor.ts
│   ├── transactions.ts
│   └── stateMachine.ts
│
├── portfolio/
│   └── positions.ts
│
├── reconciliation/
│   └── reconcile.ts
│
└── index.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets me test each section independently.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pricing tests
risk tests
quote tests
execution tests
reconciliation tests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before connecting everything into the live system.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Dry-run mode is essential
&lt;/h2&gt;

&lt;p&gt;Before live execution, the bot should support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DRY_RUN=true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In dry-run mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;detect opportunity
        ↓
calculate executable price
        ↓
calculate expected edge
        ↓
run risk checks
        ↓
simulate transaction
        ↓
record result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;do not broadcast
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful dry-run record might be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"buyPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;100.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sellPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;101.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"grossEdgeBps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;88.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"estimatedCostBps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;43.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expectedNetEdgeBps"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;45.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"approved"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you something measurable before introducing execution risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Monitoring
&lt;/h2&gt;

&lt;p&gt;Once the bot runs continuously, the most useful metrics are not only P&amp;amp;L.&lt;/p&gt;

&lt;p&gt;I would monitor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;opportunities detected
opportunities rejected
risk rejection rate
average spread
average net edge
execution success rate
transaction latency
stale quote count
RPC errors
unknown transactions
reconciliation mismatches
realized P&amp;amp;L
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Detected opportunities       1,284
Risk approved                   91
Executed                        37
Confirmed                       34
Unknown                          2
Failed                           1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives much more insight than simply looking at the wallet balance.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Where I would take this next
&lt;/h2&gt;

&lt;p&gt;A basic Stock Token arbitrage bot can start as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price Scanner
      ↓
Spread Calculator
      ↓
Risk Engine
      ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production system becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                ┌─────────────────────┐
                │   Stock Token Data  │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │ Price Normalization │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │ Opportunity Engine  │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │    Risk Engine      │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │ Execution Engine    │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │ Transaction Monitor │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │ Position Engine     │
                └──────────┬──────────┘
                           ↓
                ┌─────────────────────┐
                │ Reconciliation      │
                └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That architecture can then be extended into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multi-wallet arbitrage&lt;/li&gt;
&lt;li&gt;automated portfolio rebalancing&lt;/li&gt;
&lt;li&gt;opportunity dashboards&lt;/li&gt;
&lt;li&gt;Telegram or Discord alerts&lt;/li&gt;
&lt;li&gt;execution APIs&lt;/li&gt;
&lt;li&gt;configurable trading strategies&lt;/li&gt;
&lt;li&gt;historical opportunity backtesting&lt;/li&gt;
&lt;li&gt;automated position management&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  18. My implementation
&lt;/h2&gt;

&lt;p&gt;I built a TypeScript Stock Token arbitrage bot around this architecture for Robinhood Chain.&lt;/p&gt;

&lt;p&gt;The implementation focuses on comparing onchain and external pricing sources, normalizing the data, evaluating executable spreads, and keeping execution separate from opportunity detection.&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[0xhamssog/robinhood-stock-token-arbitrage-bot](https://github.com/0xhamssog/robinhood-stock-token-arbitrage-bot)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The repository is also a useful starting point for extending the system into a larger trading application.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Building a custom Stock Token arbitrage bot
&lt;/h2&gt;

&lt;p&gt;The same architecture can be scaled depending on what the project actually needs.&lt;/p&gt;

&lt;p&gt;A small build could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;single strategy
single wallet
basic scanner
basic execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A medium build could add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multiple assets
persistent state
risk management
dashboard
alerts
reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A larger system could include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multi-wallet execution
multiple strategies
historical data
backtesting
advanced monitoring
API access
portfolio management
custom execution logic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is that the trading logic should be designed around the client's actual execution requirements instead of starting with a generic "bot."&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A Stock Token arbitrage bot is easy to describe and much harder to engineer correctly.&lt;/p&gt;

&lt;p&gt;The price comparison is only the beginning.&lt;/p&gt;

&lt;p&gt;The real system has to answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Is the data fresh?

Is the price normalized?

Is the asset tradable?

Is there enough liquidity?

Is the spread still positive after costs?

Does the trade pass risk checks?

What exactly was submitted?

What actually happened onchain?

What is the resulting position?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the difference between an arbitrage script and trading infrastructure.&lt;/p&gt;

&lt;p&gt;For Robinhood Chain applications, I would treat &lt;strong&gt;pricing, risk, execution, state management, and reconciliation as separate engineering problems&lt;/strong&gt; and connect them through explicit interfaces.&lt;/p&gt;

&lt;p&gt;That makes the system easier to test, operate, and extend into a larger Stock Token trading product.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>blockchain</category>
      <category>trading</category>
      <category>robinhood</category>
    </item>
    <item>
      <title>Building a Pons Trading Terminal with TypeScript: Multi-Wallet Trading and Live Execution</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Fri, 25 Sep 2026 12:48:40 +0000</pubDate>
      <link>https://dev.to/hamssog/building-a-pons-trading-terminal-with-typescript-multi-wallet-trading-and-live-execution-403d</link>
      <guid>https://dev.to/hamssog/building-a-pons-trading-terminal-with-typescript-multi-wallet-trading-and-live-execution-403d</guid>
      <description>&lt;p&gt;A &lt;strong&gt;Pons trading terminal&lt;/strong&gt; should do more than display token prices.&lt;/p&gt;

&lt;p&gt;The useful version is an interface where a trader can manage wallets, inspect market data, preview a trade, control risk, execute transactions, monitor their status, and see the resulting position.&lt;/p&gt;

&lt;p&gt;The goal of this article is not to repeat the architecture from my earlier Pons Trading Terminal article.&lt;/p&gt;

&lt;p&gt;Instead, this is the implementation side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TOKEN
  ↓
MARKET DATA
  ↓
WALLET SELECTION
  ↓
TRADE PREVIEW
  ↓
RISK CHECK
  ↓
EXECUTION
  ↓
TRANSACTION STATE
  ↓
POSITION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current Pons documentation provides the contract and market-data integration surface for applications on Robinhood Chain, which uses chain ID &lt;code&gt;4663&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Terminal Needs to Solve
&lt;/h2&gt;

&lt;p&gt;A trader should be able to open a token and answer three questions quickly:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is happening?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What can I trade?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What will happen if I execute?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means a useful terminal needs more than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Price
Buy
Sell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A practical version should also expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallets
Balances
Liquidity
Recent trades
Quote
Slippage
Risk
Transaction status
Positions
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI is the visible part.&lt;/p&gt;

&lt;p&gt;The trading backend is responsible for making those values reliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Start With the Trading Workspace
&lt;/h2&gt;

&lt;p&gt;The main trading screen can combine the most important information into one workspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────┐
│ Token / Market                               │
│ Price · Liquidity · Volume                   │
├──────────────────────────┬───────────────────┤
│ Chart / Activity         │ Trade Panel       │
│                          │                   │
│                          │ Wallets           │
│                          │ Amount            │
│                          │ Slippage          │
│                          │ Risk              │
│                          │ Review            │
└──────────────────────────┴───────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user shouldn't need to switch between five pages just to make one trade.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Keep the Browser Out of the Execution Logic
&lt;/h2&gt;

&lt;p&gt;The frontend should not become the blockchain execution engine.&lt;/p&gt;

&lt;p&gt;A better structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;React / Next.js
       ↓
Trading API
       ↓
Execution Service
       ↓
Pons Integration
       ↓
Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wallet state
quotes
risk
transactions
reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in the backend where they can be shared by the terminal, sniper, copy-trading system, and other automation.&lt;/p&gt;

&lt;p&gt;This is also consistent with the architecture in my earlier Pons terminal work, where the frontend is treated as a consumer of backend trading state.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Wallet Selection
&lt;/h2&gt;

&lt;p&gt;Multi-wallet support changes the terminal substantially.&lt;/p&gt;

&lt;p&gt;Instead of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet
↓
Trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the interface can provide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;☑ Wallet A
☑ Wallet B
☐ Wallet C
☑ Wallet D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Selected wallets: 3
Available balance: ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A backend representation could be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TradingWallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;balanceAtomic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend should never receive private keys.&lt;/p&gt;

&lt;p&gt;It only receives wallet metadata and the information needed for the current workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Select the Token
&lt;/h2&gt;

&lt;p&gt;The terminal needs a clean token-selection flow.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Search token
      ↓
Resolve token
      ↓
Load market state
      ↓
Open trading workspace
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The market view can show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Symbol
Price
Liquidity
Volume
Pool / market
Recent trades
Launch information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For current Pons v1 markets, the official documentation describes token-specific pools and provides the integration data needed to resolve pool and token state. Pons v2 is a separate bonding-curve architecture and should be handled as a different execution mode.&lt;/p&gt;

&lt;p&gt;That distinction matters when building the terminal backend.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Build a Trade Preview
&lt;/h2&gt;

&lt;p&gt;Before a transaction is sent, the user should be able to see what the system intends to do.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TRADE PREVIEW

Token:
MEMESTOCK

Wallets:
A, B, D

Side:
BUY

Total input:
0.09 ETH

Expected tokens:
...

Minimum tokens:
...

Slippage:
1%

Estimated gas:
...

Risk:
PASS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user then clicks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REVIEW TRADE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and only after that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CONFIRM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives the terminal a human-readable execution checkpoint.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Quote and Risk Are Different
&lt;/h2&gt;

&lt;p&gt;The quote answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What do I approximately receive?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The risk engine answers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Am I allowed to do this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quote
$800
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maximum trade
$500
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trade should be reduced or rejected.&lt;/p&gt;

&lt;p&gt;The same applies to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;maximum position
portfolio exposure
slippage
price impact
available balance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful separation is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade Request
     ↓
Quote
     ↓
Risk Decision
     ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI can then show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quote       ✓
Balance     ✓
Position    ✓
Slippage    ✓
Risk        APPROVED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Keep Units Explicit
&lt;/h2&gt;

&lt;p&gt;Trading terminals have to deal with multiple units.&lt;/p&gt;

&lt;p&gt;I would keep them separate in TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TokenAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;QuoteAmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;BasisPoints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TradePreview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;tokenAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TokenAmount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;quoteAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuoteAmount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;slippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BasisPoints&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This prevents a common class of mistakes where blockchain atomic units are accidentally mixed with human-readable dollar values.&lt;/p&gt;

&lt;p&gt;The UI can convert values for display.&lt;/p&gt;

&lt;p&gt;The backend should keep exact values internally.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Multi-Wallet Execution
&lt;/h2&gt;

&lt;p&gt;Suppose the user selects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A → 0.02 ETH
Wallet B → 0.02 ETH
Wallet D → 0.05 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal should create separate execution records.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A
   ↓
Transaction A

Wallet B
   ↓
Transaction B

Wallet D
   ↓
Transaction D
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal should not assume that these transactions are one atomic operation.&lt;/p&gt;

&lt;p&gt;This distinction is especially important when the terminal is later connected to the Pons bundler workflow, where the launch transaction and additional wallet transactions have different execution characteristics.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Track Every Transaction Separately
&lt;/h2&gt;

&lt;p&gt;The UI should show live state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A     ✓ Confirmed
Wallet B     ● Pending
Wallet D     ✕ Failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A useful backend model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExecutionStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PREVIEWED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PENDING&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then the frontend simply subscribes to execution state.&lt;/p&gt;

&lt;p&gt;This is much more useful than returning only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  10. Transaction State Should Survive Refreshes
&lt;/h2&gt;

&lt;p&gt;Suppose the user closes the browser immediately after clicking Buy.&lt;/p&gt;

&lt;p&gt;When they return, the terminal should still know:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order:
0x1234...

Status:
PENDING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser should not be the source of transaction state.&lt;/p&gt;

&lt;p&gt;The backend should persist it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transaction
    ↓
Database
    ↓
API
    ↓
Frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is one of the reasons I prefer treating execution as a service rather than a React component.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. RPC Timeouts Need Their Own State
&lt;/h2&gt;

&lt;p&gt;An RPC timeout should not automatically be rendered as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A transaction may already have been submitted.&lt;/p&gt;

&lt;p&gt;The terminal should be able to show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;UNKNOWN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then reconcile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RPC timeout
     ↓
UNKNOWN
     ↓
Check transaction
     ↓
Confirmed / Failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the interface more truthful and reduces the risk of users or automation resubmitting the same trade unnecessarily.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Position Updates Come After Execution
&lt;/h2&gt;

&lt;p&gt;The terminal should not update the position merely because the user clicked Confirm.&lt;/p&gt;

&lt;p&gt;The actual position flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade request
     ↓
Transaction
     ↓
Receipt / onchain result
     ↓
Position update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requested:
100,000 TOKEN

Actual result:
96,420 TOKEN

Position:
96,420 TOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal should display the actual state.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Portfolio State
&lt;/h2&gt;

&lt;p&gt;Once positions are available, the terminal can aggregate them.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Portfolio

MEMESTOCK       $4,320
TOKEN X         $2,145
TOKEN Y         $1,890
WETH            $3,210
-----------------------
Total          $11,565
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A    $4,100
Wallet B    $3,700
Wallet D    $3,765
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the same product function as both a trading interface and portfolio dashboard.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Transaction History
&lt;/h2&gt;

&lt;p&gt;The terminal should retain a complete execution history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time
Wallet
Token
Side
Amount
Status
Transaction
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;09:42
Wallet A
MEMESTOCK
BUY
0.02 ETH
Confirmed

09:43
Wallet B
MEMESTOCK
BUY
0.02 ETH
Pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clicking the transaction should expose the onchain transaction reference.&lt;/p&gt;

&lt;p&gt;This gives the user an audit trail.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Live Data
&lt;/h2&gt;

&lt;p&gt;The trading interface should not require a manual browser refresh after every event.&lt;/p&gt;

&lt;p&gt;A useful setup is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blockchain
    ↓
Indexer
    ↓
Backend State
    ↓
WebSocket / SSE
    ↓
Trading Terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal can then update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;price
trades
wallet activity
transaction status
positions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in near real time.&lt;/p&gt;

&lt;p&gt;The earlier Pons Launch Monitor article already established this data layer as a reusable source for the trading terminal, sniper, copy trading, and analytics.&lt;/p&gt;

&lt;p&gt;That is why the terminal should consume the data layer rather than rebuild it.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Manual Trading and Automated Trading
&lt;/h2&gt;

&lt;p&gt;One useful design choice is allowing the same execution infrastructure to support different entry points.&lt;/p&gt;

&lt;p&gt;Manual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
 ↓
Trade Panel
 ↓
Risk
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy trading:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet Signal
 ↓
Copy Strategy
 ↓
Risk
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sniper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch Signal
 ↓
Sniper Strategy
 ↓
Risk
 ↓
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three can update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Transactions
Positions
Portfolio
Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal becomes the control surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Alerts
&lt;/h2&gt;

&lt;p&gt;A terminal should notify users about meaningful state changes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Trade confirmed
Transaction failed
Position changed
Wallet activity detected
Risk limit reached
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The notification layer can support:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Web
Telegram
Discord
Webhook
Email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same service can later be reused by the Pons wallet tracker and copy-trading system.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Risk Controls in the UI
&lt;/h2&gt;

&lt;p&gt;Risk settings should be visible and configurable.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Maximum trade:
$1,000

Maximum position:
$5,000

Maximum slippage:
1%

Maximum price impact:
3%

Daily allocation:
$10,000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The terminal can show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Risk Status

Trade size       ✓
Balance          ✓
Position         ✓
Slippage         ✓

READY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes risk understandable to users who aren't reading backend logs.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. A Better Terminal for Different Clients
&lt;/h2&gt;

&lt;p&gt;Not every client needs the same product.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple terminal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet
Token
Buy
Sell
Positions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Multi-wallet terminal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet groups
Bulk trading
Allocation
Execution monitoring
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sniper terminal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch feed
Token filters
Entry controls
Automated execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Copy-trading terminal
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tracked wallets
Copy settings
Signals
Risk
Execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Full trading platform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Markets
Trading
Wallets
Strategies
Risk
Positions
Portfolio
Analytics
Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same engineering foundation can support all of these.&lt;/p&gt;




&lt;h2&gt;
  
  
  20. From MVP to Full Product
&lt;/h2&gt;

&lt;p&gt;A client does not have to build everything on day one.&lt;/p&gt;

&lt;p&gt;A practical MVP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallets
Token Search
Token Page
Buy / Sell
Transaction History
Positions
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Multi-wallet
Risk Controls
Alerts
Automation
Copy Trading
Sniper
Analytics
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets a project start with a smaller scope and grow as requirements become clearer.&lt;/p&gt;




&lt;h2&gt;
  
  
  21. Suggested Backend Structure
&lt;/h2&gt;

&lt;p&gt;A reusable backend can separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── market/
├── wallets/
├── quotes/
├── risk/
├── execution/
├── transactions/
├── positions/
├── portfolio/
├── alerts/
└── api/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend can then remain focused on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;routes
components
state
charts
forms
tables
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than protocol-specific transaction logic.&lt;/p&gt;




&lt;h2&gt;
  
  
  22. Connecting the Terminal to the Existing Pons Stack
&lt;/h2&gt;

&lt;p&gt;This is where the earlier articles become useful.&lt;/p&gt;

&lt;p&gt;Your current Pons content already covers:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Launch Monitor&lt;/strong&gt; → data&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wallet Tracker&lt;/strong&gt; → wallet activity&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copy Trading&lt;/strong&gt; → strategy&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sniper&lt;/strong&gt; → launch strategy&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bundler&lt;/strong&gt; → multi-wallet execution&lt;/p&gt;

&lt;p&gt;The terminal can become the interface connecting those capabilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Data
   ↓
Trading Terminal
   ├── Manual Trading
   ├── Sniper
   ├── Copy Trading
   ├── Bundler
   └── Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a much stronger product story than treating every article as a standalone project.&lt;/p&gt;




&lt;h2&gt;
  
  
  23. The Development Opportunity
&lt;/h2&gt;

&lt;p&gt;A client may already have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A trading bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but no interface.&lt;/p&gt;

&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but no reliable execution backend.&lt;/p&gt;

&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An MVP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but no multi-wallet support.&lt;/p&gt;

&lt;p&gt;Or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A strategy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;but no portfolio/reconciliation layer.&lt;/p&gt;

&lt;p&gt;The terminal can be built around whichever part is missing.&lt;/p&gt;

&lt;p&gt;That means a custom project doesn't always need to start from zero.&lt;/p&gt;




&lt;h2&gt;
  
  
  24. What I Would Build
&lt;/h2&gt;

&lt;p&gt;For a full Pons terminal, I would aim for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Market Discovery
+
Wallet Management
+
Trade Preview
+
Risk Controls
+
Execution
+
Transaction Monitoring
+
Positions
+
Portfolio
+
Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then connect optional strategy modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sniper
Copy Trading
Bundler
Automation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This provides one interface while keeping the underlying systems modular.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;Pons trading terminal&lt;/strong&gt; should be more than a dashboard.&lt;/p&gt;

&lt;p&gt;It should be the interface through which a user can:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Discover
Trade
Manage Wallets
Control Risk
Monitor Transactions
Track Positions
Manage Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most useful implementation pattern is to keep the terminal itself relatively thin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Frontend
    ↓
Trading API
    ↓
Market / Risk / Execution
    ↓
  Pons
    ↓
Robinhood Chain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That lets the same backend support manual trading, sniper strategies, copy trading, bundling, and future automation.&lt;/p&gt;

&lt;p&gt;The terminal then becomes the product layer sitting above your existing Pons infrastructure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Pons Trading Terminal Development
&lt;/h2&gt;

&lt;p&gt;I build custom &lt;strong&gt;Pons and Robinhood Chain trading products&lt;/strong&gt;, including:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons trading terminals&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Multi-wallet trading interfaces&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Pons sniper dashboards&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Pons copy-trading platforms&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Pons bundlers&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Wallet analytics&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Risk and execution systems&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Portfolio dashboards&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Trading APIs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Projects can start from an idea, an existing bot, an existing codebase, or an MVP and expand into a larger trading platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The objective is to build the terminal around the client's workflow—not force the client into a generic trading interface.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>blockchain</category>
      <category>tradingbot</category>
      <category>robinhood</category>
    </item>
    <item>
      <title>Pons Bundler: Building Multi-Wallet Launch Execution on Robinhood Chain with TypeScript</title>
      <dc:creator>hamssog</dc:creator>
      <pubDate>Thu, 24 Sep 2026 08:19:11 +0000</pubDate>
      <link>https://dev.to/hamssog/pons-bundler-building-multi-wallet-launch-execution-on-robinhood-chain-with-typescript-i6</link>
      <guid>https://dev.to/hamssog/pons-bundler-building-multi-wallet-launch-execution-on-robinhood-chain-with-typescript-i6</guid>
      <description>&lt;p&gt;A &lt;strong&gt;Pons bundler&lt;/strong&gt; is easy to misunderstand.&lt;/p&gt;

&lt;p&gt;It is not an ERC-4337 UserOperation bundler.&lt;/p&gt;

&lt;p&gt;For Pons v2, the practical meaning is a launch-automation tool that creates a token, performs the opening buy, and coordinates additional wallet buys during the launch's early curve-trading window.&lt;/p&gt;

&lt;p&gt;I built a TypeScript implementation around that workflow:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/0xhamssog/pons-bundler" rel="noopener noreferrer"&gt;0xhamssog/pons-bundler&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The repository targets &lt;strong&gt;Pons v2 on Robinhood Chain&lt;/strong&gt;, using &lt;code&gt;launchAndBuy&lt;/code&gt; for the launch plus initial buy and separate bonding-curve &lt;code&gt;buy()&lt;/code&gt; transactions for additional wallets. The repository currently supports wallet generation and funding, dry runs, launching, buying, selling, and sweeping.&lt;/p&gt;

&lt;p&gt;The interesting engineering problem is not "how do I send several transactions?"&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How do I coordinate several wallets while the same bonding curve is changing underneath them?&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  What Makes a Pons Bundler Different?
&lt;/h2&gt;

&lt;p&gt;The basic Pons v2 lifecycle is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TOKEN CONFIG
     ↓
  LAUNCH
     ↓
OPENING BUY
     ↓
ADDITIONAL WALLET BUYS
     ↓
POSITION STATE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is that these operations are not all one atomic transaction.&lt;/p&gt;

&lt;p&gt;The current repository documents the model clearly:&lt;br&gt;
&lt;/p&gt;

&lt;p&gt;```text id="db02"&lt;br&gt;
Master wallet&lt;br&gt;
     ↓&lt;br&gt;
launchAndBuy()&lt;br&gt;
     ↓&lt;br&gt;
Token + curve created&lt;br&gt;
     ↓&lt;br&gt;
Buyer A → buy()&lt;br&gt;
Buyer B → buy()&lt;br&gt;
Buyer C → buy()&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;


Robinhood Chain is treated as FCFS, and there is no atomic multi-signer transaction across the additional wallets.

That changes how the implementation needs to be designed.

---

## Pons v2 Starts on a Bonding Curve

Pons v2 starts with curve trading and later graduates into Uniswap v4.



```plaintext
CREATE
  ↓
BONDING CURVE
  ↓
TRADING
  ↓
CURVE COMPLETION
  ↓
UNISWAP V4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That means the opening buys are curve transactions, not normal post-graduation swaps.&lt;/p&gt;

&lt;p&gt;The Pons documentation also makes the launch phase explicit, so a bundler should treat the curve phase and post-graduation phase as different states.&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Configure the Launch
&lt;/h2&gt;

&lt;p&gt;A launch needs structured parameters.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LaunchConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pairToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;launchConfigId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;masterBuy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eachWalletBuy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;creatorFeeRecipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;creatorTaxBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keeping these values together makes the launch process easier to validate before the first transaction is sent.&lt;/p&gt;

&lt;p&gt;The repository also keeps the master wallet separate from the generated buyer wallets.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Prepare the Buyer Wallets
&lt;/h2&gt;

&lt;p&gt;A multi-wallet workflow needs explicit wallet management.&lt;/p&gt;

&lt;p&gt;The reference implementation supports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pons &lt;span class="nt"&gt;--&lt;/span&gt; wallets generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--count&lt;/span&gt; 8 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--out&lt;/span&gt; wallets.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and funding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pons &lt;span class="nt"&gt;--&lt;/span&gt; wallets fund &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; wallets.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--each&lt;/span&gt; 0.02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository supports up to &lt;strong&gt;32 additional wallets&lt;/strong&gt; as Pons v2 snipe-tax exemptions. &lt;code&gt;wallets.json&lt;/code&gt; contains private keys and is gitignored.&lt;/p&gt;

&lt;p&gt;For a real deployment, private keys should be treated as sensitive infrastructure rather than ordinary application configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Why &lt;code&gt;launchAndBuy&lt;/code&gt; Matters
&lt;/h2&gt;

&lt;p&gt;The first important protocol operation is &lt;code&gt;launchAndBuy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch
   ↓
Wait
   ↓
Opening buy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is a window between the two transactions.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;launchAndBuy&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launchAndBuy
      ↓
CREATE + INITIAL BUY
      ↓
ONE TRANSACTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current Pons v2 documentation describes &lt;code&gt;launchAndBuy&lt;/code&gt; as a separate router operation that creates the launch and performs the first buy in a single call. This closes the gap between launch creation and the creator's initial purchase.&lt;/p&gt;

&lt;p&gt;That is the first major piece of the bundler.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Additional Wallets Are Still Separate Transactions
&lt;/h2&gt;

&lt;p&gt;This is the part that makes the word "bundler" potentially confusing.&lt;/p&gt;

&lt;p&gt;The master wallet can launch and buy atomically.&lt;/p&gt;

&lt;p&gt;Additional wallets cannot all be included as signers in that same transaction.&lt;/p&gt;

&lt;p&gt;Instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launchAndBuy()
       ↓
Buyer A → buy()
Buyer B → buy()
Buyer C → buy()
Buyer D → buy()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those are separate transactions.&lt;/p&gt;

&lt;p&gt;The repository explicitly documents this behavior and describes the additional buys as parallel curve transactions.&lt;/p&gt;

&lt;p&gt;So the application is coordinating transaction submission.&lt;/p&gt;

&lt;p&gt;It is not creating atomic multi-wallet execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Opening Snipe-Tax Exemptions
&lt;/h2&gt;

&lt;p&gt;Pons v2 applies an opening buy tax that starts very high and decays quickly.&lt;/p&gt;

&lt;p&gt;The current protocol documentation says the tax starts at 99% and decays toward zero over the first five seconds. The launching address and creator fee recipient are automatically exempt, and additional exemption addresses can be specified when the launch is created.&lt;/p&gt;

&lt;p&gt;For a team launch, the flow can therefore be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch
   ↓
snipeTaxExemptions
   ↓
Buyer A
Buyer B
Buyer C
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exemption list is fixed when the launch is created and can contain up to 32 extra addresses.&lt;/p&gt;

&lt;p&gt;That is an important implementation detail because the wallets need to be known before the launch transaction is constructed.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Build the Launch Transaction
&lt;/h2&gt;

&lt;p&gt;Conceptually, the router call looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeContract&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;launchAndBuyRouter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;abi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;routerAbi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;launchAndBuy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

  &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;tokenParams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;launchConfigId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;pairToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;quoteIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;minTokensOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;buyerWallets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;

  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;launchValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key arguments are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tokenParams
launchConfig
pair asset
initial buy
minimum output
recipient
extra exemptions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recipient is important because Pons v2's opening-tax logic is wallet-specific.&lt;/p&gt;

&lt;p&gt;The Pons documentation also notes that &lt;code&gt;creatorFeeRecipient&lt;/code&gt; needs to be explicit for &lt;code&gt;launchAndBuy&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. &lt;code&gt;minTokensOut&lt;/code&gt; Still Matters
&lt;/h2&gt;

&lt;p&gt;The launch transaction is only the first step.&lt;/p&gt;

&lt;p&gt;The initial buy needs an expected output and minimum acceptable output.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Curve State
    ↓
  Quote
    ↓
Expected Tokens
    ↓
Slippage
    ↓
minTokensOut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Pons bundler repository calculates &lt;code&gt;minTokensOut&lt;/code&gt; using the curve's official pricing state and applies configurable slippage.&lt;/p&gt;

&lt;p&gt;That means the launch cannot simply say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;buy 0.1 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and accept whatever number of tokens comes back.&lt;/p&gt;

&lt;p&gt;The transaction should protect the expected output.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Quote the Curve, Not a Generic DEX
&lt;/h2&gt;

&lt;p&gt;Pons v2 uses a bonding curve.&lt;/p&gt;

&lt;p&gt;The quote needs the curve's pricing reserves and fee state.&lt;/p&gt;

&lt;p&gt;The repository describes its quote logic as using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getReserves()
+
fees
+
slippage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;rather than relying on a generic router quote.&lt;/p&gt;

&lt;p&gt;A useful internal type is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;CurveQuote&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;quoteIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expectedTokensOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;minTokensOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;slippageBps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps quote generation separate from transaction construction.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. The Curve Changes Between Wallets
&lt;/h2&gt;

&lt;p&gt;This is one of the most interesting implementation problems.&lt;/p&gt;

&lt;p&gt;Suppose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A → 0.02 ETH
Wallet B → 0.02 ETH
Wallet C → 0.02 ETH
Wallet D → 0.02 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They all target the same launch curve.&lt;/p&gt;

&lt;p&gt;But each successful buy changes the curve state.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Curve State T0
    ↓
Wallet A trade
    ↓
Curve State T1
    ↓
Wallet B trade
    ↓
Curve State T2
    ↓
Wallet C trade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundler therefore has to consider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;quote
+
slippage
+
transaction ordering
+
changing curve state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository documents this directly: parallel buys move the curve, so each transaction is protected by a configurable slippage limit.&lt;/p&gt;

&lt;p&gt;This is one of the main reasons a multi-wallet launch tool is more complicated than a loop around &lt;code&gt;sendTransaction()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Run a Launch in Dry-Run Mode
&lt;/h2&gt;

&lt;p&gt;The bundler supports dry-run execution.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pons &lt;span class="nt"&gt;--&lt;/span&gt; launch &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Example"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--symbol&lt;/span&gt; EXMPL &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--master-buy&lt;/span&gt; 0.1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; wallets.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--each-buy&lt;/span&gt; 0.02 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The dry-run path lets the application validate the launch without broadcasting it.&lt;/p&gt;

&lt;p&gt;That creates a useful workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Configuration
    ↓
Wallet validation
    ↓
Launch checks
    ↓
  Quote
    ↓
Simulation
    ↓
  Review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For automated trading infrastructure, dry-run should be a real execution mode rather than a logging shortcut.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Check the Launch Gate
&lt;/h2&gt;

&lt;p&gt;Public launching on Pons v2 can be gated.&lt;/p&gt;

&lt;p&gt;The current repository checks &lt;code&gt;canLaunch&lt;/code&gt; before spending gas. The project documentation explicitly recommends reading that value on every run rather than assuming the public launch gate remains open.&lt;/p&gt;

&lt;p&gt;The flow is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch requested
       ↓
   canLaunch?
    /      \
   NO      YES
   ↓        ↓
Stop     Continue
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a good example of querying current contract state immediately before execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. The Additional Buys
&lt;/h2&gt;

&lt;p&gt;After the master transaction creates the launch, the buyer wallets can execute their own purchases.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;buyerWallets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;submitBuy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;quoteIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;buyAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minTokensOut&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minTokensOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;recipient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production implementation should not treat this simple loop as the final design.&lt;/p&gt;

&lt;p&gt;The important concerns are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;per-wallet nonce
quote freshness
gas balance
transaction status
failure handling
retry policy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository submits the additional curve buys separately and supports them in parallel.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. Track State Per Wallet
&lt;/h2&gt;

&lt;p&gt;Because the additional transactions are independent, state should also be tracked independently.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A
  submitted
  confirmed

Wallet B
  submitted
  confirmed

Wallet C
  submitted
  failed

Wallet D
  pending
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One aggregate status is not enough.&lt;/p&gt;

&lt;p&gt;A useful execution record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;WalletExecution&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUBMITTED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CONFIRMED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAILED&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;UNKNOWN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="s2"&gt;`0x&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;requestedAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;executedAmount&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This lets the application answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Which wallets actually completed their buys?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  14. Don't Treat Parallel Transactions as Atomic
&lt;/h2&gt;

&lt;p&gt;A multi-wallet launch can produce mixed outcomes.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch TX       CONFIRMED

Buyer A TX      CONFIRMED
Buyer B TX      CONFIRMED
Buyer C TX      FAILED
Buyer D TX      PENDING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application must represent that state honestly.&lt;/p&gt;

&lt;p&gt;There is no single rollback that undoes the successful transactions.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Coordination
≠
Atomicity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an important product-level distinction when describing a Pons bundler to users or clients.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Nonces Need Per-Wallet Coordination
&lt;/h2&gt;

&lt;p&gt;Every buyer wallet has its own nonce sequence.&lt;/p&gt;

&lt;p&gt;That means nonce management can be isolated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet A
  nonce 10
  nonce 11

Wallet B
  nonce 4
  nonce 5

Wallet C
  nonce 28
  nonce 29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A single global nonce lock would be the wrong abstraction.&lt;/p&gt;

&lt;p&gt;Use one transaction queue or nonce manager per wallet.&lt;/p&gt;

&lt;p&gt;That also makes retries easier to reason about.&lt;/p&gt;




&lt;h2&gt;
  
  
  16. Monitor Transaction Results
&lt;/h2&gt;

&lt;p&gt;After submission, the transaction manager should track:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Created
↓
Submitted
↓
Pending
↓
Confirmed / Failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not immediately assume that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;writeContract&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means the desired position exists.&lt;/p&gt;

&lt;p&gt;The actual transaction receipt and resulting wallet state should be checked afterward.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Handle Partial Curve Fills
&lt;/h2&gt;

&lt;p&gt;The final curve buy can behave differently from a normal buy when the launch is approaching completion.&lt;/p&gt;

&lt;p&gt;If the requested purchase exceeds the remaining curve capacity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requested:
1 ETH

Remaining:
0.6 ETH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the result can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Partial fill
+
Refund
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The position manager therefore needs to use the actual transaction result rather than blindly storing the requested amount.&lt;/p&gt;

&lt;p&gt;This is another reason transaction state and portfolio state should remain separate.&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Reconciliation
&lt;/h2&gt;

&lt;p&gt;After the transactions settle, the application should inspect the actual wallet state.&lt;/p&gt;

&lt;p&gt;For each buyer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Requested buy
      ↓
Transaction
      ↓
Receipt / events
      ↓
Actual token balance
      ↓
Reconciled position
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Did the wallet buy?
How much did it receive?
How much ETH remains?
Did the transaction fail?
Is the local state correct?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a multi-wallet product, this becomes one of the most important operational features.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Buy, Sell, and Sweep
&lt;/h2&gt;

&lt;p&gt;The reference implementation is not limited to launch creation.&lt;/p&gt;

&lt;p&gt;It also provides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pons &lt;span class="nt"&gt;--&lt;/span&gt; buy &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--token&lt;/span&gt; 0x... &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; wallets.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--each-buy&lt;/span&gt; 0.02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Selling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pons &lt;span class="nt"&gt;--&lt;/span&gt; sell &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--token&lt;/span&gt; 0x... &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; wallets.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--percent&lt;/span&gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And sweeping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run pons &lt;span class="nt"&gt;--&lt;/span&gt; wallets sweep &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--file&lt;/span&gt; wallets.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives the CLI a larger lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Prepare
  ↓
Launch
  ↓
Buy
  ↓
Manage
  ↓
Sell
  ↓
Sweep
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  20. Security
&lt;/h2&gt;

&lt;p&gt;Multi-wallet automation increases the security surface.&lt;/p&gt;

&lt;p&gt;At minimum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Private keys
    ↓
Encrypted / protected storage
    ↓
Transaction signer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository keeps &lt;code&gt;wallets.json&lt;/code&gt; gitignored and warns against committing it. It also recommends a configured RPC endpoint instead of relying on the rate-limited public RPC for production use.&lt;/p&gt;

&lt;p&gt;For a larger client deployment, I would also separate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Development secrets
Production secrets
Signing infrastructure
Application configuration
Audit logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundler should never log private keys.&lt;/p&gt;




&lt;h2&gt;
  
  
  21. Reference Implementation
&lt;/h2&gt;

&lt;p&gt;The complete implementation is available here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pons Bundler — TypeScript&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/0xhamssog/pons-bundler" rel="noopener noreferrer"&gt;Open the GitHub repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The current repository contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons v2 support
launchAndBuy
Up to 32 extra exemption wallets
Wallet generation
Wallet funding
Dry-run launch
Parallel wallet buys
Buy / sell commands
Wallet sweeping
Curve quotes
Slippage protection
Transaction handling
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is specifically a &lt;strong&gt;Pons v2 launch bundler&lt;/strong&gt;, not an ERC-4337 bundler.&lt;/p&gt;




&lt;h2&gt;
  
  
  22. Bundler vs Sniper
&lt;/h2&gt;

&lt;p&gt;These products should remain separate in your codebase and marketing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundler
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;YOUR LAUNCH
    ↓
YOUR WALLETS
    ↓
COORDINATED BUYS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sniper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OTHER LAUNCH
    ↓
DETECTION
    ↓
FILTER
    ↓
QUOTE
    ↓
   RISK
    ↓
   BUY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The two systems can share:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Wallet handling
Curve quote logic
Transaction manager
Risk controls
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the product objectives are different.&lt;/p&gt;




&lt;h2&gt;
  
  
  23. Where This Can Go Next
&lt;/h2&gt;

&lt;p&gt;The CLI can become the backend for a larger Pons application.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pons Bundler
      ↓
Wallet Management
      ↓
Launch Templates
      ↓
Allocation Engine
      ↓
Execution Monitor
      ↓
Portfolio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A web product could then expose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch configuration
Wallet groups
Per-wallet allocation
Dry-run preview
Live execution
Transaction status
Position tracking
Sell rules
Sweep
Audit history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The underlying transaction infrastructure can remain the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Takeaway
&lt;/h2&gt;

&lt;p&gt;The main engineering challenge in a &lt;strong&gt;Pons bundler&lt;/strong&gt; is not sending many transactions.&lt;/p&gt;

&lt;p&gt;It is coordinating a launch workflow where:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Launch creation
      ↓
Initial buy
      ↓
Multiple separate buys
      ↓
Changing curve state
      ↓
Different transaction outcomes
      ↓
Reconciliation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important technical distinctions are:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;launchAndBuy&lt;/code&gt; gives the master wallet an atomic launch + initial buy.&lt;/p&gt;

&lt;p&gt;Additional buyer wallets are separate transactions.&lt;/p&gt;

&lt;p&gt;Snipe-tax exemptions are configured at launch creation.&lt;/p&gt;

&lt;p&gt;Parallel purchases interact with a changing bonding curve.&lt;/p&gt;

&lt;p&gt;And each wallet needs independent transaction state.&lt;/p&gt;

&lt;p&gt;That is what makes a Pons bundler a real &lt;strong&gt;multi-wallet execution system&lt;/strong&gt; rather than just a script that loops through addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Pons Development
&lt;/h2&gt;

&lt;p&gt;I build custom &lt;strong&gt;Pons and Robinhood Chain infrastructure&lt;/strong&gt;, including Pons bundlers, sniper bots, copy-trading systems, wallet trackers, launch monitors, trading terminals, token scanners, risk engines, and automated execution systems.&lt;/p&gt;

&lt;p&gt;Existing implementations can be extended with dashboards, wallet groups, custom allocation rules, execution monitoring, automated exits, APIs, and client-specific strategy logic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference implementation:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://github.com/0xhamssog/pons-bundler" rel="noopener noreferrer"&gt;0xhamssog/pons-bundler&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>blockchain</category>
      <category>robinhood</category>
      <category>tradingbot</category>
    </item>
  </channel>
</rss>
