inkmaw

Fastest private MCP for pump.fun.

inkmaw / ix-01

§ Internal memo

How it works

Re
Private network indexer for pump.fun, exposed over MCP.
Rev
pre-release
Status
Closed. Public release soon.
Handling
Read once.

§00 Abstract

A private indexer for one program on one chain. inkmaw listens below the RPC layer, rebuilds pump.fun state from raw shreds and Geyser streams, keeps every bonding-curve change in one slot-ordered log, and hands that log to agents as MCP tools. No chart, no feed. Only questions with answers: who funded this dev, is this launch bundled, what did the curve do in this slot, does this event match a rule written in plain words.

 turbine shreds --+
                  +--> decode --> SLOT LOG
 geyser / grpc ---+                  |
       +-----------------+-----------+---------+
       |                 |                     |
  curve state      funding graph         bundle index
       |                 |                     |
       +-----------------+---------------------+
                         |
                    MCP server
                         |
                      agents --> rules --> armed [soon]

§01 Ingest

Two sources, merged into one order. Turbine first: shreds come off the wire, erasure sets are recovered and entries reassembled before the block is replayed, so transaction order is known early. Then Yellowstone Geyser over gRPC: account and transaction subscriptions scoped to the pump.fun program, its global config, its fee recipients and every bonding-curve PDA seen so far. Shreds give order early; Geyser gives truth. Where they disagree, Geyser wins and the shred-derived event is retracted, never patched.

shreds  udp / turbine
        > fec.recover > entry.assemble > tx.decode

geyser  grpc / yellowstone  subscribe {
          accounts      owner == PUMP
          transactions  include [PUMP]
          commitment    processed
        }

merge   key (slot, tx_idx, ix_idx)
        dedupe | retract on mismatch | upgrade

§02 Decode

Only instructions that touch the program are decoded: create, buy, sell, set-params and the migration out of the curve. Each becomes a typed event keyed by slot, transaction index and instruction index. Inner instructions are walked, so a buy routed through an aggregator or a CPI wrapper still lands as a buy against the curve. Failed transactions are kept. A reverted snipe is evidence too.

ix           event            key
create       Launch           (slot, tx, ix)
buy          CurveDelta+Fill  (slot, tx, ix)
sell         CurveDelta+Fill  (slot, tx, ix)
set_params   ConfigDelta      (slot, tx, ix)
migrate      Migration        (slot, tx, ix)
failed tx    Attempt          (slot, tx)

§03 Curve state

A bonding curve is four reserves and a flag. Price is never stored; it is derived from reserves at read time, so nothing drifts. Every event becomes a signed delta. Deltas fold into per-mint state that replays from any slot, and two replays from the same slot must agree byte for byte, or the mint is quarantined.

struct CurveDelta {
  slot      u64
  tx_idx    u32
  ix_idx    u16
  mint      Pubkey
  signer    Pubkey
  d_vsol    i128   // virtual SOL
  d_vtok    i128   // virtual token
  d_rsol    i128   // real SOL
  d_rtok    i128   // real token
  complete  bool   // curve filled
  commit    processed | confirmed | finalized
}

§04 The slot log

One append-only log, ordered by (slot, tx_idx, ix_idx). Processed events are provisional; their commitment is upgraded in place to confirmed, then finalized. When a slot is skipped or a fork abandoned, history is not rewritten: the log emits a tombstone for every event that slot produced, and every consumer tailing it sees the retraction arrive in order, like any other event.

slot  tx   ix  kind    mint       commit
s+0   014  02  create  7Hq..pump  processed
s+0   015  00  buy     7Hq..pump  processed  < dev
s+0   016  00  buy     7Hq..pump  processed  < b1
s+0   016  01  buy     7Hq..pump  processed  < b2
s+0   017  00  buy     7Hq..pump  processed  < b3
s+1   ---  --  tomb    s+0/016..017          fork

§05 Funding graphs

For every create, the dev wallet is walked backwards through SOL transfers until the walk reaches a known exchange hot wallet, a bridge, a fan-in shaped like a mixer, or the depth limit. The graph is cached and grows each time a launch reuses a funder. Clusters form around shared funders, shared rent payers and timing too regular to be human. A fresh dev with an old funder is the most common shape in the graph, and the most informative.

[cex hot] --> w1 --> w2 --+--> DEV --> create(mint)
                          |
                          +--> b1 --+
                          +--> b2 --+--> buy(mint)
                          +--> b3 --+    slot s+0
                                         one tip

§06 Bundle detection

A launch is flagged as bundled when buys in its creation slot, and the slots right after it, share two or more of: contiguous transaction indices behind a single Jito tip, a funder inside the graph, identical compute-unit limits and prices, the same token-account creation layout, a signer set seen together before. The report carries evidence, not a score. An agent can reject the flag and still use the facts.

> bundle.inspect("7Hq..pump")
{
  bundled:        true,
  create_slot:    s+0,
  linked_buyers:  [b1, b2, b3],
  evidence:       [funder:w2, tip:single,
                   cu:identical, ata:layout],
  dev_in_cluster: true,
  cluster_supply: 0.41
}

§07 MCP surface

The indexer speaks the Model Context Protocol. Any MCP-capable agent can attach, list the tools, call them and hold a stream open across slots. Tools are read-mostly and return structured objects, never prose. The two that act are marked soon.

curve.state(mint)             -> CurveState
curve.deltas(mint, from?)     -> stream<CurveDelta>
events.tail(filter, from?)    -> stream<Event>
wallet.funding(addr, depth?)  -> Graph
wallet.cluster(addr)          -> Cluster
dev.history(dev)              -> Launch[]
bundle.inspect(mint, slots?)  -> BundleReport
rules.compile(text)           -> Rule | CompileError
rules.backtest(rule, slots)   -> Trace        soon
rules.arm(rule, signer)       -> Handle       soon

§08 Plain words, compiled

Strategies are written the way a trader would say them out loud. The compiler grounds every noun against the index (dev, funder, bundle, curve, migration), turns every adjective into a predicate with an explicit threshold, and refuses what it cannot ground. The output is a rule: a deterministic predicate over the slot log, plus an action. The compiled rule is shown in full before it is armed. An ambiguous word is a compile error, not a guess.

> "buy new coins whose dev was funded by a fresh
   wallet, skip anything bundled, get out the
   moment the dev moves."

rule r_7f3a {
  on    create(mint)
  when  dev.funding.hops <= 2
  and   dev.funding.age < 10m
  and   not bundle.flagged(mint, slots 0..2)
  do    buy(mint, 0.25 SOL, slippage <= 8%)
  exit  transfer(dev -> *) or sell(dev, mint)
}

§09 Runtime

Armed rules evaluate on the provisional stream and are re-checked on confirmation. A rule that fired on an event later tombstoned is recorded as a false fire, with the slot that killed it. Every decision leaves a trace: the event, each predicate’s value at that slot, the action. From a sentence to a running rule takes hours, not a sprint.

trace r_7f3a @ s+0 / tx 015
  dev.funding.hops   2       ok
  dev.funding.age    6m      ok
  bundle.flagged     false   ok
  action             buy     queued
  confirm            s+0     ok
  if tombstoned      false fire, slot kept

§10 Non-goals

No charts. No social feed. No leaderboard. No dashboard to stare at. The index is built for agents, and agents answer to the wallet that armed them.

charts       no
feed         no
leaderboard  no
dashboard    no
opinions     no

§11 Access

Closed for now. Every wallet that connects goes into the draw for early seats. Public release soon.

status    closed
seats     draw, per connected wallet
release   public, soon

— end of memo —